Import Android SDK Platform PI [4335822]

/google/data/ro/projects/android/fetch_artifact \
    --bid 4335822 \
    --target sdk_phone_armv7-win_sdk \
    sdk-repo-linux-sources-4335822.zip

AndroidVersion.ApiLevel has been modified to appear as 28

Change-Id: Ic8f04be005a71c2b9abeaac754d8da8d6f9a2c32
diff --git a/android/graphics/AvoidXfermode.java b/android/graphics/AvoidXfermode.java
new file mode 100644
index 0000000..683c157
--- /dev/null
+++ b/android/graphics/AvoidXfermode.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2008 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;
+
+/**
+ * AvoidXfermode xfermode will draw the src everywhere except on top of the
+ * opColor or, depending on the Mode, draw only on top of the opColor.
+ *
+ * @removed
+ */
+@Deprecated
+public class AvoidXfermode extends Xfermode {
+
+    // these need to match the enum in AvoidXfermode.h on the native side
+    public enum Mode {
+        AVOID   (0),    //!< draw everywhere except on the opColor
+        TARGET  (1);    //!< draw only on top of the opColor
+        
+        Mode(int nativeInt) {
+            this.nativeInt = nativeInt;
+        }
+        final int nativeInt;
+    }
+    
+    /** This xfermode draws, or doesn't draw, based on the destination's
+     * distance from an op-color.
+     *
+     * There are two modes, and each mode interprets a tolerance value.
+     *
+     * Avoid: In this mode, drawing is allowed only on destination pixels that
+     * are different from the op-color.
+     * Tolerance near 0: avoid any colors even remotely similar to the op-color
+     * Tolerance near 255: avoid only colors nearly identical to the op-color
+     
+     * Target: In this mode, drawing only occurs on destination pixels that
+     * are similar to the op-color
+     * Tolerance near 0: draw only on colors that are nearly identical to the op-color
+     * Tolerance near 255: draw on any colors even remotely similar to the op-color
+     */
+    public AvoidXfermode(int opColor, int tolerance, Mode mode) {
+        if (tolerance < 0 || tolerance > 255) {
+            throw new IllegalArgumentException("tolerance must be 0..255");
+        }
+    }
+}
diff --git a/android/graphics/BaseCanvas.java b/android/graphics/BaseCanvas.java
new file mode 100644
index 0000000..2d8c717
--- /dev/null
+++ b/android/graphics/BaseCanvas.java
@@ -0,0 +1,633 @@
+/*
+ * Copyright (C) 2016 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;
+
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.Size;
+import android.graphics.Canvas.VertexMode;
+import android.text.GraphicsOperations;
+import android.text.SpannableString;
+import android.text.SpannedString;
+import android.text.TextUtils;
+import android.view.RecordingCanvas;
+
+/**
+ * This class is a base class for Canvas's drawing operations. Any modifications here
+ * should be accompanied by a similar modification to {@link RecordingCanvas}.
+ *
+ * The purpose of this class is to minimize the cost of deciding between regular JNI
+ * and @FastNative JNI to just the virtual call that Canvas already has.
+ *
+ * @hide
+ */
+public abstract class BaseCanvas {
+    /**
+     * Should only be assigned in constructors (or setBitmap if software canvas),
+     * freed by NativeAllocation.
+     */
+    protected long mNativeCanvasWrapper;
+
+    /**
+     * Used to determine when compatibility scaling is in effect.
+     */
+    protected int mScreenDensity = Bitmap.DENSITY_NONE;
+    protected int mDensity = Bitmap.DENSITY_NONE;
+    private boolean mAllowHwBitmapsInSwMode = false;
+
+    protected void throwIfCannotDraw(Bitmap bitmap) {
+        if (bitmap.isRecycled()) {
+            throw new RuntimeException("Canvas: trying to use a recycled bitmap " + bitmap);
+        }
+        if (!bitmap.isPremultiplied() && bitmap.getConfig() == Bitmap.Config.ARGB_8888 &&
+                bitmap.hasAlpha()) {
+            throw new RuntimeException("Canvas: trying to use a non-premultiplied bitmap "
+                    + bitmap);
+        }
+        throwIfHwBitmapInSwMode(bitmap);
+    }
+
+    protected final static void checkRange(int length, int offset, int count) {
+        if ((offset | count) < 0 || offset + count > length) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+    }
+
+    public boolean isHardwareAccelerated() {
+        return false;
+    }
+
+    // ---------------------------------------------------------------------------
+    // Drawing methods
+    // These are also implemented in DisplayListCanvas so that we can
+    // selectively apply on them
+    // Everything below here is copy/pasted from Canvas.java
+    // The JNI registration is handled by android_view_Canvas.cpp
+    // ---------------------------------------------------------------------------
+
+    public void drawArc(float left, float top, float right, float bottom, float startAngle,
+            float sweepAngle, boolean useCenter, @NonNull Paint paint) {
+        throwIfHasHwBitmapInSwMode(paint);
+        nDrawArc(mNativeCanvasWrapper, left, top, right, bottom, startAngle, sweepAngle,
+                useCenter, paint.getNativeInstance());
+    }
+
+    public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter,
+            @NonNull Paint paint) {
+        throwIfHasHwBitmapInSwMode(paint);
+        drawArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, useCenter,
+                paint);
+    }
+
+    public void drawARGB(int a, int r, int g, int b) {
+        drawColor(Color.argb(a, r, g, b));
+    }
+
+    public void drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint) {
+        throwIfCannotDraw(bitmap);
+        throwIfHasHwBitmapInSwMode(paint);
+        nDrawBitmap(mNativeCanvasWrapper, bitmap, left, top,
+                paint != null ? paint.getNativeInstance() : 0, mDensity, mScreenDensity,
+                bitmap.mDensity);
+    }
+
+    public void drawBitmap(@NonNull Bitmap bitmap, @NonNull Matrix matrix, @Nullable Paint paint) {
+        throwIfHasHwBitmapInSwMode(paint);
+        nDrawBitmapMatrix(mNativeCanvasWrapper, bitmap, matrix.ni(),
+                paint != null ? paint.getNativeInstance() : 0);
+    }
+
+    public void drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull Rect dst,
+            @Nullable Paint paint) {
+        if (dst == null) {
+            throw new NullPointerException();
+        }
+        throwIfCannotDraw(bitmap);
+        throwIfHasHwBitmapInSwMode(paint);
+        final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
+
+        int left, top, right, bottom;
+        if (src == null) {
+            left = top = 0;
+            right = bitmap.getWidth();
+            bottom = bitmap.getHeight();
+        } else {
+            left = src.left;
+            right = src.right;
+            top = src.top;
+            bottom = src.bottom;
+        }
+
+        nDrawBitmap(mNativeCanvasWrapper, bitmap, left, top, right, bottom,
+                dst.left, dst.top, dst.right, dst.bottom, nativePaint, mScreenDensity,
+                bitmap.mDensity);
+    }
+
+    public void drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull RectF dst,
+            @Nullable Paint paint) {
+        if (dst == null) {
+            throw new NullPointerException();
+        }
+        throwIfCannotDraw(bitmap);
+        throwIfHasHwBitmapInSwMode(paint);
+        final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
+
+        float left, top, right, bottom;
+        if (src == null) {
+            left = top = 0;
+            right = bitmap.getWidth();
+            bottom = bitmap.getHeight();
+        } else {
+            left = src.left;
+            right = src.right;
+            top = src.top;
+            bottom = src.bottom;
+        }
+
+        nDrawBitmap(mNativeCanvasWrapper, bitmap, left, top, right, bottom,
+                dst.left, dst.top, dst.right, dst.bottom, nativePaint, mScreenDensity,
+                bitmap.mDensity);
+    }
+
+    @Deprecated
+    public void drawBitmap(@NonNull int[] colors, int offset, int stride, float x, float y,
+            int width, int height, boolean hasAlpha, @Nullable Paint paint) {
+        // check for valid input
+        if (width < 0) {
+            throw new IllegalArgumentException("width must be >= 0");
+        }
+        if (height < 0) {
+            throw new IllegalArgumentException("height must be >= 0");
+        }
+        if (Math.abs(stride) < width) {
+            throw new IllegalArgumentException("abs(stride) must be >= width");
+        }
+        int lastScanline = offset + (height - 1) * stride;
+        int length = colors.length;
+        if (offset < 0 || (offset + width > length) || lastScanline < 0
+                || (lastScanline + width > length)) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        throwIfHasHwBitmapInSwMode(paint);
+        // quick escape if there's nothing to draw
+        if (width == 0 || height == 0) {
+            return;
+        }
+        // punch down to native for the actual draw
+        nDrawBitmap(mNativeCanvasWrapper, colors, offset, stride, x, y, width, height, hasAlpha,
+                paint != null ? paint.getNativeInstance() : 0);
+    }
+
+    @Deprecated
+    public void drawBitmap(@NonNull int[] colors, int offset, int stride, int x, int y,
+            int width, int height, boolean hasAlpha, @Nullable Paint paint) {
+        // call through to the common float version
+        drawBitmap(colors, offset, stride, (float) x, (float) y, width, height,
+                hasAlpha, paint);
+    }
+
+    public void drawBitmapMesh(@NonNull Bitmap bitmap, int meshWidth, int meshHeight,
+            @NonNull float[] verts, int vertOffset, @Nullable int[] colors, int colorOffset,
+            @Nullable Paint paint) {
+        if ((meshWidth | meshHeight | vertOffset | colorOffset) < 0) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        throwIfHasHwBitmapInSwMode(paint);
+        if (meshWidth == 0 || meshHeight == 0) {
+            return;
+        }
+        int count = (meshWidth + 1) * (meshHeight + 1);
+        // we mul by 2 since we need two floats per vertex
+        checkRange(verts.length, vertOffset, count * 2);
+        if (colors != null) {
+            // no mul by 2, since we need only 1 color per vertex
+            checkRange(colors.length, colorOffset, count);
+        }
+        nDrawBitmapMesh(mNativeCanvasWrapper, bitmap, meshWidth, meshHeight,
+                verts, vertOffset, colors, colorOffset,
+                paint != null ? paint.getNativeInstance() : 0);
+    }
+
+    public void drawCircle(float cx, float cy, float radius, @NonNull Paint paint) {
+        throwIfHasHwBitmapInSwMode(paint);
+        nDrawCircle(mNativeCanvasWrapper, cx, cy, radius, paint.getNativeInstance());
+    }
+
+    public void drawColor(@ColorInt int color) {
+        nDrawColor(mNativeCanvasWrapper, color, PorterDuff.Mode.SRC_OVER.nativeInt);
+    }
+
+    public void drawColor(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
+        nDrawColor(mNativeCanvasWrapper, color, mode.nativeInt);
+    }
+
+    public void drawLine(float startX, float startY, float stopX, float stopY,
+            @NonNull Paint paint) {
+        throwIfHasHwBitmapInSwMode(paint);
+        nDrawLine(mNativeCanvasWrapper, startX, startY, stopX, stopY, paint.getNativeInstance());
+    }
+
+    public void drawLines(@Size(multiple = 4) @NonNull float[] pts, int offset, int count,
+            @NonNull Paint paint) {
+        throwIfHasHwBitmapInSwMode(paint);
+        nDrawLines(mNativeCanvasWrapper, pts, offset, count, paint.getNativeInstance());
+    }
+
+    public void drawLines(@Size(multiple = 4) @NonNull float[] pts, @NonNull Paint paint) {
+        throwIfHasHwBitmapInSwMode(paint);
+        drawLines(pts, 0, pts.length, paint);
+    }
+
+    public void drawOval(float left, float top, float right, float bottom, @NonNull Paint paint) {
+        throwIfHasHwBitmapInSwMode(paint);
+        nDrawOval(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance());
+    }
+
+    public void drawOval(@NonNull RectF oval, @NonNull Paint paint) {
+        if (oval == null) {
+            throw new NullPointerException();
+        }
+        throwIfHasHwBitmapInSwMode(paint);
+        drawOval(oval.left, oval.top, oval.right, oval.bottom, paint);
+    }
+
+    public void drawPaint(@NonNull Paint paint) {
+        nDrawPaint(mNativeCanvasWrapper, paint.getNativeInstance());
+    }
+
+    public void drawPatch(@NonNull NinePatch patch, @NonNull Rect dst, @Nullable Paint paint) {
+        Bitmap bitmap = patch.getBitmap();
+        throwIfCannotDraw(bitmap);
+        throwIfHasHwBitmapInSwMode(paint);
+        final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
+        nDrawNinePatch(mNativeCanvasWrapper, bitmap.getNativeInstance(), patch.mNativeChunk,
+                dst.left, dst.top, dst.right, dst.bottom, nativePaint,
+                mDensity, patch.getDensity());
+    }
+
+    public void drawPatch(@NonNull NinePatch patch, @NonNull RectF dst, @Nullable Paint paint) {
+        Bitmap bitmap = patch.getBitmap();
+        throwIfCannotDraw(bitmap);
+        throwIfHasHwBitmapInSwMode(paint);
+        final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
+        nDrawNinePatch(mNativeCanvasWrapper, bitmap.getNativeInstance(), patch.mNativeChunk,
+                dst.left, dst.top, dst.right, dst.bottom, nativePaint,
+                mDensity, patch.getDensity());
+    }
+
+    public void drawPath(@NonNull Path path, @NonNull Paint paint) {
+        throwIfHasHwBitmapInSwMode(paint);
+        if (path.isSimplePath && path.rects != null) {
+            nDrawRegion(mNativeCanvasWrapper, path.rects.mNativeRegion, paint.getNativeInstance());
+        } else {
+            nDrawPath(mNativeCanvasWrapper, path.readOnlyNI(), paint.getNativeInstance());
+        }
+    }
+
+    public void drawPoint(float x, float y, @NonNull Paint paint) {
+        throwIfHasHwBitmapInSwMode(paint);
+        nDrawPoint(mNativeCanvasWrapper, x, y, paint.getNativeInstance());
+    }
+
+    public void drawPoints(@Size(multiple = 2) float[] pts, int offset, int count,
+            @NonNull Paint paint) {
+        throwIfHasHwBitmapInSwMode(paint);
+        nDrawPoints(mNativeCanvasWrapper, pts, offset, count, paint.getNativeInstance());
+    }
+
+    public void drawPoints(@Size(multiple = 2) @NonNull float[] pts, @NonNull Paint paint) {
+        throwIfHasHwBitmapInSwMode(paint);
+        drawPoints(pts, 0, pts.length, paint);
+    }
+
+    @Deprecated
+    public void drawPosText(@NonNull char[] text, int index, int count,
+            @NonNull @Size(multiple = 2) float[] pos,
+            @NonNull Paint paint) {
+        if (index < 0 || index + count > text.length || count * 2 > pos.length) {
+            throw new IndexOutOfBoundsException();
+        }
+        throwIfHasHwBitmapInSwMode(paint);
+        for (int i = 0; i < count; i++) {
+            drawText(text, index + i, 1, pos[i * 2], pos[i * 2 + 1], paint);
+        }
+    }
+
+    @Deprecated
+    public void drawPosText(@NonNull String text, @NonNull @Size(multiple = 2) float[] pos,
+            @NonNull Paint paint) {
+        throwIfHasHwBitmapInSwMode(paint);
+        drawPosText(text.toCharArray(), 0, text.length(), pos, paint);
+    }
+
+    public void drawRect(float left, float top, float right, float bottom, @NonNull Paint paint) {
+        throwIfHasHwBitmapInSwMode(paint);
+        nDrawRect(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance());
+    }
+
+    public void drawRect(@NonNull Rect r, @NonNull Paint paint) {
+        throwIfHasHwBitmapInSwMode(paint);
+        drawRect(r.left, r.top, r.right, r.bottom, paint);
+    }
+
+    public void drawRect(@NonNull RectF rect, @NonNull Paint paint) {
+        throwIfHasHwBitmapInSwMode(paint);
+        nDrawRect(mNativeCanvasWrapper,
+                rect.left, rect.top, rect.right, rect.bottom, paint.getNativeInstance());
+    }
+
+    public void drawRGB(int r, int g, int b) {
+        drawColor(Color.rgb(r, g, b));
+    }
+
+    public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
+            @NonNull Paint paint) {
+        throwIfHasHwBitmapInSwMode(paint);
+        nDrawRoundRect(mNativeCanvasWrapper, left, top, right, bottom, rx, ry,
+                paint.getNativeInstance());
+    }
+
+    public void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint) {
+        throwIfHasHwBitmapInSwMode(paint);
+        drawRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, paint);
+    }
+
+    public void drawText(@NonNull char[] text, int index, int count, float x, float y,
+            @NonNull Paint paint) {
+        if ((index | count | (index + count) |
+                (text.length - index - count)) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        throwIfHasHwBitmapInSwMode(paint);
+        nDrawText(mNativeCanvasWrapper, text, index, count, x, y, paint.mBidiFlags,
+                paint.getNativeInstance());
+    }
+
+    public void drawText(@NonNull CharSequence text, int start, int end, float x, float y,
+            @NonNull Paint paint) {
+        if ((start | end | (end - start) | (text.length() - end)) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        throwIfHasHwBitmapInSwMode(paint);
+        if (text instanceof String || text instanceof SpannedString ||
+                text instanceof SpannableString) {
+            nDrawText(mNativeCanvasWrapper, text.toString(), start, end, x, y,
+                    paint.mBidiFlags, paint.getNativeInstance());
+        } else if (text instanceof GraphicsOperations) {
+            ((GraphicsOperations) text).drawText(this, start, end, x, y,
+                    paint);
+        } else {
+            char[] buf = TemporaryBuffer.obtain(end - start);
+            TextUtils.getChars(text, start, end, buf, 0);
+            nDrawText(mNativeCanvasWrapper, buf, 0, end - start, x, y,
+                    paint.mBidiFlags, paint.getNativeInstance());
+            TemporaryBuffer.recycle(buf);
+        }
+    }
+
+    public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) {
+        throwIfHasHwBitmapInSwMode(paint);
+        nDrawText(mNativeCanvasWrapper, text, 0, text.length(), x, y, paint.mBidiFlags,
+                paint.getNativeInstance());
+    }
+
+    public void drawText(@NonNull String text, int start, int end, float x, float y,
+            @NonNull Paint paint) {
+        if ((start | end | (end - start) | (text.length() - end)) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        throwIfHasHwBitmapInSwMode(paint);
+        nDrawText(mNativeCanvasWrapper, text, start, end, x, y, paint.mBidiFlags,
+                paint.getNativeInstance());
+    }
+
+    public void drawTextOnPath(@NonNull char[] text, int index, int count, @NonNull Path path,
+            float hOffset, float vOffset, @NonNull Paint paint) {
+        if (index < 0 || index + count > text.length) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        throwIfHasHwBitmapInSwMode(paint);
+        nDrawTextOnPath(mNativeCanvasWrapper, text, index, count,
+                path.readOnlyNI(), hOffset, vOffset,
+                paint.mBidiFlags, paint.getNativeInstance());
+    }
+
+    public void drawTextOnPath(@NonNull String text, @NonNull Path path, float hOffset,
+            float vOffset, @NonNull Paint paint) {
+        if (text.length() > 0) {
+            throwIfHasHwBitmapInSwMode(paint);
+            nDrawTextOnPath(mNativeCanvasWrapper, text, path.readOnlyNI(), hOffset, vOffset,
+                    paint.mBidiFlags, paint.getNativeInstance());
+        }
+    }
+
+    public void drawTextRun(@NonNull char[] text, int index, int count, int contextIndex,
+            int contextCount, float x, float y, boolean isRtl, @NonNull Paint paint) {
+
+        if (text == null) {
+            throw new NullPointerException("text is null");
+        }
+        if (paint == null) {
+            throw new NullPointerException("paint is null");
+        }
+        if ((index | count | contextIndex | contextCount | index - contextIndex
+                | (contextIndex + contextCount) - (index + count)
+                | text.length - (contextIndex + contextCount)) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        throwIfHasHwBitmapInSwMode(paint);
+        nDrawTextRun(mNativeCanvasWrapper, text, index, count, contextIndex, contextCount,
+                x, y, isRtl, paint.getNativeInstance());
+    }
+
+    public void drawTextRun(@NonNull CharSequence text, int start, int end, int contextStart,
+            int contextEnd, float x, float y, boolean isRtl, @NonNull Paint paint) {
+
+        if (text == null) {
+            throw new NullPointerException("text is null");
+        }
+        if (paint == null) {
+            throw new NullPointerException("paint is null");
+        }
+        if ((start | end | contextStart | contextEnd | start - contextStart | end - start
+                | contextEnd - end | text.length() - contextEnd) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        throwIfHasHwBitmapInSwMode(paint);
+        if (text instanceof String || text instanceof SpannedString ||
+                text instanceof SpannableString) {
+            nDrawTextRun(mNativeCanvasWrapper, text.toString(), start, end, contextStart,
+                    contextEnd, x, y, isRtl, paint.getNativeInstance());
+        } else if (text instanceof GraphicsOperations) {
+            ((GraphicsOperations) text).drawTextRun(this, start, end,
+                    contextStart, contextEnd, x, y, isRtl, paint);
+        } else {
+            int contextLen = contextEnd - contextStart;
+            int len = end - start;
+            char[] buf = TemporaryBuffer.obtain(contextLen);
+            TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
+            nDrawTextRun(mNativeCanvasWrapper, buf, start - contextStart, len,
+                    0, contextLen, x, y, isRtl, paint.getNativeInstance());
+            TemporaryBuffer.recycle(buf);
+        }
+    }
+
+    public void drawVertices(@NonNull VertexMode mode, int vertexCount, @NonNull float[] verts,
+            int vertOffset, @Nullable float[] texs, int texOffset, @Nullable int[] colors,
+            int colorOffset, @Nullable short[] indices, int indexOffset, int indexCount,
+            @NonNull Paint paint) {
+        checkRange(verts.length, vertOffset, vertexCount);
+        if (isHardwareAccelerated()) {
+            return;
+        }
+        if (texs != null) {
+            checkRange(texs.length, texOffset, vertexCount);
+        }
+        if (colors != null) {
+            checkRange(colors.length, colorOffset, vertexCount / 2);
+        }
+        if (indices != null) {
+            checkRange(indices.length, indexOffset, indexCount);
+        }
+        throwIfHasHwBitmapInSwMode(paint);
+        nDrawVertices(mNativeCanvasWrapper, mode.nativeInt, vertexCount, verts,
+                vertOffset, texs, texOffset, colors, colorOffset,
+                indices, indexOffset, indexCount, paint.getNativeInstance());
+    }
+
+    /**
+     * @hide
+     */
+    public void setHwBitmapsInSwModeEnabled(boolean enabled) {
+        mAllowHwBitmapsInSwMode = enabled;
+    }
+
+    /**
+     * @hide
+     */
+    public boolean isHwBitmapsInSwModeEnabled() {
+        return mAllowHwBitmapsInSwMode;
+    }
+
+    private void throwIfHwBitmapInSwMode(Bitmap bitmap) {
+        if (!mAllowHwBitmapsInSwMode && !isHardwareAccelerated()
+                && bitmap.getConfig() == Bitmap.Config.HARDWARE) {
+            throw new IllegalStateException("Software rendering doesn't support hardware bitmaps");
+        }
+    }
+
+    private void throwIfHasHwBitmapInSwMode(Paint p) {
+        if (isHardwareAccelerated() || p == null) {
+            return;
+        }
+        throwIfHasHwBitmapInSwMode(p.getShader());
+    }
+
+    private void throwIfHasHwBitmapInSwMode(Shader shader) {
+        if (shader == null) {
+            return;
+        }
+        if (shader instanceof BitmapShader) {
+            throwIfHwBitmapInSwMode(((BitmapShader) shader).mBitmap);
+        }
+        if (shader instanceof ComposeShader) {
+            throwIfHasHwBitmapInSwMode(((ComposeShader) shader).mShaderA);
+            throwIfHasHwBitmapInSwMode(((ComposeShader) shader).mShaderB);
+        }
+    }
+
+    private static native void nDrawBitmap(long nativeCanvas, Bitmap bitmap, float left, float top,
+            long nativePaintOrZero, int canvasDensity, int screenDensity, int bitmapDensity);
+
+    private static native void nDrawBitmap(long nativeCanvas, Bitmap bitmap, float srcLeft,
+            float srcTop,
+            float srcRight, float srcBottom, float dstLeft, float dstTop, float dstRight,
+            float dstBottom, long nativePaintOrZero, int screenDensity, int bitmapDensity);
+
+    private static native void nDrawBitmap(long nativeCanvas, int[] colors, int offset, int stride,
+            float x, float y, int width, int height, boolean hasAlpha, long nativePaintOrZero);
+
+    private static native void nDrawColor(long nativeCanvas, int color, int mode);
+
+    private static native void nDrawPaint(long nativeCanvas, long nativePaint);
+
+    private static native void nDrawPoint(long canvasHandle, float x, float y, long paintHandle);
+
+    private static native void nDrawPoints(long canvasHandle, float[] pts, int offset, int count,
+            long paintHandle);
+
+    private static native void nDrawLine(long nativeCanvas, float startX, float startY, float stopX,
+            float stopY, long nativePaint);
+
+    private static native void nDrawLines(long canvasHandle, float[] pts, int offset, int count,
+            long paintHandle);
+
+    private static native void nDrawRect(long nativeCanvas, float left, float top, float right,
+            float bottom, long nativePaint);
+
+    private static native void nDrawOval(long nativeCanvas, float left, float top, float right,
+            float bottom, long nativePaint);
+
+    private static native void nDrawCircle(long nativeCanvas, float cx, float cy, float radius,
+            long nativePaint);
+
+    private static native void nDrawArc(long nativeCanvas, float left, float top, float right,
+            float bottom, float startAngle, float sweep, boolean useCenter, long nativePaint);
+
+    private static native void nDrawRoundRect(long nativeCanvas, float left, float top, float right,
+            float bottom, float rx, float ry, long nativePaint);
+
+    private static native void nDrawPath(long nativeCanvas, long nativePath, long nativePaint);
+
+    private static native void nDrawRegion(long nativeCanvas, long nativeRegion, long nativePaint);
+
+    private static native void nDrawNinePatch(long nativeCanvas, long nativeBitmap, long ninePatch,
+            float dstLeft, float dstTop, float dstRight, float dstBottom, long nativePaintOrZero,
+            int screenDensity, int bitmapDensity);
+
+    private static native void nDrawBitmapMatrix(long nativeCanvas, Bitmap bitmap,
+            long nativeMatrix, long nativePaint);
+
+    private static native void nDrawBitmapMesh(long nativeCanvas, Bitmap bitmap, int meshWidth,
+            int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset,
+            long nativePaint);
+
+    private static native void nDrawVertices(long nativeCanvas, int mode, int n, float[] verts,
+            int vertOffset, float[] texs, int texOffset, int[] colors, int colorOffset,
+            short[] indices, int indexOffset, int indexCount, long nativePaint);
+
+    private static native void nDrawText(long nativeCanvas, char[] text, int index, int count,
+            float x, float y, int flags, long nativePaint);
+
+    private static native void nDrawText(long nativeCanvas, String text, int start, int end,
+            float x, float y, int flags, long nativePaint);
+
+    private static native void nDrawTextRun(long nativeCanvas, String text, int start, int end,
+            int contextStart, int contextEnd, float x, float y, boolean isRtl, long nativePaint);
+
+    private static native void nDrawTextRun(long nativeCanvas, char[] text, int start, int count,
+            int contextStart, int contextCount, float x, float y, boolean isRtl, long nativePaint);
+
+    private static native void nDrawTextOnPath(long nativeCanvas, char[] text, int index, int count,
+            long nativePath, float hOffset, float vOffset, int bidiFlags, long nativePaint);
+
+    private static native void nDrawTextOnPath(long nativeCanvas, String text, long nativePath,
+            float hOffset, float vOffset, int flags, long nativePaint);
+}
diff --git a/android/graphics/BaseCanvas_Delegate.java b/android/graphics/BaseCanvas_Delegate.java
new file mode 100644
index 0000000..89fccc7
--- /dev/null
+++ b/android/graphics/BaseCanvas_Delegate.java
@@ -0,0 +1,757 @@
+/*
+ * Copyright (C) 2016 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;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.impl.GcSnapshot;
+import com.android.layoutlib.bridge.impl.PorterDuffUtility;
+import com.android.ninepatch.NinePatchChunk;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.annotation.Nullable;
+import android.text.TextUtils;
+
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Arc2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
+
+public class BaseCanvas_Delegate {
+    // ---- delegate manager ----
+    protected static DelegateManager<BaseCanvas_Delegate> sManager =
+            new DelegateManager<>(BaseCanvas_Delegate.class);
+
+    // ---- delegate helper data ----
+    private final static boolean[] sBoolOut = new boolean[1];
+
+
+    // ---- delegate data ----
+    protected Bitmap_Delegate mBitmap;
+    protected GcSnapshot mSnapshot;
+
+    // ---- Public Helper methods ----
+
+    protected BaseCanvas_Delegate(Bitmap_Delegate bitmap) {
+        mSnapshot = GcSnapshot.createDefaultSnapshot(mBitmap = bitmap);
+    }
+
+    protected BaseCanvas_Delegate() {
+        mSnapshot = GcSnapshot.createDefaultSnapshot(null /*image*/);
+    }
+
+    /**
+     * Disposes of the {@link Graphics2D} stack.
+     */
+    protected void dispose() {
+        mSnapshot.dispose();
+    }
+
+    /**
+     * Returns the current {@link Graphics2D} used to draw.
+     */
+    public GcSnapshot getSnapshot() {
+        return mSnapshot;
+    }
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawBitmap(long nativeCanvas, Bitmap bitmap, float left, float top,
+            long nativePaintOrZero, int canvasDensity, int screenDensity, int bitmapDensity) {
+        // get the delegate from the native int.
+        Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
+        if (bitmapDelegate == null) {
+            return;
+        }
+
+        BufferedImage image = bitmapDelegate.getImage();
+        float right = left + image.getWidth();
+        float bottom = top + image.getHeight();
+
+        drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero,
+                0, 0, image.getWidth(), image.getHeight(),
+                (int)left, (int)top, (int)right, (int)bottom);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawBitmap(long nativeCanvas, Bitmap bitmap, float srcLeft, float srcTop,
+            float srcRight, float srcBottom, float dstLeft, float dstTop, float dstRight,
+            float dstBottom, long nativePaintOrZero, int screenDensity, int bitmapDensity) {
+        // get the delegate from the native int.
+        Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
+        if (bitmapDelegate == null) {
+            return;
+        }
+
+        drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, (int) srcLeft, (int) srcTop,
+                (int) srcRight, (int) srcBottom, (int) dstLeft, (int) dstTop, (int) dstRight,
+                (int) dstBottom);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawBitmap(long nativeCanvas, int[] colors, int offset, int stride,
+            final float x, final float y, int width, int height, boolean hasAlpha,
+            long nativePaintOrZero) {
+        // create a temp BufferedImage containing the content.
+        final BufferedImage image = new BufferedImage(width, height,
+                hasAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB);
+        image.setRGB(0, 0, width, height, colors, offset, stride);
+
+        draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, false /*forceSrcMode*/,
+                (graphics, paint) -> {
+                    if (paint != null && paint.isFilterBitmap()) {
+                        graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+                                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+                    }
+
+                    graphics.drawImage(image, (int) x, (int) y, null);
+                });
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawColor(long nativeCanvas, final int color, final int mode) {
+        // get the delegate from the native int.
+        BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            return;
+        }
+
+        final int w = canvasDelegate.mBitmap.getImage().getWidth();
+        final int h = canvasDelegate.mBitmap.getImage().getHeight();
+        draw(nativeCanvas, (graphics, paint) -> {
+            // reset its transform just in case
+            graphics.setTransform(new AffineTransform());
+
+            // set the color
+            graphics.setColor(new java.awt.Color(color, true /*alpha*/));
+
+            Composite composite = PorterDuffUtility.getComposite(
+                    PorterDuffUtility.getPorterDuffMode(mode), 0xFF);
+            if (composite != null) {
+                graphics.setComposite(composite);
+            }
+
+            graphics.fillRect(0, 0, w, h);
+        });
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawPaint(long nativeCanvas, long paint) {
+        // FIXME
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "Canvas.drawPaint is not supported.", null, null /*data*/);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawPoint(long nativeCanvas, float x, float y,
+            long nativePaint) {
+        // TODO: need to support the attribute (e.g. stroke width) of paint
+        draw(nativeCanvas, nativePaint, false /*compositeOnly*/, false /*forceSrcMode*/,
+                (graphics, paintDelegate) -> graphics.fillRect((int)x, (int)y, 1, 1));
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawPoints(long nativeCanvas, float[] pts, int offset, int count,
+            long nativePaint) {
+        if (offset < 0 || count < 0 || offset + count > pts.length) {
+            throw new IllegalArgumentException("Invalid argument set");
+        }
+        // ignore the last point if the count is odd (It means it is not paired).
+        count = (count >> 1) << 1;
+        for (int i = offset; i < offset + count; i += 2) {
+            nDrawPoint(nativeCanvas, pts[i], pts[i + 1], nativePaint);
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawLine(long nativeCanvas,
+            final float startX, final float startY, final float stopX, final float stopY,
+            long paint) {
+        draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+                (graphics, paintDelegate) -> graphics.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY));
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawLines(long nativeCanvas,
+            final float[] pts, final int offset, final int count,
+            long nativePaint) {
+        draw(nativeCanvas, nativePaint, false /*compositeOnly*/,
+                false /*forceSrcMode*/, (graphics, paintDelegate) -> {
+                    for (int i = 0; i < count; i += 4) {
+                        graphics.drawLine((int) pts[i + offset], (int) pts[i + offset + 1],
+                                (int) pts[i + offset + 2], (int) pts[i + offset + 3]);
+                    }
+                });
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawRect(long nativeCanvas,
+            final float left, final float top, final float right, final float bottom, long paint) {
+
+        draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+                (graphics, paintDelegate) -> {
+                    int style = paintDelegate.getStyle();
+
+                    // draw
+                    if (style == Paint.Style.FILL.nativeInt ||
+                            style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                        graphics.fillRect((int)left, (int)top,
+                                (int)(right-left), (int)(bottom-top));
+                    }
+
+                    if (style == Paint.Style.STROKE.nativeInt ||
+                            style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                        graphics.drawRect((int)left, (int)top,
+                                (int)(right-left), (int)(bottom-top));
+                    }
+                });
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawOval(long nativeCanvas, final float left,
+            final float top, final float right, final float bottom, long paint) {
+        if (right > left && bottom > top) {
+            draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+                    (graphics, paintDelegate) -> {
+                        int style = paintDelegate.getStyle();
+
+                        // draw
+                        if (style == Paint.Style.FILL.nativeInt ||
+                                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                            graphics.fillOval((int)left, (int)top,
+                                    (int)(right - left), (int)(bottom - top));
+                        }
+
+                        if (style == Paint.Style.STROKE.nativeInt ||
+                                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                            graphics.drawOval((int)left, (int)top,
+                                    (int)(right - left), (int)(bottom - top));
+                        }
+                    });
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawCircle(long nativeCanvas,
+            float cx, float cy, float radius, long paint) {
+        nDrawOval(nativeCanvas,
+                cx - radius, cy - radius, cx + radius, cy + radius,
+                paint);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawArc(long nativeCanvas,
+            final float left, final float top, final float right, final float bottom,
+            final float startAngle, final float sweep,
+            final boolean useCenter, long paint) {
+        if (right > left && bottom > top) {
+            draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+                    (graphics, paintDelegate) -> {
+                        int style = paintDelegate.getStyle();
+
+                        Arc2D.Float arc = new Arc2D.Float(
+                                left, top, right - left, bottom - top,
+                                -startAngle, -sweep,
+                                useCenter ? Arc2D.PIE : Arc2D.OPEN);
+
+                        // draw
+                        if (style == Paint.Style.FILL.nativeInt ||
+                                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                            graphics.fill(arc);
+                        }
+
+                        if (style == Paint.Style.STROKE.nativeInt ||
+                                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                            graphics.draw(arc);
+                        }
+                    });
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawRoundRect(long nativeCanvas,
+            final float left, final float top, final float right, final float bottom,
+            final float rx, final float ry, long paint) {
+        draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+                (graphics, paintDelegate) -> {
+                    int style = paintDelegate.getStyle();
+
+                    // draw
+                    if (style == Paint.Style.FILL.nativeInt ||
+                            style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                        graphics.fillRoundRect(
+                                (int)left, (int)top,
+                                (int)(right - left), (int)(bottom - top),
+                                2 * (int)rx, 2 * (int)ry);
+                    }
+
+                    if (style == Paint.Style.STROKE.nativeInt ||
+                            style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                        graphics.drawRoundRect(
+                                (int)left, (int)top,
+                                (int)(right - left), (int)(bottom - top),
+                                2 * (int)rx, 2 * (int)ry);
+                    }
+                });
+    }
+
+    @LayoutlibDelegate
+    public static void nDrawPath(long nativeCanvas, long path, long paint) {
+        final Path_Delegate pathDelegate = Path_Delegate.getDelegate(path);
+        if (pathDelegate == null) {
+            return;
+        }
+
+        draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+                (graphics, paintDelegate) -> {
+                    Shape shape = pathDelegate.getJavaShape();
+                    Rectangle2D bounds = shape.getBounds2D();
+                    if (bounds.isEmpty()) {
+                        // Apple JRE 1.6 doesn't like drawing empty shapes.
+                        // http://b.android.com/178278
+
+                        if (pathDelegate.isEmpty()) {
+                            // This means that the path doesn't have any lines or curves so
+                            // nothing to draw.
+                            return;
+                        }
+
+                        // The stroke width is not consider for the size of the bounds so,
+                        // for example, a horizontal line, would be considered as an empty
+                        // rectangle.
+                        // If the strokeWidth is not 0, we use it to consider the size of the
+                        // path as well.
+                        float strokeWidth = paintDelegate.getStrokeWidth();
+                        if (strokeWidth <= 0.0f) {
+                            return;
+                        }
+                        bounds.setRect(bounds.getX(), bounds.getY(),
+                                Math.max(strokeWidth, bounds.getWidth()),
+                                Math.max(strokeWidth, bounds.getHeight()));
+                    }
+
+                    int style = paintDelegate.getStyle();
+
+                    if (style == Paint.Style.FILL.nativeInt ||
+                            style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                        graphics.fill(shape);
+                    }
+
+                    if (style == Paint.Style.STROKE.nativeInt ||
+                            style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                        graphics.draw(shape);
+                    }
+                });
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawRegion(long nativeCanvas, long nativeRegion,
+            long nativePaint) {
+        // FIXME
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "Some canvas paths may not be drawn", null, null);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawNinePatch(long nativeCanvas, long nativeBitmap, long ninePatch,
+            final float dstLeft, final float dstTop, final float dstRight, final float dstBottom,
+            long nativePaintOrZero, final int screenDensity, final int bitmapDensity) {
+
+        // get the delegate from the native int.
+        final Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmap);
+        if (bitmapDelegate == null) {
+            return;
+        }
+
+        byte[] c = NinePatch_Delegate.getChunk(ninePatch);
+        if (c == null) {
+            // not a 9-patch?
+            BufferedImage image = bitmapDelegate.getImage();
+            drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, 0, 0, image.getWidth(),
+                    image.getHeight(), (int) dstLeft, (int) dstTop, (int) dstRight,
+                    (int) dstBottom);
+            return;
+        }
+
+        final NinePatchChunk chunkObject = NinePatch_Delegate.getChunk(c);
+        if (chunkObject == null) {
+            return;
+        }
+
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            return;
+        }
+
+        // this one can be null
+        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero);
+
+        canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() {
+            @Override
+            public void draw(Graphics2D graphics, Paint_Delegate paint) {
+                chunkObject.draw(bitmapDelegate.getImage(), graphics, (int) dstLeft, (int) dstTop,
+                        (int) (dstRight - dstLeft), (int) (dstBottom - dstTop), screenDensity,
+                        bitmapDensity);
+            }
+        }, paintDelegate, true, false);
+
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawBitmapMatrix(long nCanvas, Bitmap bitmap,
+            long nMatrix, long nPaint) {
+        // get the delegate from the native int.
+        BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+        if (canvasDelegate == null) {
+            return;
+        }
+
+        // get the delegate from the native int, which can be null
+        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint);
+
+        // get the delegate from the native int.
+        Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
+        if (bitmapDelegate == null) {
+            return;
+        }
+
+        final BufferedImage image = getImageToDraw(bitmapDelegate, paintDelegate, sBoolOut);
+
+        Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
+        if (matrixDelegate == null) {
+            return;
+        }
+
+        final AffineTransform mtx = matrixDelegate.getAffineTransform();
+
+        canvasDelegate.getSnapshot().draw((graphics, paint) -> {
+            if (paint != null && paint.isFilterBitmap()) {
+                graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+                        RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+            }
+
+            //FIXME add support for canvas, screen and bitmap densities.
+            graphics.drawImage(image, mtx, null);
+        }, paintDelegate, true /*compositeOnly*/, false /*forceSrcMode*/);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawBitmapMesh(long nCanvas, Bitmap bitmap,
+            int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors,
+            int colorOffset, long nPaint) {
+        // FIXME
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "Canvas.drawBitmapMesh is not supported.", null, null /*data*/);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawVertices(long nCanvas, int mode, int n,
+            float[] verts, int vertOffset,
+            float[] texs, int texOffset,
+            int[] colors, int colorOffset,
+            short[] indices, int indexOffset,
+            int indexCount, long nPaint) {
+        // FIXME
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "Canvas.drawVertices is not supported.", null, null /*data*/);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawText(long nativeCanvas, char[] text, int index, int count,
+            float startX, float startY, int flags, long paint) {
+        drawText(nativeCanvas, text, index, count, startX, startY, flags,
+                paint);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawText(long nativeCanvas, String text,
+            int start, int end, float x, float y, final int flags, long paint) {
+        int count = end - start;
+        char[] buffer = TemporaryBuffer.obtain(count);
+        TextUtils.getChars(text, start, end, buffer, 0);
+
+        nDrawText(nativeCanvas, buffer, 0, count, x, y, flags, paint);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawTextRun(long nativeCanvas, String text,
+            int start, int end, int contextStart, int contextEnd,
+            float x, float y, boolean isRtl, long paint) {
+        int count = end - start;
+        char[] buffer = TemporaryBuffer.obtain(count);
+        TextUtils.getChars(text, start, end, buffer, 0);
+
+        drawText(nativeCanvas, buffer, 0, count, x, y, isRtl ? Paint.BIDI_RTL : Paint.BIDI_LTR,
+                paint);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawTextRun(long nativeCanvas, char[] text,
+            int start, int count, int contextStart, int contextCount,
+            float x, float y, boolean isRtl, long paint) {
+        drawText(nativeCanvas, text, start, count, x, y, isRtl ? Paint.BIDI_RTL : Paint.BIDI_LTR, paint);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawTextOnPath(long nativeCanvas,
+            char[] text, int index,
+            int count, long path,
+            float hOffset,
+            float vOffset, int bidiFlags,
+            long paint) {
+        // FIXME
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "Canvas.drawTextOnPath is not supported.", null, null /*data*/);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawTextOnPath(long nativeCanvas,
+            String text, long path,
+            float hOffset,
+            float vOffset,
+            int bidiFlags, long paint) {
+        // FIXME
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "Canvas.drawTextOnPath is not supported.", null, null /*data*/);
+    }
+
+    // ---- Private delegate/helper methods ----
+
+    /**
+     * Executes a {@link GcSnapshot.Drawable} with a given canvas and paint.
+     * <p>Note that the drawable may actually be executed several times if there are
+     * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}.
+     */
+    private static void draw(long nCanvas, long nPaint, boolean compositeOnly, boolean forceSrcMode,
+            GcSnapshot.Drawable drawable) {
+        // get the delegate from the native int.
+        BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+        if (canvasDelegate == null) {
+            return;
+        }
+
+        // get the paint which can be null if nPaint is 0;
+        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint);
+
+        canvasDelegate.getSnapshot().draw(drawable, paintDelegate, compositeOnly, forceSrcMode);
+    }
+
+    /**
+     * Executes a {@link GcSnapshot.Drawable} with a given canvas. No paint object will be provided
+     * to {@link GcSnapshot.Drawable#draw(Graphics2D, Paint_Delegate)}.
+     * <p>Note that the drawable may actually be executed several times if there are
+     * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}.
+     */
+    private static void draw(long nCanvas, GcSnapshot.Drawable drawable) {
+        // get the delegate from the native int.
+        BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+        if (canvasDelegate == null) {
+            return;
+        }
+
+        canvasDelegate.mSnapshot.draw(drawable);
+    }
+
+    private static void drawText(long nativeCanvas, final char[] text, final int index,
+            final int count, final float startX, final float startY, final int bidiFlags,
+            long paint) {
+
+        draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+                (graphics, paintDelegate) -> {
+                    // WARNING: the logic in this method is similar to Paint_Delegate.measureText.
+                    // Any change to this method should be reflected in Paint.measureText
+
+                    // Paint.TextAlign indicates how the text is positioned relative to X.
+                    // LEFT is the default and there's nothing to do.
+                    float x = startX;
+                    int limit = index + count;
+                    if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) {
+                        RectF bounds =
+                                paintDelegate.measureText(text, index, count, null, 0, bidiFlags);
+                        float m = bounds.right - bounds.left;
+                        if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) {
+                            x -= m / 2;
+                        } else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) {
+                            x -= m;
+                        }
+                    }
+
+                    new BidiRenderer(graphics, paintDelegate, text).setRenderLocation(x,
+                            startY).renderText(index, limit, bidiFlags, null, 0, true);
+                });
+    }
+
+    private static void drawBitmap(long nativeCanvas, Bitmap_Delegate bitmap,
+            long nativePaintOrZero, final int sleft, final int stop, final int sright,
+            final int sbottom, final int dleft, final int dtop, final int dright,
+            final int dbottom) {
+        // get the delegate from the native int.
+        BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            return;
+        }
+
+        // get the paint, which could be null if the int is 0
+        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero);
+
+        final BufferedImage image = getImageToDraw(bitmap, paintDelegate, sBoolOut);
+
+        draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, sBoolOut[0],
+                (graphics, paint) -> {
+                    if (paint != null && paint.isFilterBitmap()) {
+                        graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+                                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+                    }
+
+                    //FIXME add support for canvas, screen and bitmap densities.
+                    graphics.drawImage(image, dleft, dtop, dright, dbottom, sleft, stop, sright,
+                            sbottom, null);
+                });
+    }
+
+    /**
+     * Returns a BufferedImage ready for drawing, based on the bitmap and paint delegate.
+     * The image returns, through a 1-size boolean array, whether the drawing code should
+     * use a SRC composite no matter what the paint says.
+     *
+     * @param bitmap the bitmap
+     * @param paint the paint that will be used to draw
+     * @param forceSrcMode whether the composite will have to be SRC
+     * @return the image to draw
+     */
+    private static BufferedImage getImageToDraw(Bitmap_Delegate bitmap, Paint_Delegate paint,
+            boolean[] forceSrcMode) {
+        BufferedImage image = bitmap.getImage();
+        forceSrcMode[0] = false;
+
+        // if the bitmap config is alpha_8, then we erase all color value from it
+        // before drawing it or apply the texture from the shader if present.
+        if (bitmap.getConfig() == Bitmap.Config.ALPHA_8) {
+            Shader_Delegate shader = paint.getShader();
+            java.awt.Paint javaPaint = null;
+            if (shader instanceof BitmapShader_Delegate) {
+                javaPaint = shader.getJavaPaint();
+            }
+
+            fixAlpha8Bitmap(image, javaPaint);
+        } else if (!bitmap.hasAlpha()) {
+            // hasAlpha is merely a rendering hint. There can in fact be alpha values
+            // in the bitmap but it should be ignored at drawing time.
+            // There is two ways to do this:
+            // - override the composite to be SRC. This can only be used if the composite
+            //   was going to be SRC or SRC_OVER in the first place
+            // - Create a different bitmap to draw in which all the alpha channel values is set
+            //   to 0xFF.
+            if (paint != null) {
+                PorterDuff.Mode mode = PorterDuff.intToMode(paint.getPorterDuffMode());
+
+                forceSrcMode[0] = mode == PorterDuff.Mode.SRC_OVER || mode == PorterDuff.Mode.SRC;
+            }
+
+            // if we can't force SRC mode, then create a temp bitmap of TYPE_RGB
+            if (!forceSrcMode[0]) {
+                image = Bitmap_Delegate.createCopy(image, BufferedImage.TYPE_INT_RGB, 0xFF);
+            }
+        }
+
+        return image;
+    }
+
+    /**
+     * This method will apply the correct color to the passed "only alpha" image. Colors on the
+     * passed image will be destroyed.
+     * If the passed javaPaint is null, the color will be set to 0. If a paint is passed, it will
+     * be used to obtain the color that will be applied.
+     * <p/>
+     * This will destroy the passed image color channel.
+     */
+    private static void fixAlpha8Bitmap(final BufferedImage image,
+            @Nullable java.awt.Paint javaPaint) {
+        int w = image.getWidth();
+        int h = image.getHeight();
+
+        DataBuffer texture = null;
+        if (javaPaint != null) {
+            PaintContext context = javaPaint.createContext(ColorModel.getRGBdefault(), null, null,
+                    new AffineTransform(), null);
+            texture = context.getRaster(0, 0, w, h).getDataBuffer();
+        }
+
+        int[] argb = new int[w * h];
+        image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth());
+
+        final int length = argb.length;
+        for (int i = 0; i < length; i++) {
+            argb[i] &= 0xFF000000;
+            if (texture != null) {
+                argb[i] |= texture.getElem(i) & 0x00FFFFFF;
+            }
+        }
+
+        image.setRGB(0, 0, w, h, argb, 0, w);
+    }
+
+    protected int save(int saveFlags) {
+        // get the current save count
+        int count = mSnapshot.size();
+
+        mSnapshot = mSnapshot.save(saveFlags);
+
+        // return the old save count
+        return count;
+    }
+
+    protected int saveLayerAlpha(RectF rect, int alpha, int saveFlags) {
+        Paint_Delegate paint = new Paint_Delegate();
+        paint.setAlpha(alpha);
+        return saveLayer(rect, paint, saveFlags);
+    }
+
+    protected int saveLayer(RectF rect, Paint_Delegate paint, int saveFlags) {
+        // get the current save count
+        int count = mSnapshot.size();
+
+        mSnapshot = mSnapshot.saveLayer(rect, paint, saveFlags);
+
+        // return the old save count
+        return count;
+    }
+
+    /**
+     * Restores the {@link GcSnapshot} to <var>saveCount</var>
+     * @param saveCount the saveCount
+     */
+    protected void restoreTo(int saveCount) {
+        mSnapshot = mSnapshot.restoreTo(saveCount);
+    }
+
+    /**
+     * Restores the top {@link GcSnapshot}
+     */
+    protected void restore() {
+        mSnapshot = mSnapshot.restore();
+    }
+
+    protected boolean clipRect(float left, float top, float right, float bottom, int regionOp) {
+        return mSnapshot.clipRect(left, top, right, bottom, regionOp);
+    }
+}
diff --git a/android/graphics/BidiRenderer.java b/android/graphics/BidiRenderer.java
new file mode 100644
index 0000000..9664a58
--- /dev/null
+++ b/android/graphics/BidiRenderer.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2013 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;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Paint_Delegate.FontInfo;
+import android.icu.lang.UScriptRun;
+import android.icu.text.Bidi;
+import android.icu.text.BidiRun;
+
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.Toolkit;
+import java.awt.font.FontRenderContext;
+import java.awt.font.GlyphVector;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Render the text by breaking it into various scripts and using the right font for each script.
+ * Can be used to measure the text without actually drawing it.
+ */
+@SuppressWarnings("deprecation")
+public class BidiRenderer {
+    private static String JAVA_VENDOR = System.getProperty("java.vendor");
+
+    private static class ScriptRun {
+        private final int start;
+        private final int limit;
+        private final Font font;
+
+        private ScriptRun(int start, int limit, @NonNull Font font) {
+            this.start = start;
+            this.limit = limit;
+            this.font = font;
+        }
+    }
+
+    private final Graphics2D mGraphics;
+    private final Paint_Delegate mPaint;
+    private char[] mText;
+    // Bounds of the text drawn so far.
+    private RectF mBounds;
+    private float mBaseline;
+    private final Bidi mBidi = new Bidi();
+
+
+    /**
+     * @param graphics May be null.
+     * @param paint The Paint to use to get the fonts. Should not be null.
+     * @param text Unidirectional text. Should not be null.
+     */
+    public BidiRenderer(Graphics2D graphics, Paint_Delegate paint, char[] text) {
+        assert (paint != null);
+        mGraphics = graphics;
+        mPaint = paint;
+        mText = text;
+        mBounds = new RectF();
+    }
+
+    /**
+     *
+     * @param x The x-coordinate of the left edge of where the text should be drawn on the given
+     *            graphics.
+     * @param y The y-coordinate at which to draw the text on the given mGraphics.
+     *
+     */
+    public BidiRenderer setRenderLocation(float x, float y) {
+        mBounds.set(x, y, x, y);
+        mBaseline = y;
+        return this;
+    }
+
+    /**
+     * Perform Bidi Analysis on the text and then render it.
+     * <p/>
+     * To skip the analysis and render unidirectional text, see {@link
+     * #renderText(int, int, boolean, float[], int, boolean)}
+     */
+    public RectF renderText(int start, int limit, int bidiFlags, float[] advances,
+            int advancesIndex, boolean draw) {
+        mBidi.setPara(Arrays.copyOfRange(mText, start, limit), (byte)getIcuFlags(bidiFlags), null);
+        mText = mBidi.getText();
+        for (int i = 0; i < mBidi.countRuns(); i++) {
+            BidiRun visualRun = mBidi.getVisualRun(i);
+            boolean isRtl = visualRun.getDirection() == Bidi.RTL;
+            renderText(visualRun.getStart(), visualRun.getLimit(), isRtl, advances,
+                    advancesIndex, draw);
+        }
+        return mBounds;
+    }
+
+    /**
+     * Render unidirectional text.
+     * <p/>
+     * This method can also be used to measure the width of the text without actually drawing it.
+     * <p/>
+     * @param start index of the first character
+     * @param limit index of the first character that should not be rendered.
+     * @param isRtl is the text right-to-left
+     * @param advances If not null, then advances for each character to be rendered are returned
+     *            here.
+     * @param advancesIndex index into advances from where the advances need to be filled.
+     * @param draw If true and {@code graphics} is not null, draw the rendered text on the graphics
+     *            at the given co-ordinates
+     * @return A rectangle specifying the bounds of the text drawn.
+     */
+    public RectF renderText(int start, int limit, boolean isRtl, float[] advances,
+            int advancesIndex, boolean draw) {
+        // We break the text into scripts and then select font based on it and then render each of
+        // the script runs.
+        for (ScriptRun run : getScriptRuns(mText, start, limit, mPaint.getFonts())) {
+            int flag = Font.LAYOUT_NO_LIMIT_CONTEXT | Font.LAYOUT_NO_START_CONTEXT;
+            flag |= isRtl ? Font.LAYOUT_RIGHT_TO_LEFT : Font.LAYOUT_LEFT_TO_RIGHT;
+            renderScript(run.start, run.limit, run.font, flag, advances, advancesIndex, draw);
+            advancesIndex += run.limit - run.start;
+        }
+        return mBounds;
+    }
+
+    /**
+     * Render a script run to the right of the bounds passed. Use the preferred font to render as
+     * much as possible. This also implements a fallback mechanism to render characters that cannot
+     * be drawn using the preferred font.
+     */
+    private void renderScript(int start, int limit, Font preferredFont, int flag,
+            float[] advances, int advancesIndex, boolean draw) {
+        if (mPaint.getFonts().size() == 0 || preferredFont == null) {
+            return;
+        }
+
+        while (start < limit) {
+            boolean foundFont = false;
+            int canDisplayUpTo = preferredFont.canDisplayUpTo(mText, start, limit);
+            if (canDisplayUpTo == -1) {
+                // We can draw all characters in the text.
+                render(start, limit, preferredFont, flag, advances, advancesIndex, draw);
+                return;
+            }
+            if (canDisplayUpTo > start) {
+                // We can draw something.
+                render(start, canDisplayUpTo, preferredFont, flag, advances, advancesIndex, draw);
+                advancesIndex += canDisplayUpTo - start;
+                start = canDisplayUpTo;
+            }
+
+            // The current character cannot be drawn with the preferred font. Cycle through all the
+            // fonts to check which one can draw it.
+            int charCount = Character.isHighSurrogate(mText[start]) ? 2 : 1;
+            List<FontInfo> fontInfos = mPaint.getFonts();
+            //noinspection ForLoopReplaceableByForEach (avoid iterator allocation)
+            for (int i = 0; i < fontInfos.size(); i++) {
+                Font font = fontInfos.get(i).mFont;
+                if (font == null) {
+                    logFontWarning();
+                    continue;
+                }
+                canDisplayUpTo = font.canDisplayUpTo(mText, start, start + charCount);
+                if (canDisplayUpTo == -1) {
+                    render(start, start+charCount, font, flag, advances, advancesIndex, draw);
+                    start += charCount;
+                    advancesIndex += charCount;
+                    foundFont = true;
+                    break;
+                }
+            }
+            if (!foundFont) {
+                // No font can display this char. Use the preferred font. The char will most
+                // probably appear as a box or a blank space. We could, probably, use some
+                // heuristics and break the character into the base character and diacritics and
+                // then draw it, but it's probably not worth the effort.
+                render(start, start + charCount, preferredFont, flag, advances, advancesIndex,
+                        draw);
+                start += charCount;
+                advancesIndex += charCount;
+            }
+        }
+    }
+
+    private static void logFontWarning() {
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN,
+                "Some fonts could not be loaded. The rendering may not be perfect. " +
+                        "Try running the IDE with JRE 7.", null, null);
+    }
+
+    /**
+     * Renders the text to the right of the bounds with the given font.
+     * @param font The font to render the text with.
+     */
+    private void render(int start, int limit, Font font, int flag, float[] advances,
+            int advancesIndex, boolean draw) {
+
+        FontRenderContext frc;
+        if (mGraphics != null) {
+            frc = mGraphics.getFontRenderContext();
+        } else {
+            frc = Toolkit.getDefaultToolkit().getFontMetrics(font).getFontRenderContext();
+
+            // Metrics obtained this way don't have anti-aliasing set. So,
+            // we create a new FontRenderContext with anti-aliasing set.
+            AffineTransform transform = font.getTransform();
+            if (mPaint.isAntiAliased() &&
+                    // Workaround for http://b.android.com/211659
+                    (transform.getScaleX() <= 9.9 ||
+                    !"JetBrains s.r.o".equals(JAVA_VENDOR))) {
+                frc = new FontRenderContext(transform, true, frc.usesFractionalMetrics());
+            }
+        }
+        GlyphVector gv = font.layoutGlyphVector(frc, mText, start, limit, flag);
+        int ng = gv.getNumGlyphs();
+        int[] ci = gv.getGlyphCharIndices(0, ng, null);
+        if (advances != null) {
+            for (int i = 0; i < ng; i++) {
+                int adv_idx = advancesIndex + ci[i];
+                advances[adv_idx] += gv.getGlyphMetrics(i).getAdvanceX();
+            }
+        }
+        if (draw && mGraphics != null) {
+            mGraphics.drawGlyphVector(gv, mBounds.right, mBaseline);
+        }
+
+        // Update the bounds.
+        Rectangle2D awtBounds = gv.getLogicalBounds();
+        // If the width of the bounds is zero, no text had been drawn earlier. Hence, use the
+        // coordinates from the bounds as an offset.
+        if (Math.abs(mBounds.right - mBounds.left) == 0) {
+            mBounds = awtRectToAndroidRect(awtBounds, mBounds.right, mBaseline, mBounds);
+        } else {
+            mBounds.union(awtRectToAndroidRect(awtBounds, mBounds.right, mBaseline, null));
+        }
+    }
+
+    // --- Static helper methods ---
+
+    private static RectF awtRectToAndroidRect(Rectangle2D awtRec, float offsetX, float offsetY,
+            @Nullable RectF destination) {
+        float left = (float) awtRec.getX();
+        float top = (float) awtRec.getY();
+        float right = (float) (left + awtRec.getWidth());
+        float bottom = (float) (top + awtRec.getHeight());
+        if (destination != null) {
+            destination.set(left, top, right, bottom);
+        } else {
+            destination = new RectF(left, top, right, bottom);
+        }
+        destination.offset(offsetX, offsetY);
+        return destination;
+    }
+
+    private static List<ScriptRun> getScriptRuns(char[] text, int start, int limit, List<FontInfo> fonts) {
+        LinkedList<ScriptRun> scriptRuns = new LinkedList<>();
+
+        int count = limit - start;
+        UScriptRun uScriptRun = new UScriptRun(text, start, count);
+        while (uScriptRun.next()) {
+            int scriptStart = uScriptRun.getScriptStart();
+            int scriptLimit = uScriptRun.getScriptLimit();
+            ScriptRun run = new ScriptRun(
+                    scriptStart, scriptLimit,
+                    getScriptFont(text, scriptStart, scriptLimit, fonts));
+            scriptRuns.add(run);
+        }
+        return scriptRuns;
+    }
+
+    // TODO: Replace this method with one which returns the font based on the scriptCode.
+    @NonNull
+    private static Font getScriptFont(char[] text, int start, int limit, List<FontInfo> fonts) {
+        for (FontInfo fontInfo : fonts) {
+            if (fontInfo.mFont == null) {
+                logFontWarning();
+                continue;
+            }
+            if (fontInfo.mFont.canDisplayUpTo(text, start, limit) == -1) {
+                return fontInfo.mFont;
+            }
+        }
+
+        return fonts.get(0).mFont;
+    }
+
+    private static int getIcuFlags(int bidiFlag) {
+        switch (bidiFlag) {
+            case Paint.BIDI_LTR:
+            case Paint.BIDI_FORCE_LTR:
+                return Bidi.DIRECTION_LEFT_TO_RIGHT;
+            case Paint.BIDI_RTL:
+            case Paint.BIDI_FORCE_RTL:
+                return Bidi.DIRECTION_RIGHT_TO_LEFT;
+            case Paint.BIDI_DEFAULT_LTR:
+                return Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT;
+            case Paint.BIDI_DEFAULT_RTL:
+                return Bidi.DIRECTION_DEFAULT_RIGHT_TO_LEFT;
+            default:
+                assert false;
+                return Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT;
+        }
+    }
+}
diff --git a/android/graphics/Bitmap.java b/android/graphics/Bitmap.java
new file mode 100644
index 0000000..57c7549
--- /dev/null
+++ b/android/graphics/Bitmap.java
@@ -0,0 +1,1993 @@
+/*
+ * 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;
+
+import android.annotation.CheckResult;
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.Size;
+import android.content.res.ResourcesImpl;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.StrictMode;
+import android.os.Trace;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import libcore.util.NativeAllocationRegistry;
+
+import java.io.OutputStream;
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+
+public final class Bitmap implements Parcelable {
+    private static final String TAG = "Bitmap";
+
+    /**
+     * Indicates that the bitmap was created for an unknown pixel density.
+     *
+     * @see Bitmap#getDensity()
+     * @see Bitmap#setDensity(int)
+     */
+    public static final int DENSITY_NONE = 0;
+
+    // Estimated size of the Bitmap native allocation, not including
+    // pixel data.
+    private static final long NATIVE_ALLOCATION_SIZE = 32;
+
+    // Convenience for JNI access
+    private final long mNativePtr;
+
+    private final boolean mIsMutable;
+
+    /**
+     * Represents whether the Bitmap's content is requested to be pre-multiplied.
+     * Note that isPremultiplied() does not directly return this value, because
+     * isPremultiplied() may never return true for a 565 Bitmap or a bitmap
+     * without alpha.
+     *
+     * setPremultiplied() does directly set the value so that setConfig() and
+     * setPremultiplied() aren't order dependent, despite being setters.
+     *
+     * The native bitmap's premultiplication state is kept up to date by
+     * pushing down this preference for every config change.
+     */
+    private boolean mRequestPremultiplied;
+
+    private byte[] mNinePatchChunk; // may be null
+    private NinePatch.InsetStruct mNinePatchInsets; // may be null
+    private int mWidth;
+    private int mHeight;
+    private boolean mRecycled;
+
+    private ColorSpace mColorSpace;
+
+    /** @hide */
+    public int mDensity = getDefaultDensity();
+
+    private static volatile int sDefaultDensity = -1;
+
+    /** @hide Used only when ResourcesImpl.TRACE_FOR_DETAILED_PRELOAD is true. */
+    public static volatile int sPreloadTracingNumInstantiatedBitmaps;
+
+    /** @hide Used only when ResourcesImpl.TRACE_FOR_DETAILED_PRELOAD is true. */
+    public static volatile long sPreloadTracingTotalBitmapsSize;
+
+    /**
+     * For backwards compatibility, allows the app layer to change the default
+     * density when running old apps.
+     * @hide
+     */
+    public static void setDefaultDensity(int density) {
+        sDefaultDensity = density;
+    }
+
+    @SuppressWarnings("deprecation")
+    static int getDefaultDensity() {
+        if (sDefaultDensity >= 0) {
+            return sDefaultDensity;
+        }
+        sDefaultDensity = DisplayMetrics.DENSITY_DEVICE;
+        return sDefaultDensity;
+    }
+
+    /**
+     * Private constructor that must received an already allocated native bitmap
+     * int (pointer).
+     */
+    // called from JNI
+    Bitmap(long nativeBitmap, int width, int height, int density,
+            boolean isMutable, boolean requestPremultiplied,
+            byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {
+        if (nativeBitmap == 0) {
+            throw new RuntimeException("internal error: native bitmap is 0");
+        }
+
+        mWidth = width;
+        mHeight = height;
+        mIsMutable = isMutable;
+        mRequestPremultiplied = requestPremultiplied;
+
+        mNinePatchChunk = ninePatchChunk;
+        mNinePatchInsets = ninePatchInsets;
+        if (density >= 0) {
+            mDensity = density;
+        }
+
+        mNativePtr = nativeBitmap;
+        long nativeSize = NATIVE_ALLOCATION_SIZE + getAllocationByteCount();
+        NativeAllocationRegistry registry = new NativeAllocationRegistry(
+            Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), nativeSize);
+        registry.registerNativeAllocation(this, nativeBitmap);
+
+        if (ResourcesImpl.TRACE_FOR_DETAILED_PRELOAD) {
+            sPreloadTracingNumInstantiatedBitmaps++;
+            sPreloadTracingTotalBitmapsSize += nativeSize;
+        }
+    }
+
+    /**
+     * Return the pointer to the native object.
+     * @hide
+     */
+    public long getNativeInstance() {
+        return mNativePtr;
+    }
+
+    /**
+     * Native bitmap has been reconfigured, so set premult and cached
+     * width/height values
+     */
+    @SuppressWarnings("unused") // called from JNI
+    void reinit(int width, int height, boolean requestPremultiplied) {
+        mWidth = width;
+        mHeight = height;
+        mRequestPremultiplied = requestPremultiplied;
+        mColorSpace = null;
+    }
+
+    /**
+     * <p>Returns the density for this bitmap.</p>
+     *
+     * <p>The default density is the same density as the current display,
+     * unless the current application does not support different screen
+     * densities in which case it is
+     * {@link android.util.DisplayMetrics#DENSITY_DEFAULT}.  Note that
+     * compatibility mode is determined by the application that was initially
+     * loaded into a process -- applications that share the same process should
+     * all have the same compatibility, or ensure they explicitly set the
+     * density of their bitmaps appropriately.</p>
+     *
+     * @return A scaling factor of the default density or {@link #DENSITY_NONE}
+     *         if the scaling factor is unknown.
+     *
+     * @see #setDensity(int)
+     * @see android.util.DisplayMetrics#DENSITY_DEFAULT
+     * @see android.util.DisplayMetrics#densityDpi
+     * @see #DENSITY_NONE
+     */
+    public int getDensity() {
+        if (mRecycled) {
+            Log.w(TAG, "Called getDensity() on a recycle()'d bitmap! This is undefined behavior!");
+        }
+        return mDensity;
+    }
+
+    /**
+     * <p>Specifies the density for this bitmap.  When the bitmap is
+     * drawn to a Canvas that also has a density, it will be scaled
+     * appropriately.</p>
+     *
+     * @param density The density scaling factor to use with this bitmap or
+     *        {@link #DENSITY_NONE} if the density is unknown.
+     *
+     * @see #getDensity()
+     * @see android.util.DisplayMetrics#DENSITY_DEFAULT
+     * @see android.util.DisplayMetrics#densityDpi
+     * @see #DENSITY_NONE
+     */
+    public void setDensity(int density) {
+        mDensity = density;
+    }
+
+    /**
+     * <p>Modifies the bitmap to have a specified width, height, and {@link
+     * Config}, without affecting the underlying allocation backing the bitmap.
+     * Bitmap pixel data is not re-initialized for the new configuration.</p>
+     *
+     * <p>This method can be used to avoid allocating a new bitmap, instead
+     * reusing an existing bitmap's allocation for a new configuration of equal
+     * or lesser size. If the Bitmap's allocation isn't large enough to support
+     * the new configuration, an IllegalArgumentException will be thrown and the
+     * bitmap will not be modified.</p>
+     *
+     * <p>The result of {@link #getByteCount()} will reflect the new configuration,
+     * while {@link #getAllocationByteCount()} will reflect that of the initial
+     * configuration.</p>
+     *
+     * <p>Note: This may change this result of hasAlpha(). When converting to 565,
+     * the new bitmap will always be considered opaque. When converting from 565,
+     * the new bitmap will be considered non-opaque, and will respect the value
+     * set by setPremultiplied().</p>
+     *
+     * <p>WARNING: This method should NOT be called on a bitmap currently in use
+     * by the view system, Canvas, or the AndroidBitmap NDK API. It does not
+     * make guarantees about how the underlying pixel buffer is remapped to the
+     * new config, just that the allocation is reused. Additionally, the view
+     * system does not account for bitmap properties being modifying during use,
+     * e.g. while attached to drawables.</p>
+     *
+     * <p>In order to safely ensure that a Bitmap is no longer in use by the
+     * View system it is necessary to wait for a draw pass to occur after
+     * invalidate()'ing any view that had previously drawn the Bitmap in the last
+     * draw pass due to hardware acceleration's caching of draw commands. As
+     * an example, here is how this can be done for an ImageView:
+     * <pre class="prettyprint">
+     *      ImageView myImageView = ...;
+     *      final Bitmap myBitmap = ...;
+     *      myImageView.setImageDrawable(null);
+     *      myImageView.post(new Runnable() {
+     *          public void run() {
+     *              // myBitmap is now no longer in use by the ImageView
+     *              // and can be safely reconfigured.
+     *              myBitmap.reconfigure(...);
+     *          }
+     *      });
+     * </pre></p>
+     *
+     * @see #setWidth(int)
+     * @see #setHeight(int)
+     * @see #setConfig(Config)
+     */
+    public void reconfigure(int width, int height, Config config) {
+        checkRecycled("Can't call reconfigure() on a recycled bitmap");
+        if (width <= 0 || height <= 0) {
+            throw new IllegalArgumentException("width and height must be > 0");
+        }
+        if (!isMutable()) {
+            throw new IllegalStateException("only mutable bitmaps may be reconfigured");
+        }
+
+        nativeReconfigure(mNativePtr, width, height, config.nativeInt, mRequestPremultiplied);
+        mWidth = width;
+        mHeight = height;
+        mColorSpace = null;
+    }
+
+    /**
+     * <p>Convenience method for calling {@link #reconfigure(int, int, Config)}
+     * with the current height and config.</p>
+     *
+     * <p>WARNING: this method should not be used on bitmaps currently used by
+     * the view system, see {@link #reconfigure(int, int, Config)} for more
+     * details.</p>
+     *
+     * @see #reconfigure(int, int, Config)
+     * @see #setHeight(int)
+     * @see #setConfig(Config)
+     */
+    public void setWidth(int width) {
+        reconfigure(width, getHeight(), getConfig());
+    }
+
+    /**
+     * <p>Convenience method for calling {@link #reconfigure(int, int, Config)}
+     * with the current width and config.</p>
+     *
+     * <p>WARNING: this method should not be used on bitmaps currently used by
+     * the view system, see {@link #reconfigure(int, int, Config)} for more
+     * details.</p>
+     *
+     * @see #reconfigure(int, int, Config)
+     * @see #setWidth(int)
+     * @see #setConfig(Config)
+     */
+    public void setHeight(int height) {
+        reconfigure(getWidth(), height, getConfig());
+    }
+
+    /**
+     * <p>Convenience method for calling {@link #reconfigure(int, int, Config)}
+     * with the current height and width.</p>
+     *
+     * <p>WARNING: this method should not be used on bitmaps currently used by
+     * the view system, see {@link #reconfigure(int, int, Config)} for more
+     * details.</p>
+     *
+     * @see #reconfigure(int, int, Config)
+     * @see #setWidth(int)
+     * @see #setHeight(int)
+     */
+    public void setConfig(Config config) {
+        reconfigure(getWidth(), getHeight(), config);
+    }
+
+    /**
+     * Sets the nine patch chunk.
+     *
+     * @param chunk The definition of the nine patch
+     *
+     * @hide
+     */
+    public void setNinePatchChunk(byte[] chunk) {
+        mNinePatchChunk = chunk;
+    }
+
+    /**
+     * Free the native object associated with this bitmap, and clear the
+     * reference to the pixel data. This will not free the pixel data synchronously;
+     * it simply allows it to be garbage collected if there are no other references.
+     * The bitmap is marked as "dead", meaning it will throw an exception if
+     * getPixels() or setPixels() is called, and will draw nothing. This operation
+     * cannot be reversed, so it should only be called if you are sure there are no
+     * further uses for the bitmap. This is an advanced call, and normally need
+     * not be called, since the normal GC process will free up this memory when
+     * there are no more references to this bitmap.
+     */
+    public void recycle() {
+        if (!mRecycled && mNativePtr != 0) {
+            if (nativeRecycle(mNativePtr)) {
+                // return value indicates whether native pixel object was actually recycled.
+                // false indicates that it is still in use at the native level and these
+                // objects should not be collected now. They will be collected later when the
+                // Bitmap itself is collected.
+                mNinePatchChunk = null;
+            }
+            mRecycled = true;
+        }
+    }
+
+    /**
+     * Returns true if this bitmap has been recycled. If so, then it is an error
+     * to try to access its pixels, and the bitmap will not draw.
+     *
+     * @return true if the bitmap has been recycled
+     */
+    public final boolean isRecycled() {
+        return mRecycled;
+    }
+
+    /**
+     * Returns the generation ID of this bitmap. The generation ID changes
+     * whenever the bitmap is modified. This can be used as an efficient way to
+     * check if a bitmap has changed.
+     *
+     * @return The current generation ID for this bitmap.
+     */
+    public int getGenerationId() {
+        if (mRecycled) {
+            Log.w(TAG, "Called getGenerationId() on a recycle()'d bitmap! This is undefined behavior!");
+        }
+        return nativeGenerationId(mNativePtr);
+    }
+
+    /**
+     * This is called by methods that want to throw an exception if the bitmap
+     * has already been recycled.
+     */
+    private void checkRecycled(String errorMessage) {
+        if (mRecycled) {
+            throw new IllegalStateException(errorMessage);
+        }
+    }
+
+    /**
+     * This is called by methods that want to throw an exception if the bitmap
+     * is {@link Config#HARDWARE}.
+     */
+    private void checkHardware(String errorMessage) {
+        if (getConfig() == Config.HARDWARE) {
+            throw new IllegalStateException(errorMessage);
+        }
+    }
+
+    /**
+     * Common code for checking that x and y are >= 0
+     *
+     * @param x x coordinate to ensure is >= 0
+     * @param y y coordinate to ensure is >= 0
+     */
+    private static void checkXYSign(int x, int y) {
+        if (x < 0) {
+            throw new IllegalArgumentException("x must be >= 0");
+        }
+        if (y < 0) {
+            throw new IllegalArgumentException("y must be >= 0");
+        }
+    }
+
+    /**
+     * Common code for checking that width and height are > 0
+     *
+     * @param width  width to ensure is > 0
+     * @param height height to ensure is > 0
+     */
+    private static void checkWidthHeight(int width, int height) {
+        if (width <= 0) {
+            throw new IllegalArgumentException("width must be > 0");
+        }
+        if (height <= 0) {
+            throw new IllegalArgumentException("height must be > 0");
+        }
+    }
+
+    /**
+     * Possible bitmap configurations. A bitmap configuration describes
+     * how pixels are stored. This affects the quality (color depth) as
+     * well as the ability to display transparent/translucent colors.
+     */
+    public enum Config {
+        // these native values must match up with the enum in SkBitmap.h
+
+        /**
+         * Each pixel is stored as a single translucency (alpha) channel.
+         * This is very useful to efficiently store masks for instance.
+         * No color information is stored.
+         * With this configuration, each pixel requires 1 byte of memory.
+         */
+        ALPHA_8     (1),
+
+        /**
+         * Each pixel is stored on 2 bytes and only the RGB channels are
+         * encoded: red is stored with 5 bits of precision (32 possible
+         * values), green is stored with 6 bits of precision (64 possible
+         * values) and blue is stored with 5 bits of precision.
+         *
+         * This configuration can produce slight visual artifacts depending
+         * on the configuration of the source. For instance, without
+         * dithering, the result might show a greenish tint. To get better
+         * results dithering should be applied.
+         *
+         * This configuration may be useful when using opaque bitmaps
+         * that do not require high color fidelity.
+         */
+        RGB_565     (3),
+
+        /**
+         * Each pixel is stored on 2 bytes. The three RGB color channels
+         * and the alpha channel (translucency) are stored with a 4 bits
+         * precision (16 possible values.)
+         *
+         * This configuration is mostly useful if the application needs
+         * to store translucency information but also needs to save
+         * memory.
+         *
+         * It is recommended to use {@link #ARGB_8888} instead of this
+         * configuration.
+         *
+         * Note: as of {@link android.os.Build.VERSION_CODES#KITKAT},
+         * any bitmap created with this configuration will be created
+         * using {@link #ARGB_8888} instead.
+         *
+         * @deprecated Because of the poor quality of this configuration,
+         *             it is advised to use {@link #ARGB_8888} instead.
+         */
+        @Deprecated
+        ARGB_4444   (4),
+
+        /**
+         * Each pixel is stored on 4 bytes. Each channel (RGB and alpha
+         * for translucency) is stored with 8 bits of precision (256
+         * possible values.)
+         *
+         * This configuration is very flexible and offers the best
+         * quality. It should be used whenever possible.
+         */
+        ARGB_8888   (5),
+
+        /**
+         * Each pixels is stored on 8 bytes. Each channel (RGB and alpha
+         * for translucency) is stored as a
+         * {@link android.util.Half half-precision floating point value}.
+         *
+         * This configuration is particularly suited for wide-gamut and
+         * HDR content.
+         */
+        RGBA_F16    (6),
+
+        /**
+         * Special configuration, when bitmap is stored only in graphic memory.
+         * Bitmaps in this configuration are always immutable.
+         *
+         * It is optimal for cases, when the only operation with the bitmap is to draw it on a
+         * screen.
+         */
+        HARDWARE    (7);
+
+        final int nativeInt;
+
+        private static Config sConfigs[] = {
+            null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888, RGBA_F16, HARDWARE
+        };
+
+        Config(int ni) {
+            this.nativeInt = ni;
+        }
+
+        static Config nativeToConfig(int ni) {
+            return sConfigs[ni];
+        }
+    }
+
+    /**
+     * <p>Copy the bitmap's pixels into the specified buffer (allocated by the
+     * caller). An exception is thrown if the buffer is not large enough to
+     * hold all of the pixels (taking into account the number of bytes per
+     * pixel) or if the Buffer subclass is not one of the support types
+     * (ByteBuffer, ShortBuffer, IntBuffer).</p>
+     * <p>The content of the bitmap is copied into the buffer as-is. This means
+     * that if this bitmap stores its pixels pre-multiplied
+     * (see {@link #isPremultiplied()}, the values in the buffer will also be
+     * pre-multiplied. The pixels remain in the color space of the bitmap.</p>
+     * <p>After this method returns, the current position of the buffer is
+     * updated: the position is incremented by the number of elements written
+     * in the buffer.</p>
+     * @throws IllegalStateException if the bitmap's config is {@link Config#HARDWARE}
+     */
+    public void copyPixelsToBuffer(Buffer dst) {
+        checkHardware("unable to copyPixelsToBuffer, "
+                + "pixel access is not supported on Config#HARDWARE bitmaps");
+        int elements = dst.remaining();
+        int shift;
+        if (dst instanceof ByteBuffer) {
+            shift = 0;
+        } else if (dst instanceof ShortBuffer) {
+            shift = 1;
+        } else if (dst instanceof IntBuffer) {
+            shift = 2;
+        } else {
+            throw new RuntimeException("unsupported Buffer subclass");
+        }
+
+        long bufferSize = (long)elements << shift;
+        long pixelSize = getByteCount();
+
+        if (bufferSize < pixelSize) {
+            throw new RuntimeException("Buffer not large enough for pixels");
+        }
+
+        nativeCopyPixelsToBuffer(mNativePtr, dst);
+
+        // now update the buffer's position
+        int position = dst.position();
+        position += pixelSize >> shift;
+        dst.position(position);
+    }
+
+    /**
+     * <p>Copy the pixels from the buffer, beginning at the current position,
+     * overwriting the bitmap's pixels. The data in the buffer is not changed
+     * in any way (unlike setPixels(), which converts from unpremultipled 32bit
+     * to whatever the bitmap's native format is. The pixels in the source
+     * buffer are assumed to be in the bitmap's color space.</p>
+     * <p>After this method returns, the current position of the buffer is
+     * updated: the position is incremented by the number of elements read from
+     * the buffer. If you need to read the bitmap from the buffer again you must
+     * first rewind the buffer.</p>
+     * @throws IllegalStateException if the bitmap's config is {@link Config#HARDWARE}
+     */
+    public void copyPixelsFromBuffer(Buffer src) {
+        checkRecycled("copyPixelsFromBuffer called on recycled bitmap");
+        checkHardware("unable to copyPixelsFromBuffer, Config#HARDWARE bitmaps are immutable");
+
+        int elements = src.remaining();
+        int shift;
+        if (src instanceof ByteBuffer) {
+            shift = 0;
+        } else if (src instanceof ShortBuffer) {
+            shift = 1;
+        } else if (src instanceof IntBuffer) {
+            shift = 2;
+        } else {
+            throw new RuntimeException("unsupported Buffer subclass");
+        }
+
+        long bufferBytes = (long) elements << shift;
+        long bitmapBytes = getByteCount();
+
+        if (bufferBytes < bitmapBytes) {
+            throw new RuntimeException("Buffer not large enough for pixels");
+        }
+
+        nativeCopyPixelsFromBuffer(mNativePtr, src);
+
+        // now update the buffer's position
+        int position = src.position();
+        position += bitmapBytes >> shift;
+        src.position(position);
+    }
+
+    private void noteHardwareBitmapSlowCall() {
+        if (getConfig() == Config.HARDWARE) {
+            StrictMode.noteSlowCall("Warning: attempt to read pixels from hardware "
+                    + "bitmap, which is very slow operation");
+        }
+    }
+
+    /**
+     * Tries to make a new bitmap based on the dimensions of this bitmap,
+     * setting the new bitmap's config to the one specified, and then copying
+     * this bitmap's pixels into the new bitmap. If the conversion is not
+     * supported, or the allocator fails, then this returns NULL.  The returned
+     * bitmap has the same density and color space as the original.
+     *
+     * @param config    The desired config for the resulting bitmap
+     * @param isMutable True if the resulting bitmap should be mutable (i.e.
+     *                  its pixels can be modified)
+     * @return the new bitmap, or null if the copy could not be made.
+     * @throws IllegalArgumentException if config is {@link Config#HARDWARE} and isMutable is true
+     */
+    public Bitmap copy(Config config, boolean isMutable) {
+        checkRecycled("Can't copy a recycled bitmap");
+        if (config == Config.HARDWARE && isMutable) {
+            throw new IllegalArgumentException("Hardware bitmaps are always immutable");
+        }
+        noteHardwareBitmapSlowCall();
+        Bitmap b = nativeCopy(mNativePtr, config.nativeInt, isMutable);
+        if (b != null) {
+            b.setPremultiplied(mRequestPremultiplied);
+            b.mDensity = mDensity;
+        }
+        return b;
+    }
+
+    /**
+     * Creates a new immutable bitmap backed by ashmem which can efficiently
+     * be passed between processes. The bitmap is assumed to be in the sRGB
+     * color space.
+     *
+     * @hide
+     */
+    public Bitmap createAshmemBitmap() {
+        checkRecycled("Can't copy a recycled bitmap");
+        noteHardwareBitmapSlowCall();
+        Bitmap b = nativeCopyAshmem(mNativePtr);
+        if (b != null) {
+            b.setPremultiplied(mRequestPremultiplied);
+            b.mDensity = mDensity;
+        }
+        return b;
+    }
+
+    /**
+     * Creates a new immutable bitmap backed by ashmem which can efficiently
+     * be passed between processes. The bitmap is assumed to be in the sRGB
+     * color space.
+     *
+     * @hide
+     */
+    public Bitmap createAshmemBitmap(Config config) {
+        checkRecycled("Can't copy a recycled bitmap");
+        noteHardwareBitmapSlowCall();
+        Bitmap b = nativeCopyAshmemConfig(mNativePtr, config.nativeInt);
+        if (b != null) {
+            b.setPremultiplied(mRequestPremultiplied);
+            b.mDensity = mDensity;
+        }
+        return b;
+    }
+
+    /**
+     * Create hardware bitmap backed GraphicBuffer.
+     *
+     * @return Bitmap or null if this GraphicBuffer has unsupported PixelFormat.
+     *         currently PIXEL_FORMAT_RGBA_8888 is the only supported format
+     * @hide
+     */
+    public static Bitmap createHardwareBitmap(@NonNull GraphicBuffer graphicBuffer) {
+        return nativeCreateHardwareBitmap(graphicBuffer);
+    }
+
+    /**
+     * Creates a new bitmap, scaled from an existing bitmap, when possible. If the
+     * specified width and height are the same as the current width and height of
+     * the source bitmap, the source bitmap is returned and no new bitmap is
+     * created.
+     *
+     * @param src       The source bitmap.
+     * @param dstWidth  The new bitmap's desired width.
+     * @param dstHeight The new bitmap's desired height.
+     * @param filter    true if the source should be filtered.
+     * @return The new scaled bitmap or the source bitmap if no scaling is required.
+     * @throws IllegalArgumentException if width is <= 0, or height is <= 0
+     */
+    public static Bitmap createScaledBitmap(@NonNull Bitmap src, int dstWidth, int dstHeight,
+            boolean filter) {
+        Matrix m = new Matrix();
+
+        final int width = src.getWidth();
+        final int height = src.getHeight();
+        if (width != dstWidth || height != dstHeight) {
+            final float sx = dstWidth / (float) width;
+            final float sy = dstHeight / (float) height;
+            m.setScale(sx, sy);
+        }
+        return Bitmap.createBitmap(src, 0, 0, width, height, m, filter);
+    }
+
+    /**
+     * Returns an immutable bitmap from the source bitmap. The new bitmap may
+     * be the same object as source, or a copy may have been made.  It is
+     * initialized with the same density and color space as the original bitmap.
+     */
+    public static Bitmap createBitmap(@NonNull Bitmap src) {
+        return createBitmap(src, 0, 0, src.getWidth(), src.getHeight());
+    }
+
+    /**
+     * Returns an immutable bitmap from the specified subset of the source
+     * bitmap. The new bitmap may be the same object as source, or a copy may
+     * have been made. It is initialized with the same density and color space
+     * as the original bitmap.
+     *
+     * @param source   The bitmap we are subsetting
+     * @param x        The x coordinate of the first pixel in source
+     * @param y        The y coordinate of the first pixel in source
+     * @param width    The number of pixels in each row
+     * @param height   The number of rows
+     * @return A copy of a subset of the source bitmap or the source bitmap itself.
+     * @throws IllegalArgumentException if the x, y, width, height values are
+     *         outside of the dimensions of the source bitmap, or width is <= 0,
+     *         or height is <= 0
+     */
+    public static Bitmap createBitmap(@NonNull Bitmap source, int x, int y, int width, int height) {
+        return createBitmap(source, x, y, width, height, null, false);
+    }
+
+    /**
+     * Returns an immutable bitmap from subset of the source bitmap,
+     * transformed by the optional matrix. The new bitmap may be the
+     * same object as source, or a copy may have been made. It is
+     * initialized with the same density and color space as the original
+     * bitmap.
+     *
+     * If the source bitmap is immutable and the requested subset is the
+     * same as the source bitmap itself, then the source bitmap is
+     * returned and no new bitmap is created.
+     *
+     * @param source   The bitmap we are subsetting
+     * @param x        The x coordinate of the first pixel in source
+     * @param y        The y coordinate of the first pixel in source
+     * @param width    The number of pixels in each row
+     * @param height   The number of rows
+     * @param m        Optional matrix to be applied to the pixels
+     * @param filter   true if the source should be filtered.
+     *                   Only applies if the matrix contains more than just
+     *                   translation.
+     * @return A bitmap that represents the specified subset of source
+     * @throws IllegalArgumentException if the x, y, width, height values are
+     *         outside of the dimensions of the source bitmap, or width is <= 0,
+     *         or height is <= 0
+     */
+    public static Bitmap createBitmap(@NonNull Bitmap source, int x, int y, int width, int height,
+            @Nullable Matrix m, boolean filter) {
+
+        checkXYSign(x, y);
+        checkWidthHeight(width, height);
+        if (x + width > source.getWidth()) {
+            throw new IllegalArgumentException("x + width must be <= bitmap.width()");
+        }
+        if (y + height > source.getHeight()) {
+            throw new IllegalArgumentException("y + height must be <= bitmap.height()");
+        }
+
+        // check if we can just return our argument unchanged
+        if (!source.isMutable() && x == 0 && y == 0 && width == source.getWidth() &&
+                height == source.getHeight() && (m == null || m.isIdentity())) {
+            return source;
+        }
+
+        boolean isHardware = source.getConfig() == Config.HARDWARE;
+        if (isHardware) {
+            source.noteHardwareBitmapSlowCall();
+            source = nativeCopyPreserveInternalConfig(source.mNativePtr);
+        }
+
+        int neww = width;
+        int newh = height;
+        Bitmap bitmap;
+        Paint paint;
+
+        Rect srcR = new Rect(x, y, x + width, y + height);
+        RectF dstR = new RectF(0, 0, width, height);
+        RectF deviceR = new RectF();
+
+        Config newConfig = Config.ARGB_8888;
+        final Config config = source.getConfig();
+        // GIF files generate null configs, assume ARGB_8888
+        if (config != null) {
+            switch (config) {
+                case RGB_565:
+                    newConfig = Config.RGB_565;
+                    break;
+                case ALPHA_8:
+                    newConfig = Config.ALPHA_8;
+                    break;
+                case RGBA_F16:
+                    newConfig = Config.RGBA_F16;
+                    break;
+                //noinspection deprecation
+                case ARGB_4444:
+                case ARGB_8888:
+                default:
+                    newConfig = Config.ARGB_8888;
+                    break;
+            }
+        }
+
+        if (m == null || m.isIdentity()) {
+            bitmap = createBitmap(neww, newh, newConfig, source.hasAlpha());
+            paint = null;   // not needed
+        } else {
+            final boolean transformed = !m.rectStaysRect();
+
+            m.mapRect(deviceR, dstR);
+
+            neww = Math.round(deviceR.width());
+            newh = Math.round(deviceR.height());
+
+            Config transformedConfig = newConfig;
+            if (transformed) {
+                if (transformedConfig != Config.ARGB_8888 && transformedConfig != Config.RGBA_F16) {
+                    transformedConfig = Config.ARGB_8888;
+                }
+            }
+            bitmap = createBitmap(neww, newh, transformedConfig, transformed || source.hasAlpha());
+
+            paint = new Paint();
+            paint.setFilterBitmap(filter);
+            if (transformed) {
+                paint.setAntiAlias(true);
+            }
+        }
+
+        nativeCopyColorSpace(source.mNativePtr, bitmap.mNativePtr);
+
+        // The new bitmap was created from a known bitmap source so assume that
+        // they use the same density
+        bitmap.mDensity = source.mDensity;
+        bitmap.setHasAlpha(source.hasAlpha());
+        bitmap.setPremultiplied(source.mRequestPremultiplied);
+
+        Canvas canvas = new Canvas(bitmap);
+        canvas.translate(-deviceR.left, -deviceR.top);
+        canvas.concat(m);
+        canvas.drawBitmap(source, srcR, dstR, paint);
+        canvas.setBitmap(null);
+        if (isHardware) {
+            return bitmap.copy(Config.HARDWARE, false);
+        }
+        return bitmap;
+    }
+
+    /**
+     * Returns a mutable bitmap with the specified width and height.  Its
+     * initial density is as per {@link #getDensity}. The newly created
+     * bitmap is in the {@link ColorSpace.Named#SRGB sRGB} color space.
+     *
+     * @param width    The width of the bitmap
+     * @param height   The height of the bitmap
+     * @param config   The bitmap config to create.
+     * @throws IllegalArgumentException if the width or height are <= 0, or if
+     *         Config is Config.HARDWARE, because hardware bitmaps are always immutable
+     */
+    public static Bitmap createBitmap(int width, int height, @NonNull Config config) {
+        return createBitmap(width, height, config, true);
+    }
+
+    /**
+     * Returns a mutable bitmap with the specified width and height.  Its
+     * initial density is determined from the given {@link DisplayMetrics}.
+     * The newly created bitmap is in the {@link ColorSpace.Named#SRGB sRGB}
+     * color space.
+     *
+     * @param display  Display metrics for the display this bitmap will be
+     *                 drawn on.
+     * @param width    The width of the bitmap
+     * @param height   The height of the bitmap
+     * @param config   The bitmap config to create.
+     * @throws IllegalArgumentException if the width or height are <= 0, or if
+     *         Config is Config.HARDWARE, because hardware bitmaps are always immutable
+     */
+    public static Bitmap createBitmap(@Nullable DisplayMetrics display, int width,
+            int height, @NonNull Config config) {
+        return createBitmap(display, width, height, config, true);
+    }
+
+    /**
+     * Returns a mutable bitmap with the specified width and height.  Its
+     * initial density is as per {@link #getDensity}. The newly created
+     * bitmap is in the {@link ColorSpace.Named#SRGB sRGB} color space.
+     *
+     * @param width    The width of the bitmap
+     * @param height   The height of the bitmap
+     * @param config   The bitmap config to create.
+     * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to
+     *                 mark the bitmap as opaque. Doing so will clear the bitmap in black
+     *                 instead of transparent.
+     *
+     * @throws IllegalArgumentException if the width or height are <= 0, or if
+     *         Config is Config.HARDWARE, because hardware bitmaps are always immutable
+     */
+    public static Bitmap createBitmap(int width, int height,
+            @NonNull Config config, boolean hasAlpha) {
+        return createBitmap(null, width, height, config, hasAlpha);
+    }
+
+    /**
+     * Returns a mutable bitmap with the specified width and height.  Its
+     * initial density is as per {@link #getDensity}.
+     *
+     * @param width    The width of the bitmap
+     * @param height   The height of the bitmap
+     * @param config   The bitmap config to create.
+     * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to
+     *                 mark the bitmap as opaque. Doing so will clear the bitmap in black
+     *                 instead of transparent.
+     * @param colorSpace The color space of the bitmap. If the config is {@link Config#RGBA_F16},
+     *                   {@link ColorSpace.Named#EXTENDED_SRGB scRGB} is assumed, and if the
+     *                   config is not {@link Config#ARGB_8888}, {@link ColorSpace.Named#SRGB sRGB}
+     *                   is assumed.
+     *
+     * @throws IllegalArgumentException if the width or height are <= 0, if
+     *         Config is Config.HARDWARE (because hardware bitmaps are always
+     *         immutable), if the specified color space is not {@link ColorSpace.Model#RGB RGB},
+     *         if the specified color space's transfer function is not an
+     *         {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}, or if
+     *         the color space is null
+     */
+    public static Bitmap createBitmap(int width, int height, @NonNull Config config,
+            boolean hasAlpha, @NonNull ColorSpace colorSpace) {
+        return createBitmap(null, width, height, config, hasAlpha, colorSpace);
+    }
+
+    /**
+     * Returns a mutable bitmap with the specified width and height.  Its
+     * initial density is determined from the given {@link DisplayMetrics}.
+     * The newly created bitmap is in the {@link ColorSpace.Named#SRGB sRGB}
+     * color space.
+     *
+     * @param display  Display metrics for the display this bitmap will be
+     *                 drawn on.
+     * @param width    The width of the bitmap
+     * @param height   The height of the bitmap
+     * @param config   The bitmap config to create.
+     * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to
+     *                 mark the bitmap as opaque. Doing so will clear the bitmap in black
+     *                 instead of transparent.
+     *
+     * @throws IllegalArgumentException if the width or height are <= 0, or if
+     *         Config is Config.HARDWARE, because hardware bitmaps are always immutable
+     */
+    public static Bitmap createBitmap(@Nullable DisplayMetrics display, int width, int height,
+            @NonNull Config config, boolean hasAlpha) {
+        return createBitmap(display, width, height, config, hasAlpha,
+                ColorSpace.get(ColorSpace.Named.SRGB));
+    }
+
+    /**
+     * Returns a mutable bitmap with the specified width and height.  Its
+     * initial density is determined from the given {@link DisplayMetrics}.
+     * The newly created bitmap is in the {@link ColorSpace.Named#SRGB sRGB}
+     * color space.
+     *
+     * @param display  Display metrics for the display this bitmap will be
+     *                 drawn on.
+     * @param width    The width of the bitmap
+     * @param height   The height of the bitmap
+     * @param config   The bitmap config to create.
+     * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to
+     *                 mark the bitmap as opaque. Doing so will clear the bitmap in black
+     *                 instead of transparent.
+     * @param colorSpace The color space of the bitmap. If the config is {@link Config#RGBA_F16},
+     *                   {@link ColorSpace.Named#EXTENDED_SRGB scRGB} is assumed, and if the
+     *                   config is not {@link Config#ARGB_8888}, {@link ColorSpace.Named#SRGB sRGB}
+     *                   is assumed.
+     *
+     * @throws IllegalArgumentException if the width or height are <= 0, if
+     *         Config is Config.HARDWARE (because hardware bitmaps are always
+     *         immutable), if the specified color space is not {@link ColorSpace.Model#RGB RGB},
+     *         if the specified color space's transfer function is not an
+     *         {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}, or if
+     *         the color space is null
+     */
+    public static Bitmap createBitmap(@Nullable DisplayMetrics display, int width, int height,
+            @NonNull Config config, boolean hasAlpha, @NonNull ColorSpace colorSpace) {
+        if (width <= 0 || height <= 0) {
+            throw new IllegalArgumentException("width and height must be > 0");
+        }
+        if (config == Config.HARDWARE) {
+            throw new IllegalArgumentException("can't create mutable bitmap with Config.HARDWARE");
+        }
+        if (colorSpace == null) {
+            throw new IllegalArgumentException("can't create bitmap without a color space");
+        }
+
+        Bitmap bm;
+        // nullptr color spaces have a particular meaning in native and are interpreted as sRGB
+        // (we also avoid the unnecessary extra work of the else branch)
+        if (config != Config.ARGB_8888 || colorSpace == ColorSpace.get(ColorSpace.Named.SRGB)) {
+            bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true, null, null);
+        } else {
+            if (!(colorSpace instanceof ColorSpace.Rgb)) {
+                throw new IllegalArgumentException("colorSpace must be an RGB color space");
+            }
+            ColorSpace.Rgb rgb = (ColorSpace.Rgb) colorSpace;
+            ColorSpace.Rgb.TransferParameters parameters = rgb.getTransferParameters();
+            if (parameters == null) {
+                throw new IllegalArgumentException("colorSpace must use an ICC "
+                        + "parametric transfer function");
+            }
+
+            ColorSpace.Rgb d50 = (ColorSpace.Rgb) ColorSpace.adapt(rgb, ColorSpace.ILLUMINANT_D50);
+            bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true,
+                    d50.getTransform(), parameters);
+        }
+
+        if (display != null) {
+            bm.mDensity = display.densityDpi;
+        }
+        bm.setHasAlpha(hasAlpha);
+        if ((config == Config.ARGB_8888 || config == Config.RGBA_F16) && !hasAlpha) {
+            nativeErase(bm.mNativePtr, 0xff000000);
+        }
+        // No need to initialize the bitmap to zeroes with other configs;
+        // it is backed by a VM byte array which is by definition preinitialized
+        // to all zeroes.
+        return bm;
+    }
+
+    /**
+     * Returns a immutable bitmap with the specified width and height, with each
+     * pixel value set to the corresponding value in the colors array.  Its
+     * initial density is as per {@link #getDensity}. The newly created
+     * bitmap is in the {@link ColorSpace.Named#SRGB sRGB} color space.
+     *
+     * @param colors   Array of sRGB {@link Color colors} used to initialize the pixels.
+     * @param offset   Number of values to skip before the first color in the
+     *                 array of colors.
+     * @param stride   Number of colors in the array between rows (must be >=
+     *                 width or <= -width).
+     * @param width    The width of the bitmap
+     * @param height   The height of the bitmap
+     * @param config   The bitmap config to create. If the config does not
+     *                 support per-pixel alpha (e.g. RGB_565), then the alpha
+     *                 bytes in the colors[] will be ignored (assumed to be FF)
+     * @throws IllegalArgumentException if the width or height are <= 0, or if
+     *         the color array's length is less than the number of pixels.
+     */
+    public static Bitmap createBitmap(@NonNull @ColorInt int[] colors, int offset, int stride,
+            int width, int height, @NonNull Config config) {
+        return createBitmap(null, colors, offset, stride, width, height, config);
+    }
+
+    /**
+     * Returns a immutable bitmap with the specified width and height, with each
+     * pixel value set to the corresponding value in the colors array.  Its
+     * initial density is determined from the given {@link DisplayMetrics}.
+     * The newly created bitmap is in the {@link ColorSpace.Named#SRGB sRGB}
+     * color space.
+     *
+     * @param display  Display metrics for the display this bitmap will be
+     *                 drawn on.
+     * @param colors   Array of sRGB {@link Color colors} used to initialize the pixels.
+     * @param offset   Number of values to skip before the first color in the
+     *                 array of colors.
+     * @param stride   Number of colors in the array between rows (must be >=
+     *                 width or <= -width).
+     * @param width    The width of the bitmap
+     * @param height   The height of the bitmap
+     * @param config   The bitmap config to create. If the config does not
+     *                 support per-pixel alpha (e.g. RGB_565), then the alpha
+     *                 bytes in the colors[] will be ignored (assumed to be FF)
+     * @throws IllegalArgumentException if the width or height are <= 0, or if
+     *         the color array's length is less than the number of pixels.
+     */
+    public static Bitmap createBitmap(@NonNull DisplayMetrics display,
+            @NonNull @ColorInt int[] colors, int offset, int stride,
+            int width, int height, @NonNull Config config) {
+
+        checkWidthHeight(width, height);
+        if (Math.abs(stride) < width) {
+            throw new IllegalArgumentException("abs(stride) must be >= width");
+        }
+        int lastScanline = offset + (height - 1) * stride;
+        int length = colors.length;
+        if (offset < 0 || (offset + width > length) || lastScanline < 0 ||
+                (lastScanline + width > length)) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        if (width <= 0 || height <= 0) {
+            throw new IllegalArgumentException("width and height must be > 0");
+        }
+        Bitmap bm = nativeCreate(colors, offset, stride, width, height,
+                            config.nativeInt, false, null, null);
+        if (display != null) {
+            bm.mDensity = display.densityDpi;
+        }
+        return bm;
+    }
+
+    /**
+     * Returns a immutable bitmap with the specified width and height, with each
+     * pixel value set to the corresponding value in the colors array.  Its
+     * initial density is as per {@link #getDensity}. The newly created
+     * bitmap is in the {@link ColorSpace.Named#SRGB sRGB} color space.
+     *
+     * @param colors   Array of sRGB {@link Color colors} used to initialize the pixels.
+     *                 This array must be at least as large as width * height.
+     * @param width    The width of the bitmap
+     * @param height   The height of the bitmap
+     * @param config   The bitmap config to create. If the config does not
+     *                 support per-pixel alpha (e.g. RGB_565), then the alpha
+     *                 bytes in the colors[] will be ignored (assumed to be FF)
+     * @throws IllegalArgumentException if the width or height are <= 0, or if
+     *         the color array's length is less than the number of pixels.
+     */
+    public static Bitmap createBitmap(@NonNull @ColorInt int[] colors,
+            int width, int height, Config config) {
+        return createBitmap(null, colors, 0, width, width, height, config);
+    }
+
+    /**
+     * Returns a immutable bitmap with the specified width and height, with each
+     * pixel value set to the corresponding value in the colors array.  Its
+     * initial density is determined from the given {@link DisplayMetrics}.
+     * The newly created bitmap is in the {@link ColorSpace.Named#SRGB sRGB}
+     * color space.
+     *
+     * @param display  Display metrics for the display this bitmap will be
+     *                 drawn on.
+     * @param colors   Array of sRGB {@link Color colors} used to initialize the pixels.
+     *                 This array must be at least as large as width * height.
+     * @param width    The width of the bitmap
+     * @param height   The height of the bitmap
+     * @param config   The bitmap config to create. If the config does not
+     *                 support per-pixel alpha (e.g. RGB_565), then the alpha
+     *                 bytes in the colors[] will be ignored (assumed to be FF)
+     * @throws IllegalArgumentException if the width or height are <= 0, or if
+     *         the color array's length is less than the number of pixels.
+     */
+    public static Bitmap createBitmap(@Nullable DisplayMetrics display,
+            @NonNull @ColorInt int colors[], int width, int height, @NonNull Config config) {
+        return createBitmap(display, colors, 0, width, width, height, config);
+    }
+
+    /**
+     * Returns an optional array of private data, used by the UI system for
+     * some bitmaps. Not intended to be called by applications.
+     */
+    public byte[] getNinePatchChunk() {
+        return mNinePatchChunk;
+    }
+
+    /**
+     * Populates a rectangle with the bitmap's optical insets.
+     *
+     * @param outInsets Rect to populate with optical insets
+     * @hide
+     */
+    public void getOpticalInsets(@NonNull Rect outInsets) {
+        if (mNinePatchInsets == null) {
+            outInsets.setEmpty();
+        } else {
+            outInsets.set(mNinePatchInsets.opticalRect);
+        }
+    }
+
+    /** @hide */
+    public NinePatch.InsetStruct getNinePatchInsets() {
+        return mNinePatchInsets;
+    }
+
+    /**
+     * Specifies the known formats a bitmap can be compressed into
+     */
+    public enum CompressFormat {
+        JPEG    (0),
+        PNG     (1),
+        WEBP    (2);
+
+        CompressFormat(int nativeInt) {
+            this.nativeInt = nativeInt;
+        }
+        final int nativeInt;
+    }
+
+    /**
+     * Number of bytes of temp storage we use for communicating between the
+     * native compressor and the java OutputStream.
+     */
+    private final static int WORKING_COMPRESS_STORAGE = 4096;
+
+    /**
+     * Write a compressed version of the bitmap to the specified outputstream.
+     * If this returns true, the bitmap can be reconstructed by passing a
+     * corresponding inputstream to BitmapFactory.decodeStream(). Note: not
+     * all Formats support all bitmap configs directly, so it is possible that
+     * the returned bitmap from BitmapFactory could be in a different bitdepth,
+     * and/or may have lost per-pixel alpha (e.g. JPEG only supports opaque
+     * pixels).
+     *
+     * @param format   The format of the compressed image
+     * @param quality  Hint to the compressor, 0-100. 0 meaning compress for
+     *                 small size, 100 meaning compress for max quality. Some
+     *                 formats, like PNG which is lossless, will ignore the
+     *                 quality setting
+     * @param stream   The outputstream to write the compressed data.
+     * @return true if successfully compressed to the specified stream.
+     */
+    public boolean compress(CompressFormat format, int quality, OutputStream stream) {
+        checkRecycled("Can't compress a recycled bitmap");
+        // do explicit check before calling the native method
+        if (stream == null) {
+            throw new NullPointerException();
+        }
+        if (quality < 0 || quality > 100) {
+            throw new IllegalArgumentException("quality must be 0..100");
+        }
+        StrictMode.noteSlowCall("Compression of a bitmap is slow");
+        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "Bitmap.compress");
+        boolean result = nativeCompress(mNativePtr, format.nativeInt,
+                quality, stream, new byte[WORKING_COMPRESS_STORAGE]);
+        Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+        return result;
+    }
+
+    /**
+     * Returns true if the bitmap is marked as mutable (i.e.&nbsp;can be drawn into)
+     */
+    public final boolean isMutable() {
+        return mIsMutable;
+    }
+
+    /**
+     * <p>Indicates whether pixels stored in this bitmaps are stored pre-multiplied.
+     * When a pixel is pre-multiplied, the RGB components have been multiplied by
+     * the alpha component. For instance, if the original color is a 50%
+     * translucent red <code>(128, 255, 0, 0)</code>, the pre-multiplied form is
+     * <code>(128, 128, 0, 0)</code>.</p>
+     *
+     * <p>This method always returns false if {@link #getConfig()} is
+     * {@link Bitmap.Config#RGB_565}.</p>
+     *
+     * <p>The return value is undefined if {@link #getConfig()} is
+     * {@link Bitmap.Config#ALPHA_8}.</p>
+     *
+     * <p>This method only returns true if {@link #hasAlpha()} returns true.
+     * A bitmap with no alpha channel can be used both as a pre-multiplied and
+     * as a non pre-multiplied bitmap.</p>
+     *
+     * <p>Only pre-multiplied bitmaps may be drawn by the view system or
+     * {@link Canvas}. If a non-pre-multiplied bitmap with an alpha channel is
+     * drawn to a Canvas, a RuntimeException will be thrown.</p>
+     *
+     * @return true if the underlying pixels have been pre-multiplied, false
+     *         otherwise
+     *
+     * @see Bitmap#setPremultiplied(boolean)
+     * @see BitmapFactory.Options#inPremultiplied
+     */
+    public final boolean isPremultiplied() {
+        if (mRecycled) {
+            Log.w(TAG, "Called isPremultiplied() on a recycle()'d bitmap! This is undefined behavior!");
+        }
+        return nativeIsPremultiplied(mNativePtr);
+    }
+
+    /**
+     * Sets whether the bitmap should treat its data as pre-multiplied.
+     *
+     * <p>Bitmaps are always treated as pre-multiplied by the view system and
+     * {@link Canvas} for performance reasons. Storing un-pre-multiplied data in
+     * a Bitmap (through {@link #setPixel}, {@link #setPixels}, or {@link
+     * BitmapFactory.Options#inPremultiplied BitmapFactory.Options.inPremultiplied})
+     * can lead to incorrect blending if drawn by the framework.</p>
+     *
+     * <p>This method will not affect the behavior of a bitmap without an alpha
+     * channel, or if {@link #hasAlpha()} returns false.</p>
+     *
+     * <p>Calling {@link #createBitmap} or {@link #createScaledBitmap} with a source
+     * Bitmap whose colors are not pre-multiplied may result in a RuntimeException,
+     * since those functions require drawing the source, which is not supported for
+     * un-pre-multiplied Bitmaps.</p>
+     *
+     * @see Bitmap#isPremultiplied()
+     * @see BitmapFactory.Options#inPremultiplied
+     */
+    public final void setPremultiplied(boolean premultiplied) {
+        checkRecycled("setPremultiplied called on a recycled bitmap");
+        mRequestPremultiplied = premultiplied;
+        nativeSetPremultiplied(mNativePtr, premultiplied);
+    }
+
+    /** Returns the bitmap's width */
+    public final int getWidth() {
+        if (mRecycled) {
+            Log.w(TAG, "Called getWidth() on a recycle()'d bitmap! This is undefined behavior!");
+        }
+        return mWidth;
+    }
+
+    /** Returns the bitmap's height */
+    public final int getHeight() {
+        if (mRecycled) {
+            Log.w(TAG, "Called getHeight() on a recycle()'d bitmap! This is undefined behavior!");
+        }
+        return mHeight;
+    }
+
+    /**
+     * Convenience for calling {@link #getScaledWidth(int)} with the target
+     * density of the given {@link Canvas}.
+     */
+    public int getScaledWidth(Canvas canvas) {
+        return scaleFromDensity(getWidth(), mDensity, canvas.mDensity);
+    }
+
+    /**
+     * Convenience for calling {@link #getScaledHeight(int)} with the target
+     * density of the given {@link Canvas}.
+     */
+    public int getScaledHeight(Canvas canvas) {
+        return scaleFromDensity(getHeight(), mDensity, canvas.mDensity);
+    }
+
+    /**
+     * Convenience for calling {@link #getScaledWidth(int)} with the target
+     * density of the given {@link DisplayMetrics}.
+     */
+    public int getScaledWidth(DisplayMetrics metrics) {
+        return scaleFromDensity(getWidth(), mDensity, metrics.densityDpi);
+    }
+
+    /**
+     * Convenience for calling {@link #getScaledHeight(int)} with the target
+     * density of the given {@link DisplayMetrics}.
+     */
+    public int getScaledHeight(DisplayMetrics metrics) {
+        return scaleFromDensity(getHeight(), mDensity, metrics.densityDpi);
+    }
+
+    /**
+     * Convenience method that returns the width of this bitmap divided
+     * by the density scale factor.
+     *
+     * @param targetDensity The density of the target canvas of the bitmap.
+     * @return The scaled width of this bitmap, according to the density scale factor.
+     */
+    public int getScaledWidth(int targetDensity) {
+        return scaleFromDensity(getWidth(), mDensity, targetDensity);
+    }
+
+    /**
+     * Convenience method that returns the height of this bitmap divided
+     * by the density scale factor.
+     *
+     * @param targetDensity The density of the target canvas of the bitmap.
+     * @return The scaled height of this bitmap, according to the density scale factor.
+     */
+    public int getScaledHeight(int targetDensity) {
+        return scaleFromDensity(getHeight(), mDensity, targetDensity);
+    }
+
+    /**
+     * @hide
+     */
+    static public int scaleFromDensity(int size, int sdensity, int tdensity) {
+        if (sdensity == DENSITY_NONE || tdensity == DENSITY_NONE || sdensity == tdensity) {
+            return size;
+        }
+
+        // Scale by tdensity / sdensity, rounding up.
+        return ((size * tdensity) + (sdensity >> 1)) / sdensity;
+    }
+
+    /**
+     * Return the number of bytes between rows in the bitmap's pixels. Note that
+     * this refers to the pixels as stored natively by the bitmap. If you call
+     * getPixels() or setPixels(), then the pixels are uniformly treated as
+     * 32bit values, packed according to the Color class.
+     *
+     * <p>As of {@link android.os.Build.VERSION_CODES#KITKAT}, this method
+     * should not be used to calculate the memory usage of the bitmap. Instead,
+     * see {@link #getAllocationByteCount()}.
+     *
+     * @return number of bytes between rows of the native bitmap pixels.
+     */
+    public final int getRowBytes() {
+        if (mRecycled) {
+            Log.w(TAG, "Called getRowBytes() on a recycle()'d bitmap! This is undefined behavior!");
+        }
+        return nativeRowBytes(mNativePtr);
+    }
+
+    /**
+     * Returns the minimum number of bytes that can be used to store this bitmap's pixels.
+     *
+     * <p>As of {@link android.os.Build.VERSION_CODES#KITKAT}, the result of this method can
+     * no longer be used to determine memory usage of a bitmap. See {@link
+     * #getAllocationByteCount()}.</p>
+     */
+    public final int getByteCount() {
+        if (mRecycled) {
+            Log.w(TAG, "Called getByteCount() on a recycle()'d bitmap! "
+                    + "This is undefined behavior!");
+            return 0;
+        }
+        // int result permits bitmaps up to 46,340 x 46,340
+        return getRowBytes() * getHeight();
+    }
+
+    /**
+     * Returns the size of the allocated memory used to store this bitmap's pixels.
+     *
+     * <p>This can be larger than the result of {@link #getByteCount()} if a bitmap is reused to
+     * decode other bitmaps of smaller size, or by manual reconfiguration. See {@link
+     * #reconfigure(int, int, Config)}, {@link #setWidth(int)}, {@link #setHeight(int)}, {@link
+     * #setConfig(Bitmap.Config)}, and {@link BitmapFactory.Options#inBitmap
+     * BitmapFactory.Options.inBitmap}. If a bitmap is not modified in this way, this value will be
+     * the same as that returned by {@link #getByteCount()}.</p>
+     *
+     * <p>This value will not change over the lifetime of a Bitmap.</p>
+     *
+     * @see #reconfigure(int, int, Config)
+     */
+    public final int getAllocationByteCount() {
+        if (mRecycled) {
+            Log.w(TAG, "Called getAllocationByteCount() on a recycle()'d bitmap! "
+                    + "This is undefined behavior!");
+            return 0;
+        }
+        return nativeGetAllocationByteCount(mNativePtr);
+    }
+
+    /**
+     * If the bitmap's internal config is in one of the public formats, return
+     * that config, otherwise return null.
+     */
+    public final Config getConfig() {
+        if (mRecycled) {
+            Log.w(TAG, "Called getConfig() on a recycle()'d bitmap! This is undefined behavior!");
+        }
+        return Config.nativeToConfig(nativeConfig(mNativePtr));
+    }
+
+    /** Returns true if the bitmap's config supports per-pixel alpha, and
+     * if the pixels may contain non-opaque alpha values. For some configs,
+     * this is always false (e.g. RGB_565), since they do not support per-pixel
+     * alpha. However, for configs that do, the bitmap may be flagged to be
+     * known that all of its pixels are opaque. In this case hasAlpha() will
+     * also return false. If a config such as ARGB_8888 is not so flagged,
+     * it will return true by default.
+     */
+    public final boolean hasAlpha() {
+        if (mRecycled) {
+            Log.w(TAG, "Called hasAlpha() on a recycle()'d bitmap! This is undefined behavior!");
+        }
+        return nativeHasAlpha(mNativePtr);
+    }
+
+    /**
+     * Tell the bitmap if all of the pixels are known to be opaque (false)
+     * or if some of the pixels may contain non-opaque alpha values (true).
+     * Note, for some configs (e.g. RGB_565) this call is ignored, since it
+     * does not support per-pixel alpha values.
+     *
+     * This is meant as a drawing hint, as in some cases a bitmap that is known
+     * to be opaque can take a faster drawing case than one that may have
+     * non-opaque per-pixel alpha values.
+     */
+    public void setHasAlpha(boolean hasAlpha) {
+        checkRecycled("setHasAlpha called on a recycled bitmap");
+        nativeSetHasAlpha(mNativePtr, hasAlpha, mRequestPremultiplied);
+    }
+
+    /**
+     * Indicates whether the renderer responsible for drawing this
+     * bitmap should attempt to use mipmaps when this bitmap is drawn
+     * scaled down.
+     *
+     * If you know that you are going to draw this bitmap at less than
+     * 50% of its original size, you may be able to obtain a higher
+     * quality
+     *
+     * This property is only a suggestion that can be ignored by the
+     * renderer. It is not guaranteed to have any effect.
+     *
+     * @return true if the renderer should attempt to use mipmaps,
+     *         false otherwise
+     *
+     * @see #setHasMipMap(boolean)
+     */
+    public final boolean hasMipMap() {
+        if (mRecycled) {
+            Log.w(TAG, "Called hasMipMap() on a recycle()'d bitmap! This is undefined behavior!");
+        }
+        return nativeHasMipMap(mNativePtr);
+    }
+
+    /**
+     * Set a hint for the renderer responsible for drawing this bitmap
+     * indicating that it should attempt to use mipmaps when this bitmap
+     * is drawn scaled down.
+     *
+     * If you know that you are going to draw this bitmap at less than
+     * 50% of its original size, you may be able to obtain a higher
+     * quality by turning this property on.
+     *
+     * Note that if the renderer respects this hint it might have to
+     * allocate extra memory to hold the mipmap levels for this bitmap.
+     *
+     * This property is only a suggestion that can be ignored by the
+     * renderer. It is not guaranteed to have any effect.
+     *
+     * @param hasMipMap indicates whether the renderer should attempt
+     *                  to use mipmaps
+     *
+     * @see #hasMipMap()
+     */
+    public final void setHasMipMap(boolean hasMipMap) {
+        checkRecycled("setHasMipMap called on a recycled bitmap");
+        nativeSetHasMipMap(mNativePtr, hasMipMap);
+    }
+
+    /**
+     * Returns the color space associated with this bitmap. If the color
+     * space is unknown, this method returns null.
+     */
+    @Nullable
+    public final ColorSpace getColorSpace() {
+        // A reconfigure can change the configuration and rgba16f is
+        // always linear scRGB at this time
+        if (getConfig() == Config.RGBA_F16) {
+            // Reset the color space for potential future reconfigurations
+            mColorSpace = null;
+            return ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB);
+        }
+
+        // Cache the color space retrieval since it can be fairly expensive
+        if (mColorSpace == null) {
+            if (nativeIsSRGB(mNativePtr)) {
+                mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
+            } else {
+                float[] xyz = new float[9];
+                float[] params = new float[7];
+
+                boolean hasColorSpace = nativeGetColorSpace(mNativePtr, xyz, params);
+                if (hasColorSpace) {
+                    ColorSpace.Rgb.TransferParameters parameters =
+                            new ColorSpace.Rgb.TransferParameters(
+                                    params[0], params[1], params[2],
+                                    params[3], params[4], params[5], params[6]);
+                    ColorSpace cs = ColorSpace.match(xyz, parameters);
+                    if (cs != null) {
+                        mColorSpace = cs;
+                    } else {
+                        mColorSpace = new ColorSpace.Rgb("Unknown", xyz, parameters);
+                    }
+                }
+            }
+        }
+
+        return mColorSpace;
+    }
+
+    /**
+     * Fills the bitmap's pixels with the specified {@link Color}.
+     *
+     * @throws IllegalStateException if the bitmap is not mutable.
+     */
+    public void eraseColor(@ColorInt int c) {
+        checkRecycled("Can't erase a recycled bitmap");
+        if (!isMutable()) {
+            throw new IllegalStateException("cannot erase immutable bitmaps");
+        }
+        nativeErase(mNativePtr, c);
+    }
+
+    /**
+     * Returns the {@link Color} at the specified location. Throws an exception
+     * if x or y are out of bounds (negative or >= to the width or height
+     * respectively). The returned color is a non-premultiplied ARGB value in
+     * the {@link ColorSpace.Named#SRGB sRGB} color space.
+     *
+     * @param x    The x coordinate (0...width-1) of the pixel to return
+     * @param y    The y coordinate (0...height-1) of the pixel to return
+     * @return     The argb {@link Color} at the specified coordinate
+     * @throws IllegalArgumentException if x, y exceed the bitmap's bounds
+     * @throws IllegalStateException if the bitmap's config is {@link Config#HARDWARE}
+     */
+    @ColorInt
+    public int getPixel(int x, int y) {
+        checkRecycled("Can't call getPixel() on a recycled bitmap");
+        checkHardware("unable to getPixel(), "
+                + "pixel access is not supported on Config#HARDWARE bitmaps");
+        checkPixelAccess(x, y);
+        return nativeGetPixel(mNativePtr, x, y);
+    }
+
+    /**
+     * Returns in pixels[] a copy of the data in the bitmap. Each value is
+     * a packed int representing a {@link Color}. The stride parameter allows
+     * the caller to allow for gaps in the returned pixels array between
+     * rows. For normal packed results, just pass width for the stride value.
+     * The returned colors are non-premultiplied ARGB values in the
+     * {@link ColorSpace.Named#SRGB sRGB} color space.
+     *
+     * @param pixels   The array to receive the bitmap's colors
+     * @param offset   The first index to write into pixels[]
+     * @param stride   The number of entries in pixels[] to skip between
+     *                 rows (must be >= bitmap's width). Can be negative.
+     * @param x        The x coordinate of the first pixel to read from
+     *                 the bitmap
+     * @param y        The y coordinate of the first pixel to read from
+     *                 the bitmap
+     * @param width    The number of pixels to read from each row
+     * @param height   The number of rows to read
+     *
+     * @throws IllegalArgumentException if x, y, width, height exceed the
+     *         bounds of the bitmap, or if abs(stride) < width.
+     * @throws ArrayIndexOutOfBoundsException if the pixels array is too small
+     *         to receive the specified number of pixels.
+     * @throws IllegalStateException if the bitmap's config is {@link Config#HARDWARE}
+     */
+    public void getPixels(@ColorInt int[] pixels, int offset, int stride,
+                          int x, int y, int width, int height) {
+        checkRecycled("Can't call getPixels() on a recycled bitmap");
+        checkHardware("unable to getPixels(), "
+                + "pixel access is not supported on Config#HARDWARE bitmaps");
+        if (width == 0 || height == 0) {
+            return; // nothing to do
+        }
+        checkPixelsAccess(x, y, width, height, offset, stride, pixels);
+        nativeGetPixels(mNativePtr, pixels, offset, stride,
+                        x, y, width, height);
+    }
+
+    /**
+     * Shared code to check for illegal arguments passed to getPixel()
+     * or setPixel()
+     *
+     * @param x x coordinate of the pixel
+     * @param y y coordinate of the pixel
+     */
+    private void checkPixelAccess(int x, int y) {
+        checkXYSign(x, y);
+        if (x >= getWidth()) {
+            throw new IllegalArgumentException("x must be < bitmap.width()");
+        }
+        if (y >= getHeight()) {
+            throw new IllegalArgumentException("y must be < bitmap.height()");
+        }
+    }
+
+    /**
+     * Shared code to check for illegal arguments passed to getPixels()
+     * or setPixels()
+     *
+     * @param x left edge of the area of pixels to access
+     * @param y top edge of the area of pixels to access
+     * @param width width of the area of pixels to access
+     * @param height height of the area of pixels to access
+     * @param offset offset into pixels[] array
+     * @param stride number of elements in pixels[] between each logical row
+     * @param pixels array to hold the area of pixels being accessed
+    */
+    private void checkPixelsAccess(int x, int y, int width, int height,
+                                   int offset, int stride, int pixels[]) {
+        checkXYSign(x, y);
+        if (width < 0) {
+            throw new IllegalArgumentException("width must be >= 0");
+        }
+        if (height < 0) {
+            throw new IllegalArgumentException("height must be >= 0");
+        }
+        if (x + width > getWidth()) {
+            throw new IllegalArgumentException(
+                    "x + width must be <= bitmap.width()");
+        }
+        if (y + height > getHeight()) {
+            throw new IllegalArgumentException(
+                    "y + height must be <= bitmap.height()");
+        }
+        if (Math.abs(stride) < width) {
+            throw new IllegalArgumentException("abs(stride) must be >= width");
+        }
+        int lastScanline = offset + (height - 1) * stride;
+        int length = pixels.length;
+        if (offset < 0 || (offset + width > length)
+                || lastScanline < 0
+                || (lastScanline + width > length)) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+    }
+
+    /**
+     * <p>Write the specified {@link Color} into the bitmap (assuming it is
+     * mutable) at the x,y coordinate. The color must be a
+     * non-premultiplied ARGB value in the {@link ColorSpace.Named#SRGB sRGB}
+     * color space.</p>
+     *
+     * @param x     The x coordinate of the pixel to replace (0...width-1)
+     * @param y     The y coordinate of the pixel to replace (0...height-1)
+     * @param color The ARGB color to write into the bitmap
+     *
+     * @throws IllegalStateException if the bitmap is not mutable
+     * @throws IllegalArgumentException if x, y are outside of the bitmap's
+     *         bounds.
+     */
+    public void setPixel(int x, int y, @ColorInt int color) {
+        checkRecycled("Can't call setPixel() on a recycled bitmap");
+        if (!isMutable()) {
+            throw new IllegalStateException();
+        }
+        checkPixelAccess(x, y);
+        nativeSetPixel(mNativePtr, x, y, color);
+    }
+
+    /**
+     * <p>Replace pixels in the bitmap with the colors in the array. Each element
+     * in the array is a packed int representing a non-premultiplied ARGB
+     * {@link Color} in the {@link ColorSpace.Named#SRGB sRGB} color space.</p>
+     *
+     * @param pixels   The colors to write to the bitmap
+     * @param offset   The index of the first color to read from pixels[]
+     * @param stride   The number of colors in pixels[] to skip between rows.
+     *                 Normally this value will be the same as the width of
+     *                 the bitmap, but it can be larger (or negative).
+     * @param x        The x coordinate of the first pixel to write to in
+     *                 the bitmap.
+     * @param y        The y coordinate of the first pixel to write to in
+     *                 the bitmap.
+     * @param width    The number of colors to copy from pixels[] per row
+     * @param height   The number of rows to write to the bitmap
+     *
+     * @throws IllegalStateException if the bitmap is not mutable
+     * @throws IllegalArgumentException if x, y, width, height are outside of
+     *         the bitmap's bounds.
+     * @throws ArrayIndexOutOfBoundsException if the pixels array is too small
+     *         to receive the specified number of pixels.
+     */
+    public void setPixels(@ColorInt int[] pixels, int offset, int stride,
+            int x, int y, int width, int height) {
+        checkRecycled("Can't call setPixels() on a recycled bitmap");
+        if (!isMutable()) {
+            throw new IllegalStateException();
+        }
+        if (width == 0 || height == 0) {
+            return; // nothing to do
+        }
+        checkPixelsAccess(x, y, width, height, offset, stride, pixels);
+        nativeSetPixels(mNativePtr, pixels, offset, stride,
+                        x, y, width, height);
+    }
+
+    public static final Parcelable.Creator<Bitmap> CREATOR
+            = new Parcelable.Creator<Bitmap>() {
+        /**
+         * Rebuilds a bitmap previously stored with writeToParcel().
+         *
+         * @param p    Parcel object to read the bitmap from
+         * @return a new bitmap created from the data in the parcel
+         */
+        public Bitmap createFromParcel(Parcel p) {
+            Bitmap bm = nativeCreateFromParcel(p);
+            if (bm == null) {
+                throw new RuntimeException("Failed to unparcel Bitmap");
+            }
+            return bm;
+        }
+        public Bitmap[] newArray(int size) {
+            return new Bitmap[size];
+        }
+    };
+
+    /**
+     * No special parcel contents.
+     */
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Write the bitmap and its pixels to the parcel. The bitmap can be
+     * rebuilt from the parcel by calling CREATOR.createFromParcel().
+     *
+     * If this bitmap is {@link Config#HARDWARE}, it may be unparceled with a different pixel
+     * format (e.g. 565, 8888), but the content will be preserved to the best quality permitted
+     * by the final pixel format
+     * @param p    Parcel object to write the bitmap data into
+     */
+    public void writeToParcel(Parcel p, int flags) {
+        checkRecycled("Can't parcel a recycled bitmap");
+        noteHardwareBitmapSlowCall();
+        if (!nativeWriteToParcel(mNativePtr, mIsMutable, mDensity, p)) {
+            throw new RuntimeException("native writeToParcel failed");
+        }
+    }
+
+    /**
+     * Returns a new bitmap that captures the alpha values of the original.
+     * This may be drawn with Canvas.drawBitmap(), where the color(s) will be
+     * taken from the paint that is passed to the draw call.
+     *
+     * @return new bitmap containing the alpha channel of the original bitmap.
+     */
+    @CheckResult
+    public Bitmap extractAlpha() {
+        return extractAlpha(null, null);
+    }
+
+    /**
+     * Returns a new bitmap that captures the alpha values of the original.
+     * These values may be affected by the optional Paint parameter, which
+     * can contain its own alpha, and may also contain a MaskFilter which
+     * could change the actual dimensions of the resulting bitmap (e.g.
+     * a blur maskfilter might enlarge the resulting bitmap). If offsetXY
+     * is not null, it returns the amount to offset the returned bitmap so
+     * that it will logically align with the original. For example, if the
+     * paint contains a blur of radius 2, then offsetXY[] would contains
+     * -2, -2, so that drawing the alpha bitmap offset by (-2, -2) and then
+     * drawing the original would result in the blur visually aligning with
+     * the original.
+     *
+     * <p>The initial density of the returned bitmap is the same as the original's.
+     *
+     * @param paint Optional paint used to modify the alpha values in the
+     *              resulting bitmap. Pass null for default behavior.
+     * @param offsetXY Optional array that returns the X (index 0) and Y
+     *                 (index 1) offset needed to position the returned bitmap
+     *                 so that it visually lines up with the original.
+     * @return new bitmap containing the (optionally modified by paint) alpha
+     *         channel of the original bitmap. This may be drawn with
+     *         Canvas.drawBitmap(), where the color(s) will be taken from the
+     *         paint that is passed to the draw call.
+     */
+    @CheckResult
+    public Bitmap extractAlpha(Paint paint, int[] offsetXY) {
+        checkRecycled("Can't extractAlpha on a recycled bitmap");
+        long nativePaint = paint != null ? paint.getNativeInstance() : 0;
+        noteHardwareBitmapSlowCall();
+        Bitmap bm = nativeExtractAlpha(mNativePtr, nativePaint, offsetXY);
+        if (bm == null) {
+            throw new RuntimeException("Failed to extractAlpha on Bitmap");
+        }
+        bm.mDensity = mDensity;
+        return bm;
+    }
+
+    /**
+     *  Given another bitmap, return true if it has the same dimensions, config,
+     *  and pixel data as this bitmap. If any of those differ, return false.
+     *  If other is null, return false.
+     */
+    public boolean sameAs(Bitmap other) {
+        checkRecycled("Can't call sameAs on a recycled bitmap!");
+        noteHardwareBitmapSlowCall();
+        if (this == other) return true;
+        if (other == null) return false;
+        other.noteHardwareBitmapSlowCall();
+        if (other.isRecycled()) {
+            throw new IllegalArgumentException("Can't compare to a recycled bitmap!");
+        }
+        return nativeSameAs(mNativePtr, other.mNativePtr);
+    }
+
+    /**
+     * Builds caches associated with the bitmap that are used for drawing it.
+     *
+     * <p>Starting in {@link android.os.Build.VERSION_CODES#N}, this call initiates an asynchronous
+     * upload to the GPU on RenderThread, if the Bitmap is not already uploaded. With Hardware
+     * Acceleration, Bitmaps must be uploaded to the GPU in order to be rendered. This is done by
+     * default the first time a Bitmap is drawn, but the process can take several milliseconds,
+     * depending on the size of the Bitmap. Each time a Bitmap is modified and drawn again, it must
+     * be re-uploaded.</p>
+     *
+     * <p>Calling this method in advance can save time in the first frame it's used. For example, it
+     * is recommended to call this on an image decoding worker thread when a decoded Bitmap is about
+     * to be displayed. It is recommended to make any pre-draw modifications to the Bitmap before
+     * calling this method, so the cached, uploaded copy may be reused without re-uploading.</p>
+     *
+     * In {@link android.os.Build.VERSION_CODES#KITKAT} and below, for purgeable bitmaps, this call
+     * would attempt to ensure that the pixels have been decoded.
+     */
+    public void prepareToDraw() {
+        checkRecycled("Can't prepareToDraw on a recycled bitmap!");
+        // Kick off an update/upload of the bitmap outside of the normal
+        // draw path.
+        nativePrepareToDraw(mNativePtr);
+    }
+
+    /**
+     *
+     * @return {@link GraphicBuffer} which is internally used by hardware bitmap
+     * @hide
+     */
+    public GraphicBuffer createGraphicBufferHandle() {
+        return nativeCreateGraphicBufferHandle(mNativePtr);
+    }
+
+    //////////// native methods
+
+    private static native Bitmap nativeCreate(int[] colors, int offset,
+                                              int stride, int width, int height,
+                                              int nativeConfig, boolean mutable,
+                                              @Nullable @Size(9) float[] xyzD50,
+                                              @Nullable ColorSpace.Rgb.TransferParameters p);
+    private static native Bitmap nativeCopy(long nativeSrcBitmap, int nativeConfig,
+                                            boolean isMutable);
+    private static native Bitmap nativeCopyAshmem(long nativeSrcBitmap);
+    private static native Bitmap nativeCopyAshmemConfig(long nativeSrcBitmap, int nativeConfig);
+    private static native long nativeGetNativeFinalizer();
+    private static native boolean nativeRecycle(long nativeBitmap);
+    private static native void nativeReconfigure(long nativeBitmap, int width, int height,
+                                                 int config, boolean isPremultiplied);
+
+    private static native boolean nativeCompress(long nativeBitmap, int format,
+                                            int quality, OutputStream stream,
+                                            byte[] tempStorage);
+    private static native void nativeErase(long nativeBitmap, int color);
+    private static native int nativeRowBytes(long nativeBitmap);
+    private static native int nativeConfig(long nativeBitmap);
+
+    private static native int nativeGetPixel(long nativeBitmap, int x, int y);
+    private static native void nativeGetPixels(long nativeBitmap, int[] pixels,
+                                               int offset, int stride, int x, int y,
+                                               int width, int height);
+
+    private static native void nativeSetPixel(long nativeBitmap, int x, int y, int color);
+    private static native void nativeSetPixels(long nativeBitmap, int[] colors,
+                                               int offset, int stride, int x, int y,
+                                               int width, int height);
+    private static native void nativeCopyPixelsToBuffer(long nativeBitmap,
+                                                        Buffer dst);
+    private static native void nativeCopyPixelsFromBuffer(long nativeBitmap, Buffer src);
+    private static native int nativeGenerationId(long nativeBitmap);
+
+    private static native Bitmap nativeCreateFromParcel(Parcel p);
+    // returns true on success
+    private static native boolean nativeWriteToParcel(long nativeBitmap,
+                                                      boolean isMutable,
+                                                      int density,
+                                                      Parcel p);
+    // returns a new bitmap built from the native bitmap's alpha, and the paint
+    private static native Bitmap nativeExtractAlpha(long nativeBitmap,
+                                                    long nativePaint,
+                                                    int[] offsetXY);
+
+    private static native boolean nativeHasAlpha(long nativeBitmap);
+    private static native boolean nativeIsPremultiplied(long nativeBitmap);
+    private static native void nativeSetPremultiplied(long nativeBitmap,
+                                                      boolean isPremul);
+    private static native void nativeSetHasAlpha(long nativeBitmap,
+                                                 boolean hasAlpha,
+                                                 boolean requestPremul);
+    private static native boolean nativeHasMipMap(long nativeBitmap);
+    private static native void nativeSetHasMipMap(long nativeBitmap, boolean hasMipMap);
+    private static native boolean nativeSameAs(long nativeBitmap0, long nativeBitmap1);
+    private static native void nativePrepareToDraw(long nativeBitmap);
+    private static native int nativeGetAllocationByteCount(long nativeBitmap);
+    private static native Bitmap nativeCopyPreserveInternalConfig(long nativeBitmap);
+    private static native Bitmap nativeCreateHardwareBitmap(GraphicBuffer buffer);
+    private static native GraphicBuffer nativeCreateGraphicBufferHandle(long nativeBitmap);
+    private static native boolean nativeGetColorSpace(long nativePtr, float[] xyz, float[] params);
+    private static native boolean nativeIsSRGB(long nativePtr);
+    private static native void nativeCopyColorSpace(long srcBitmap, long dstBitmap);
+}
diff --git a/android/graphics/BitmapFactory.java b/android/graphics/BitmapFactory.java
new file mode 100644
index 0000000..3b272c8
--- /dev/null
+++ b/android/graphics/BitmapFactory.java
@@ -0,0 +1,828 @@
+/*
+ * Copyright (C) 2007 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;
+
+import static android.graphics.BitmapFactory.Options.validate;
+
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.os.Trace;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Creates Bitmap objects from various sources, including files, streams,
+ * and byte-arrays.
+ */
+public class BitmapFactory {
+    private static final int DECODE_BUFFER_SIZE = 16 * 1024;
+
+    public static class Options {
+        /**
+         * Create a default Options object, which if left unchanged will give
+         * the same result from the decoder as if null were passed.
+         */
+        public Options() {
+            inScaled = true;
+            inPremultiplied = true;
+        }
+
+        /**
+         * If set, decode methods that take the Options object will attempt to
+         * reuse this bitmap when loading content. If the decode operation
+         * cannot use this bitmap, the decode method will throw an
+         * {@link java.lang.IllegalArgumentException}. The
+         * current implementation necessitates that the reused bitmap be
+         * mutable, and the resulting reused bitmap will continue to remain
+         * mutable even when decoding a resource which would normally result in
+         * an immutable bitmap.</p>
+         *
+         * <p>You should still always use the returned Bitmap of the decode
+         * method and not assume that reusing the bitmap worked, due to the
+         * constraints outlined above and failure situations that can occur.
+         * Checking whether the return value matches the value of the inBitmap
+         * set in the Options structure will indicate if the bitmap was reused,
+         * but in all cases you should use the Bitmap returned by the decoding
+         * function to ensure that you are using the bitmap that was used as the
+         * decode destination.</p>
+         *
+         * <h3>Usage with BitmapFactory</h3>
+         *
+         * <p>As of {@link android.os.Build.VERSION_CODES#KITKAT}, any
+         * mutable bitmap can be reused by {@link BitmapFactory} to decode any
+         * other bitmaps as long as the resulting {@link Bitmap#getByteCount()
+         * byte count} of the decoded bitmap is less than or equal to the {@link
+         * Bitmap#getAllocationByteCount() allocated byte count} of the reused
+         * bitmap. This can be because the intrinsic size is smaller, or its
+         * size post scaling (for density / sample size) is smaller.</p>
+         *
+         * <p class="note">Prior to {@link android.os.Build.VERSION_CODES#KITKAT}
+         * additional constraints apply: The image being decoded (whether as a
+         * resource or as a stream) must be in jpeg or png format. Only equal
+         * sized bitmaps are supported, with {@link #inSampleSize} set to 1.
+         * Additionally, the {@link android.graphics.Bitmap.Config
+         * configuration} of the reused bitmap will override the setting of
+         * {@link #inPreferredConfig}, if set.</p>
+         *
+         * <h3>Usage with BitmapRegionDecoder</h3>
+         *
+         * <p>BitmapRegionDecoder will draw its requested content into the Bitmap
+         * provided, clipping if the output content size (post scaling) is larger
+         * than the provided Bitmap. The provided Bitmap's width, height, and
+         * {@link Bitmap.Config} will not be changed.
+         *
+         * <p class="note">BitmapRegionDecoder support for {@link #inBitmap} was
+         * introduced in {@link android.os.Build.VERSION_CODES#JELLY_BEAN}. All
+         * formats supported by BitmapRegionDecoder support Bitmap reuse via
+         * {@link #inBitmap}.</p>
+         *
+         * @see Bitmap#reconfigure(int,int, android.graphics.Bitmap.Config)
+         */
+        public Bitmap inBitmap;
+
+        /**
+         * If set, decode methods will always return a mutable Bitmap instead of
+         * an immutable one. This can be used for instance to programmatically apply
+         * effects to a Bitmap loaded through BitmapFactory.
+         * <p>Can not be set simultaneously with inPreferredConfig =
+         * {@link android.graphics.Bitmap.Config#HARDWARE},
+         * because hardware bitmaps are always immutable.
+         */
+        @SuppressWarnings({"UnusedDeclaration"}) // used in native code
+        public boolean inMutable;
+
+        /**
+         * If set to true, the decoder will return null (no bitmap), but
+         * the <code>out...</code> fields will still be set, allowing the caller to
+         * query the bitmap without having to allocate the memory for its pixels.
+         */
+        public boolean inJustDecodeBounds;
+
+        /**
+         * If set to a value > 1, requests the decoder to subsample the original
+         * image, returning a smaller image to save memory. The sample size is
+         * the number of pixels in either dimension that correspond to a single
+         * pixel in the decoded bitmap. For example, inSampleSize == 4 returns
+         * an image that is 1/4 the width/height of the original, and 1/16 the
+         * number of pixels. Any value <= 1 is treated the same as 1. Note: the
+         * decoder uses a final value based on powers of 2, any other value will
+         * be rounded down to the nearest power of 2.
+         */
+        public int inSampleSize;
+
+        /**
+         * If this is non-null, the decoder will try to decode into this
+         * internal configuration. If it is null, or the request cannot be met,
+         * the decoder will try to pick the best matching config based on the
+         * system's screen depth, and characteristics of the original image such
+         * as if it has per-pixel alpha (requiring a config that also does).
+         * 
+         * Image are loaded with the {@link Bitmap.Config#ARGB_8888} config by
+         * default.
+         */
+        public Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888;
+
+        /**
+         * <p>If this is non-null, the decoder will try to decode into this
+         * color space. If it is null, or the request cannot be met,
+         * the decoder will pick either the color space embedded in the image
+         * or the color space best suited for the requested image configuration
+         * (for instance {@link ColorSpace.Named#SRGB sRGB} for
+         * the {@link Bitmap.Config#ARGB_8888} configuration).</p>
+         *
+         * <p>{@link Bitmap.Config#RGBA_F16} always uses the
+         * {@link ColorSpace.Named#LINEAR_EXTENDED_SRGB scRGB} color space).
+         * Bitmaps in other configurations without an embedded color space are
+         * assumed to be in the {@link ColorSpace.Named#SRGB sRGB} color space.</p>
+         *
+         * <p class="note">Only {@link ColorSpace.Model#RGB} color spaces are
+         * currently supported. An <code>IllegalArgumentException</code> will
+         * be thrown by the decode methods when setting a non-RGB color space
+         * such as {@link ColorSpace.Named#CIE_LAB Lab}.</p>
+         *
+         * <p class="note">The specified color space's transfer function must be
+         * an {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}. An
+         * <code>IllegalArgumentException</code> will be thrown by the decode methods
+         * if calling {@link ColorSpace.Rgb#getTransferParameters()} on the
+         * specified color space returns null.</p>
+         *
+         * <p>After decode, the bitmap's color space is stored in
+         * {@link #outColorSpace}.</p>
+         */
+        public ColorSpace inPreferredColorSpace = null;
+
+        /**
+         * If true (which is the default), the resulting bitmap will have its
+         * color channels pre-multipled by the alpha channel.
+         *
+         * <p>This should NOT be set to false for images to be directly drawn by
+         * the view system or through a {@link Canvas}. The view system and
+         * {@link Canvas} assume all drawn images are pre-multiplied to simplify
+         * draw-time blending, and will throw a RuntimeException when
+         * un-premultiplied are drawn.</p>
+         *
+         * <p>This is likely only useful if you want to manipulate raw encoded
+         * image data, e.g. with RenderScript or custom OpenGL.</p>
+         *
+         * <p>This does not affect bitmaps without an alpha channel.</p>
+         *
+         * <p>Setting this flag to false while setting {@link #inScaled} to true
+         * may result in incorrect colors.</p>
+         *
+         * @see Bitmap#hasAlpha()
+         * @see Bitmap#isPremultiplied()
+         * @see #inScaled
+         */
+        public boolean inPremultiplied;
+
+        /**
+         * @deprecated As of {@link android.os.Build.VERSION_CODES#N}, this is
+         * ignored.
+         *
+         * In {@link android.os.Build.VERSION_CODES#M} and below, if dither is
+         * true, the decoder will attempt to dither the decoded image.
+         */
+        public boolean inDither;
+
+        /**
+         * The pixel density to use for the bitmap.  This will always result
+         * in the returned bitmap having a density set for it (see
+         * {@link Bitmap#setDensity(int) Bitmap.setDensity(int)}).  In addition,
+         * if {@link #inScaled} is set (which it is by default} and this
+         * density does not match {@link #inTargetDensity}, then the bitmap
+         * will be scaled to the target density before being returned.
+         * 
+         * <p>If this is 0,
+         * {@link BitmapFactory#decodeResource(Resources, int)}, 
+         * {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)},
+         * and {@link BitmapFactory#decodeResourceStream}
+         * will fill in the density associated with the resource.  The other
+         * functions will leave it as-is and no density will be applied.
+         *
+         * @see #inTargetDensity
+         * @see #inScreenDensity
+         * @see #inScaled
+         * @see Bitmap#setDensity(int)
+         * @see android.util.DisplayMetrics#densityDpi
+         */
+        public int inDensity;
+
+        /**
+         * The pixel density of the destination this bitmap will be drawn to.
+         * This is used in conjunction with {@link #inDensity} and
+         * {@link #inScaled} to determine if and how to scale the bitmap before
+         * returning it.
+         * 
+         * <p>If this is 0,
+         * {@link BitmapFactory#decodeResource(Resources, int)}, 
+         * {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)},
+         * and {@link BitmapFactory#decodeResourceStream}
+         * will fill in the density associated the Resources object's
+         * DisplayMetrics.  The other
+         * functions will leave it as-is and no scaling for density will be
+         * performed.
+         * 
+         * @see #inDensity
+         * @see #inScreenDensity
+         * @see #inScaled
+         * @see android.util.DisplayMetrics#densityDpi
+         */
+        public int inTargetDensity;
+        
+        /**
+         * The pixel density of the actual screen that is being used.  This is
+         * purely for applications running in density compatibility code, where
+         * {@link #inTargetDensity} is actually the density the application
+         * sees rather than the real screen density.
+         * 
+         * <p>By setting this, you
+         * allow the loading code to avoid scaling a bitmap that is currently
+         * in the screen density up/down to the compatibility density.  Instead,
+         * if {@link #inDensity} is the same as {@link #inScreenDensity}, the
+         * bitmap will be left as-is.  Anything using the resulting bitmap
+         * must also used {@link Bitmap#getScaledWidth(int)
+         * Bitmap.getScaledWidth} and {@link Bitmap#getScaledHeight
+         * Bitmap.getScaledHeight} to account for any different between the
+         * bitmap's density and the target's density.
+         * 
+         * <p>This is never set automatically for the caller by
+         * {@link BitmapFactory} itself.  It must be explicitly set, since the
+         * caller must deal with the resulting bitmap in a density-aware way.
+         * 
+         * @see #inDensity
+         * @see #inTargetDensity
+         * @see #inScaled
+         * @see android.util.DisplayMetrics#densityDpi
+         */
+        public int inScreenDensity;
+        
+        /**
+         * When this flag is set, if {@link #inDensity} and
+         * {@link #inTargetDensity} are not 0, the
+         * bitmap will be scaled to match {@link #inTargetDensity} when loaded,
+         * rather than relying on the graphics system scaling it each time it
+         * is drawn to a Canvas.
+         *
+         * <p>BitmapRegionDecoder ignores this flag, and will not scale output
+         * based on density. (though {@link #inSampleSize} is supported)</p>
+         *
+         * <p>This flag is turned on by default and should be turned off if you need
+         * a non-scaled version of the bitmap.  Nine-patch bitmaps ignore this
+         * flag and are always scaled.
+         *
+         * <p>If {@link #inPremultiplied} is set to false, and the image has alpha,
+         * setting this flag to true may result in incorrect colors.
+         */
+        public boolean inScaled;
+
+        /**
+         * @deprecated As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this is
+         * ignored.
+         *
+         * In {@link android.os.Build.VERSION_CODES#KITKAT} and below, if this
+         * is set to true, then the resulting bitmap will allocate its
+         * pixels such that they can be purged if the system needs to reclaim
+         * memory. In that instance, when the pixels need to be accessed again
+         * (e.g. the bitmap is drawn, getPixels() is called), they will be
+         * automatically re-decoded.
+         *
+         * <p>For the re-decode to happen, the bitmap must have access to the
+         * encoded data, either by sharing a reference to the input
+         * or by making a copy of it. This distinction is controlled by
+         * inInputShareable. If this is true, then the bitmap may keep a shallow
+         * reference to the input. If this is false, then the bitmap will
+         * explicitly make a copy of the input data, and keep that. Even if
+         * sharing is allowed, the implementation may still decide to make a
+         * deep copy of the input data.</p>
+         *
+         * <p>While inPurgeable can help avoid big Dalvik heap allocations (from
+         * API level 11 onward), it sacrifices performance predictability since any
+         * image that the view system tries to draw may incur a decode delay which
+         * can lead to dropped frames. Therefore, most apps should avoid using
+         * inPurgeable to allow for a fast and fluid UI. To minimize Dalvik heap
+         * allocations use the {@link #inBitmap} flag instead.</p>
+         *
+         * <p class="note"><strong>Note:</strong> This flag is ignored when used
+         * with {@link #decodeResource(Resources, int,
+         * android.graphics.BitmapFactory.Options)} or {@link #decodeFile(String,
+         * android.graphics.BitmapFactory.Options)}.</p>
+         */
+        @Deprecated
+        public boolean inPurgeable;
+
+        /**
+         * @deprecated As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this is
+         * ignored.
+         *
+         * In {@link android.os.Build.VERSION_CODES#KITKAT} and below, this
+         * field works in conjuction with inPurgeable. If inPurgeable is false,
+         * then this field is ignored. If inPurgeable is true, then this field
+         * determines whether the bitmap can share a reference to the input
+         * data (inputstream, array, etc.) or if it must make a deep copy.
+         */
+        @Deprecated
+        public boolean inInputShareable;
+
+        /**
+         * @deprecated As of {@link android.os.Build.VERSION_CODES#N}, this is
+         * ignored.  The output will always be high quality.
+         *
+         * In {@link android.os.Build.VERSION_CODES#M} and below, if
+         * inPreferQualityOverSpeed is set to true, the decoder will try to
+         * decode the reconstructed image to a higher quality even at the
+         * expense of the decoding speed. Currently the field only affects JPEG
+         * decode, in the case of which a more accurate, but slightly slower,
+         * IDCT method will be used instead.
+         */
+        public boolean inPreferQualityOverSpeed;
+
+        /**
+         * The resulting width of the bitmap. If {@link #inJustDecodeBounds} is
+         * set to false, this will be width of the output bitmap after any
+         * scaling is applied. If true, it will be the width of the input image
+         * without any accounting for scaling.
+         *
+         * <p>outWidth will be set to -1 if there is an error trying to decode.</p>
+         */
+        public int outWidth;
+
+        /**
+         * The resulting height of the bitmap. If {@link #inJustDecodeBounds} is
+         * set to false, this will be height of the output bitmap after any
+         * scaling is applied. If true, it will be the height of the input image
+         * without any accounting for scaling.
+         *
+         * <p>outHeight will be set to -1 if there is an error trying to decode.</p>
+         */
+        public int outHeight;
+
+        /**
+         * If known, this string is set to the mimetype of the decoded image.
+         * If not known, or there is an error, it is set to null.
+         */
+        public String outMimeType;
+
+        /**
+         * If known, the config the decoded bitmap will have.
+         * If not known, or there is an error, it is set to null.
+         */
+        public Bitmap.Config outConfig;
+
+        /**
+         * If known, the color space the decoded bitmap will have. Note that the
+         * output color space is not guaranteed to be the color space the bitmap
+         * is encoded with. If not known (when the config is
+         * {@link Bitmap.Config#ALPHA_8} for instance), or there is an error,
+         * it is set to null.
+         */
+        public ColorSpace outColorSpace;
+
+        /**
+         * Temp storage to use for decoding.  Suggest 16K or so.
+         */
+        public byte[] inTempStorage;
+
+        /**
+         * @deprecated As of {@link android.os.Build.VERSION_CODES#N}, see
+         * comments on {@link #requestCancelDecode()}.
+         *
+         * Flag to indicate that cancel has been called on this object.  This
+         * is useful if there's an intermediary that wants to first decode the
+         * bounds and then decode the image.  In that case the intermediary
+         * can check, inbetween the bounds decode and the image decode, to see
+         * if the operation is canceled.
+         */
+        public boolean mCancel;
+
+        /**
+         *  @deprecated As of {@link android.os.Build.VERSION_CODES#N}, this
+         *  will not affect the decode, though it will still set mCancel.
+         *
+         *  In {@link android.os.Build.VERSION_CODES#M} and below, if this can
+         *  be called from another thread while this options object is inside
+         *  a decode... call. Calling this will notify the decoder that it
+         *  should cancel its operation. This is not guaranteed to cancel the
+         *  decode, but if it does, the decoder... operation will return null,
+         *  or if inJustDecodeBounds is true, will set outWidth/outHeight
+         *  to -1
+         */
+        public void requestCancelDecode() {
+            mCancel = true;
+        }
+
+        static void validate(Options opts) {
+            if (opts == null) return;
+
+            if (opts.inMutable && opts.inPreferredConfig == Bitmap.Config.HARDWARE) {
+                throw new IllegalArgumentException("Bitmaps with Config.HARWARE are always immutable");
+            }
+
+            if (opts.inPreferredColorSpace != null) {
+                if (!(opts.inPreferredColorSpace instanceof ColorSpace.Rgb)) {
+                    throw new IllegalArgumentException("The destination color space must use the " +
+                            "RGB color model");
+                }
+                if (((ColorSpace.Rgb) opts.inPreferredColorSpace).getTransferParameters() == null) {
+                    throw new IllegalArgumentException("The destination color space must use an " +
+                            "ICC parametric transfer function");
+                }
+            }
+        }
+    }
+
+    /**
+     * Decode a file path into a bitmap. If the specified file name is null,
+     * or cannot be decoded into a bitmap, the function returns null.
+     *
+     * @param pathName complete path name for the file to be decoded.
+     * @param opts null-ok; Options that control downsampling and whether the
+     *             image should be completely decoded, or just is size returned.
+     * @return The decoded bitmap, or null if the image data could not be
+     *         decoded, or, if opts is non-null, if opts requested only the
+     *         size be returned (in opts.outWidth and opts.outHeight)
+     * @throws IllegalArgumentException if {@link BitmapFactory.Options#inPreferredConfig}
+     *         is {@link android.graphics.Bitmap.Config#HARDWARE}
+     *         and {@link BitmapFactory.Options#inMutable} is set, if the specified color space
+     *         is not {@link ColorSpace.Model#RGB RGB}, or if the specified color space's transfer
+     *         function is not an {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}
+     */
+    public static Bitmap decodeFile(String pathName, Options opts) {
+        validate(opts);
+        Bitmap bm = null;
+        InputStream stream = null;
+        try {
+            stream = new FileInputStream(pathName);
+            bm = decodeStream(stream, null, opts);
+        } catch (Exception e) {
+            /*  do nothing.
+                If the exception happened on open, bm will be null.
+            */
+            Log.e("BitmapFactory", "Unable to decode stream: " + e);
+        } finally {
+            if (stream != null) {
+                try {
+                    stream.close();
+                } catch (IOException e) {
+                    // do nothing here
+                }
+            }
+        }
+        return bm;
+    }
+
+    /**
+     * Decode a file path into a bitmap. If the specified file name is null,
+     * or cannot be decoded into a bitmap, the function returns null.
+     *
+     * @param pathName complete path name for the file to be decoded.
+     * @return the resulting decoded bitmap, or null if it could not be decoded.
+     */
+    public static Bitmap decodeFile(String pathName) {
+        return decodeFile(pathName, null);
+    }
+
+    /**
+     * Decode a new Bitmap from an InputStream. This InputStream was obtained from
+     * resources, which we pass to be able to scale the bitmap accordingly.
+     * @throws IllegalArgumentException if {@link BitmapFactory.Options#inPreferredConfig}
+     *         is {@link android.graphics.Bitmap.Config#HARDWARE}
+     *         and {@link BitmapFactory.Options#inMutable} is set, if the specified color space
+     *         is not {@link ColorSpace.Model#RGB RGB}, or if the specified color space's transfer
+     *         function is not an {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}
+     */
+    public static Bitmap decodeResourceStream(Resources res, TypedValue value,
+            InputStream is, Rect pad, Options opts) {
+        validate(opts);
+        if (opts == null) {
+            opts = new Options();
+        }
+
+        if (opts.inDensity == 0 && value != null) {
+            final int density = value.density;
+            if (density == TypedValue.DENSITY_DEFAULT) {
+                opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
+            } else if (density != TypedValue.DENSITY_NONE) {
+                opts.inDensity = density;
+            }
+        }
+        
+        if (opts.inTargetDensity == 0 && res != null) {
+            opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
+        }
+        
+        return decodeStream(is, pad, opts);
+    }
+
+    /**
+     * Synonym for opening the given resource and calling
+     * {@link #decodeResourceStream}.
+     *
+     * @param res   The resources object containing the image data
+     * @param id The resource id of the image data
+     * @param opts null-ok; Options that control downsampling and whether the
+     *             image should be completely decoded, or just is size returned.
+     * @return The decoded bitmap, or null if the image data could not be
+     *         decoded, or, if opts is non-null, if opts requested only the
+     *         size be returned (in opts.outWidth and opts.outHeight)
+     * @throws IllegalArgumentException if {@link BitmapFactory.Options#inPreferredConfig}
+     *         is {@link android.graphics.Bitmap.Config#HARDWARE}
+     *         and {@link BitmapFactory.Options#inMutable} is set, if the specified color space
+     *         is not {@link ColorSpace.Model#RGB RGB}, or if the specified color space's transfer
+     *         function is not an {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}
+     */
+    public static Bitmap decodeResource(Resources res, int id, Options opts) {
+        validate(opts);
+        Bitmap bm = null;
+        InputStream is = null; 
+        
+        try {
+            final TypedValue value = new TypedValue();
+            is = res.openRawResource(id, value);
+
+            bm = decodeResourceStream(res, value, is, null, opts);
+        } catch (Exception e) {
+            /*  do nothing.
+                If the exception happened on open, bm will be null.
+                If it happened on close, bm is still valid.
+            */
+        } finally {
+            try {
+                if (is != null) is.close();
+            } catch (IOException e) {
+                // Ignore
+            }
+        }
+
+        if (bm == null && opts != null && opts.inBitmap != null) {
+            throw new IllegalArgumentException("Problem decoding into existing bitmap");
+        }
+
+        return bm;
+    }
+
+    /**
+     * Synonym for {@link #decodeResource(Resources, int, android.graphics.BitmapFactory.Options)}
+     * with null Options.
+     *
+     * @param res The resources object containing the image data
+     * @param id The resource id of the image data
+     * @return The decoded bitmap, or null if the image could not be decoded.
+     */
+    public static Bitmap decodeResource(Resources res, int id) {
+        return decodeResource(res, id, null);
+    }
+
+    /**
+     * Decode an immutable bitmap from the specified byte array.
+     *
+     * @param data byte array of compressed image data
+     * @param offset offset into imageData for where the decoder should begin
+     *               parsing.
+     * @param length the number of bytes, beginning at offset, to parse
+     * @param opts null-ok; Options that control downsampling and whether the
+     *             image should be completely decoded, or just is size returned.
+     * @return The decoded bitmap, or null if the image data could not be
+     *         decoded, or, if opts is non-null, if opts requested only the
+     *         size be returned (in opts.outWidth and opts.outHeight)
+     * @throws IllegalArgumentException if {@link BitmapFactory.Options#inPreferredConfig}
+     *         is {@link android.graphics.Bitmap.Config#HARDWARE}
+     *         and {@link BitmapFactory.Options#inMutable} is set, if the specified color space
+     *         is not {@link ColorSpace.Model#RGB RGB}, or if the specified color space's transfer
+     *         function is not an {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}
+     */
+    public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts) {
+        if ((offset | length) < 0 || data.length < offset + length) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        validate(opts);
+
+        Bitmap bm;
+
+        Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");
+        try {
+            bm = nativeDecodeByteArray(data, offset, length, opts);
+
+            if (bm == null && opts != null && opts.inBitmap != null) {
+                throw new IllegalArgumentException("Problem decoding into existing bitmap");
+            }
+            setDensityFromOptions(bm, opts);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
+        }
+
+        return bm;
+    }
+
+    /**
+     * Decode an immutable bitmap from the specified byte array.
+     *
+     * @param data byte array of compressed image data
+     * @param offset offset into imageData for where the decoder should begin
+     *               parsing.
+     * @param length the number of bytes, beginning at offset, to parse
+     * @return The decoded bitmap, or null if the image could not be decoded.
+     */
+    public static Bitmap decodeByteArray(byte[] data, int offset, int length) {
+        return decodeByteArray(data, offset, length, null);
+    }
+
+    /**
+     * Set the newly decoded bitmap's density based on the Options.
+     */
+    private static void setDensityFromOptions(Bitmap outputBitmap, Options opts) {
+        if (outputBitmap == null || opts == null) return;
+
+        final int density = opts.inDensity;
+        if (density != 0) {
+            outputBitmap.setDensity(density);
+            final int targetDensity = opts.inTargetDensity;
+            if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) {
+                return;
+            }
+
+            byte[] np = outputBitmap.getNinePatchChunk();
+            final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np);
+            if (opts.inScaled || isNinePatch) {
+                outputBitmap.setDensity(targetDensity);
+            }
+        } else if (opts.inBitmap != null) {
+            // bitmap was reused, ensure density is reset
+            outputBitmap.setDensity(Bitmap.getDefaultDensity());
+        }
+    }
+
+    /**
+     * Decode an input stream into a bitmap. If the input stream is null, or
+     * cannot be used to decode a bitmap, the function returns null.
+     * The stream's position will be where ever it was after the encoded data
+     * was read.
+     *
+     * @param is The input stream that holds the raw data to be decoded into a
+     *           bitmap.
+     * @param outPadding If not null, return the padding rect for the bitmap if
+     *                   it exists, otherwise set padding to [-1,-1,-1,-1]. If
+     *                   no bitmap is returned (null) then padding is
+     *                   unchanged.
+     * @param opts null-ok; Options that control downsampling and whether the
+     *             image should be completely decoded, or just is size returned.
+     * @return The decoded bitmap, or null if the image data could not be
+     *         decoded, or, if opts is non-null, if opts requested only the
+     *         size be returned (in opts.outWidth and opts.outHeight)
+     * @throws IllegalArgumentException if {@link BitmapFactory.Options#inPreferredConfig}
+     *         is {@link android.graphics.Bitmap.Config#HARDWARE}
+     *         and {@link BitmapFactory.Options#inMutable} is set, if the specified color space
+     *         is not {@link ColorSpace.Model#RGB RGB}, or if the specified color space's transfer
+     *         function is not an {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}
+     *
+     * <p class="note">Prior to {@link android.os.Build.VERSION_CODES#KITKAT},
+     * if {@link InputStream#markSupported is.markSupported()} returns true,
+     * <code>is.mark(1024)</code> would be called. As of
+     * {@link android.os.Build.VERSION_CODES#KITKAT}, this is no longer the case.</p>
+     */
+    public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
+        // we don't throw in this case, thus allowing the caller to only check
+        // the cache, and not force the image to be decoded.
+        if (is == null) {
+            return null;
+        }
+        validate(opts);
+
+        Bitmap bm = null;
+
+        Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");
+        try {
+            if (is instanceof AssetManager.AssetInputStream) {
+                final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
+                bm = nativeDecodeAsset(asset, outPadding, opts);
+            } else {
+                bm = decodeStreamInternal(is, outPadding, opts);
+            }
+
+            if (bm == null && opts != null && opts.inBitmap != null) {
+                throw new IllegalArgumentException("Problem decoding into existing bitmap");
+            }
+
+            setDensityFromOptions(bm, opts);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
+        }
+
+        return bm;
+    }
+
+    /**
+     * Private helper function for decoding an InputStream natively. Buffers the input enough to
+     * do a rewind as needed, and supplies temporary storage if necessary. is MUST NOT be null.
+     */
+    private static Bitmap decodeStreamInternal(InputStream is, Rect outPadding, Options opts) {
+        // ASSERT(is != null);
+        byte [] tempStorage = null;
+        if (opts != null) tempStorage = opts.inTempStorage;
+        if (tempStorage == null) tempStorage = new byte[DECODE_BUFFER_SIZE];
+        return nativeDecodeStream(is, tempStorage, outPadding, opts);
+    }
+
+    /**
+     * Decode an input stream into a bitmap. If the input stream is null, or
+     * cannot be used to decode a bitmap, the function returns null.
+     * The stream's position will be where ever it was after the encoded data
+     * was read.
+     *
+     * @param is The input stream that holds the raw data to be decoded into a
+     *           bitmap.
+     * @return The decoded bitmap, or null if the image data could not be decoded.
+     */
+    public static Bitmap decodeStream(InputStream is) {
+        return decodeStream(is, null, null);
+    }
+
+    /**
+     * Decode a bitmap from the file descriptor. If the bitmap cannot be decoded
+     * return null. The position within the descriptor will not be changed when
+     * this returns, so the descriptor can be used again as-is.
+     *
+     * @param fd The file descriptor containing the bitmap data to decode
+     * @param outPadding If not null, return the padding rect for the bitmap if
+     *                   it exists, otherwise set padding to [-1,-1,-1,-1]. If
+     *                   no bitmap is returned (null) then padding is
+     *                   unchanged.
+     * @param opts null-ok; Options that control downsampling and whether the
+     *             image should be completely decoded, or just its size returned.
+     * @return the decoded bitmap, or null
+     * @throws IllegalArgumentException if {@link BitmapFactory.Options#inPreferredConfig}
+     *         is {@link android.graphics.Bitmap.Config#HARDWARE}
+     *         and {@link BitmapFactory.Options#inMutable} is set, if the specified color space
+     *         is not {@link ColorSpace.Model#RGB RGB}, or if the specified color space's transfer
+     *         function is not an {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}
+     */
+    public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts) {
+        validate(opts);
+        Bitmap bm;
+
+        Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeFileDescriptor");
+        try {
+            if (nativeIsSeekable(fd)) {
+                bm = nativeDecodeFileDescriptor(fd, outPadding, opts);
+            } else {
+                FileInputStream fis = new FileInputStream(fd);
+                try {
+                    bm = decodeStreamInternal(fis, outPadding, opts);
+                } finally {
+                    try {
+                        fis.close();
+                    } catch (Throwable t) {/* ignore */}
+                }
+            }
+
+            if (bm == null && opts != null && opts.inBitmap != null) {
+                throw new IllegalArgumentException("Problem decoding into existing bitmap");
+            }
+
+            setDensityFromOptions(bm, opts);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
+        }
+        return bm;
+    }
+
+    /**
+     * Decode a bitmap from the file descriptor. If the bitmap cannot be decoded
+     * return null. The position within the descriptor will not be changed when
+     * this returns, so the descriptor can be used again as is.
+     *
+     * @param fd The file descriptor containing the bitmap data to decode
+     * @return the decoded bitmap, or null
+     */
+    public static Bitmap decodeFileDescriptor(FileDescriptor fd) {
+        return decodeFileDescriptor(fd, null, null);
+    }
+
+    private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage,
+            Rect padding, Options opts);
+    private static native Bitmap nativeDecodeFileDescriptor(FileDescriptor fd,
+            Rect padding, Options opts);
+    private static native Bitmap nativeDecodeAsset(long nativeAsset, Rect padding, Options opts);
+    private static native Bitmap nativeDecodeByteArray(byte[] data, int offset,
+            int length, Options opts);
+    private static native boolean nativeIsSeekable(FileDescriptor fd);
+}
diff --git a/android/graphics/BitmapFactory_Delegate.java b/android/graphics/BitmapFactory_Delegate.java
new file mode 100644
index 0000000..8bd2a7a
--- /dev/null
+++ b/android/graphics/BitmapFactory_Delegate.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2011 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;
+
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.ninepatch.NinePatchChunk;
+import com.android.resources.Density;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.annotation.Nullable;
+import com.android.layoutlib.bridge.util.NinePatchInputStream;
+import android.graphics.BitmapFactory.Options;
+import android.graphics.Bitmap_Delegate.BitmapCreateFlags;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.EnumSet;
+import java.util.Set;
+
+/**
+ * Delegate implementing the native methods of android.graphics.BitmapFactory
+ *
+ * Through the layoutlib_create tool, the original native methods of BitmapFactory have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager}
+ * around to map int to instance of the delegate.
+ *
+ */
+/*package*/ class BitmapFactory_Delegate {
+
+    // ------ Native Delegates ------
+
+    @LayoutlibDelegate
+    /*package*/ static Bitmap nativeDecodeStream(InputStream is, byte[] storage,
+            @Nullable Rect padding, @Nullable Options opts) {
+        Bitmap bm = null;
+
+        Density density = Density.MEDIUM;
+        Set<BitmapCreateFlags> bitmapCreateFlags = EnumSet.of(BitmapCreateFlags.MUTABLE);
+        if (opts != null) {
+            density = Density.getEnum(opts.inDensity);
+            if (opts.inPremultiplied) {
+                bitmapCreateFlags.add(BitmapCreateFlags.PREMULTIPLIED);
+            }
+            opts.inScaled = false;
+        }
+
+        try {
+            if (is instanceof NinePatchInputStream) {
+                NinePatchInputStream npis = (NinePatchInputStream) is;
+                npis.disableFakeMarkSupport();
+
+                // load the bitmap as a nine patch
+                com.android.ninepatch.NinePatch ninePatch = com.android.ninepatch.NinePatch.load(
+                        npis, true /*is9Patch*/, false /*convert*/);
+
+                // get the bitmap and chunk objects.
+                bm = Bitmap_Delegate.createBitmap(ninePatch.getImage(), bitmapCreateFlags,
+                        density);
+                NinePatchChunk chunk = ninePatch.getChunk();
+
+                // put the chunk in the bitmap
+                bm.setNinePatchChunk(NinePatch_Delegate.serialize(chunk));
+
+                if (padding != null) {
+                    // read the padding
+                    int[] paddingArray = chunk.getPadding();
+                    padding.left = paddingArray[0];
+                    padding.top = paddingArray[1];
+                    padding.right = paddingArray[2];
+                    padding.bottom = paddingArray[3];
+                }
+            } else {
+                // load the bitmap directly.
+                bm = Bitmap_Delegate.createBitmap(is, bitmapCreateFlags, density);
+            }
+        } catch (IOException e) {
+            Bridge.getLog().error(null, "Failed to load image", e, null);
+        }
+
+        return bm;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static Bitmap nativeDecodeFileDescriptor(FileDescriptor fd,
+            Rect padding, Options opts) {
+        opts.inBitmap = null;
+        return null;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static Bitmap nativeDecodeAsset(long asset, Rect padding, Options opts) {
+        opts.inBitmap = null;
+        return null;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static Bitmap nativeDecodeByteArray(byte[] data, int offset,
+            int length, Options opts) {
+        opts.inBitmap = null;
+        return null;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nativeIsSeekable(FileDescriptor fd) {
+        return true;
+    }
+
+    /**
+     * Set the newly decoded bitmap's density based on the Options.
+     *
+     * Copied from {@link BitmapFactory#setDensityFromOptions(Bitmap, Options)}.
+     */
+    @LayoutlibDelegate
+    /*package*/ static void setDensityFromOptions(Bitmap outputBitmap, Options opts) {
+        if (outputBitmap == null || opts == null) return;
+
+        final int density = opts.inDensity;
+        if (density != 0) {
+            outputBitmap.setDensity(density);
+            final int targetDensity = opts.inTargetDensity;
+            if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) {
+                return;
+            }
+
+            // --- Change from original implementation begins ---
+            // LayoutLib doesn't scale the nine patch when decoding it. Hence, don't change the
+            // density of the source bitmap in case of ninepatch.
+
+            if (opts.inScaled) {
+            // --- Change from original implementation ends. ---
+                outputBitmap.setDensity(targetDensity);
+            }
+        } else if (opts.inBitmap != null) {
+            // bitmap was reused, ensure density is reset
+            outputBitmap.setDensity(Bitmap.getDefaultDensity());
+        }
+    }
+}
diff --git a/android/graphics/BitmapRegionDecoder.java b/android/graphics/BitmapRegionDecoder.java
new file mode 100644
index 0000000..2da27c7
--- /dev/null
+++ b/android/graphics/BitmapRegionDecoder.java
@@ -0,0 +1,278 @@
+/* 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;
+
+import android.content.res.AssetManager;
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * BitmapRegionDecoder can be used to decode a rectangle region from an image.
+ * BitmapRegionDecoder is particularly useful when an original image is large and
+ * you only need parts of the image.
+ *
+ * <p>To create a BitmapRegionDecoder, call newInstance(...).
+ * Given a BitmapRegionDecoder, users can call decodeRegion() repeatedly
+ * to get a decoded Bitmap of the specified region.
+ *
+ */
+public final class BitmapRegionDecoder {
+    private long mNativeBitmapRegionDecoder;
+    private boolean mRecycled;
+    // ensures that the native decoder object exists and that only one decode can
+    // occur at a time.
+    private final Object mNativeLock = new Object();
+
+    /**
+     * Create a BitmapRegionDecoder from the specified byte array.
+     * Currently only the JPEG and PNG formats are supported.
+     *
+     * @param data byte array of compressed image data.
+     * @param offset offset into data for where the decoder should begin
+     *               parsing.
+     * @param length the number of bytes, beginning at offset, to parse
+     * @param isShareable If this is true, then the BitmapRegionDecoder may keep a
+     *                    shallow reference to the input. If this is false,
+     *                    then the BitmapRegionDecoder will explicitly make a copy of the
+     *                    input data, and keep that. Even if sharing is allowed,
+     *                    the implementation may still decide to make a deep
+     *                    copy of the input data. If an image is progressively encoded,
+     *                    allowing sharing may degrade the decoding speed.
+     * @return BitmapRegionDecoder, or null if the image data could not be decoded.
+     * @throws IOException if the image format is not supported or can not be decoded.
+     */
+    public static BitmapRegionDecoder newInstance(byte[] data,
+            int offset, int length, boolean isShareable) throws IOException {
+        if ((offset | length) < 0 || data.length < offset + length) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        return nativeNewInstance(data, offset, length, isShareable);
+    }
+
+    /**
+     * Create a BitmapRegionDecoder from the file descriptor.
+     * The position within the descriptor will not be changed when
+     * this returns, so the descriptor can be used again as is.
+     * Currently only the JPEG and PNG formats are supported.
+     *
+     * @param fd The file descriptor containing the data to decode
+     * @param isShareable If this is true, then the BitmapRegionDecoder may keep a
+     *                    shallow reference to the input. If this is false,
+     *                    then the BitmapRegionDecoder will explicitly make a copy of the
+     *                    input data, and keep that. Even if sharing is allowed,
+     *                    the implementation may still decide to make a deep
+     *                    copy of the input data. If an image is progressively encoded,
+     *                    allowing sharing may degrade the decoding speed.
+     * @return BitmapRegionDecoder, or null if the image data could not be decoded.
+     * @throws IOException if the image format is not supported or can not be decoded.
+     */
+    public static BitmapRegionDecoder newInstance(
+            FileDescriptor fd, boolean isShareable) throws IOException {
+        return nativeNewInstance(fd, isShareable);
+    }
+
+    /**
+     * Create a BitmapRegionDecoder from an input stream.
+     * The stream's position will be where ever it was after the encoded data
+     * was read.
+     * Currently only the JPEG and PNG formats are supported.
+     *
+     * @param is The input stream that holds the raw data to be decoded into a
+     *           BitmapRegionDecoder.
+     * @param isShareable If this is true, then the BitmapRegionDecoder may keep a
+     *                    shallow reference to the input. If this is false,
+     *                    then the BitmapRegionDecoder will explicitly make a copy of the
+     *                    input data, and keep that. Even if sharing is allowed,
+     *                    the implementation may still decide to make a deep
+     *                    copy of the input data. If an image is progressively encoded,
+     *                    allowing sharing may degrade the decoding speed.
+     * @return BitmapRegionDecoder, or null if the image data could not be decoded.
+     * @throws IOException if the image format is not supported or can not be decoded.
+     *
+     * <p class="note">Prior to {@link android.os.Build.VERSION_CODES#KITKAT},
+     * if {@link InputStream#markSupported is.markSupported()} returns true,
+     * <code>is.mark(1024)</code> would be called. As of
+     * {@link android.os.Build.VERSION_CODES#KITKAT}, this is no longer the case.</p>
+     */
+    public static BitmapRegionDecoder newInstance(InputStream is,
+            boolean isShareable) throws IOException {
+        if (is instanceof AssetManager.AssetInputStream) {
+            return nativeNewInstance(
+                    ((AssetManager.AssetInputStream) is).getNativeAsset(),
+                    isShareable);
+        } else {
+            // pass some temp storage down to the native code. 1024 is made up,
+            // but should be large enough to avoid too many small calls back
+            // into is.read(...).
+            byte [] tempStorage = new byte[16 * 1024];
+            return nativeNewInstance(is, tempStorage, isShareable);
+        }
+    }
+
+    /**
+     * Create a BitmapRegionDecoder from a file path.
+     * Currently only the JPEG and PNG formats are supported.
+     *
+     * @param pathName complete path name for the file to be decoded.
+     * @param isShareable If this is true, then the BitmapRegionDecoder may keep a
+     *                    shallow reference to the input. If this is false,
+     *                    then the BitmapRegionDecoder will explicitly make a copy of the
+     *                    input data, and keep that. Even if sharing is allowed,
+     *                    the implementation may still decide to make a deep
+     *                    copy of the input data. If an image is progressively encoded,
+     *                    allowing sharing may degrade the decoding speed.
+     * @return BitmapRegionDecoder, or null if the image data could not be decoded.
+     * @throws IOException if the image format is not supported or can not be decoded.
+     */
+    public static BitmapRegionDecoder newInstance(String pathName,
+            boolean isShareable) throws IOException {
+        BitmapRegionDecoder decoder = null;
+        InputStream stream = null;
+
+        try {
+            stream = new FileInputStream(pathName);
+            decoder = newInstance(stream, isShareable);
+        } finally {
+            if (stream != null) {
+                try {
+                    stream.close();
+                } catch (IOException e) {
+                    // do nothing here
+                }
+            }
+        }
+        return decoder;
+    }
+
+    /*  Private constructor that must receive an already allocated native
+        region decoder int (pointer).
+
+        This can be called from JNI code.
+    */
+    private BitmapRegionDecoder(long decoder) {
+        mNativeBitmapRegionDecoder = decoder;
+        mRecycled = false;
+    }
+
+    /**
+     * Decodes a rectangle region in the image specified by rect.
+     *
+     * @param rect The rectangle that specified the region to be decode.
+     * @param options null-ok; Options that control downsampling.
+     *             inPurgeable is not supported.
+     * @return The decoded bitmap, or null if the image data could not be
+     *         decoded.
+     * @throws IllegalArgumentException if {@link BitmapFactory.Options#inPreferredConfig}
+     *         is {@link android.graphics.Bitmap.Config#HARDWARE}
+     *         and {@link BitmapFactory.Options#inMutable} is set, if the specified color space
+     *         is not {@link ColorSpace.Model#RGB RGB}, or if the specified color space's transfer
+     *         function is not an {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}
+     */
+    public Bitmap decodeRegion(Rect rect, BitmapFactory.Options options) {
+        BitmapFactory.Options.validate(options);
+        synchronized (mNativeLock) {
+            checkRecycled("decodeRegion called on recycled region decoder");
+            if (rect.right <= 0 || rect.bottom <= 0 || rect.left >= getWidth()
+                    || rect.top >= getHeight())
+                throw new IllegalArgumentException("rectangle is outside the image");
+            return nativeDecodeRegion(mNativeBitmapRegionDecoder, rect.left, rect.top,
+                    rect.right - rect.left, rect.bottom - rect.top, options);
+        }
+    }
+
+    /** Returns the original image's width */
+    public int getWidth() {
+        synchronized (mNativeLock) {
+            checkRecycled("getWidth called on recycled region decoder");
+            return nativeGetWidth(mNativeBitmapRegionDecoder);
+        }
+    }
+
+    /** Returns the original image's height */
+    public int getHeight() {
+        synchronized (mNativeLock) {
+            checkRecycled("getHeight called on recycled region decoder");
+            return nativeGetHeight(mNativeBitmapRegionDecoder);
+        }
+    }
+
+    /**
+     * Frees up the memory associated with this region decoder, and mark the
+     * region decoder as "dead", meaning it will throw an exception if decodeRegion(),
+     * getWidth() or getHeight() is called.
+     *
+     * <p>This operation cannot be reversed, so it should only be called if you are
+     * sure there are no further uses for the region decoder. This is an advanced call,
+     * and normally need not be called, since the normal GC process will free up this
+     * memory when there are no more references to this region decoder.
+     */
+    public void recycle() {
+        synchronized (mNativeLock) {
+            if (!mRecycled) {
+                nativeClean(mNativeBitmapRegionDecoder);
+                mRecycled = true;
+            }
+        }
+    }
+
+    /**
+     * Returns true if this region decoder has been recycled.
+     * If so, then it is an error to try use its method.
+     *
+     * @return true if the region decoder has been recycled
+     */
+    public final boolean isRecycled() {
+        return mRecycled;
+    }
+
+    /**
+     * Called by methods that want to throw an exception if the region decoder
+     * has already been recycled.
+     */
+    private void checkRecycled(String errorMessage) {
+        if (mRecycled) {
+            throw new IllegalStateException(errorMessage);
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            recycle();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private static native Bitmap nativeDecodeRegion(long lbm,
+            int start_x, int start_y, int width, int height,
+            BitmapFactory.Options options);
+    private static native int nativeGetWidth(long lbm);
+    private static native int nativeGetHeight(long lbm);
+    private static native void nativeClean(long lbm);
+
+    private static native BitmapRegionDecoder nativeNewInstance(
+            byte[] data, int offset, int length, boolean isShareable);
+    private static native BitmapRegionDecoder nativeNewInstance(
+            FileDescriptor fd, boolean isShareable);
+    private static native BitmapRegionDecoder nativeNewInstance(
+            InputStream is, byte[] storage, boolean isShareable);
+    private static native BitmapRegionDecoder nativeNewInstance(
+            long asset, boolean isShareable);
+}
diff --git a/android/graphics/BitmapShader.java b/android/graphics/BitmapShader.java
new file mode 100644
index 0000000..5577f53
--- /dev/null
+++ b/android/graphics/BitmapShader.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2007 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;
+
+import android.annotation.NonNull;
+
+/**
+ * Shader used to draw a bitmap as a texture. The bitmap can be repeated or
+ * mirrored by setting the tiling mode.
+ */
+public class BitmapShader extends Shader {
+    /**
+     * Prevent garbage collection.
+     * @hide
+     */
+    @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
+    public Bitmap mBitmap;
+
+    private int mTileX;
+    private int mTileY;
+
+    /**
+     * Call this to create a new shader that will draw with a bitmap.
+     *
+     * @param bitmap The bitmap to use inside the shader
+     * @param tileX The tiling mode for x to draw the bitmap in.
+     * @param tileY The tiling mode for y to draw the bitmap in.
+     */
+    public BitmapShader(@NonNull Bitmap bitmap, @NonNull TileMode tileX, @NonNull TileMode tileY) {
+        this(bitmap, tileX.nativeInt, tileY.nativeInt);
+    }
+
+    private BitmapShader(Bitmap bitmap, int tileX, int tileY) {
+        if (bitmap == null) {
+            throw new IllegalArgumentException("Bitmap must be non-null");
+        }
+        if (bitmap == mBitmap && tileX == mTileX && tileY == mTileY) {
+            return;
+        }
+        mBitmap = bitmap;
+        mTileX = tileX;
+        mTileY = tileY;
+    }
+
+    @Override
+    long createNativeInstance(long nativeMatrix) {
+        return nativeCreate(nativeMatrix, mBitmap, mTileX, mTileY);
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    protected Shader copy() {
+        final BitmapShader copy = new BitmapShader(mBitmap, mTileX, mTileY);
+        copyLocalMatrix(copy);
+        return copy;
+    }
+
+    private static native long nativeCreate(long nativeMatrix, Bitmap bitmap,
+            int shaderTileModeX, int shaderTileModeY);
+}
diff --git a/android/graphics/BitmapShader_Delegate.java b/android/graphics/BitmapShader_Delegate.java
new file mode 100644
index 0000000..891b07f
--- /dev/null
+++ b/android/graphics/BitmapShader_Delegate.java
@@ -0,0 +1,259 @@
+/*
+ * 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;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.graphics.Shader.TileMode;
+
+import java.awt.PaintContext;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.NoninvertibleTransformException;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBufferInt;
+import java.awt.image.Raster;
+import java.awt.image.SampleModel;
+
+/**
+ * Delegate implementing the native methods of android.graphics.BitmapShader
+ *
+ * Through the layoutlib_create tool, the original native methods of BitmapShader have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original BitmapShader class.
+ *
+ * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager},
+ * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}.
+ *
+ * @see Shader_Delegate
+ *
+ */
+public class BitmapShader_Delegate extends Shader_Delegate {
+
+    // ---- delegate data ----
+    private java.awt.Paint mJavaPaint;
+
+    // ---- Public Helper methods ----
+
+    @Override
+    public java.awt.Paint getJavaPaint() {
+        return mJavaPaint;
+    }
+
+    @Override
+    public boolean isSupported() {
+        return true;
+    }
+
+    @Override
+    public String getSupportMessage() {
+        // no message since isSupported returns true;
+        return null;
+    }
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeCreate(long nativeMatrix, Bitmap androidBitmap,
+            int shaderTileModeX, int shaderTileModeY) {
+        Bitmap_Delegate bitmap = Bitmap_Delegate.getDelegate(androidBitmap);
+        if (bitmap == null) {
+            return 0;
+        }
+
+        BitmapShader_Delegate newDelegate = new BitmapShader_Delegate(nativeMatrix,
+                bitmap.getImage(),
+                Shader_Delegate.getTileMode(shaderTileModeX),
+                Shader_Delegate.getTileMode(shaderTileModeY));
+        return sManager.addNewDelegate(newDelegate);
+    }
+
+    // ---- Private delegate/helper methods ----
+
+    private BitmapShader_Delegate(long matrix, BufferedImage image,
+            TileMode tileModeX, TileMode tileModeY) {
+        super(matrix);
+        mJavaPaint = new BitmapShaderPaint(image, tileModeX, tileModeY);
+    }
+
+    private class BitmapShaderPaint implements java.awt.Paint {
+        private final BufferedImage mImage;
+        private final TileMode mTileModeX;
+        private final TileMode mTileModeY;
+
+        BitmapShaderPaint(BufferedImage image,
+                TileMode tileModeX, TileMode tileModeY) {
+            mImage = image;
+            mTileModeX = tileModeX;
+            mTileModeY = tileModeY;
+        }
+
+        @Override
+        public PaintContext createContext(ColorModel colorModel, Rectangle deviceBounds,
+                Rectangle2D userBounds, AffineTransform xform, RenderingHints hints) {
+            AffineTransform canvasMatrix;
+            try {
+                canvasMatrix = xform.createInverse();
+            } catch (NoninvertibleTransformException e) {
+                Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE,
+                        "Unable to inverse matrix in BitmapShader", e, null /*data*/);
+                canvasMatrix = new AffineTransform();
+            }
+
+            AffineTransform localMatrix = getLocalMatrix();
+            try {
+                localMatrix = localMatrix.createInverse();
+            } catch (NoninvertibleTransformException e) {
+                Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE,
+                        "Unable to inverse matrix in BitmapShader", e, null /*data*/);
+                localMatrix = new AffineTransform();
+            }
+
+            if (!colorModel.isCompatibleRaster(mImage.getRaster())) {
+                // Fallback to the default ARGB color model
+                colorModel = ColorModel.getRGBdefault();
+            }
+
+            return new BitmapShaderContext(canvasMatrix, localMatrix, colorModel);
+        }
+
+        private class BitmapShaderContext implements PaintContext {
+
+            private final AffineTransform mCanvasMatrix;
+            private final AffineTransform mLocalMatrix;
+            private final ColorModel mColorModel;
+
+            public BitmapShaderContext(
+                    AffineTransform canvasMatrix,
+                    AffineTransform localMatrix,
+                    ColorModel colorModel) {
+                mCanvasMatrix = canvasMatrix;
+                mLocalMatrix = localMatrix;
+                mColorModel = colorModel;
+            }
+
+            @Override
+            public void dispose() {
+            }
+
+            @Override
+            public ColorModel getColorModel() {
+                return mColorModel;
+            }
+
+            @Override
+            public Raster getRaster(int x, int y, int w, int h) {
+                BufferedImage image = new BufferedImage(
+                    mColorModel, mColorModel.createCompatibleWritableRaster(w, h),
+                    mColorModel.isAlphaPremultiplied(), null);
+
+                int[] data = new int[w*h];
+
+                int index = 0;
+                float[] pt1 = new float[2];
+                float[] pt2 = new float[2];
+                for (int iy = 0 ; iy < h ; iy++) {
+                    for (int ix = 0 ; ix < w ; ix++) {
+                        // handle the canvas transform
+                        pt1[0] = x + ix;
+                        pt1[1] = y + iy;
+                        mCanvasMatrix.transform(pt1, 0, pt2, 0, 1);
+
+                        // handle the local matrix.
+                        pt1[0] = pt2[0];
+                        pt1[1] = pt2[1];
+                        mLocalMatrix.transform(pt1, 0, pt2, 0, 1);
+
+                        data[index++] = getColor(pt2[0], pt2[1]);
+                    }
+                }
+
+                DataBufferInt dataBuffer = new DataBufferInt(data, data.length);
+                SampleModel colorModel = mColorModel.createCompatibleSampleModel(w, h);
+                return Raster.createWritableRaster(colorModel, dataBuffer, null);
+            }
+        }
+
+        /**
+         * Returns a color for an arbitrary point.
+         */
+        private int getColor(float fx, float fy) {
+            int x = getCoordinate(Math.round(fx), mImage.getWidth(), mTileModeX);
+            int y = getCoordinate(Math.round(fy), mImage.getHeight(), mTileModeY);
+
+            return mImage.getRGB(x, y);
+        }
+
+        private int getCoordinate(int i, int size, TileMode mode) {
+            if (i < 0) {
+                switch (mode) {
+                    case CLAMP:
+                        i = 0;
+                        break;
+                    case REPEAT:
+                        i = size - 1 - (-i % size);
+                        break;
+                    case MIRROR:
+                        // this is the same as the positive side, just make the value positive
+                        // first.
+                        i = -i;
+                        int count = i / size;
+                        i = i % size;
+
+                        if ((count % 2) == 1) {
+                            i = size - 1 - i;
+                        }
+                        break;
+                }
+            } else if (i >= size) {
+                switch (mode) {
+                    case CLAMP:
+                        i = size - 1;
+                        break;
+                    case REPEAT:
+                        i = i % size;
+                        break;
+                    case MIRROR:
+                        int count = i / size;
+                        i = i % size;
+
+                        if ((count % 2) == 1) {
+                            i = size - 1 - i;
+                        }
+                        break;
+                }
+            }
+
+            return i;
+        }
+
+
+        @Override
+        public int getTransparency() {
+            return java.awt.Paint.TRANSLUCENT;
+        }
+    }
+}
diff --git a/android/graphics/Bitmap_Delegate.java b/android/graphics/Bitmap_Delegate.java
new file mode 100644
index 0000000..0064537
--- /dev/null
+++ b/android/graphics/Bitmap_Delegate.java
@@ -0,0 +1,736 @@
+/*
+ * 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;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.impl.RenderAction;
+import com.android.resources.Density;
+import com.android.resources.ResourceType;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.annotation.Nullable;
+import android.graphics.Bitmap.Config;
+import android.os.Parcel;
+
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.Buffer;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.Set;
+
+import javax.imageio.ImageIO;
+import libcore.util.NativeAllocationRegistry_Delegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.Bitmap
+ *
+ * Through the layoutlib_create tool, the original native methods of Bitmap have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original Bitmap class.
+ *
+ * @see DelegateManager
+ *
+ */
+public final class Bitmap_Delegate {
+
+
+    public enum BitmapCreateFlags {
+        NONE, PREMULTIPLIED, MUTABLE
+    }
+
+    // ---- delegate manager ----
+    private static final DelegateManager<Bitmap_Delegate> sManager =
+            new DelegateManager<>(Bitmap_Delegate.class);
+    private static long sFinalizer = -1;
+
+    // ---- delegate helper data ----
+
+    // ---- delegate data ----
+    private final Config mConfig;
+    private final BufferedImage mImage;
+    private boolean mHasAlpha = true;
+    private boolean mHasMipMap = false;      // TODO: check the default.
+    private boolean mIsPremultiplied = true;
+    private int mGenerationId = 0;
+
+
+    // ---- Public Helper methods ----
+
+    /**
+     * Returns the native delegate associated to a given an int referencing a {@link Bitmap} object.
+     */
+    public static Bitmap_Delegate getDelegate(long native_bitmap) {
+        return sManager.getDelegate(native_bitmap);
+    }
+
+    @Nullable
+    public static Bitmap_Delegate getDelegate(@Nullable Bitmap bitmap) {
+        return bitmap == null ? null : getDelegate(bitmap.getNativeInstance());
+    }
+
+    /**
+     * Creates and returns a {@link Bitmap} initialized with the given file content.
+     *
+     * @param input the file from which to read the bitmap content
+     * @param isMutable whether the bitmap is mutable
+     * @param density the density associated with the bitmap
+     *
+     * @see Bitmap#isMutable()
+     * @see Bitmap#getDensity()
+     */
+    public static Bitmap createBitmap(File input, boolean isMutable, Density density)
+            throws IOException {
+        return createBitmap(input, getPremultipliedBitmapCreateFlags(isMutable), density);
+    }
+
+    /**
+     * Creates and returns a {@link Bitmap} initialized with the given file content.
+     *
+     * @param input the file from which to read the bitmap content
+     * @param density the density associated with the bitmap
+     *
+     * @see Bitmap#isPremultiplied()
+     * @see Bitmap#isMutable()
+     * @see Bitmap#getDensity()
+     */
+    private static Bitmap createBitmap(File input, Set<BitmapCreateFlags> createFlags,
+            Density density) throws IOException {
+        // create a delegate with the content of the file.
+        BufferedImage image = ImageIO.read(input);
+        if (image == null && input.exists()) {
+            // There was a problem decoding the image, or the decoder isn't registered. Webp maybe.
+            // Replace with a broken image icon.
+            BridgeContext currentContext = RenderAction.getCurrentContext();
+            if (currentContext != null) {
+                RenderResources resources = currentContext.getRenderResources();
+                ResourceValue broken = resources.getFrameworkResource(ResourceType.DRAWABLE,
+                        "ic_menu_report_image");
+                File brokenFile = new File(broken.getValue());
+                if (brokenFile.exists()) {
+                    image = ImageIO.read(brokenFile);
+                }
+            }
+        }
+        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ARGB_8888);
+
+        return createBitmap(delegate, createFlags, density.getDpiValue());
+    }
+
+    /**
+     * Creates and returns a {@link Bitmap} initialized with the given stream content.
+     *
+     * @param input the stream from which to read the bitmap content
+     * @param isMutable whether the bitmap is mutable
+     * @param density the density associated with the bitmap
+     *
+     * @see Bitmap#isMutable()
+     * @see Bitmap#getDensity()
+     */
+    public static Bitmap createBitmap(InputStream input, boolean isMutable, Density density)
+            throws IOException {
+        return createBitmap(input, getPremultipliedBitmapCreateFlags(isMutable), density);
+    }
+
+    /**
+     * Creates and returns a {@link Bitmap} initialized with the given stream content.
+     *
+     * @param input the stream from which to read the bitmap content
+     * @param density the density associated with the bitmap
+     *
+     * @see Bitmap#isPremultiplied()
+     * @see Bitmap#isMutable()
+     * @see Bitmap#getDensity()
+     */
+    public static Bitmap createBitmap(InputStream input, Set<BitmapCreateFlags> createFlags,
+            Density density) throws IOException {
+        // create a delegate with the content of the stream.
+        Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input), Config.ARGB_8888);
+
+        return createBitmap(delegate, createFlags, density.getDpiValue());
+    }
+
+    /**
+     * Creates and returns a {@link Bitmap} initialized with the given {@link BufferedImage}
+     *
+     * @param image the bitmap content
+     * @param isMutable whether the bitmap is mutable
+     * @param density the density associated with the bitmap
+     *
+     * @see Bitmap#isMutable()
+     * @see Bitmap#getDensity()
+     */
+    public static Bitmap createBitmap(BufferedImage image, boolean isMutable, Density density) {
+        return createBitmap(image, getPremultipliedBitmapCreateFlags(isMutable), density);
+    }
+
+    /**
+     * Creates and returns a {@link Bitmap} initialized with the given {@link BufferedImage}
+     *
+     * @param image the bitmap content
+     * @param density the density associated with the bitmap
+     *
+     * @see Bitmap#isPremultiplied()
+     * @see Bitmap#isMutable()
+     * @see Bitmap#getDensity()
+     */
+    public static Bitmap createBitmap(BufferedImage image, Set<BitmapCreateFlags> createFlags,
+            Density density) {
+        // create a delegate with the given image.
+        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ARGB_8888);
+
+        return createBitmap(delegate, createFlags, density.getDpiValue());
+    }
+
+    private static int getBufferedImageType() {
+        return BufferedImage.TYPE_INT_ARGB;
+    }
+
+    /**
+     * Returns the {@link BufferedImage} used by the delegate of the given {@link Bitmap}.
+     */
+    public BufferedImage getImage() {
+        return mImage;
+    }
+
+    /**
+     * Returns the Android bitmap config. Note that this not the config of the underlying
+     * Java2D bitmap.
+     */
+    public Config getConfig() {
+        return mConfig;
+    }
+
+    /**
+     * Returns the hasAlpha rendering hint
+     * @return true if the bitmap alpha should be used at render time
+     */
+    public boolean hasAlpha() {
+        return mHasAlpha && mConfig != Config.RGB_565;
+    }
+
+    /**
+     * Update the generationId.
+     *
+     * @see Bitmap#getGenerationId()
+     */
+    public void change() {
+        mGenerationId++;
+    }
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static Bitmap nativeCreate(int[] colors, int offset, int stride, int width,
+            int height, int nativeConfig, boolean isMutable, @Nullable float[] xyzD50,
+            @Nullable ColorSpace.Rgb.TransferParameters p) {
+        int imageType = getBufferedImageType();
+
+        // create the image
+        BufferedImage image = new BufferedImage(width, height, imageType);
+
+        if (colors != null) {
+            image.setRGB(0, 0, width, height, colors, offset, stride);
+        }
+
+        // create a delegate with the content of the stream.
+        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.nativeToConfig(nativeConfig));
+
+        return createBitmap(delegate, getPremultipliedBitmapCreateFlags(isMutable),
+                            Bitmap.getDefaultDensity());
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static Bitmap nativeCopy(long srcBitmap, int nativeConfig, boolean isMutable) {
+        Bitmap_Delegate srcBmpDelegate = sManager.getDelegate(srcBitmap);
+        if (srcBmpDelegate == null) {
+            return null;
+        }
+
+        BufferedImage srcImage = srcBmpDelegate.getImage();
+
+        int width = srcImage.getWidth();
+        int height = srcImage.getHeight();
+
+        int imageType = getBufferedImageType();
+
+        // create the image
+        BufferedImage image = new BufferedImage(width, height, imageType);
+
+        // copy the source image into the image.
+        int[] argb = new int[width * height];
+        srcImage.getRGB(0, 0, width, height, argb, 0, width);
+        image.setRGB(0, 0, width, height, argb, 0, width);
+
+        // create a delegate with the content of the stream.
+        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.nativeToConfig(nativeConfig));
+
+        return createBitmap(delegate, getPremultipliedBitmapCreateFlags(isMutable),
+                Bitmap.getDefaultDensity());
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static Bitmap nativeCopyAshmem(long nativeSrcBitmap) {
+        // Unused method; no implementation provided.
+        assert false;
+        return null;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static Bitmap nativeCopyAshmemConfig(long nativeSrcBitmap, int nativeConfig) {
+        // Unused method; no implementation provided.
+        assert false;
+        return null;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeGetNativeFinalizer() {
+        synchronized (Bitmap_Delegate.class) {
+            if (sFinalizer == -1) {
+                sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(sManager::removeJavaReferenceFor);
+            }
+            return sFinalizer;
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nativeRecycle(long nativeBitmap) {
+        // In our case reycle() is a no-op. We will let the finalizer to dispose the bitmap.
+        return true;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nativeReconfigure(long nativeBitmap, int width, int height,
+            int config, boolean isPremultiplied) {
+        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+                "Bitmap.reconfigure() is not supported", null /*data*/);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nativeCompress(long nativeBitmap, int format, int quality,
+            OutputStream stream, byte[] tempStorage) {
+        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+                "Bitmap.compress() is not supported", null /*data*/);
+        return true;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nativeErase(long nativeBitmap, int color) {
+        // get the delegate from the native int.
+        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+        if (delegate == null) {
+            return;
+        }
+
+        BufferedImage image = delegate.mImage;
+
+        Graphics2D g = image.createGraphics();
+        try {
+            g.setColor(new java.awt.Color(color, true));
+
+            g.fillRect(0, 0, image.getWidth(), image.getHeight());
+        } finally {
+            g.dispose();
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static int nativeRowBytes(long nativeBitmap) {
+        // get the delegate from the native int.
+        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+        if (delegate == null) {
+            return 0;
+        }
+
+        return delegate.mImage.getWidth();
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static int nativeConfig(long nativeBitmap) {
+        // get the delegate from the native int.
+        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+        if (delegate == null) {
+            return 0;
+        }
+
+        return delegate.mConfig.nativeInt;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nativeHasAlpha(long nativeBitmap) {
+        // get the delegate from the native int.
+        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+        return delegate == null || delegate.mHasAlpha;
+
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nativeHasMipMap(long nativeBitmap) {
+        // get the delegate from the native int.
+        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+        return delegate == null || delegate.mHasMipMap;
+
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static int nativeGetPixel(long nativeBitmap, int x, int y) {
+        // get the delegate from the native int.
+        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+        if (delegate == null) {
+            return 0;
+        }
+
+        return delegate.mImage.getRGB(x, y);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nativeGetPixels(long nativeBitmap, int[] pixels, int offset,
+            int stride, int x, int y, int width, int height) {
+        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+        if (delegate == null) {
+            return;
+        }
+
+        delegate.getImage().getRGB(x, y, width, height, pixels, offset, stride);
+    }
+
+
+    @LayoutlibDelegate
+    /*package*/ static void nativeSetPixel(long nativeBitmap, int x, int y, int color) {
+        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+        if (delegate == null) {
+            return;
+        }
+
+        delegate.getImage().setRGB(x, y, color);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nativeSetPixels(long nativeBitmap, int[] colors, int offset,
+            int stride, int x, int y, int width, int height) {
+        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+        if (delegate == null) {
+            return;
+        }
+
+        delegate.getImage().setRGB(x, y, width, height, colors, offset, stride);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nativeCopyPixelsToBuffer(long nativeBitmap, Buffer dst) {
+        // FIXME implement native delegate
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "Bitmap.copyPixelsToBuffer is not supported.", null, null /*data*/);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nativeCopyPixelsFromBuffer(long nb, Buffer src) {
+        // FIXME implement native delegate
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "Bitmap.copyPixelsFromBuffer is not supported.", null, null /*data*/);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static int nativeGenerationId(long nativeBitmap) {
+        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+        if (delegate == null) {
+            return 0;
+        }
+
+        return delegate.mGenerationId;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static Bitmap nativeCreateFromParcel(Parcel p) {
+        // This is only called by Bitmap.CREATOR (Parcelable.Creator<Bitmap>), which is only
+        // used during aidl call so really this should not be called.
+        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+                "AIDL is not suppored, and therefore Bitmaps cannot be created from parcels.",
+                null /*data*/);
+        return null;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nativeWriteToParcel(long nativeBitmap, boolean isMutable,
+            int density, Parcel p) {
+        // This is only called when sending a bitmap through aidl, so really this should not
+        // be called.
+        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+                "AIDL is not suppored, and therefore Bitmaps cannot be written to parcels.",
+                null /*data*/);
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static Bitmap nativeExtractAlpha(long nativeBitmap, long nativePaint,
+            int[] offsetXY) {
+        Bitmap_Delegate bitmap = sManager.getDelegate(nativeBitmap);
+        if (bitmap == null) {
+            return null;
+        }
+
+        // get the paint which can be null if nativePaint is 0.
+        Paint_Delegate paint = Paint_Delegate.getDelegate(nativePaint);
+
+        if (paint != null && paint.getMaskFilter() != null) {
+            Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER,
+                    "MaskFilter not supported in Bitmap.extractAlpha",
+                    null, null /*data*/);
+        }
+
+        int alpha = paint != null ? paint.getAlpha() : 0xFF;
+        BufferedImage image = createCopy(bitmap.getImage(), BufferedImage.TYPE_INT_ARGB, alpha);
+
+        // create the delegate. The actual Bitmap config is only an alpha channel
+        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ALPHA_8);
+
+        // the density doesn't matter, it's set by the Java method.
+        return createBitmap(delegate, EnumSet.of(BitmapCreateFlags.MUTABLE),
+                Density.DEFAULT_DENSITY /*density*/);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nativeIsPremultiplied(long nativeBitmap) {
+        // get the delegate from the native int.
+        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+        return delegate != null && delegate.mIsPremultiplied;
+
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nativeSetPremultiplied(long nativeBitmap, boolean isPremul) {
+        // get the delegate from the native int.
+        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+        if (delegate == null) {
+            return;
+        }
+
+        delegate.mIsPremultiplied = isPremul;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nativeSetHasAlpha(long nativeBitmap, boolean hasAlpha,
+            boolean isPremul) {
+        // get the delegate from the native int.
+        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+        if (delegate == null) {
+            return;
+        }
+
+        delegate.mHasAlpha = hasAlpha;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nativeSetHasMipMap(long nativeBitmap, boolean hasMipMap) {
+        // get the delegate from the native int.
+        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+        if (delegate == null) {
+            return;
+        }
+
+        delegate.mHasMipMap = hasMipMap;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nativeSameAs(long nb0, long nb1) {
+        Bitmap_Delegate delegate1 = sManager.getDelegate(nb0);
+        if (delegate1 == null) {
+            return false;
+        }
+
+        Bitmap_Delegate delegate2 = sManager.getDelegate(nb1);
+        if (delegate2 == null) {
+            return false;
+        }
+
+        BufferedImage image1 = delegate1.getImage();
+        BufferedImage image2 = delegate2.getImage();
+        if (delegate1.mConfig != delegate2.mConfig ||
+                image1.getWidth() != image2.getWidth() ||
+                image1.getHeight() != image2.getHeight()) {
+            return false;
+        }
+
+        // get the internal data
+        int w = image1.getWidth();
+        int h = image2.getHeight();
+        int[] argb1 = new int[w*h];
+        int[] argb2 = new int[w*h];
+
+        image1.getRGB(0, 0, w, h, argb1, 0, w);
+        image2.getRGB(0, 0, w, h, argb2, 0, w);
+
+        // compares
+        if (delegate1.mConfig == Config.ALPHA_8) {
+            // in this case we have to manually compare the alpha channel as the rest is garbage.
+            final int length = w*h;
+            for (int i = 0 ; i < length ; i++) {
+                if ((argb1[i] & 0xFF000000) != (argb2[i] & 0xFF000000)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        return Arrays.equals(argb1, argb2);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static int nativeGetAllocationByteCount(long nativeBitmap) {
+        // get the delegate from the native int.
+        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+        if (delegate == null) {
+            return 0;
+        }
+        return nativeRowBytes(nativeBitmap) * delegate.mImage.getHeight();
+
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nativePrepareToDraw(long nativeBitmap) {
+        // do nothing as Bitmap_Delegate does not have caches
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static Bitmap nativeCopyPreserveInternalConfig(long nativeBitmap) {
+        Bitmap_Delegate srcBmpDelegate = sManager.getDelegate(nativeBitmap);
+        if (srcBmpDelegate == null) {
+            return null;
+        }
+
+        BufferedImage srcImage = srcBmpDelegate.getImage();
+
+        // create the image
+        BufferedImage image = new BufferedImage(srcImage.getColorModel(), srcImage.copyData(null),
+                srcImage.isAlphaPremultiplied(), null);
+
+        // create a delegate with the content of the stream.
+        Bitmap_Delegate delegate = new Bitmap_Delegate(image, srcBmpDelegate.getConfig());
+
+        return createBitmap(delegate, EnumSet.of(BitmapCreateFlags.NONE),
+                Bitmap.getDefaultDensity());
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static Bitmap nativeCreateHardwareBitmap(GraphicBuffer buffer) {
+        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+                "Bitmap.nativeCreateHardwareBitmap() is not supported", null /*data*/);
+        return null;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static GraphicBuffer nativeCreateGraphicBufferHandle(long nativeBitmap) {
+        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+                "Bitmap.nativeCreateGraphicBufferHandle() is not supported", null /*data*/);
+        return null;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nativeIsSRGB(long nativeBitmap) {
+        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+                "Color spaces are not supported", null /*data*/);
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nativeGetColorSpace(long nativePtr, float[] xyz, float[] params) {
+        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+                "Color spaces are not supported", null /*data*/);
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nativeCopyColorSpace(long srcBitmap, long dstBitmap) {
+        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+                "Color spaces are not supported", null /*data*/);
+    }
+
+    // ---- Private delegate/helper methods ----
+
+    private Bitmap_Delegate(BufferedImage image, Config config) {
+        mImage = image;
+        mConfig = config;
+    }
+
+    private static Bitmap createBitmap(Bitmap_Delegate delegate,
+            Set<BitmapCreateFlags> createFlags, int density) {
+        // get its native_int
+        long nativeInt = sManager.addNewDelegate(delegate);
+
+        int width = delegate.mImage.getWidth();
+        int height = delegate.mImage.getHeight();
+        boolean isMutable = createFlags.contains(BitmapCreateFlags.MUTABLE);
+        boolean isPremultiplied = createFlags.contains(BitmapCreateFlags.PREMULTIPLIED);
+
+        // and create/return a new Bitmap with it
+        return new Bitmap(nativeInt, width, height, density, isMutable,
+                          isPremultiplied, null /*ninePatchChunk*/, null /* layoutBounds */);
+    }
+
+    private static Set<BitmapCreateFlags> getPremultipliedBitmapCreateFlags(boolean isMutable) {
+        Set<BitmapCreateFlags> createFlags = EnumSet.of(BitmapCreateFlags.PREMULTIPLIED);
+        if (isMutable) {
+            createFlags.add(BitmapCreateFlags.MUTABLE);
+        }
+        return createFlags;
+    }
+
+    /**
+     * Creates and returns a copy of a given BufferedImage.
+     * <p/>
+     * if alpha is different than 255, then it is applied to the alpha channel of each pixel.
+     *
+     * @param image the image to copy
+     * @param imageType the type of the new image
+     * @param alpha an optional alpha modifier
+     * @return a new BufferedImage
+     */
+    /*package*/ static BufferedImage createCopy(BufferedImage image, int imageType, int alpha) {
+        int w = image.getWidth();
+        int h = image.getHeight();
+
+        BufferedImage result = new BufferedImage(w, h, imageType);
+
+        int[] argb = new int[w * h];
+        image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth());
+
+        if (alpha != 255) {
+            final int length = argb.length;
+            for (int i = 0 ; i < length; i++) {
+                int a = (argb[i] >>> 24 * alpha) / 255;
+                argb[i] = (a << 24) | (argb[i] & 0x00FFFFFF);
+            }
+        }
+
+        result.setRGB(0, 0, w, h, argb, 0, w);
+
+        return result;
+    }
+
+}
diff --git a/android/graphics/BlendComposite.java b/android/graphics/BlendComposite.java
new file mode 100644
index 0000000..5cc964a
--- /dev/null
+++ b/android/graphics/BlendComposite.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2014 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;
+
+import java.awt.Composite;
+import java.awt.CompositeContext;
+import java.awt.RenderingHints;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
+
+/*
+ * (non-Javadoc)
+ * The class is adapted from a demo tool for Blending Modes written by
+ * Romain Guy ([email protected]). The tool is available at
+ * http://www.curious-creature.org/2006/09/20/new-blendings-modes-for-java2d/
+ *
+ * This class has been adapted for applying color filters. When applying color filters, the src
+ * image should not extend beyond the dest image, but in our implementation of the filters, it does.
+ * To compensate for the effect, we recompute the alpha value of the src image before applying
+ * the color filter as it should have been applied.
+ */
+public final class BlendComposite implements Composite {
+    public enum BlendingMode {
+        MULTIPLY(),
+        SCREEN(),
+        DARKEN(),
+        LIGHTEN(),
+        OVERLAY(),
+        ADD();
+
+        private final BlendComposite mComposite;
+
+        BlendingMode() {
+            mComposite = new BlendComposite(this);
+        }
+
+        BlendComposite getBlendComposite() {
+            return mComposite;
+        }
+    }
+
+    private float alpha;
+    private BlendingMode mode;
+
+    private BlendComposite(BlendingMode mode) {
+        this(mode, 1.0f);
+    }
+
+    private BlendComposite(BlendingMode mode, float alpha) {
+        this.mode = mode;
+        setAlpha(alpha);
+    }
+
+    public static BlendComposite getInstance(BlendingMode mode) {
+        return mode.getBlendComposite();
+    }
+
+    public static BlendComposite getInstance(BlendingMode mode, float alpha) {
+        if (alpha > 0.9999f) {
+            return getInstance(mode);
+        }
+        return new BlendComposite(mode, alpha);
+    }
+
+    public float getAlpha() {
+        return alpha;
+    }
+
+    public BlendingMode getMode() {
+        return mode;
+    }
+
+    private void setAlpha(float alpha) {
+        if (alpha < 0.0f || alpha > 1.0f) {
+            assert false : "alpha must be comprised between 0.0f and 1.0f";
+            alpha = Math.min(alpha, 1.0f);
+            alpha = Math.max(alpha, 0.0f);
+        }
+
+        this.alpha = alpha;
+    }
+
+    @Override
+    public int hashCode() {
+        return Float.floatToIntBits(alpha) * 31 + mode.ordinal();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof BlendComposite)) {
+            return false;
+        }
+
+        BlendComposite bc = (BlendComposite) obj;
+
+        return mode == bc.mode && alpha == bc.alpha;
+    }
+
+    public CompositeContext createContext(ColorModel srcColorModel,
+                                          ColorModel dstColorModel,
+                                          RenderingHints hints) {
+        return new BlendingContext(this);
+    }
+
+    private static final class BlendingContext implements CompositeContext {
+        private final Blender blender;
+        private final BlendComposite composite;
+
+        private BlendingContext(BlendComposite composite) {
+            this.composite = composite;
+            this.blender = Blender.getBlenderFor(composite);
+        }
+
+        public void dispose() {
+        }
+
+        public void compose(Raster src, Raster dstIn, WritableRaster dstOut) {
+            if (src.getSampleModel().getDataType() != DataBuffer.TYPE_INT ||
+                dstIn.getSampleModel().getDataType() != DataBuffer.TYPE_INT ||
+                dstOut.getSampleModel().getDataType() != DataBuffer.TYPE_INT) {
+                throw new IllegalStateException(
+                        "Source and destination must store pixels as INT.");
+            }
+
+            int width = Math.min(src.getWidth(), dstIn.getWidth());
+            int height = Math.min(src.getHeight(), dstIn.getHeight());
+
+            float alpha = composite.getAlpha();
+
+            int[] srcPixel = new int[4];
+            int[] dstPixel = new int[4];
+            int[] result = new int[4];
+            int[] srcPixels = new int[width];
+            int[] dstPixels = new int[width];
+
+            for (int y = 0; y < height; y++) {
+                dstIn.getDataElements(0, y, width, 1, dstPixels);
+                if (alpha != 0) {
+                    src.getDataElements(0, y, width, 1, srcPixels);
+                    for (int x = 0; x < width; x++) {
+                        // pixels are stored as INT_ARGB
+                        // our arrays are [R, G, B, A]
+                        int pixel = srcPixels[x];
+                        srcPixel[0] = (pixel >> 16) & 0xFF;
+                        srcPixel[1] = (pixel >>  8) & 0xFF;
+                        srcPixel[2] = (pixel      ) & 0xFF;
+                        srcPixel[3] = (pixel >> 24) & 0xFF;
+
+                        pixel = dstPixels[x];
+                        dstPixel[0] = (pixel >> 16) & 0xFF;
+                        dstPixel[1] = (pixel >>  8) & 0xFF;
+                        dstPixel[2] = (pixel      ) & 0xFF;
+                        dstPixel[3] = (pixel >> 24) & 0xFF;
+
+                        // ---- Modified from original ----
+                        // recompute src pixel for transparency.
+                        srcPixel[3] *= dstPixel[3] / 0xFF;
+                        // ---- Modification ends ----
+
+                        result = blender.blend(srcPixel, dstPixel, result);
+
+                        // mixes the result with the opacity
+                        if (alpha == 1) {
+                            dstPixels[x] = (result[3] & 0xFF) << 24 |
+                                           (result[0] & 0xFF) << 16 |
+                                           (result[1] & 0xFF) <<  8 |
+                                           result[2] & 0xFF;
+                        } else {
+                            dstPixels[x] =
+                                    ((int) (dstPixel[3] + (result[3] - dstPixel[3]) * alpha) & 0xFF) << 24 |
+                                    ((int) (dstPixel[0] + (result[0] - dstPixel[0]) * alpha) & 0xFF) << 16 |
+                                    ((int) (dstPixel[1] + (result[1] - dstPixel[1]) * alpha) & 0xFF) <<  8 |
+                                    (int) (dstPixel[2] + (result[2] - dstPixel[2]) * alpha) & 0xFF;
+                        }
+
+                    }
+            }
+                dstOut.setDataElements(0, y, width, 1, dstPixels);
+            }
+        }
+    }
+
+    private static abstract class Blender {
+        public abstract int[] blend(int[] src, int[] dst, int[] result);
+
+        public static Blender getBlenderFor(BlendComposite composite) {
+            switch (composite.getMode()) {
+                case ADD:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 4; i++) {
+                                result[i] = Math.min(255, src[i] + dst[i]);
+                            }
+                            return result;
+                        }
+                    };
+                case DARKEN:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 3; i++) {
+                                result[i] = Math.min(src[i], dst[i]);
+                            }
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                case LIGHTEN:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 3; i++) {
+                                result[i] = Math.max(src[i], dst[i]);
+                            }
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                case MULTIPLY:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 3; i++) {
+                                result[i] = (src[i] * dst[i]) >> 8;
+                            }
+                            result[3] = Math.min(255, src[3] + dst[3] - (src[3] * dst[3]) / 255);
+                            return result;
+                        }
+                    };
+                case OVERLAY:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 3; i++) {
+                                result[i] = dst[i] < 128 ? dst[i] * src[i] >> 7 :
+                                    255 - ((255 - dst[i]) * (255 - src[i]) >> 7);
+                            }
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                case SCREEN:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            result[0] = 255 - ((255 - src[0]) * (255 - dst[0]) >> 8);
+                            result[1] = 255 - ((255 - src[1]) * (255 - dst[1]) >> 8);
+                            result[2] = 255 - ((255 - src[2]) * (255 - dst[2]) >> 8);
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                default:
+                    assert false : "Blender not implement for " + composite.getMode().name();
+
+                    // Ignore the blend
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            result[0] = dst[0];
+                            result[1] = dst[1];
+                            result[2] = dst[2];
+                            result[3] = dst[3];
+                            return result;
+                        }
+                    };
+            }
+        }
+    }
+}
diff --git a/android/graphics/BlurMaskFilter.java b/android/graphics/BlurMaskFilter.java
new file mode 100644
index 0000000..f3064f8
--- /dev/null
+++ b/android/graphics/BlurMaskFilter.java
@@ -0,0 +1,66 @@
+/*
+ * 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;
+
+/**
+ * This takes a mask, and blurs its edge by the specified radius. Whether or
+ * or not to include the original mask, and whether the blur goes outside,
+ * inside, or straddles, the original mask's border, is controlled by the
+ * Blur enum.
+ */
+public class BlurMaskFilter extends MaskFilter {
+
+    public enum Blur {
+        /**
+         * Blur inside and outside the original border.
+         */
+        NORMAL(0),
+
+        /**
+         * Draw solid inside the border, blur outside.
+         */
+        SOLID(1),
+
+        /**
+         * Draw nothing inside the border, blur outside.
+         */
+        OUTER(2),
+
+        /**
+         * Blur inside the border, draw nothing outside.
+         */
+        INNER(3);
+        
+        Blur(int value) {
+            native_int = value;
+        }
+        final int native_int;
+    }
+    
+    /**
+     * Create a blur maskfilter.
+     *
+     * @param radius The radius to extend the blur from the original mask. Must be > 0.
+     * @param style  The Blur to use
+     * @return       The new blur maskfilter
+     */
+    public BlurMaskFilter(float radius, Blur style) {
+        native_instance = nativeConstructor(radius, style.native_int);
+    }
+
+    private static native long nativeConstructor(float radius, int style);
+}
diff --git a/android/graphics/BlurMaskFilter_Delegate.java b/android/graphics/BlurMaskFilter_Delegate.java
new file mode 100644
index 0000000..d2569c7
--- /dev/null
+++ b/android/graphics/BlurMaskFilter_Delegate.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.BlurMaskFilter
+ *
+ * Through the layoutlib_create tool, the original native methods of BlurMaskFilter have
+ * been replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original BlurMaskFilter class.
+ *
+ * Because this extends {@link MaskFilter_Delegate}, there's no need to use a
+ * {@link DelegateManager}, as all the Shader classes will be added to the manager
+ * owned by {@link MaskFilter_Delegate}.
+ *
+ * @see MaskFilter_Delegate
+ *
+ */
+public class BlurMaskFilter_Delegate extends MaskFilter_Delegate {
+
+    // ---- delegate data ----
+
+    // ---- Public Helper methods ----
+
+    @Override
+    public boolean isSupported() {
+        return false;
+    }
+
+    @Override
+    public String getSupportMessage() {
+        return "Blur Mask Filters are not supported.";
+    }
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeConstructor(float radius, int style) {
+        BlurMaskFilter_Delegate newDelegate = new BlurMaskFilter_Delegate();
+        return sManager.addNewDelegate(newDelegate);
+    }
+
+    // ---- Private delegate/helper methods ----
+}
diff --git a/android/graphics/Camera.java b/android/graphics/Camera.java
new file mode 100644
index 0000000..60588d0
--- /dev/null
+++ b/android/graphics/Camera.java
@@ -0,0 +1,178 @@
+/*
+ * 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;
+
+/**
+ * A camera instance can be used to compute 3D transformations and
+ * generate a matrix that can be applied, for instance, on a
+ * {@link Canvas}.
+ */
+public class Camera {
+    private Matrix mMatrix;
+
+    /**
+     * Creates a new camera, with empty transformations.
+     */
+    public Camera() {
+        nativeConstructor();
+    }
+
+    /**
+     * Saves the camera state. Each save should be balanced
+     * with a call to {@link #restore()}.
+     * 
+     * @see #save() 
+     */
+    public native void save();
+
+    /**
+     * Restores the saved state, if any.
+     * 
+     * @see #restore() 
+     */
+    public native void restore();
+
+    /**
+     * Applies a translation transform on all three axis.
+     * 
+     * @param x The distance to translate by on the X axis
+     * @param y The distance to translate by on the Y axis
+     * @param z The distance to translate by on the Z axis
+     */
+    public native void translate(float x, float y, float z);
+
+    /**
+     * Applies a rotation transform around the X axis.
+     * 
+     * @param deg The angle of rotation around the X axis, in degrees
+     * 
+     * @see #rotateY(float)
+     * @see #rotateZ(float)
+     * @see #rotate(float, float, float)
+     */
+    public native void rotateX(float deg);
+
+    /**
+     * Applies a rotation transform around the Y axis.
+     * 
+     * @param deg The angle of rotation around the Y axis, in degrees
+     * 
+     * @see #rotateX(float)
+     * @see #rotateZ(float)
+     * @see #rotate(float, float, float) 
+     */
+    public native void rotateY(float deg);
+
+    /**
+     * Applies a rotation transform around the Z axis.
+     * 
+     * @param deg The angle of rotation around the Z axis, in degrees
+     * 
+     * @see #rotateX(float)
+     * @see #rotateY(float)
+     * @see #rotate(float, float, float)
+     */    
+    public native void rotateZ(float deg);
+
+    /**
+     * Applies a rotation transform around all three axis.
+     * 
+     * @param x The angle of rotation around the X axis, in degrees
+     * @param y The angle of rotation around the Y axis, in degrees
+     * @param z The angle of rotation around the Z axis, in degrees
+     * 
+     * @see #rotateX(float)
+     * @see #rotateY(float)
+     * @see #rotateZ(float)
+     */
+    public native void rotate(float x, float y, float z);
+
+    /**
+     * Gets the x location of the camera.
+     *
+     * @see #setLocation(float, float, float)
+     */
+    public native float getLocationX();
+
+    /**
+     * Gets the y location of the camera.
+     *
+     * @see #setLocation(float, float, float)
+     */
+    public native float getLocationY();
+
+    /**
+     * Gets the z location of the camera.
+     *
+     * @see #setLocation(float, float, float)
+     */
+    public native float getLocationZ();
+
+    /**
+     * Sets the location of the camera. The default location is set at
+     * 0, 0, -8.
+     * 
+     * @param x The x location of the camera
+     * @param y The y location of the camera
+     * @param z The z location of the camera
+     */
+    public native void setLocation(float x, float y, float z);
+
+    /**
+     * Computes the matrix corresponding to the current transformation
+     * and copies it to the supplied matrix object.
+     * 
+     * @param matrix The matrix to copy the current transforms into
+     */
+    public void getMatrix(Matrix matrix) {
+        nativeGetMatrix(matrix.native_instance);
+    }
+
+    /**
+     * Computes the matrix corresponding to the current transformation
+     * and applies it to the specified Canvas.
+     * 
+     * @param canvas The Canvas to set the transform matrix onto
+     */
+    public void applyToCanvas(Canvas canvas) {
+        if (canvas.isHardwareAccelerated()) {
+            if (mMatrix == null) mMatrix = new Matrix();
+            getMatrix(mMatrix);
+            canvas.concat(mMatrix);
+        } else {
+            nativeApplyToCanvas(canvas.getNativeCanvasWrapper());
+        }
+    }
+
+    public native float dotWithNormal(float dx, float dy, float dz);
+
+    protected void finalize() throws Throwable {
+        try {
+            nativeDestructor();
+            native_instance = 0;
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private native void nativeConstructor();
+    private native void nativeDestructor();
+    private native void nativeGetMatrix(long native_matrix);
+    private native void nativeApplyToCanvas(long native_canvas);
+
+    long native_instance;
+}
diff --git a/android/graphics/Canvas.java b/android/graphics/Canvas.java
new file mode 100644
index 0000000..f5e8633
--- /dev/null
+++ b/android/graphics/Canvas.java
@@ -0,0 +1,1978 @@
+/*
+ * 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;
+
+import android.annotation.ColorInt;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.Size;
+import android.os.Build;
+
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.microedition.khronos.opengles.GL;
+
+/**
+ * The Canvas class holds the "draw" calls. To draw something, you need
+ * 4 basic components: A Bitmap to hold the pixels, a Canvas to host
+ * the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect,
+ * Path, text, Bitmap), and a paint (to describe the colors and styles for the
+ * drawing).
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about how to use Canvas, read the
+ * <a href="{@docRoot}guide/topics/graphics/2d-graphics.html">
+ * Canvas and Drawables</a> developer guide.</p></div>
+ */
+public class Canvas extends BaseCanvas {
+    /** @hide */
+    public static boolean sCompatibilityRestore = false;
+    /** @hide */
+    public static boolean sCompatibilitySetBitmap = false;
+
+    /** @hide */
+    public long getNativeCanvasWrapper() {
+        return mNativeCanvasWrapper;
+    }
+
+    /** @hide */
+    public boolean isRecordingFor(Object o) { return false; }
+
+    // may be null
+    private Bitmap mBitmap;
+
+    // optional field set by the caller
+    private DrawFilter mDrawFilter;
+
+    // Maximum bitmap size as defined in Skia's native code
+    // (see SkCanvas.cpp, SkDraw.cpp)
+    private static final int MAXMIMUM_BITMAP_SIZE = 32766;
+
+    // The approximate size of the native allocation associated with
+    // a Canvas object.
+    private static final long NATIVE_ALLOCATION_SIZE = 525;
+
+    // Use a Holder to allow static initialization of Canvas in the boot image.
+    private static class NoImagePreloadHolder {
+        public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+                Canvas.class.getClassLoader(), nGetNativeFinalizer(), NATIVE_ALLOCATION_SIZE);
+    }
+
+    // This field is used to finalize the native Canvas properly
+    private Runnable mFinalizer;
+
+    /**
+     * Construct an empty raster canvas. Use setBitmap() to specify a bitmap to
+     * draw into.  The initial target density is {@link Bitmap#DENSITY_NONE};
+     * this will typically be replaced when a target bitmap is set for the
+     * canvas.
+     */
+    public Canvas() {
+        if (!isHardwareAccelerated()) {
+            // 0 means no native bitmap
+            mNativeCanvasWrapper = nInitRaster(null);
+            mFinalizer = NoImagePreloadHolder.sRegistry.registerNativeAllocation(
+                    this, mNativeCanvasWrapper);
+        } else {
+            mFinalizer = null;
+        }
+    }
+
+    /**
+     * Construct a canvas with the specified bitmap to draw into. The bitmap
+     * must be mutable.
+     *
+     * <p>The initial target density of the canvas is the same as the given
+     * bitmap's density.
+     *
+     * @param bitmap Specifies a mutable bitmap for the canvas to draw into.
+     */
+    public Canvas(@NonNull Bitmap bitmap) {
+        if (!bitmap.isMutable()) {
+            throw new IllegalStateException("Immutable bitmap passed to Canvas constructor");
+        }
+        throwIfCannotDraw(bitmap);
+        mNativeCanvasWrapper = nInitRaster(bitmap);
+        mFinalizer = NoImagePreloadHolder.sRegistry.registerNativeAllocation(
+                this, mNativeCanvasWrapper);
+        mBitmap = bitmap;
+        mDensity = bitmap.mDensity;
+    }
+
+    /** @hide */
+    public Canvas(long nativeCanvas) {
+        if (nativeCanvas == 0) {
+            throw new IllegalStateException();
+        }
+        mNativeCanvasWrapper = nativeCanvas;
+        mFinalizer = NoImagePreloadHolder.sRegistry.registerNativeAllocation(
+                this, mNativeCanvasWrapper);
+        mDensity = Bitmap.getDefaultDensity();
+    }
+
+    /**
+     * Returns null.
+     *
+     * @deprecated This method is not supported and should not be invoked.
+     *
+     * @hide
+     */
+    @Deprecated
+    protected GL getGL() {
+        return null;
+    }
+
+    /**
+     * Indicates whether this Canvas uses hardware acceleration.
+     *
+     * Note that this method does not define what type of hardware acceleration
+     * may or may not be used.
+     *
+     * @return True if drawing operations are hardware accelerated,
+     *         false otherwise.
+     */
+    public boolean isHardwareAccelerated() {
+        return false;
+    }
+
+    /**
+     * Specify a bitmap for the canvas to draw into. All canvas state such as
+     * layers, filters, and the save/restore stack are reset. Additionally,
+     * the canvas' target density is updated to match that of the bitmap.
+     *
+     * Prior to API level {@value Build.VERSION_CODES#O} the current matrix and
+     * clip stack were preserved.
+     *
+     * @param bitmap Specifies a mutable bitmap for the canvas to draw into.
+     * @see #setDensity(int)
+     * @see #getDensity()
+     */
+    public void setBitmap(@Nullable Bitmap bitmap) {
+        if (isHardwareAccelerated()) {
+            throw new RuntimeException("Can't set a bitmap device on a HW accelerated canvas");
+        }
+
+        Matrix preservedMatrix = null;
+        if (bitmap != null && sCompatibilitySetBitmap) {
+            preservedMatrix = getMatrix();
+        }
+
+        if (bitmap == null) {
+            nSetBitmap(mNativeCanvasWrapper, null);
+            mDensity = Bitmap.DENSITY_NONE;
+        } else {
+            if (!bitmap.isMutable()) {
+                throw new IllegalStateException();
+            }
+            throwIfCannotDraw(bitmap);
+
+            nSetBitmap(mNativeCanvasWrapper, bitmap);
+            mDensity = bitmap.mDensity;
+        }
+
+        if (preservedMatrix != null) {
+            setMatrix(preservedMatrix);
+        }
+
+        mBitmap = bitmap;
+    }
+
+    /** @hide */
+    public void insertReorderBarrier() {}
+
+    /** @hide */
+    public void insertInorderBarrier() {}
+
+    /**
+     * Return true if the device that the current layer draws into is opaque
+     * (i.e. does not support per-pixel alpha).
+     *
+     * @return true if the device that the current layer draws into is opaque
+     */
+    public boolean isOpaque() {
+        return nIsOpaque(mNativeCanvasWrapper);
+    }
+
+    /**
+     * Returns the width of the current drawing layer
+     *
+     * @return the width of the current drawing layer
+     */
+    public int getWidth() {
+        return nGetWidth(mNativeCanvasWrapper);
+    }
+
+    /**
+     * Returns the height of the current drawing layer
+     *
+     * @return the height of the current drawing layer
+     */
+    public int getHeight() {
+        return nGetHeight(mNativeCanvasWrapper);
+    }
+
+    /**
+     * <p>Returns the target density of the canvas.  The default density is
+     * derived from the density of its backing bitmap, or
+     * {@link Bitmap#DENSITY_NONE} if there is not one.</p>
+     *
+     * @return Returns the current target density of the canvas, which is used
+     * to determine the scaling factor when drawing a bitmap into it.
+     *
+     * @see #setDensity(int)
+     * @see Bitmap#getDensity()
+     */
+    public int getDensity() {
+        return mDensity;
+    }
+
+    /**
+     * <p>Specifies the density for this Canvas' backing bitmap.  This modifies
+     * the target density of the canvas itself, as well as the density of its
+     * backing bitmap via {@link Bitmap#setDensity(int) Bitmap.setDensity(int)}.
+     *
+     * @param density The new target density of the canvas, which is used
+     * to determine the scaling factor when drawing a bitmap into it.  Use
+     * {@link Bitmap#DENSITY_NONE} to disable bitmap scaling.
+     *
+     * @see #getDensity()
+     * @see Bitmap#setDensity(int)
+     */
+    public void setDensity(int density) {
+        if (mBitmap != null) {
+            mBitmap.setDensity(density);
+        }
+        mDensity = density;
+    }
+
+    /** @hide */
+    public void setScreenDensity(int density) {
+        mScreenDensity = density;
+    }
+
+    /**
+     * Returns the maximum allowed width for bitmaps drawn with this canvas.
+     * Attempting to draw with a bitmap wider than this value will result
+     * in an error.
+     *
+     * @see #getMaximumBitmapHeight()
+     */
+    public int getMaximumBitmapWidth() {
+        return MAXMIMUM_BITMAP_SIZE;
+    }
+
+    /**
+     * Returns the maximum allowed height for bitmaps drawn with this canvas.
+     * Attempting to draw with a bitmap taller than this value will result
+     * in an error.
+     *
+     * @see #getMaximumBitmapWidth()
+     */
+    public int getMaximumBitmapHeight() {
+        return MAXMIMUM_BITMAP_SIZE;
+    }
+
+    // the SAVE_FLAG constants must match their native equivalents
+
+    /** @hide */
+    @IntDef(flag = true,
+            value = {
+                ALL_SAVE_FLAG
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Saveflags {}
+
+    /**
+     * Restore the current matrix when restore() is called.
+     *
+     * @deprecated Use the flagless version of {@link #save()}, {@link #saveLayer(RectF, Paint)} or
+     *             {@link #saveLayerAlpha(RectF, int)}. For saveLayer() calls the matrix
+     *             was always restored for {@link #isHardwareAccelerated() Hardware accelerated}
+     *             canvases and as of API level {@value Build.VERSION_CODES#O} that is the default
+     *             behavior for all canvas types.
+     */
+    public static final int MATRIX_SAVE_FLAG = 0x01;
+
+    /**
+     * Restore the current clip when restore() is called.
+     *
+     * @deprecated Use the flagless version of {@link #save()}, {@link #saveLayer(RectF, Paint)} or
+     *             {@link #saveLayerAlpha(RectF, int)}. For saveLayer() calls the clip
+     *             was always restored for {@link #isHardwareAccelerated() Hardware accelerated}
+     *             canvases and as of API level {@value Build.VERSION_CODES#O} that is the default
+     *             behavior for all canvas types.
+     */
+    public static final int CLIP_SAVE_FLAG = 0x02;
+
+    /**
+     * The layer requires a per-pixel alpha channel.
+     *
+     * @deprecated This flag is ignored. Use the flagless version of {@link #saveLayer(RectF, Paint)}
+     *             {@link #saveLayerAlpha(RectF, int)}.
+     */
+    public static final int HAS_ALPHA_LAYER_SAVE_FLAG = 0x04;
+
+    /**
+     * The layer requires full 8-bit precision for each color channel.
+     *
+     * @deprecated This flag is ignored. Use the flagless version of {@link #saveLayer(RectF, Paint)}
+     *             {@link #saveLayerAlpha(RectF, int)}.
+     */
+    public static final int FULL_COLOR_LAYER_SAVE_FLAG = 0x08;
+
+    /**
+     * Clip drawing to the bounds of the offscreen layer, omit at your own peril.
+     * <p class="note"><strong>Note:</strong> it is strongly recommended to not
+     * omit this flag for any call to <code>saveLayer()</code> and
+     * <code>saveLayerAlpha()</code> variants. Not passing this flag generally
+     * triggers extremely poor performance with hardware accelerated rendering.
+     *
+     * @deprecated This flag results in poor performance and the same effect can be achieved with
+     *             a single layer or multiple draw commands with different clips.
+     *
+     */
+    public static final int CLIP_TO_LAYER_SAVE_FLAG = 0x10;
+
+    /**
+     * Restore everything when restore() is called (standard save flags).
+     * <p class="note"><strong>Note:</strong> for performance reasons, it is
+     * strongly recommended to pass this - the complete set of flags - to any
+     * call to <code>saveLayer()</code> and <code>saveLayerAlpha()</code>
+     * variants.
+     *
+     * <p class="note"><strong>Note:</strong> all methods that accept this flag
+     * have flagless versions that are equivalent to passing this flag.
+     */
+    public static final int ALL_SAVE_FLAG = 0x1F;
+
+    /**
+     * Saves the current matrix and clip onto a private stack.
+     * <p>
+     * Subsequent calls to translate,scale,rotate,skew,concat or clipRect,
+     * clipPath will all operate as usual, but when the balancing call to
+     * restore() is made, those calls will be forgotten, and the settings that
+     * existed before the save() will be reinstated.
+     *
+     * @return The value to pass to restoreToCount() to balance this save()
+     */
+    public int save() {
+        return nSave(mNativeCanvasWrapper, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
+    }
+
+    /**
+     * Based on saveFlags, can save the current matrix and clip onto a private
+     * stack.
+     * <p class="note"><strong>Note:</strong> if possible, use the
+     * parameter-less save(). It is simpler and faster than individually
+     * disabling the saving of matrix or clip with this method.
+     * <p>
+     * Subsequent calls to translate,scale,rotate,skew,concat or clipRect,
+     * clipPath will all operate as usual, but when the balancing call to
+     * restore() is made, those calls will be forgotten, and the settings that
+     * existed before the save() will be reinstated.
+     *
+     * @deprecated Use {@link #save()} instead.
+     * @param saveFlags flag bits that specify which parts of the Canvas state
+     *                  to save/restore
+     * @return The value to pass to restoreToCount() to balance this save()
+     */
+    public int save(@Saveflags int saveFlags) {
+        return nSave(mNativeCanvasWrapper, saveFlags);
+    }
+
+    /**
+     * This behaves the same as save(), but in addition it allocates and
+     * redirects drawing to an offscreen bitmap.
+     * <p class="note"><strong>Note:</strong> this method is very expensive,
+     * incurring more than double rendering cost for contained content. Avoid
+     * using this method, especially if the bounds provided are large, or if
+     * the {@link #CLIP_TO_LAYER_SAVE_FLAG} is omitted from the
+     * {@code saveFlags} parameter. It is recommended to use a
+     * {@link android.view.View#LAYER_TYPE_HARDWARE hardware layer} on a View
+     * to apply an xfermode, color filter, or alpha, as it will perform much
+     * better than this method.
+     * <p>
+     * All drawing calls are directed to a newly allocated offscreen bitmap.
+     * Only when the balancing call to restore() is made, is that offscreen
+     * buffer drawn back to the current target of the Canvas (either the
+     * screen, it's target Bitmap, or the previous layer).
+     * <p>
+     * Attributes of the Paint - {@link Paint#getAlpha() alpha},
+     * {@link Paint#getXfermode() Xfermode}, and
+     * {@link Paint#getColorFilter() ColorFilter} are applied when the
+     * offscreen bitmap is drawn back when restore() is called.
+     *
+     * @deprecated Use {@link #saveLayer(RectF, Paint)} instead.
+     * @param bounds May be null. The maximum size the offscreen bitmap
+     *               needs to be (in local coordinates)
+     * @param paint  This is copied, and is applied to the offscreen when
+     *               restore() is called.
+     * @param saveFlags see _SAVE_FLAG constants, generally {@link #ALL_SAVE_FLAG} is recommended
+     *               for performance reasons.
+     * @return       value to pass to restoreToCount() to balance this save()
+     */
+    public int saveLayer(@Nullable RectF bounds, @Nullable Paint paint, @Saveflags int saveFlags) {
+        if (bounds == null) {
+            bounds = new RectF(getClipBounds());
+        }
+        return saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, paint, saveFlags);
+    }
+
+    /**
+     * This behaves the same as save(), but in addition it allocates and
+     * redirects drawing to an offscreen rendering target.
+     * <p class="note"><strong>Note:</strong> this method is very expensive,
+     * incurring more than double rendering cost for contained content. Avoid
+     * using this method when possible and instead use a
+     * {@link android.view.View#LAYER_TYPE_HARDWARE hardware layer} on a View
+     * to apply an xfermode, color filter, or alpha, as it will perform much
+     * better than this method.
+     * <p>
+     * All drawing calls are directed to a newly allocated offscreen rendering target.
+     * Only when the balancing call to restore() is made, is that offscreen
+     * buffer drawn back to the current target of the Canvas (which can potentially be a previous
+     * layer if these calls are nested).
+     * <p>
+     * Attributes of the Paint - {@link Paint#getAlpha() alpha},
+     * {@link Paint#getXfermode() Xfermode}, and
+     * {@link Paint#getColorFilter() ColorFilter} are applied when the
+     * offscreen rendering target is drawn back when restore() is called.
+     *
+     * @param bounds May be null. The maximum size the offscreen render target
+     *               needs to be (in local coordinates)
+     * @param paint  This is copied, and is applied to the offscreen when
+     *               restore() is called.
+     * @return       value to pass to restoreToCount() to balance this save()
+     */
+    public int saveLayer(@Nullable RectF bounds, @Nullable Paint paint) {
+        return saveLayer(bounds, paint, ALL_SAVE_FLAG);
+    }
+
+    /**
+     * Helper version of saveLayer() that takes 4 values rather than a RectF.
+     *
+     * @deprecated Use {@link #saveLayer(float, float, float, float, Paint)} instead.
+     */
+    public int saveLayer(float left, float top, float right, float bottom, @Nullable Paint paint,
+            @Saveflags int saveFlags) {
+        return nSaveLayer(mNativeCanvasWrapper, left, top, right, bottom,
+                paint != null ? paint.getNativeInstance() : 0,
+                saveFlags);
+    }
+
+    /**
+     * Convenience for {@link #saveLayer(RectF, Paint)} that takes the four float coordinates of the
+     * bounds rectangle.
+     */
+    public int saveLayer(float left, float top, float right, float bottom, @Nullable Paint paint) {
+        return saveLayer(left, top, right, bottom, paint, ALL_SAVE_FLAG);
+    }
+
+    /**
+     * This behaves the same as save(), but in addition it allocates and
+     * redirects drawing to an offscreen bitmap.
+     * <p class="note"><strong>Note:</strong> this method is very expensive,
+     * incurring more than double rendering cost for contained content. Avoid
+     * using this method, especially if the bounds provided are large, or if
+     * the {@link #CLIP_TO_LAYER_SAVE_FLAG} is omitted from the
+     * {@code saveFlags} parameter. It is recommended to use a
+     * {@link android.view.View#LAYER_TYPE_HARDWARE hardware layer} on a View
+     * to apply an xfermode, color filter, or alpha, as it will perform much
+     * better than this method.
+     * <p>
+     * All drawing calls are directed to a newly allocated offscreen bitmap.
+     * Only when the balancing call to restore() is made, is that offscreen
+     * buffer drawn back to the current target of the Canvas (either the
+     * screen, it's target Bitmap, or the previous layer).
+     * <p>
+     * The {@code alpha} parameter is applied when the offscreen bitmap is
+     * drawn back when restore() is called.
+     *
+     * @deprecated Use {@link #saveLayerAlpha(RectF, int)} instead.
+     * @param bounds    The maximum size the offscreen bitmap needs to be
+     *                  (in local coordinates)
+     * @param alpha     The alpha to apply to the offscreen when it is
+                        drawn during restore()
+     * @param saveFlags see _SAVE_FLAG constants, generally {@link #ALL_SAVE_FLAG} is recommended
+     *                  for performance reasons.
+     * @return          value to pass to restoreToCount() to balance this call
+     */
+    public int saveLayerAlpha(@Nullable RectF bounds, int alpha, @Saveflags int saveFlags) {
+        if (bounds == null) {
+            bounds = new RectF(getClipBounds());
+        }
+        return saveLayerAlpha(bounds.left, bounds.top, bounds.right, bounds.bottom, alpha, saveFlags);
+    }
+
+    /**
+     * Convenience for {@link #saveLayer(RectF, Paint)} but instead of taking a entire Paint object
+     * it takes only the {@code alpha} parameter.
+     *
+     * @param bounds    The maximum size the offscreen bitmap needs to be
+     *                  (in local coordinates)
+     * @param alpha     The alpha to apply to the offscreen when it is
+                        drawn during restore()
+     */
+    public int saveLayerAlpha(@Nullable RectF bounds, int alpha) {
+        return saveLayerAlpha(bounds, alpha, ALL_SAVE_FLAG);
+    }
+
+    /**
+     * Helper for saveLayerAlpha() that takes 4 values instead of a RectF.
+     *
+     * @deprecated Use {@link #saveLayerAlpha(float, float, float, float, int)} instead.
+     */
+    public int saveLayerAlpha(float left, float top, float right, float bottom, int alpha,
+            @Saveflags int saveFlags) {
+        alpha = Math.min(255, Math.max(0, alpha));
+        return nSaveLayerAlpha(mNativeCanvasWrapper, left, top, right, bottom,
+                                     alpha, saveFlags);
+    }
+
+    /**
+     * Convenience for {@link #saveLayerAlpha(RectF, int)} that takes the four float coordinates of
+     * the bounds rectangle.
+     */
+    public int saveLayerAlpha(float left, float top, float right, float bottom, int alpha) {
+        return saveLayerAlpha(left, top, right, bottom, alpha, ALL_SAVE_FLAG);
+    }
+
+    /**
+     * This call balances a previous call to save(), and is used to remove all
+     * modifications to the matrix/clip state since the last save call. It is
+     * an error to call restore() more times than save() was called.
+     */
+    public void restore() {
+        if (!nRestore(mNativeCanvasWrapper)
+                && (!sCompatibilityRestore || !isHardwareAccelerated())) {
+            throw new IllegalStateException("Underflow in restore - more restores than saves");
+        }
+    }
+
+    /**
+     * Returns the number of matrix/clip states on the Canvas' private stack.
+     * This will equal # save() calls - # restore() calls.
+     */
+    public int getSaveCount() {
+        return nGetSaveCount(mNativeCanvasWrapper);
+    }
+
+    /**
+     * Efficient way to pop any calls to save() that happened after the save
+     * count reached saveCount. It is an error for saveCount to be less than 1.
+     *
+     * Example:
+     *    int count = canvas.save();
+     *    ... // more calls potentially to save()
+     *    canvas.restoreToCount(count);
+     *    // now the canvas is back in the same state it was before the initial
+     *    // call to save().
+     *
+     * @param saveCount The save level to restore to.
+     */
+    public void restoreToCount(int saveCount) {
+        if (saveCount < 1) {
+            if (!sCompatibilityRestore || !isHardwareAccelerated()) {
+                // do nothing and throw without restoring
+                throw new IllegalArgumentException(
+                        "Underflow in restoreToCount - more restores than saves");
+            }
+            // compat behavior - restore as far as possible
+            saveCount = 1;
+        }
+        nRestoreToCount(mNativeCanvasWrapper, saveCount);
+    }
+
+    /**
+     * Preconcat the current matrix with the specified translation
+     *
+     * @param dx The distance to translate in X
+     * @param dy The distance to translate in Y
+    */
+    public void translate(float dx, float dy) {
+        if (dx == 0.0f && dy == 0.0f) return;
+        nTranslate(mNativeCanvasWrapper, dx, dy);
+    }
+
+    /**
+     * Preconcat the current matrix with the specified scale.
+     *
+     * @param sx The amount to scale in X
+     * @param sy The amount to scale in Y
+     */
+    public void scale(float sx, float sy) {
+        if (sx == 1.0f && sy == 1.0f) return;
+        nScale(mNativeCanvasWrapper, sx, sy);
+    }
+
+    /**
+     * Preconcat the current matrix with the specified scale.
+     *
+     * @param sx The amount to scale in X
+     * @param sy The amount to scale in Y
+     * @param px The x-coord for the pivot point (unchanged by the scale)
+     * @param py The y-coord for the pivot point (unchanged by the scale)
+     */
+    public final void scale(float sx, float sy, float px, float py) {
+        if (sx == 1.0f && sy == 1.0f) return;
+        translate(px, py);
+        scale(sx, sy);
+        translate(-px, -py);
+    }
+
+    /**
+     * Preconcat the current matrix with the specified rotation.
+     *
+     * @param degrees The amount to rotate, in degrees
+     */
+    public void rotate(float degrees) {
+        if (degrees == 0.0f) return;
+        nRotate(mNativeCanvasWrapper, degrees);
+    }
+
+    /**
+     * Preconcat the current matrix with the specified rotation.
+     *
+     * @param degrees The amount to rotate, in degrees
+     * @param px The x-coord for the pivot point (unchanged by the rotation)
+     * @param py The y-coord for the pivot point (unchanged by the rotation)
+     */
+    public final void rotate(float degrees, float px, float py) {
+        if (degrees == 0.0f) return;
+        translate(px, py);
+        rotate(degrees);
+        translate(-px, -py);
+    }
+
+    /**
+     * Preconcat the current matrix with the specified skew.
+     *
+     * @param sx The amount to skew in X
+     * @param sy The amount to skew in Y
+     */
+    public void skew(float sx, float sy) {
+        if (sx == 0.0f && sy == 0.0f) return;
+        nSkew(mNativeCanvasWrapper, sx, sy);
+    }
+
+    /**
+     * Preconcat the current matrix with the specified matrix. If the specified
+     * matrix is null, this method does nothing.
+     *
+     * @param matrix The matrix to preconcatenate with the current matrix
+     */
+    public void concat(@Nullable Matrix matrix) {
+        if (matrix != null) nConcat(mNativeCanvasWrapper, matrix.native_instance);
+    }
+
+    /**
+     * Completely replace the current matrix with the specified matrix. If the
+     * matrix parameter is null, then the current matrix is reset to identity.
+     *
+     * <strong>Note:</strong> it is recommended to use {@link #concat(Matrix)},
+     * {@link #scale(float, float)}, {@link #translate(float, float)} and
+     * {@link #rotate(float)} instead of this method.
+     *
+     * @param matrix The matrix to replace the current matrix with. If it is
+     *               null, set the current matrix to identity.
+     *
+     * @see #concat(Matrix)
+     */
+    public void setMatrix(@Nullable Matrix matrix) {
+        nSetMatrix(mNativeCanvasWrapper,
+                         matrix == null ? 0 : matrix.native_instance);
+    }
+
+    /**
+     * Return, in ctm, the current transformation matrix. This does not alter
+     * the matrix in the canvas, but just returns a copy of it.
+     *
+     * @deprecated {@link #isHardwareAccelerated() Hardware accelerated} canvases may have any
+     * matrix when passed to a View or Drawable, as it is implementation defined where in the
+     * hierarchy such canvases are created. It is recommended in such cases to either draw contents
+     * irrespective of the current matrix, or to track relevant transform state outside of the
+     * canvas.
+     */
+    @Deprecated
+    public void getMatrix(@NonNull Matrix ctm) {
+        nGetMatrix(mNativeCanvasWrapper, ctm.native_instance);
+    }
+
+    /**
+     * Return a new matrix with a copy of the canvas' current transformation
+     * matrix.
+     *
+     * @deprecated {@link #isHardwareAccelerated() Hardware accelerated} canvases may have any
+     * matrix when passed to a View or Drawable, as it is implementation defined where in the
+     * hierarchy such canvases are created. It is recommended in such cases to either draw contents
+     * irrespective of the current matrix, or to track relevant transform state outside of the
+     * canvas.
+     */
+    @Deprecated
+    public final @NonNull Matrix getMatrix() {
+        Matrix m = new Matrix();
+        //noinspection deprecation
+        getMatrix(m);
+        return m;
+    }
+
+    /**
+     * Modify the current clip with the specified rectangle.
+     *
+     * @param rect The rect to intersect with the current clip
+     * @param op How the clip is modified
+     * @return true if the resulting clip is non-empty
+     *
+     * @deprecated Region.Op values other than {@link Region.Op#INTERSECT} and
+     * {@link Region.Op#DIFFERENCE} have the ability to expand the clip. The canvas clipping APIs
+     * are intended to only expand the clip as a result of a restore operation. This enables a view
+     * parent to clip a canvas to clearly define the maximal drawing area of its children. The
+     * recommended alternative calls are {@link #clipRect(RectF)} and {@link #clipOutRect(RectF)};
+     */
+    @Deprecated
+    public boolean clipRect(@NonNull RectF rect, @NonNull Region.Op op) {
+        return nClipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom,
+                op.nativeInt);
+    }
+
+    /**
+     * Modify the current clip with the specified rectangle, which is
+     * expressed in local coordinates.
+     *
+     * @param rect The rectangle to intersect with the current clip.
+     * @param op How the clip is modified
+     * @return true if the resulting clip is non-empty
+     *
+     * @deprecated Region.Op values other than {@link Region.Op#INTERSECT} and
+     * {@link Region.Op#DIFFERENCE} have the ability to expand the clip. The canvas clipping APIs
+     * are intended to only expand the clip as a result of a restore operation. This enables a view
+     * parent to clip a canvas to clearly define the maximal drawing area of its children. The
+     * recommended alternative calls are {@link #clipRect(Rect)} and {@link #clipOutRect(Rect)};
+     */
+    @Deprecated
+    public boolean clipRect(@NonNull Rect rect, @NonNull Region.Op op) {
+        return nClipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom,
+                op.nativeInt);
+    }
+
+    /**
+     * Intersect the current clip with the specified rectangle, which is
+     * expressed in local coordinates.
+     *
+     * @param rect The rectangle to intersect with the current clip.
+     * @return true if the resulting clip is non-empty
+     */
+    public boolean clipRect(@NonNull RectF rect) {
+        return nClipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom,
+                Region.Op.INTERSECT.nativeInt);
+    }
+
+    /**
+     * Set the clip to the difference of the current clip and the specified rectangle, which is
+     * expressed in local coordinates.
+     *
+     * @param rect The rectangle to perform a difference op with the current clip.
+     * @return true if the resulting clip is non-empty
+     */
+    public boolean clipOutRect(@NonNull RectF rect) {
+        return nClipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom,
+                Region.Op.DIFFERENCE.nativeInt);
+    }
+
+    /**
+     * Intersect the current clip with the specified rectangle, which is
+     * expressed in local coordinates.
+     *
+     * @param rect The rectangle to intersect with the current clip.
+     * @return true if the resulting clip is non-empty
+     */
+    public boolean clipRect(@NonNull Rect rect) {
+        return nClipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom,
+                Region.Op.INTERSECT.nativeInt);
+    }
+
+    /**
+     * Set the clip to the difference of the current clip and the specified rectangle, which is
+     * expressed in local coordinates.
+     *
+     * @param rect The rectangle to perform a difference op with the current clip.
+     * @return true if the resulting clip is non-empty
+     */
+    public boolean clipOutRect(@NonNull Rect rect) {
+        return nClipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom,
+                Region.Op.DIFFERENCE.nativeInt);
+    }
+
+    /**
+     * Modify the current clip with the specified rectangle, which is
+     * expressed in local coordinates.
+     *
+     * @param left   The left side of the rectangle to intersect with the
+     *               current clip
+     * @param top    The top of the rectangle to intersect with the current
+     *               clip
+     * @param right  The right side of the rectangle to intersect with the
+     *               current clip
+     * @param bottom The bottom of the rectangle to intersect with the current
+     *               clip
+     * @param op     How the clip is modified
+     * @return       true if the resulting clip is non-empty
+     *
+     * @deprecated Region.Op values other than {@link Region.Op#INTERSECT} and
+     * {@link Region.Op#DIFFERENCE} have the ability to expand the clip. The canvas clipping APIs
+     * are intended to only expand the clip as a result of a restore operation. This enables a view
+     * parent to clip a canvas to clearly define the maximal drawing area of its children. The
+     * recommended alternative calls are {@link #clipRect(float,float,float,float)} and
+     * {@link #clipOutRect(float,float,float,float)};
+     */
+    @Deprecated
+    public boolean clipRect(float left, float top, float right, float bottom,
+            @NonNull Region.Op op) {
+        return nClipRect(mNativeCanvasWrapper, left, top, right, bottom, op.nativeInt);
+    }
+
+    /**
+     * Intersect the current clip with the specified rectangle, which is
+     * expressed in local coordinates.
+     *
+     * @param left   The left side of the rectangle to intersect with the
+     *               current clip
+     * @param top    The top of the rectangle to intersect with the current clip
+     * @param right  The right side of the rectangle to intersect with the
+     *               current clip
+     * @param bottom The bottom of the rectangle to intersect with the current
+     *               clip
+     * @return       true if the resulting clip is non-empty
+     */
+    public boolean clipRect(float left, float top, float right, float bottom) {
+        return nClipRect(mNativeCanvasWrapper, left, top, right, bottom,
+                Region.Op.INTERSECT.nativeInt);
+    }
+
+    /**
+     * Set the clip to the difference of the current clip and the specified rectangle, which is
+     * expressed in local coordinates.
+     *
+     * @param left   The left side of the rectangle used in the difference operation
+     * @param top    The top of the rectangle used in the difference operation
+     * @param right  The right side of the rectangle used in the difference operation
+     * @param bottom The bottom of the rectangle used in the difference operation
+     * @return       true if the resulting clip is non-empty
+     */
+    public boolean clipOutRect(float left, float top, float right, float bottom) {
+        return nClipRect(mNativeCanvasWrapper, left, top, right, bottom,
+                Region.Op.DIFFERENCE.nativeInt);
+    }
+
+    /**
+     * Intersect the current clip with the specified rectangle, which is
+     * expressed in local coordinates.
+     *
+     * @param left   The left side of the rectangle to intersect with the
+     *               current clip
+     * @param top    The top of the rectangle to intersect with the current clip
+     * @param right  The right side of the rectangle to intersect with the
+     *               current clip
+     * @param bottom The bottom of the rectangle to intersect with the current
+     *               clip
+     * @return       true if the resulting clip is non-empty
+     */
+    public boolean clipRect(int left, int top, int right, int bottom) {
+        return nClipRect(mNativeCanvasWrapper, left, top, right, bottom,
+                Region.Op.INTERSECT.nativeInt);
+    }
+
+    /**
+     * Set the clip to the difference of the current clip and the specified rectangle, which is
+     * expressed in local coordinates.
+     *
+     * @param left   The left side of the rectangle used in the difference operation
+     * @param top    The top of the rectangle used in the difference operation
+     * @param right  The right side of the rectangle used in the difference operation
+     * @param bottom The bottom of the rectangle used in the difference operation
+     * @return       true if the resulting clip is non-empty
+     */
+    public boolean clipOutRect(int left, int top, int right, int bottom) {
+        return nClipRect(mNativeCanvasWrapper, left, top, right, bottom,
+                Region.Op.DIFFERENCE.nativeInt);
+    }
+
+    /**
+        * Modify the current clip with the specified path.
+     *
+     * @param path The path to operate on the current clip
+     * @param op   How the clip is modified
+     * @return     true if the resulting is non-empty
+     *
+     * @deprecated Region.Op values other than {@link Region.Op#INTERSECT} and
+     * {@link Region.Op#DIFFERENCE} have the ability to expand the clip. The canvas clipping APIs
+     * are intended to only expand the clip as a result of a restore operation. This enables a view
+     * parent to clip a canvas to clearly define the maximal drawing area of its children. The
+     * recommended alternative calls are {@link #clipPath(Path)} and
+     * {@link #clipOutPath(Path)};
+     */
+    @Deprecated
+    public boolean clipPath(@NonNull Path path, @NonNull Region.Op op) {
+        return nClipPath(mNativeCanvasWrapper, path.readOnlyNI(), op.nativeInt);
+    }
+
+    /**
+     * Intersect the current clip with the specified path.
+     *
+     * @param path The path to intersect with the current clip
+     * @return     true if the resulting clip is non-empty
+     */
+    public boolean clipPath(@NonNull Path path) {
+        return clipPath(path, Region.Op.INTERSECT);
+    }
+
+    /**
+     * Set the clip to the difference of the current clip and the specified path.
+     *
+     * @param path The path used in the difference operation
+     * @return     true if the resulting clip is non-empty
+     */
+    public boolean clipOutPath(@NonNull Path path) {
+        return clipPath(path, Region.Op.DIFFERENCE);
+    }
+
+    /**
+     * Modify the current clip with the specified region. Note that unlike
+     * clipRect() and clipPath() which transform their arguments by the
+     * current matrix, clipRegion() assumes its argument is already in the
+     * coordinate system of the current layer's bitmap, and so not
+     * transformation is performed.
+     *
+     * @param region The region to operate on the current clip, based on op
+     * @param op How the clip is modified
+     * @return true if the resulting is non-empty
+     *
+     * @removed
+     * @deprecated Unlike all other clip calls this API does not respect the
+     *             current matrix. Use {@link #clipRect(Rect)} as an alternative.
+     */
+    @Deprecated
+    public boolean clipRegion(@NonNull Region region, @NonNull Region.Op op) {
+        return false;
+    }
+
+    /**
+     * Intersect the current clip with the specified region. Note that unlike
+     * clipRect() and clipPath() which transform their arguments by the
+     * current matrix, clipRegion() assumes its argument is already in the
+     * coordinate system of the current layer's bitmap, and so not
+     * transformation is performed.
+     *
+     * @param region The region to operate on the current clip, based on op
+     * @return true if the resulting is non-empty
+     *
+     * @removed
+     * @deprecated Unlike all other clip calls this API does not respect the
+     *             current matrix. Use {@link #clipRect(Rect)} as an alternative.
+     */
+    @Deprecated
+    public boolean clipRegion(@NonNull Region region) {
+        return false;
+    }
+
+    public @Nullable DrawFilter getDrawFilter() {
+        return mDrawFilter;
+    }
+
+    public void setDrawFilter(@Nullable DrawFilter filter) {
+        long nativeFilter = 0;
+        if (filter != null) {
+            nativeFilter = filter.mNativeInt;
+        }
+        mDrawFilter = filter;
+        nSetDrawFilter(mNativeCanvasWrapper, nativeFilter);
+    }
+
+    /**
+     * Constant values used as parameters to {@code quickReject()} calls. These values
+     * specify how much space around the shape should be accounted for, depending on whether
+     * the shaped area is antialiased or not.
+     *
+     * @see #quickReject(float, float, float, float, EdgeType)
+     * @see #quickReject(Path, EdgeType)
+     * @see #quickReject(RectF, EdgeType)
+     */
+    public enum EdgeType {
+
+        /**
+         * Black-and-White: Treat edges by just rounding to nearest pixel boundary
+         */
+        BW(0),  //!< treat edges by just rounding to nearest pixel boundary
+
+        /**
+         * Antialiased: Treat edges by rounding-out, since they may be antialiased
+         */
+        AA(1);
+
+        EdgeType(int nativeInt) {
+            this.nativeInt = nativeInt;
+        }
+
+        /**
+         * @hide
+         */
+        public final int nativeInt;
+    }
+
+    /**
+     * Return true if the specified rectangle, after being transformed by the
+     * current matrix, would lie completely outside of the current clip. Call
+     * this to check if an area you intend to draw into is clipped out (and
+     * therefore you can skip making the draw calls).
+     *
+     * @param rect  the rect to compare with the current clip
+     * @param type  {@link Canvas.EdgeType#AA} if the path should be considered antialiased,
+     *              since that means it may affect a larger area (more pixels) than
+     *              non-antialiased ({@link Canvas.EdgeType#BW}).
+     * @return      true if the rect (transformed by the canvas' matrix)
+     *              does not intersect with the canvas' clip
+     */
+    public boolean quickReject(@NonNull RectF rect, @NonNull EdgeType type) {
+        return nQuickReject(mNativeCanvasWrapper,
+                rect.left, rect.top, rect.right, rect.bottom);
+    }
+
+    /**
+     * Return true if the specified path, after being transformed by the
+     * current matrix, would lie completely outside of the current clip. Call
+     * this to check if an area you intend to draw into is clipped out (and
+     * therefore you can skip making the draw calls). Note: for speed it may
+     * return false even if the path itself might not intersect the clip
+     * (i.e. the bounds of the path intersects, but the path does not).
+     *
+     * @param path        The path to compare with the current clip
+     * @param type        {@link Canvas.EdgeType#AA} if the path should be considered antialiased,
+     *                    since that means it may affect a larger area (more pixels) than
+     *                    non-antialiased ({@link Canvas.EdgeType#BW}).
+     * @return            true if the path (transformed by the canvas' matrix)
+     *                    does not intersect with the canvas' clip
+     */
+    public boolean quickReject(@NonNull Path path, @NonNull EdgeType type) {
+        return nQuickReject(mNativeCanvasWrapper, path.readOnlyNI());
+    }
+
+    /**
+     * Return true if the specified rectangle, after being transformed by the
+     * current matrix, would lie completely outside of the current clip. Call
+     * this to check if an area you intend to draw into is clipped out (and
+     * therefore you can skip making the draw calls).
+     *
+     * @param left        The left side of the rectangle to compare with the
+     *                    current clip
+     * @param top         The top of the rectangle to compare with the current
+     *                    clip
+     * @param right       The right side of the rectangle to compare with the
+     *                    current clip
+     * @param bottom      The bottom of the rectangle to compare with the
+     *                    current clip
+     * @param type        {@link Canvas.EdgeType#AA} if the path should be considered antialiased,
+     *                    since that means it may affect a larger area (more pixels) than
+     *                    non-antialiased ({@link Canvas.EdgeType#BW}).
+     * @return            true if the rect (transformed by the canvas' matrix)
+     *                    does not intersect with the canvas' clip
+     */
+    public boolean quickReject(float left, float top, float right, float bottom,
+            @NonNull EdgeType type) {
+        return nQuickReject(mNativeCanvasWrapper, left, top, right, bottom);
+    }
+
+    /**
+     * Return the bounds of the current clip (in local coordinates) in the
+     * bounds parameter, and return true if it is non-empty. This can be useful
+     * in a way similar to quickReject, in that it tells you that drawing
+     * outside of these bounds will be clipped out.
+     *
+     * @param bounds Return the clip bounds here. If it is null, ignore it but
+     *               still return true if the current clip is non-empty.
+     * @return true if the current clip is non-empty.
+     */
+    public boolean getClipBounds(@Nullable Rect bounds) {
+        return nGetClipBounds(mNativeCanvasWrapper, bounds);
+    }
+
+    /**
+     * Retrieve the bounds of the current clip (in local coordinates).
+     *
+     * @return the clip bounds, or [0, 0, 0, 0] if the clip is empty.
+     */
+    public final @NonNull Rect getClipBounds() {
+        Rect r = new Rect();
+        getClipBounds(r);
+        return r;
+    }
+
+    /**
+     * Save the canvas state, draw the picture, and restore the canvas state.
+     * This differs from picture.draw(canvas), which does not perform any
+     * save/restore.
+     *
+     * <p>
+     * <strong>Note:</strong> This forces the picture to internally call
+     * {@link Picture#endRecording} in order to prepare for playback.
+     *
+     * @param picture  The picture to be drawn
+     */
+    public void drawPicture(@NonNull Picture picture) {
+        picture.endRecording();
+        int restoreCount = save();
+        picture.draw(this);
+        restoreToCount(restoreCount);
+    }
+
+    /**
+     * Draw the picture, stretched to fit into the dst rectangle.
+     */
+    public void drawPicture(@NonNull Picture picture, @NonNull RectF dst) {
+        save();
+        translate(dst.left, dst.top);
+        if (picture.getWidth() > 0 && picture.getHeight() > 0) {
+            scale(dst.width() / picture.getWidth(), dst.height() / picture.getHeight());
+        }
+        drawPicture(picture);
+        restore();
+    }
+
+    /**
+     * Draw the picture, stretched to fit into the dst rectangle.
+     */
+    public void drawPicture(@NonNull Picture picture, @NonNull Rect dst) {
+        save();
+        translate(dst.left, dst.top);
+        if (picture.getWidth() > 0 && picture.getHeight() > 0) {
+            scale((float) dst.width() / picture.getWidth(),
+                    (float) dst.height() / picture.getHeight());
+        }
+        drawPicture(picture);
+        restore();
+    }
+
+    public enum VertexMode {
+        TRIANGLES(0),
+        TRIANGLE_STRIP(1),
+        TRIANGLE_FAN(2);
+
+        VertexMode(int nativeInt) {
+            this.nativeInt = nativeInt;
+        }
+
+        /**
+         * @hide
+         */
+        public final int nativeInt;
+    }
+
+    /**
+     * Releases the resources associated with this canvas.
+     *
+     * @hide
+     */
+    public void release() {
+        mNativeCanvasWrapper = 0;
+        if (mFinalizer != null) {
+            mFinalizer.run();
+            mFinalizer = null;
+        }
+    }
+
+    /**
+     * Free up as much memory as possible from private caches (e.g. fonts, images)
+     *
+     * @hide
+     */
+    public static void freeCaches() {
+        nFreeCaches();
+    }
+
+    /**
+     * Free up text layout caches
+     *
+     * @hide
+     */
+    public static void freeTextLayoutCaches() {
+        nFreeTextLayoutCaches();
+    }
+
+    private static native void nFreeCaches();
+    private static native void nFreeTextLayoutCaches();
+    private static native long nInitRaster(Bitmap bitmap);
+    private static native long nGetNativeFinalizer();
+
+    // ---------------- @FastNative -------------------
+
+    @FastNative
+    private static native void nSetBitmap(long canvasHandle, Bitmap bitmap);
+
+    @FastNative
+    private static native boolean nGetClipBounds(long nativeCanvas, Rect bounds);
+
+    // ---------------- @CriticalNative -------------------
+
+    @CriticalNative
+    private static native boolean nIsOpaque(long canvasHandle);
+    @CriticalNative
+    private static native int nGetWidth(long canvasHandle);
+    @CriticalNative
+    private static native int nGetHeight(long canvasHandle);
+
+    @CriticalNative
+    private static native int nSave(long canvasHandle, int saveFlags);
+    @CriticalNative
+    private static native int nSaveLayer(long nativeCanvas, float l, float t, float r, float b,
+            long nativePaint, int layerFlags);
+    @CriticalNative
+    private static native int nSaveLayerAlpha(long nativeCanvas, float l, float t, float r, float b,
+            int alpha, int layerFlags);
+    @CriticalNative
+    private static native boolean nRestore(long canvasHandle);
+    @CriticalNative
+    private static native void nRestoreToCount(long canvasHandle, int saveCount);
+    @CriticalNative
+    private static native int nGetSaveCount(long canvasHandle);
+
+    @CriticalNative
+    private static native void nTranslate(long canvasHandle, float dx, float dy);
+    @CriticalNative
+    private static native void nScale(long canvasHandle, float sx, float sy);
+    @CriticalNative
+    private static native void nRotate(long canvasHandle, float degrees);
+    @CriticalNative
+    private static native void nSkew(long canvasHandle, float sx, float sy);
+    @CriticalNative
+    private static native void nConcat(long nativeCanvas, long nativeMatrix);
+    @CriticalNative
+    private static native void nSetMatrix(long nativeCanvas, long nativeMatrix);
+    @CriticalNative
+    private static native boolean nClipRect(long nativeCanvas,
+            float left, float top, float right, float bottom, int regionOp);
+    @CriticalNative
+    private static native boolean nClipPath(long nativeCanvas, long nativePath, int regionOp);
+    @CriticalNative
+    private static native void nSetDrawFilter(long nativeCanvas, long nativeFilter);
+    @CriticalNative
+    private static native void nGetMatrix(long nativeCanvas, long nativeMatrix);
+    @CriticalNative
+    private static native boolean nQuickReject(long nativeCanvas, long nativePath);
+    @CriticalNative
+    private static native boolean nQuickReject(long nativeCanvas, float left, float top,
+            float right, float bottom);
+
+
+    // ---------------- Draw Methods -------------------
+
+    /**
+     * <p>
+     * Draw the specified arc, which will be scaled to fit inside the specified oval.
+     * </p>
+     * <p>
+     * If the start angle is negative or >= 360, the start angle is treated as start angle modulo
+     * 360.
+     * </p>
+     * <p>
+     * If the sweep angle is >= 360, then the oval is drawn completely. Note that this differs
+     * slightly from SkPath::arcTo, which treats the sweep angle modulo 360. If the sweep angle is
+     * negative, the sweep angle is treated as sweep angle modulo 360
+     * </p>
+     * <p>
+     * The arc is drawn clockwise. An angle of 0 degrees correspond to the geometric angle of 0
+     * degrees (3 o'clock on a watch.)
+     * </p>
+     *
+     * @param oval The bounds of oval used to define the shape and size of the arc
+     * @param startAngle Starting angle (in degrees) where the arc begins
+     * @param sweepAngle Sweep angle (in degrees) measured clockwise
+     * @param useCenter If true, include the center of the oval in the arc, and close it if it is
+     *            being stroked. This will draw a wedge
+     * @param paint The paint used to draw the arc
+     */
+    public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter,
+            @NonNull Paint paint) {
+        super.drawArc(oval, startAngle, sweepAngle, useCenter, paint);
+    }
+
+    /**
+     * <p>
+     * Draw the specified arc, which will be scaled to fit inside the specified oval.
+     * </p>
+     * <p>
+     * If the start angle is negative or >= 360, the start angle is treated as start angle modulo
+     * 360.
+     * </p>
+     * <p>
+     * If the sweep angle is >= 360, then the oval is drawn completely. Note that this differs
+     * slightly from SkPath::arcTo, which treats the sweep angle modulo 360. If the sweep angle is
+     * negative, the sweep angle is treated as sweep angle modulo 360
+     * </p>
+     * <p>
+     * The arc is drawn clockwise. An angle of 0 degrees correspond to the geometric angle of 0
+     * degrees (3 o'clock on a watch.)
+     * </p>
+     *
+     * @param startAngle Starting angle (in degrees) where the arc begins
+     * @param sweepAngle Sweep angle (in degrees) measured clockwise
+     * @param useCenter If true, include the center of the oval in the arc, and close it if it is
+     *            being stroked. This will draw a wedge
+     * @param paint The paint used to draw the arc
+     */
+    public void drawArc(float left, float top, float right, float bottom, float startAngle,
+            float sweepAngle, boolean useCenter, @NonNull Paint paint) {
+        super.drawArc(left, top, right, bottom, startAngle, sweepAngle, useCenter, paint);
+    }
+
+    /**
+     * Fill the entire canvas' bitmap (restricted to the current clip) with the specified ARGB
+     * color, using srcover porterduff mode.
+     *
+     * @param a alpha component (0..255) of the color to draw onto the canvas
+     * @param r red component (0..255) of the color to draw onto the canvas
+     * @param g green component (0..255) of the color to draw onto the canvas
+     * @param b blue component (0..255) of the color to draw onto the canvas
+     */
+    public void drawARGB(int a, int r, int g, int b) {
+        super.drawARGB(a, r, g, b);
+    }
+
+    /**
+     * Draw the specified bitmap, with its top/left corner at (x,y), using the specified paint,
+     * transformed by the current matrix.
+     * <p>
+     * Note: if the paint contains a maskfilter that generates a mask which extends beyond the
+     * bitmap's original width/height (e.g. BlurMaskFilter), then the bitmap will be drawn as if it
+     * were in a Shader with CLAMP mode. Thus the color outside of the original width/height will be
+     * the edge color replicated.
+     * <p>
+     * If the bitmap and canvas have different densities, this function will take care of
+     * automatically scaling the bitmap to draw at the same density as the canvas.
+     *
+     * @param bitmap The bitmap to be drawn
+     * @param left The position of the left side of the bitmap being drawn
+     * @param top The position of the top side of the bitmap being drawn
+     * @param paint The paint used to draw the bitmap (may be null)
+     */
+    public void drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint) {
+        super.drawBitmap(bitmap, left, top, paint);
+    }
+
+    /**
+     * Draw the specified bitmap, scaling/translating automatically to fill the destination
+     * rectangle. If the source rectangle is not null, it specifies the subset of the bitmap to
+     * draw.
+     * <p>
+     * Note: if the paint contains a maskfilter that generates a mask which extends beyond the
+     * bitmap's original width/height (e.g. BlurMaskFilter), then the bitmap will be drawn as if it
+     * were in a Shader with CLAMP mode. Thus the color outside of the original width/height will be
+     * the edge color replicated.
+     * <p>
+     * This function <em>ignores the density associated with the bitmap</em>. This is because the
+     * source and destination rectangle coordinate spaces are in their respective densities, so must
+     * already have the appropriate scaling factor applied.
+     *
+     * @param bitmap The bitmap to be drawn
+     * @param src May be null. The subset of the bitmap to be drawn
+     * @param dst The rectangle that the bitmap will be scaled/translated to fit into
+     * @param paint May be null. The paint used to draw the bitmap
+     */
+    public void drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull RectF dst,
+            @Nullable Paint paint) {
+        super.drawBitmap(bitmap, src, dst, paint);
+    }
+
+    /**
+     * Draw the specified bitmap, scaling/translating automatically to fill the destination
+     * rectangle. If the source rectangle is not null, it specifies the subset of the bitmap to
+     * draw.
+     * <p>
+     * Note: if the paint contains a maskfilter that generates a mask which extends beyond the
+     * bitmap's original width/height (e.g. BlurMaskFilter), then the bitmap will be drawn as if it
+     * were in a Shader with CLAMP mode. Thus the color outside of the original width/height will be
+     * the edge color replicated.
+     * <p>
+     * This function <em>ignores the density associated with the bitmap</em>. This is because the
+     * source and destination rectangle coordinate spaces are in their respective densities, so must
+     * already have the appropriate scaling factor applied.
+     *
+     * @param bitmap The bitmap to be drawn
+     * @param src May be null. The subset of the bitmap to be drawn
+     * @param dst The rectangle that the bitmap will be scaled/translated to fit into
+     * @param paint May be null. The paint used to draw the bitmap
+     */
+    public void drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull Rect dst,
+            @Nullable Paint paint) {
+        super.drawBitmap(bitmap, src, dst, paint);
+    }
+
+    /**
+     * Treat the specified array of colors as a bitmap, and draw it. This gives the same result as
+     * first creating a bitmap from the array, and then drawing it, but this method avoids
+     * explicitly creating a bitmap object which can be more efficient if the colors are changing
+     * often.
+     *
+     * @param colors Array of colors representing the pixels of the bitmap
+     * @param offset Offset into the array of colors for the first pixel
+     * @param stride The number of colors in the array between rows (must be >= width or <= -width).
+     * @param x The X coordinate for where to draw the bitmap
+     * @param y The Y coordinate for where to draw the bitmap
+     * @param width The width of the bitmap
+     * @param height The height of the bitmap
+     * @param hasAlpha True if the alpha channel of the colors contains valid values. If false, the
+     *            alpha byte is ignored (assumed to be 0xFF for every pixel).
+     * @param paint May be null. The paint used to draw the bitmap
+     * @deprecated Usage with a {@link #isHardwareAccelerated() hardware accelerated} canvas
+     *             requires an internal copy of color buffer contents every time this method is
+     *             called. Using a Bitmap avoids this copy, and allows the application to more
+     *             explicitly control the lifetime and copies of pixel data.
+     */
+    @Deprecated
+    public void drawBitmap(@NonNull int[] colors, int offset, int stride, float x, float y,
+            int width, int height, boolean hasAlpha, @Nullable Paint paint) {
+        super.drawBitmap(colors, offset, stride, x, y, width, height, hasAlpha, paint);
+    }
+
+    /**
+     * Legacy version of drawBitmap(int[] colors, ...) that took ints for x,y
+     *
+     * @deprecated Usage with a {@link #isHardwareAccelerated() hardware accelerated} canvas
+     *             requires an internal copy of color buffer contents every time this method is
+     *             called. Using a Bitmap avoids this copy, and allows the application to more
+     *             explicitly control the lifetime and copies of pixel data.
+     */
+    @Deprecated
+    public void drawBitmap(@NonNull int[] colors, int offset, int stride, int x, int y,
+            int width, int height, boolean hasAlpha, @Nullable Paint paint) {
+        super.drawBitmap(colors, offset, stride, x, y, width, height, hasAlpha, paint);
+    }
+
+    /**
+     * Draw the bitmap using the specified matrix.
+     *
+     * @param bitmap The bitmap to draw
+     * @param matrix The matrix used to transform the bitmap when it is drawn
+     * @param paint May be null. The paint used to draw the bitmap
+     */
+    public void drawBitmap(@NonNull Bitmap bitmap, @NonNull Matrix matrix, @Nullable Paint paint) {
+        super.drawBitmap(bitmap, matrix, paint);
+    }
+
+    /**
+     * Draw the bitmap through the mesh, where mesh vertices are evenly distributed across the
+     * bitmap. There are meshWidth+1 vertices across, and meshHeight+1 vertices down. The verts
+     * array is accessed in row-major order, so that the first meshWidth+1 vertices are distributed
+     * across the top of the bitmap from left to right. A more general version of this method is
+     * drawVertices().
+     *
+     * @param bitmap The bitmap to draw using the mesh
+     * @param meshWidth The number of columns in the mesh. Nothing is drawn if this is 0
+     * @param meshHeight The number of rows in the mesh. Nothing is drawn if this is 0
+     * @param verts Array of x,y pairs, specifying where the mesh should be drawn. There must be at
+     *            least (meshWidth+1) * (meshHeight+1) * 2 + vertOffset values in the array
+     * @param vertOffset Number of verts elements to skip before drawing
+     * @param colors May be null. Specifies a color at each vertex, which is interpolated across the
+     *            cell, and whose values are multiplied by the corresponding bitmap colors. If not
+     *            null, there must be at least (meshWidth+1) * (meshHeight+1) + colorOffset values
+     *            in the array.
+     * @param colorOffset Number of color elements to skip before drawing
+     * @param paint May be null. The paint used to draw the bitmap
+     */
+    public void drawBitmapMesh(@NonNull Bitmap bitmap, int meshWidth, int meshHeight,
+            @NonNull float[] verts, int vertOffset, @Nullable int[] colors, int colorOffset,
+            @Nullable Paint paint) {
+        super.drawBitmapMesh(bitmap, meshWidth, meshHeight, verts, vertOffset, colors, colorOffset,
+                paint);
+    }
+
+    /**
+     * Draw the specified circle using the specified paint. If radius is <= 0, then nothing will be
+     * drawn. The circle will be filled or framed based on the Style in the paint.
+     *
+     * @param cx The x-coordinate of the center of the cirle to be drawn
+     * @param cy The y-coordinate of the center of the cirle to be drawn
+     * @param radius The radius of the cirle to be drawn
+     * @param paint The paint used to draw the circle
+     */
+    public void drawCircle(float cx, float cy, float radius, @NonNull Paint paint) {
+        super.drawCircle(cx, cy, radius, paint);
+    }
+
+    /**
+     * Fill the entire canvas' bitmap (restricted to the current clip) with the specified color,
+     * using srcover porterduff mode.
+     *
+     * @param color the color to draw onto the canvas
+     */
+    public void drawColor(@ColorInt int color) {
+        super.drawColor(color);
+    }
+
+    /**
+     * Fill the entire canvas' bitmap (restricted to the current clip) with the specified color and
+     * porter-duff xfermode.
+     *
+     * @param color the color to draw with
+     * @param mode the porter-duff mode to apply to the color
+     */
+    public void drawColor(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
+        super.drawColor(color, mode);
+    }
+
+    /**
+     * Draw a line segment with the specified start and stop x,y coordinates, using the specified
+     * paint.
+     * <p>
+     * Note that since a line is always "framed", the Style is ignored in the paint.
+     * </p>
+     * <p>
+     * Degenerate lines (length is 0) will not be drawn.
+     * </p>
+     *
+     * @param startX The x-coordinate of the start point of the line
+     * @param startY The y-coordinate of the start point of the line
+     * @param paint The paint used to draw the line
+     */
+    public void drawLine(float startX, float startY, float stopX, float stopY,
+            @NonNull Paint paint) {
+        super.drawLine(startX, startY, stopX, stopY, paint);
+    }
+
+    /**
+     * Draw a series of lines. Each line is taken from 4 consecutive values in the pts array. Thus
+     * to draw 1 line, the array must contain at least 4 values. This is logically the same as
+     * drawing the array as follows: drawLine(pts[0], pts[1], pts[2], pts[3]) followed by
+     * drawLine(pts[4], pts[5], pts[6], pts[7]) and so on.
+     *
+     * @param pts Array of points to draw [x0 y0 x1 y1 x2 y2 ...]
+     * @param offset Number of values in the array to skip before drawing.
+     * @param count The number of values in the array to process, after skipping "offset" of them.
+     *            Since each line uses 4 values, the number of "lines" that are drawn is really
+     *            (count >> 2).
+     * @param paint The paint used to draw the points
+     */
+    public void drawLines(@Size(multiple = 4) @NonNull float[] pts, int offset, int count,
+            @NonNull Paint paint) {
+        super.drawLines(pts, offset, count, paint);
+    }
+
+    public void drawLines(@Size(multiple = 4) @NonNull float[] pts, @NonNull Paint paint) {
+        super.drawLines(pts, paint);
+    }
+
+    /**
+     * Draw the specified oval using the specified paint. The oval will be filled or framed based on
+     * the Style in the paint.
+     *
+     * @param oval The rectangle bounds of the oval to be drawn
+     */
+    public void drawOval(@NonNull RectF oval, @NonNull Paint paint) {
+        super.drawOval(oval, paint);
+    }
+
+    /**
+     * Draw the specified oval using the specified paint. The oval will be filled or framed based on
+     * the Style in the paint.
+     */
+    public void drawOval(float left, float top, float right, float bottom, @NonNull Paint paint) {
+        super.drawOval(left, top, right, bottom, paint);
+    }
+
+    /**
+     * Fill the entire canvas' bitmap (restricted to the current clip) with the specified paint.
+     * This is equivalent (but faster) to drawing an infinitely large rectangle with the specified
+     * paint.
+     *
+     * @param paint The paint used to draw onto the canvas
+     */
+    public void drawPaint(@NonNull Paint paint) {
+        super.drawPaint(paint);
+    }
+
+    /**
+     * Draws the specified bitmap as an N-patch (most often, a 9-patches.)
+     *
+     * @param patch The ninepatch object to render
+     * @param dst The destination rectangle.
+     * @param paint The paint to draw the bitmap with. may be null
+     * @hide
+     */
+    public void drawPatch(@NonNull NinePatch patch, @NonNull Rect dst, @Nullable Paint paint) {
+        super.drawPatch(patch, dst, paint);
+    }
+
+    /**
+     * Draws the specified bitmap as an N-patch (most often, a 9-patches.)
+     *
+     * @param patch The ninepatch object to render
+     * @param dst The destination rectangle.
+     * @param paint The paint to draw the bitmap with. may be null
+     * @hide
+     */
+    public void drawPatch(@NonNull NinePatch patch, @NonNull RectF dst, @Nullable Paint paint) {
+        super.drawPatch(patch, dst, paint);
+    }
+
+    /**
+     * Draw the specified path using the specified paint. The path will be filled or framed based on
+     * the Style in the paint.
+     *
+     * @param path The path to be drawn
+     * @param paint The paint used to draw the path
+     */
+    public void drawPath(@NonNull Path path, @NonNull Paint paint) {
+        super.drawPath(path, paint);
+    }
+
+    /**
+     * Helper for drawPoints() for drawing a single point.
+     */
+    public void drawPoint(float x, float y, @NonNull Paint paint) {
+        super.drawPoint(x, y, paint);
+    }
+
+    /**
+     * Draw a series of points. Each point is centered at the coordinate specified by pts[], and its
+     * diameter is specified by the paint's stroke width (as transformed by the canvas' CTM), with
+     * special treatment for a stroke width of 0, which always draws exactly 1 pixel (or at most 4
+     * if antialiasing is enabled). The shape of the point is controlled by the paint's Cap type.
+     * The shape is a square, unless the cap type is Round, in which case the shape is a circle.
+     *
+     * @param pts Array of points to draw [x0 y0 x1 y1 x2 y2 ...]
+     * @param offset Number of values to skip before starting to draw.
+     * @param count The number of values to process, after skipping offset of them. Since one point
+     *            uses two values, the number of "points" that are drawn is really (count >> 1).
+     * @param paint The paint used to draw the points
+     */
+    public void drawPoints(@Size(multiple = 2) float[] pts, int offset, int count,
+            @NonNull Paint paint) {
+        super.drawPoints(pts, offset, count, paint);
+    }
+
+    /**
+     * Helper for drawPoints() that assumes you want to draw the entire array
+     */
+    public void drawPoints(@Size(multiple = 2) @NonNull float[] pts, @NonNull Paint paint) {
+        super.drawPoints(pts, paint);
+    }
+
+    /**
+     * Draw the text in the array, with each character's origin specified by the pos array.
+     *
+     * @param text The text to be drawn
+     * @param index The index of the first character to draw
+     * @param count The number of characters to draw, starting from index.
+     * @param pos Array of [x,y] positions, used to position each character
+     * @param paint The paint used for the text (e.g. color, size, style)
+     * @deprecated This method does not support glyph composition and decomposition and should
+     *             therefore not be used to render complex scripts. It also doesn't handle
+     *             supplementary characters (eg emoji).
+     */
+    @Deprecated
+    public void drawPosText(@NonNull char[] text, int index, int count,
+            @NonNull @Size(multiple = 2) float[] pos,
+            @NonNull Paint paint) {
+        super.drawPosText(text, index, count, pos, paint);
+    }
+
+    /**
+     * Draw the text in the array, with each character's origin specified by the pos array.
+     *
+     * @param text The text to be drawn
+     * @param pos Array of [x,y] positions, used to position each character
+     * @param paint The paint used for the text (e.g. color, size, style)
+     * @deprecated This method does not support glyph composition and decomposition and should
+     *             therefore not be used to render complex scripts. It also doesn't handle
+     *             supplementary characters (eg emoji).
+     */
+    @Deprecated
+    public void drawPosText(@NonNull String text, @NonNull @Size(multiple = 2) float[] pos,
+            @NonNull Paint paint) {
+        super.drawPosText(text, pos, paint);
+    }
+
+    /**
+     * Draw the specified Rect using the specified paint. The rectangle will be filled or framed
+     * based on the Style in the paint.
+     *
+     * @param rect The rect to be drawn
+     * @param paint The paint used to draw the rect
+     */
+    public void drawRect(@NonNull RectF rect, @NonNull Paint paint) {
+        super.drawRect(rect, paint);
+    }
+
+    /**
+     * Draw the specified Rect using the specified Paint. The rectangle will be filled or framed
+     * based on the Style in the paint.
+     *
+     * @param r The rectangle to be drawn.
+     * @param paint The paint used to draw the rectangle
+     */
+    public void drawRect(@NonNull Rect r, @NonNull Paint paint) {
+        super.drawRect(r, paint);
+    }
+
+    /**
+     * Draw the specified Rect using the specified paint. The rectangle will be filled or framed
+     * based on the Style in the paint.
+     *
+     * @param left The left side of the rectangle to be drawn
+     * @param top The top side of the rectangle to be drawn
+     * @param right The right side of the rectangle to be drawn
+     * @param bottom The bottom side of the rectangle to be drawn
+     * @param paint The paint used to draw the rect
+     */
+    public void drawRect(float left, float top, float right, float bottom, @NonNull Paint paint) {
+        super.drawRect(left, top, right, bottom, paint);
+    }
+
+    /**
+     * Fill the entire canvas' bitmap (restricted to the current clip) with the specified RGB color,
+     * using srcover porterduff mode.
+     *
+     * @param r red component (0..255) of the color to draw onto the canvas
+     * @param g green component (0..255) of the color to draw onto the canvas
+     * @param b blue component (0..255) of the color to draw onto the canvas
+     */
+    public void drawRGB(int r, int g, int b) {
+        super.drawRGB(r, g, b);
+    }
+
+    /**
+     * Draw the specified round-rect using the specified paint. The roundrect will be filled or
+     * framed based on the Style in the paint.
+     *
+     * @param rect The rectangular bounds of the roundRect to be drawn
+     * @param rx The x-radius of the oval used to round the corners
+     * @param ry The y-radius of the oval used to round the corners
+     * @param paint The paint used to draw the roundRect
+     */
+    public void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint) {
+        super.drawRoundRect(rect, rx, ry, paint);
+    }
+
+    /**
+     * Draw the specified round-rect using the specified paint. The roundrect will be filled or
+     * framed based on the Style in the paint.
+     *
+     * @param rx The x-radius of the oval used to round the corners
+     * @param ry The y-radius of the oval used to round the corners
+     * @param paint The paint used to draw the roundRect
+     */
+    public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
+            @NonNull Paint paint) {
+        super.drawRoundRect(left, top, right, bottom, rx, ry, paint);
+    }
+
+    /**
+     * Draw the text, with origin at (x,y), using the specified paint. The origin is interpreted
+     * based on the Align setting in the paint.
+     *
+     * @param text The text to be drawn
+     * @param x The x-coordinate of the origin of the text being drawn
+     * @param y The y-coordinate of the baseline of the text being drawn
+     * @param paint The paint used for the text (e.g. color, size, style)
+     */
+    public void drawText(@NonNull char[] text, int index, int count, float x, float y,
+            @NonNull Paint paint) {
+        super.drawText(text, index, count, x, y, paint);
+    }
+
+    /**
+     * Draw the text, with origin at (x,y), using the specified paint. The origin is interpreted
+     * based on the Align setting in the paint.
+     *
+     * @param text The text to be drawn
+     * @param x The x-coordinate of the origin of the text being drawn
+     * @param y The y-coordinate of the baseline of the text being drawn
+     * @param paint The paint used for the text (e.g. color, size, style)
+     */
+    public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) {
+        super.drawText(text, x, y, paint);
+    }
+
+    /**
+     * Draw the text, with origin at (x,y), using the specified paint. The origin is interpreted
+     * based on the Align setting in the paint.
+     *
+     * @param text The text to be drawn
+     * @param start The index of the first character in text to draw
+     * @param end (end - 1) is the index of the last character in text to draw
+     * @param x The x-coordinate of the origin of the text being drawn
+     * @param y The y-coordinate of the baseline of the text being drawn
+     * @param paint The paint used for the text (e.g. color, size, style)
+     */
+    public void drawText(@NonNull String text, int start, int end, float x, float y,
+            @NonNull Paint paint) {
+        super.drawText(text, start, end, x, y, paint);
+    }
+
+    /**
+     * Draw the specified range of text, specified by start/end, with its origin at (x,y), in the
+     * specified Paint. The origin is interpreted based on the Align setting in the Paint.
+     *
+     * @param text The text to be drawn
+     * @param start The index of the first character in text to draw
+     * @param end (end - 1) is the index of the last character in text to draw
+     * @param x The x-coordinate of origin for where to draw the text
+     * @param y The y-coordinate of origin for where to draw the text
+     * @param paint The paint used for the text (e.g. color, size, style)
+     */
+    public void drawText(@NonNull CharSequence text, int start, int end, float x, float y,
+            @NonNull Paint paint) {
+        super.drawText(text, start, end, x, y, paint);
+    }
+
+    /**
+     * Draw the text, with origin at (x,y), using the specified paint, along the specified path. The
+     * paint's Align setting determins where along the path to start the text.
+     *
+     * @param text The text to be drawn
+     * @param path The path the text should follow for its baseline
+     * @param hOffset The distance along the path to add to the text's starting position
+     * @param vOffset The distance above(-) or below(+) the path to position the text
+     * @param paint The paint used for the text (e.g. color, size, style)
+     */
+    public void drawTextOnPath(@NonNull char[] text, int index, int count, @NonNull Path path,
+            float hOffset, float vOffset, @NonNull Paint paint) {
+        super.drawTextOnPath(text, index, count, path, hOffset, vOffset, paint);
+    }
+
+    /**
+     * Draw the text, with origin at (x,y), using the specified paint, along the specified path. The
+     * paint's Align setting determins where along the path to start the text.
+     *
+     * @param text The text to be drawn
+     * @param path The path the text should follow for its baseline
+     * @param hOffset The distance along the path to add to the text's starting position
+     * @param vOffset The distance above(-) or below(+) the path to position the text
+     * @param paint The paint used for the text (e.g. color, size, style)
+     */
+    public void drawTextOnPath(@NonNull String text, @NonNull Path path, float hOffset,
+            float vOffset, @NonNull Paint paint) {
+        super.drawTextOnPath(text, path, hOffset, vOffset, paint);
+    }
+
+    /**
+     * Draw a run of text, all in a single direction, with optional context for complex text
+     * shaping.
+     * <p>
+     * See {@link #drawTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)} for
+     * more details. This method uses a character array rather than CharSequence to represent the
+     * string. Also, to be consistent with the pattern established in {@link #drawText}, in this
+     * method {@code count} and {@code contextCount} are used rather than offsets of the end
+     * position; {@code count = end - start, contextCount = contextEnd -
+     * contextStart}.
+     *
+     * @param text the text to render
+     * @param index the start of the text to render
+     * @param count the count of chars to render
+     * @param contextIndex the start of the context for shaping. Must be no greater than index.
+     * @param contextCount the number of characters in the context for shaping. contexIndex +
+     *            contextCount must be no less than index + count.
+     * @param x the x position at which to draw the text
+     * @param y the y position at which to draw the text
+     * @param isRtl whether the run is in RTL direction
+     * @param paint the paint
+     */
+    public void drawTextRun(@NonNull char[] text, int index, int count, int contextIndex,
+            int contextCount, float x, float y, boolean isRtl, @NonNull Paint paint) {
+        super.drawTextRun(text, index, count, contextIndex, contextCount, x, y, isRtl, paint);
+    }
+
+    /**
+     * Draw a run of text, all in a single direction, with optional context for complex text
+     * shaping.
+     * <p>
+     * The run of text includes the characters from {@code start} to {@code end} in the text. In
+     * addition, the range {@code contextStart} to {@code contextEnd} is used as context for the
+     * purpose of complex text shaping, such as Arabic text potentially shaped differently based on
+     * the text next to it.
+     * <p>
+     * All text outside the range {@code contextStart..contextEnd} is ignored. The text between
+     * {@code start} and {@code end} will be laid out and drawn.
+     * <p>
+     * The direction of the run is explicitly specified by {@code isRtl}. Thus, this method is
+     * suitable only for runs of a single direction. Alignment of the text is as determined by the
+     * Paint's TextAlign value. Further, {@code 0 <= contextStart <= start <= end <= contextEnd
+     * <= text.length} must hold on entry.
+     * <p>
+     * Also see {@link android.graphics.Paint#getRunAdvance} for a corresponding method to measure
+     * the text; the advance width of the text drawn matches the value obtained from that method.
+     *
+     * @param text the text to render
+     * @param start the start of the text to render. Data before this position can be used for
+     *            shaping context.
+     * @param end the end of the text to render. Data at or after this position can be used for
+     *            shaping context.
+     * @param contextStart the index of the start of the shaping context
+     * @param contextEnd the index of the end of the shaping context
+     * @param x the x position at which to draw the text
+     * @param y the y position at which to draw the text
+     * @param isRtl whether the run is in RTL direction
+     * @param paint the paint
+     * @see #drawTextRun(char[], int, int, int, int, float, float, boolean, Paint)
+     */
+    public void drawTextRun(@NonNull CharSequence text, int start, int end, int contextStart,
+            int contextEnd, float x, float y, boolean isRtl, @NonNull Paint paint) {
+        super.drawTextRun(text, start, end, contextStart, contextEnd, x, y, isRtl, paint);
+    }
+
+    /**
+     * Draw the array of vertices, interpreted as triangles (based on mode). The verts array is
+     * required, and specifies the x,y pairs for each vertex. If texs is non-null, then it is used
+     * to specify the coordinate in shader coordinates to use at each vertex (the paint must have a
+     * shader in this case). If there is no texs array, but there is a color array, then each color
+     * is interpolated across its corresponding triangle in a gradient. If both texs and colors
+     * arrays are present, then they behave as before, but the resulting color at each pixels is the
+     * result of multiplying the colors from the shader and the color-gradient together. The indices
+     * array is optional, but if it is present, then it is used to specify the index of each
+     * triangle, rather than just walking through the arrays in order.
+     *
+     * @param mode How to interpret the array of vertices
+     * @param vertexCount The number of values in the vertices array (and corresponding texs and
+     *            colors arrays if non-null). Each logical vertex is two values (x, y), vertexCount
+     *            must be a multiple of 2.
+     * @param verts Array of vertices for the mesh
+     * @param vertOffset Number of values in the verts to skip before drawing.
+     * @param texs May be null. If not null, specifies the coordinates to sample into the current
+     *            shader (e.g. bitmap tile or gradient)
+     * @param texOffset Number of values in texs to skip before drawing.
+     * @param colors May be null. If not null, specifies a color for each vertex, to be interpolated
+     *            across the triangle.
+     * @param colorOffset Number of values in colors to skip before drawing.
+     * @param indices If not null, array of indices to reference into the vertex (texs, colors)
+     *            array.
+     * @param indexCount number of entries in the indices array (if not null).
+     * @param paint Specifies the shader to use if the texs array is non-null.
+     */
+    public void drawVertices(@NonNull VertexMode mode, int vertexCount, @NonNull float[] verts,
+            int vertOffset, @Nullable float[] texs, int texOffset, @Nullable int[] colors,
+            int colorOffset, @Nullable short[] indices, int indexOffset, int indexCount,
+            @NonNull Paint paint) {
+        super.drawVertices(mode, vertexCount, verts, vertOffset, texs, texOffset,
+                colors, colorOffset, indices, indexOffset, indexCount, paint);
+    }
+}
diff --git a/android/graphics/CanvasProperty.java b/android/graphics/CanvasProperty.java
new file mode 100644
index 0000000..ea3886c
--- /dev/null
+++ b/android/graphics/CanvasProperty.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2014 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;
+
+import com.android.internal.util.VirtualRefBasePtr;
+
+/**
+ * TODO: Make public?
+ * @hide
+ */
+public final class CanvasProperty<T> {
+
+    private VirtualRefBasePtr mProperty;
+
+    public static CanvasProperty<Float> createFloat(float initialValue) {
+        return new CanvasProperty<Float>(nCreateFloat(initialValue));
+    }
+
+    public static CanvasProperty<Paint> createPaint(Paint initialValue) {
+        return new CanvasProperty<Paint>(nCreatePaint(initialValue.getNativeInstance()));
+    }
+
+    private CanvasProperty(long nativeContainer) {
+        mProperty = new VirtualRefBasePtr(nativeContainer);
+    }
+
+    /** @hide */
+    public long getNativeContainer() {
+        return mProperty.get();
+    }
+
+    private static native long nCreateFloat(float initialValue);
+    private static native long nCreatePaint(long initialValuePaintPtr);
+}
diff --git a/android/graphics/Canvas_Delegate.java b/android/graphics/Canvas_Delegate.java
new file mode 100644
index 0000000..9d8af38
--- /dev/null
+++ b/android/graphics/Canvas_Delegate.java
@@ -0,0 +1,481 @@
+/*
+ * 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;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.impl.GcSnapshot;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.annotation.Nullable;
+import android.graphics.Bitmap.Config;
+
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.geom.AffineTransform;
+
+import libcore.util.NativeAllocationRegistry_Delegate;
+
+
+/**
+ * Delegate implementing the native methods of android.graphics.Canvas
+ *
+ * Through the layoutlib_create tool, the original native methods of Canvas have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original Canvas class.
+ *
+ * @see DelegateManager
+ *
+ */
+public final class Canvas_Delegate extends BaseCanvas_Delegate {
+
+    // ---- delegate manager ----
+    private static long sFinalizer = -1;
+
+    private DrawFilter_Delegate mDrawFilter = null;
+
+    // ---- Public Helper methods ----
+
+    /**
+     * Returns the native delegate associated to a given {@link Canvas} object.
+     */
+    public static Canvas_Delegate getDelegate(Canvas canvas) {
+        return (Canvas_Delegate) sManager.getDelegate(canvas.getNativeCanvasWrapper());
+    }
+
+    /**
+     * Returns the native delegate associated to a given an int referencing a {@link Canvas} object.
+     */
+    public static Canvas_Delegate getDelegate(long native_canvas) {
+        return (Canvas_Delegate) sManager.getDelegate(native_canvas);
+    }
+
+    /**
+     * Returns the current {@link Graphics2D} used to draw.
+     */
+    public GcSnapshot getSnapshot() {
+        return mSnapshot;
+    }
+
+    /**
+     * Returns the {@link DrawFilter} delegate or null if none have been set.
+     *
+     * @return the delegate or null.
+     */
+    public DrawFilter_Delegate getDrawFilter() {
+        return mDrawFilter;
+    }
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static void nFreeCaches() {
+        // nothing to be done here.
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nFreeTextLayoutCaches() {
+        // nothing to be done here yet.
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nInitRaster(@Nullable Bitmap bitmap) {
+        long nativeBitmapOrZero = 0;
+        if (bitmap != null) {
+            nativeBitmapOrZero = bitmap.getNativeInstance();
+        }
+        if (nativeBitmapOrZero > 0) {
+            // get the Bitmap from the int
+            Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmapOrZero);
+
+            // create a new Canvas_Delegate with the given bitmap and return its new native int.
+            Canvas_Delegate newDelegate = new Canvas_Delegate(bitmapDelegate);
+
+            return sManager.addNewDelegate(newDelegate);
+        }
+
+        // create a new Canvas_Delegate and return its new native int.
+        Canvas_Delegate newDelegate = new Canvas_Delegate();
+
+        return sManager.addNewDelegate(newDelegate);
+    }
+
+    @LayoutlibDelegate
+    public static void nSetBitmap(long canvas, Bitmap bitmap) {
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(canvas);
+        Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
+        if (canvasDelegate == null || bitmapDelegate==null) {
+            return;
+        }
+        canvasDelegate.mBitmap = bitmapDelegate;
+        canvasDelegate.mSnapshot = GcSnapshot.createDefaultSnapshot(bitmapDelegate);
+    }
+
+    @LayoutlibDelegate
+    public static boolean nIsOpaque(long nativeCanvas) {
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            return false;
+        }
+
+        return canvasDelegate.mBitmap.getConfig() == Config.RGB_565;
+    }
+
+    @LayoutlibDelegate
+    public static int nGetWidth(long nativeCanvas) {
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            return 0;
+        }
+
+        return canvasDelegate.mBitmap.getImage().getWidth();
+    }
+
+    @LayoutlibDelegate
+    public static int nGetHeight(long nativeCanvas) {
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            return 0;
+        }
+
+        return canvasDelegate.mBitmap.getImage().getHeight();
+    }
+
+    @LayoutlibDelegate
+    public static int nSave(long nativeCanvas, int saveFlags) {
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            return 0;
+        }
+
+        return canvasDelegate.save(saveFlags);
+    }
+
+    @LayoutlibDelegate
+    public static int nSaveLayer(long nativeCanvas, float l,
+                                               float t, float r, float b,
+                                               long paint, int layerFlags) {
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            return 0;
+        }
+
+        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
+        if (paintDelegate == null) {
+            return 0;
+        }
+
+        return canvasDelegate.saveLayer(new RectF(l, t, r, b),
+                paintDelegate, layerFlags);
+    }
+
+    @LayoutlibDelegate
+    public static int nSaveLayerAlpha(long nativeCanvas, float l,
+                                                    float t, float r, float b,
+                                                    int alpha, int layerFlags) {
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            return 0;
+        }
+
+        return canvasDelegate.saveLayerAlpha(new RectF(l, t, r, b), alpha, layerFlags);
+    }
+
+    @LayoutlibDelegate
+    public static boolean nRestore(long nativeCanvas) {
+        // FIXME: implement throwOnUnderflow.
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            return false;
+        }
+
+        canvasDelegate.restore();
+        return true;
+    }
+
+    @LayoutlibDelegate
+    public static void nRestoreToCount(long nativeCanvas, int saveCount) {
+        // FIXME: implement throwOnUnderflow.
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            return;
+        }
+
+        canvasDelegate.restoreTo(saveCount);
+    }
+
+    @LayoutlibDelegate
+    public static int nGetSaveCount(long nativeCanvas) {
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            return 0;
+        }
+
+        return canvasDelegate.getSnapshot().size();
+    }
+
+    @LayoutlibDelegate
+   public static void nTranslate(long nativeCanvas, float dx, float dy) {
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            return;
+        }
+
+        canvasDelegate.getSnapshot().translate(dx, dy);
+    }
+
+    @LayoutlibDelegate
+       public static void nScale(long nativeCanvas, float sx, float sy) {
+            // get the delegate from the native int.
+            Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
+            if (canvasDelegate == null) {
+                return;
+            }
+
+            canvasDelegate.getSnapshot().scale(sx, sy);
+        }
+
+    @LayoutlibDelegate
+    public static void nRotate(long nativeCanvas, float degrees) {
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            return;
+        }
+
+        canvasDelegate.getSnapshot().rotate(Math.toRadians(degrees));
+    }
+
+    @LayoutlibDelegate
+   public static void nSkew(long nativeCanvas, float kx, float ky) {
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            return;
+        }
+
+        // get the current top graphics2D object.
+        GcSnapshot g = canvasDelegate.getSnapshot();
+
+        // get its current matrix
+        AffineTransform currentTx = g.getTransform();
+        // get the AffineTransform for the given skew.
+        float[] mtx = Matrix_Delegate.getSkew(kx, ky);
+        AffineTransform matrixTx = Matrix_Delegate.getAffineTransform(mtx);
+
+        // combine them so that the given matrix is applied after.
+        currentTx.preConcatenate(matrixTx);
+
+        // give it to the graphics2D as a new matrix replacing all previous transform
+        g.setTransform(currentTx);
+    }
+
+    @LayoutlibDelegate
+    public static void nConcat(long nCanvas, long nMatrix) {
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nCanvas);
+        if (canvasDelegate == null) {
+            return;
+        }
+
+        Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
+        if (matrixDelegate == null) {
+            return;
+        }
+
+        // get the current top graphics2D object.
+        GcSnapshot snapshot = canvasDelegate.getSnapshot();
+
+        // get its current matrix
+        AffineTransform currentTx = snapshot.getTransform();
+        // get the AffineTransform of the given matrix
+        AffineTransform matrixTx = matrixDelegate.getAffineTransform();
+
+        // combine them so that the given matrix is applied after.
+        currentTx.concatenate(matrixTx);
+
+        // give it to the graphics2D as a new matrix replacing all previous transform
+        snapshot.setTransform(currentTx);
+    }
+
+    @LayoutlibDelegate
+    public static void nSetMatrix(long nCanvas, long nMatrix) {
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nCanvas);
+        if (canvasDelegate == null) {
+            return;
+        }
+
+        Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
+        if (matrixDelegate == null) {
+            return;
+        }
+
+        // get the current top graphics2D object.
+        GcSnapshot snapshot = canvasDelegate.getSnapshot();
+
+        // get the AffineTransform of the given matrix
+        AffineTransform matrixTx = matrixDelegate.getAffineTransform();
+
+        // give it to the graphics2D as a new matrix replacing all previous transform
+        snapshot.setTransform(matrixTx);
+
+        if (matrixDelegate.hasPerspective()) {
+            assert false;
+            Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_AFFINE,
+                    "android.graphics.Canvas#setMatrix(android.graphics.Matrix) only " +
+                    "supports affine transformations.", null, null /*data*/);
+        }
+    }
+
+    @LayoutlibDelegate
+    public static boolean nClipRect(long nCanvas,
+                                                  float left, float top,
+                                                  float right, float bottom,
+                                                  int regionOp) {
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nCanvas);
+        if (canvasDelegate == null) {
+            return false;
+        }
+
+        return canvasDelegate.clipRect(left, top, right, bottom, regionOp);
+    }
+
+    @LayoutlibDelegate
+    public static boolean nClipPath(long nativeCanvas,
+                                                  long nativePath,
+                                                  int regionOp) {
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            return true;
+        }
+
+        Path_Delegate pathDelegate = Path_Delegate.getDelegate(nativePath);
+        if (pathDelegate == null) {
+            return true;
+        }
+
+        return canvasDelegate.mSnapshot.clip(pathDelegate.getJavaShape(), regionOp);
+    }
+
+    @LayoutlibDelegate
+    public static void nSetDrawFilter(long nativeCanvas, long nativeFilter) {
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            return;
+        }
+
+        canvasDelegate.mDrawFilter = DrawFilter_Delegate.getDelegate(nativeFilter);
+
+        if (canvasDelegate.mDrawFilter != null && !canvasDelegate.mDrawFilter.isSupported()) {
+            Bridge.getLog().fidelityWarning(LayoutLog.TAG_DRAWFILTER,
+                    canvasDelegate.mDrawFilter.getSupportMessage(), null, null /*data*/);
+        }
+    }
+
+    @LayoutlibDelegate
+    public static boolean nGetClipBounds(long nativeCanvas,
+                                                       Rect bounds) {
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            return false;
+        }
+
+        Rectangle rect = canvasDelegate.getSnapshot().getClip().getBounds();
+        if (rect != null && !rect.isEmpty()) {
+            bounds.left = rect.x;
+            bounds.top = rect.y;
+            bounds.right = rect.x + rect.width;
+            bounds.bottom = rect.y + rect.height;
+            return true;
+        }
+
+        return false;
+    }
+
+    @LayoutlibDelegate
+    public static void nGetMatrix(long canvas, long matrix) {
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(canvas);
+        if (canvasDelegate == null) {
+            return;
+        }
+
+        Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix);
+        if (matrixDelegate == null) {
+            return;
+        }
+
+        AffineTransform transform = canvasDelegate.getSnapshot().getTransform();
+        matrixDelegate.set(Matrix_Delegate.makeValues(transform));
+    }
+
+    @LayoutlibDelegate
+    public static boolean nQuickReject(long nativeCanvas, long path) {
+        // FIXME properly implement quickReject
+        return false;
+    }
+
+    @LayoutlibDelegate
+    public static boolean nQuickReject(long nativeCanvas,
+                                                     float left, float top,
+                                                     float right, float bottom) {
+        // FIXME properly implement quickReject
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nGetNativeFinalizer() {
+        synchronized (Canvas_Delegate.class) {
+            if (sFinalizer == -1) {
+                sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(nativePtr -> {
+                    Canvas_Delegate delegate = Canvas_Delegate.getDelegate(nativePtr);
+                    if (delegate != null) {
+                        delegate.dispose();
+                    }
+                    sManager.removeJavaReferenceFor(nativePtr);
+                });
+            }
+        }
+        return sFinalizer;
+    }
+
+    private Canvas_Delegate(Bitmap_Delegate bitmap) {
+        super(bitmap);
+    }
+
+    private Canvas_Delegate() {
+        super();
+    }
+}
+
diff --git a/android/graphics/Color.java b/android/graphics/Color.java
new file mode 100644
index 0000000..c4bf9d3
--- /dev/null
+++ b/android/graphics/Color.java
@@ -0,0 +1,1530 @@
+/*
+ * 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;
+
+import android.annotation.AnyThread;
+import android.annotation.ColorInt;
+import android.annotation.ColorLong;
+import android.annotation.HalfFloat;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.Size;
+import android.annotation.SuppressAutoDoc;
+import android.util.Half;
+import com.android.internal.util.XmlUtils;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.function.DoubleUnaryOperator;
+
+/**
+ * {@usesMathJax}
+ *
+ * <p>The <code>Color</code> class provides methods for creating, converting and
+ * manipulating colors. Colors have three different representations:</p>
+ * <ul>
+ *     <li>Color ints, the most common representation</li>
+ *     <li>Color longs</li>
+ *     <li><code>Color</code> instances</li>
+ * </ul>
+ * <p>The section below describe each representation in detail.</p>
+ *
+ * <h3>Color ints</h3>
+ * <p>Color ints are the most common representation of colors on Android and
+ * have been used since {@link android.os.Build.VERSION_CODES#BASE API level 1}.</p>
+ *
+ * <p>A color int always defines a color in the {@link ColorSpace.Named#SRGB sRGB}
+ * color space using 4 components packed in a single 32 bit integer value:</p>
+ *
+ * <table summary="Color int definition">
+ *     <tr>
+ *         <th>Component</th><th>Name</th><th>Size</th><th>Range</th>
+ *     </tr>
+ *     <tr><td>A</td><td>Alpha</td><td>8 bits</td><td>\([0..255]\)</td></tr>
+ *     <tr><td>R</td><td>Red</td><td>8 bits</td><td>\([0..255]\)</td></tr>
+ *     <tr><td>G</td><td>Green</td><td>8 bits</td><td>\([0..255]\)</td></tr>
+ *     <tr><td>B</td><td>Blue</td><td>8 bits</td><td>\([0..255]\)</td></tr>
+ * </table>
+ *
+ * <p>The components in this table are listed in encoding order (see below),
+ * which is why color ints are called ARGB colors.</p>
+ *
+ * <h4>Usage in code</h4>
+ * <p>To avoid confusing color ints with arbitrary integer values, it is a
+ * good practice to annotate them with the <code>@ColorInt</code> annotation
+ * found in the Android Support Library.</p>
+ *
+ * <h4>Encoding</h4>
+ * <p>The four components of a color int are encoded in the following way:</p>
+ * <pre class="prettyprint">
+ * int color = (A & 0xff) << 24 | (R & 0xff) << 16 | (G & 0xff) << 8 | (B & 0xff);
+ * </pre>
+ *
+ * <p>Because of this encoding, color ints can easily be described as an integer
+ * constant in source. For instance, opaque blue is <code>0xff0000ff</code>
+ * and yellow is <code>0xffffff00</code>.</p>
+ *
+ * <p>To easily encode color ints, it is recommended to use the static methods
+ * {@link #argb(int, int, int, int)} and {@link #rgb(int, int, int)}. The second
+ * method omits the alpha component and assumes the color is opaque (alpha is 255).
+ * As a convenience this class also offers methods to encode color ints from components
+ * defined in the \([0..1]\) range: {@link #argb(float, float, float, float)} and
+ * {@link #rgb(float, float, float)}.</p>
+ *
+ * <p>Color longs (defined below) can be easily converted to color ints by invoking
+ * the {@link #toArgb(long)} method. This method performs a color space conversion
+ * if needed.</p>
+ *
+ * <p>It is also possible to create a color int by invoking the method {@link #toArgb()}
+ * on a color instance.</p>
+ *
+ * <h4>Decoding</h4>
+ * <p>The four ARGB components can be individually extracted from a color int
+ * using the following expressions:</p>
+ * <pre class="prettyprint">
+ * int A = (color >> 24) & 0xff; // or color >>> 24
+ * int R = (color >> 16) & 0xff;
+ * int G = (color >>  8) & 0xff;
+ * int B = (color      ) & 0xff;
+ * </pre>
+ *
+ * <p>This class offers convenience methods to easily extract these components:</p>
+ * <ul>
+ *     <li>{@link #alpha(int)} to extract the alpha component</li>
+ *     <li>{@link #red(int)} to extract the red component</li>
+ *     <li>{@link #green(int)} to extract the green component</li>
+ *     <li>{@link #blue(int)} to extract the blue component</li>
+ * </ul>
+ *
+ * <h3>Color longs</h3>
+ * <p>Color longs are a representation introduced in
+ * {@link android.os.Build.VERSION_CODES#O Android O} to store colors in different
+ * {@link ColorSpace color spaces}, with more precision than color ints.</p>
+ *
+ * <p>A color long always defines a color using 4 components packed in a single
+ * 64 bit long value. One of these components is always alpha while the other
+ * three components depend on the color space's {@link ColorSpace.Model color model}.
+ * The most common color model is the {@link ColorSpace.Model#RGB RGB} model in
+ * which the components represent red, green and blue values.</p>
+ *
+ * <p class="note"><b>Component ranges:</b> the ranges defined in the tables
+ * below indicate the ranges that can be encoded in a color long. They do not
+ * represent the actual ranges as they may differ per color space. For instance,
+ * the RGB components of a color in the {@link ColorSpace.Named#DISPLAY_P3 Display P3}
+ * color space use the \([0..1]\) range. Please refer to the documentation of the
+ * various {@link ColorSpace.Named color spaces} to find their respective ranges.</p>
+ *
+ * <p class="note"><b>Alpha range:</b> while alpha is encoded in a color long using
+ * a 10 bit integer (thus using a range of \([0..1023]\)), it is converted to and
+ * from \([0..1]\) float values when decoding and encoding color longs.</p>
+ *
+ * <p class="note"><b>sRGB color space:</b> for compatibility reasons and ease of
+ * use, color longs encoding {@link ColorSpace.Named#SRGB sRGB} colors do not
+ * use the same encoding as other color longs.</p>
+ *
+ * <table summary="Color long definition">
+ *     <tr>
+ *         <th>Component</th><th>Name</th><th>Size</th><th>Range</th>
+ *     </tr>
+ *     <tr><td colspan="4">{@link ColorSpace.Model#RGB RGB} color model</td></tr>
+ *     <tr><td>R</td><td>Red</td><td>16 bits</td><td>\([-65504.0, 65504.0]\)</td></tr>
+ *     <tr><td>G</td><td>Green</td><td>16 bits</td><td>\([-65504.0, 65504.0]\)</td></tr>
+ *     <tr><td>B</td><td>Blue</td><td>16 bits</td><td>\([-65504.0, 65504.0]\)</td></tr>
+ *     <tr><td>A</td><td>Alpha</td><td>10 bits</td><td>\([0..1023]\)</td></tr>
+ *     <tr><td></td><td>Color space</td><td>6 bits</td><td>\([0..63]\)</td></tr>
+ *     <tr><td colspan="4">{@link ColorSpace.Named#SRGB sRGB} color space</td></tr>
+ *     <tr><td>A</td><td>Alpha</td><td>8 bits</td><td>\([0..255]\)</td></tr>
+ *     <tr><td>R</td><td>Red</td><td>8 bits</td><td>\([0..255]\)</td></tr>
+ *     <tr><td>G</td><td>Green</td><td>8 bits</td><td>\([0..255]\)</td></tr>
+ *     <tr><td>B</td><td>Blue</td><td>8 bits</td><td>\([0..255]\)</td></tr>
+ *     <tr><td>X</td><td>Unused</td><td>32 bits</td><td>\(0\)</td></tr>
+ *     <tr><td colspan="4">{@link ColorSpace.Model#XYZ XYZ} color model</td></tr>
+ *     <tr><td>X</td><td>X</td><td>16 bits</td><td>\([-65504.0, 65504.0]\)</td></tr>
+ *     <tr><td>Y</td><td>Y</td><td>16 bits</td><td>\([-65504.0, 65504.0]\)</td></tr>
+ *     <tr><td>Z</td><td>Z</td><td>16 bits</td><td>\([-65504.0, 65504.0]\)</td></tr>
+ *     <tr><td>A</td><td>Alpha</td><td>10 bits</td><td>\([0..1023]\)</td></tr>
+ *     <tr><td></td><td>Color space</td><td>6 bits</td><td>\([0..63]\)</td></tr>
+ *     <tr><td colspan="4">{@link ColorSpace.Model#XYZ Lab} color model</td></tr>
+ *     <tr><td>L</td><td>L</td><td>16 bits</td><td>\([-65504.0, 65504.0]\)</td></tr>
+ *     <tr><td>a</td><td>a</td><td>16 bits</td><td>\([-65504.0, 65504.0]\)</td></tr>
+ *     <tr><td>b</td><td>b</td><td>16 bits</td><td>\([-65504.0, 65504.0]\)</td></tr>
+ *     <tr><td>A</td><td>Alpha</td><td>10 bits</td><td>\([0..1023]\)</td></tr>
+ *     <tr><td></td><td>Color space</td><td>6 bits</td><td>\([0..63]\)</td></tr>
+ *     <tr><td colspan="4">{@link ColorSpace.Model#CMYK CMYK} color model</td></tr>
+ *     <tr><td colspan="4">Unsupported</td></tr>
+ * </table>
+ *
+ * <p>The components in this table are listed in encoding order (see below),
+ * which is why color longs in the RGB model are called RGBA colors (even if
+ * this doesn't quite hold for the special case of sRGB colors).</p>
+ *
+ * <p>The color long encoding relies on half-precision float values (fp16). If you
+ * wish to know more about the limitations of half-precision float values, please
+ * refer to the documentation of the {@link Half} class.</p>
+ *
+ * <h4>Usage in code</h4>
+ * <p>To avoid confusing color longs with arbitrary long values, it is a
+ * good practice to annotate them with the <code>@ColorLong</code> annotation
+ * found in the Android Support Library.</p>
+ *
+ * <h4>Encoding</h4>
+ *
+ * <p>Given the complex nature of color longs, it is strongly encouraged to use
+ * the various methods provided by this class to encode them.</p>
+ *
+ * <p>The most flexible way to encode a color long is to use the method
+ * {@link #pack(float, float, float, float, ColorSpace)}. This method allows you
+ * to specify three color components (typically RGB), an alpha component and a
+ * color space. To encode sRGB colors, use {@link #pack(float, float, float)}
+ * and {@link #pack(float, float, float, float)} which are the
+ * equivalent of {@link #rgb(int, int, int)} and {@link #argb(int, int, int, int)}
+ * for color ints. If you simply need to convert a color int into a color long,
+ * use {@link #pack(int)}.</p>
+ *
+ * <p>It is also possible to create a color long value by invoking the method
+ * {@link #pack()} on a color instance.</p>
+ *
+ * <h4>Decoding</h4>
+ *
+ * <p>This class offers convenience methods to easily extract the components
+ * of a color long:</p>
+ * <ul>
+ *     <li>{@link #alpha(long)} to extract the alpha component</li>
+ *     <li>{@link #red(long)} to extract the red/X/L component</li>
+ *     <li>{@link #green(long)} to extract the green/Y/a component</li>
+ *     <li>{@link #blue(long)} to extract the blue/Z/b component</li>
+ * </ul>
+ *
+ * <p>The values returned by these methods depend on the color space encoded
+ * in the color long. The values are however typically in the \([0..1]\) range
+ * for RGB colors. Please refer to the documentation of the various
+ * {@link ColorSpace.Named color spaces} for the exact ranges.</p>
+ *
+ * <h3>Color instances</h3>
+ * <p>Color instances are a representation introduced in
+ * {@link android.os.Build.VERSION_CODES#O Android O} to store colors in different
+ * {@link ColorSpace color spaces}, with more precision than both color ints and
+ * color longs. Color instances also offer the ability to store more than 4
+ * components if necessary.</p>
+ *
+ * <p>Colors instances are immutable and can be created using one of the various
+ * <code>valueOf</code> methods. For instance:</p>
+ * <pre class="prettyprint">
+ * // sRGB
+ * Color opaqueRed = Color.valueOf(0xffff0000); // from a color int
+ * Color translucentRed = Color.valueOf(1.0f, 0.0f, 0.0f, 0.5f);
+ *
+ * // Wide gamut color
+ * {@literal @}ColorLong long p3 = pack(1.0f, 1.0f, 0.0f, 1.0f, colorSpaceP3);
+ * Color opaqueYellow = Color.valueOf(p3); // from a color long
+ *
+ * // CIE L*a*b* color space
+ * ColorSpace lab = ColorSpace.get(ColorSpace.Named.LAB);
+ * Color green = Color.valueOf(100.0f, -128.0f, 128.0f, 1.0f, lab);
+ * </pre>
+ *
+ * <p>Color instances can be converted to color ints ({@link #toArgb()}) or
+ * color longs ({@link #pack()}). They also offer easy access to their various
+ * components using the following methods:</p>
+ * <ul>
+ *     <li>{@link #alpha()}, returns the alpha component value</li>
+ *     <li>{@link #red()}, returns the red component value (or first
+ *     component value in non-RGB models)</li>
+ *     <li>{@link #green()}, returns the green component value (or second
+ *     component value in non-RGB models)</li>
+ *     <li>{@link #blue()}, returns the blue component value (or third
+ *     component value in non-RGB models)</li>
+ *     <li>{@link #getComponent(int)}, returns a specific component value</li>
+ *     <li>{@link #getComponents()}, returns all component values as an array</li>
+ * </ul>
+ *
+ * <h3>Color space conversions</h3>
+ * <p>You can convert colors from one color space to another using
+ * {@link ColorSpace#connect(ColorSpace, ColorSpace)} and its variants. However,
+ * the <code>Color</code> class provides a few convenience methods to simplify
+ * the process. Here is a brief description of some of them:</p>
+ * <ul>
+ *     <li>{@link #convert(ColorSpace)} to convert a color instance in a color
+ *     space to a new color instance in a different color space</li>
+ *     <li>{@link #convert(float, float, float, float, ColorSpace, ColorSpace)} to
+ *     convert a color from a source color space to a destination color space</li>
+ *     <li>{@link #convert(long, ColorSpace)} to convert a color long from its
+ *     built-in color space to a destination color space</li>
+ *     <li>{@link #convert(int, ColorSpace)} to convert a color int from sRGB
+ *     to a destination color space</li>
+ * </ul>
+ *
+ * <p>Please refere to the {@link ColorSpace} documentation for more
+ * information.</p>
+ *
+ * <h3>Alpha and transparency</h3>
+ * <p>The alpha component of a color defines the level of transparency of a
+ * color. When the alpha component is 0, the color is completely transparent.
+ * When the alpha is component is 1 (in the \([0..1]\) range) or 255 (in the
+ * \([0..255]\) range), the color is completely opaque.</p>
+ *
+ * <p>The color representations described above do not use pre-multiplied
+ * color components (a pre-multiplied color component is a color component
+ * that has been multiplied by the value of the alpha component).
+ * For instance, the color int representation of opaque red is
+ * <code>0xffff0000</code>. For semi-transparent (50%) red, the
+ * representation becomes <code>0x80ff0000</code>. The equivalent color
+ * instance representations would be <code>(1.0, 0.0, 0.0, 1.0)</code>
+ * and <code>(1.0, 0.0, 0.0, 0.5)</code>.</p>
+ */
+@AnyThread
+@SuppressAutoDoc
+public class Color {
+    @ColorInt public static final int BLACK       = 0xFF000000;
+    @ColorInt public static final int DKGRAY      = 0xFF444444;
+    @ColorInt public static final int GRAY        = 0xFF888888;
+    @ColorInt public static final int LTGRAY      = 0xFFCCCCCC;
+    @ColorInt public static final int WHITE       = 0xFFFFFFFF;
+    @ColorInt public static final int RED         = 0xFFFF0000;
+    @ColorInt public static final int GREEN       = 0xFF00FF00;
+    @ColorInt public static final int BLUE        = 0xFF0000FF;
+    @ColorInt public static final int YELLOW      = 0xFFFFFF00;
+    @ColorInt public static final int CYAN        = 0xFF00FFFF;
+    @ColorInt public static final int MAGENTA     = 0xFFFF00FF;
+    @ColorInt public static final int TRANSPARENT = 0;
+
+    @NonNull
+    @Size(min = 4, max = 5)
+    private final float[] mComponents;
+
+    @NonNull
+    private final ColorSpace mColorSpace;
+
+    /**
+     * Creates a new color instance set to opaque black in the
+     * {@link ColorSpace.Named#SRGB sRGB} color space.
+     *
+     * @see #valueOf(float, float, float)
+     * @see #valueOf(float, float, float, float)
+     * @see #valueOf(float, float, float, float, ColorSpace)
+     * @see #valueOf(float[], ColorSpace)
+     * @see #valueOf(int)
+     * @see #valueOf(long)
+     */
+    public Color() {
+        // This constructor is required for compatibility with previous APIs
+        mComponents = new float[] { 0.0f, 0.0f, 0.0f, 1.0f };
+        mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
+    }
+
+    /**
+     * Creates a new color instance in the {@link ColorSpace.Named#SRGB sRGB}
+     * color space.
+     *
+     * @param r The value of the red channel, must be in [0..1] range
+     * @param g The value of the green channel, must be in [0..1] range
+     * @param b The value of the blue channel, must be in [0..1] range
+     * @param a The value of the alpha channel, must be in [0..1] range
+     */
+    private Color(float r, float g, float b, float a) {
+        this(r, g, b, a, ColorSpace.get(ColorSpace.Named.SRGB));
+    }
+
+    /**
+     * Creates a new color instance in the specified color space. The color space
+     * must have a 3 components model.
+     *
+     * @param r The value of the red channel, must be in the color space defined range
+     * @param g The value of the green channel, must be in the color space defined range
+     * @param b The value of the blue channel, must be in the color space defined range
+     * @param a The value of the alpha channel, must be in [0..1] range
+     * @param colorSpace This color's color space, cannot be null
+     */
+    private Color(float r, float g, float b, float a, @NonNull ColorSpace colorSpace) {
+        mComponents = new float[] { r, g, b, a };
+        mColorSpace = colorSpace;
+    }
+
+    /**
+     * Creates a new color instance in the specified color space.
+     *
+     * @param components An array of color components, plus alpha
+     * @param colorSpace This color's color space, cannot be null
+     */
+    private Color(@Size(min = 4, max = 5) float[] components, @NonNull ColorSpace colorSpace) {
+        mComponents = components;
+        mColorSpace = colorSpace;
+    }
+
+    /**
+     * Returns this color's color space.
+     *
+     * @return A non-null instance of {@link ColorSpace}
+     */
+    @NonNull
+    public ColorSpace getColorSpace() {
+        return mColorSpace;
+    }
+
+    /**
+     * Returns the color model of this color.
+     *
+     * @return A non-null {@link ColorSpace.Model}
+     */
+    public ColorSpace.Model getModel() {
+        return mColorSpace.getModel();
+    }
+
+    /**
+     * Indicates whether this color color is in a wide-gamut color space.
+     * See {@link ColorSpace#isWideGamut()} for a definition of a wide-gamut
+     * color space.
+     *
+     * @return True if this color is in a wide-gamut color space, false otherwise
+     *
+     * @see #isSrgb()
+     * @see ColorSpace#isWideGamut()
+     */
+    public boolean isWideGamut() {
+        return getColorSpace().isWideGamut();
+    }
+
+    /**
+     * Indicates whether this color is in the {@link ColorSpace.Named#SRGB sRGB}
+     * color space.
+     *
+     * @return True if this color is in the sRGB color space, false otherwise
+     *
+     * @see #isWideGamut()
+     */
+    public boolean isSrgb() {
+        return getColorSpace().isSrgb();
+    }
+
+    /**
+     * Returns the number of components that form a color value according
+     * to this color space's color model, plus one extra component for
+     * alpha.
+     *
+     * @return The integer 4 or 5
+     */
+    @IntRange(from = 4, to = 5)
+    public int getComponentCount() {
+        return mColorSpace.getComponentCount() + 1;
+    }
+
+    /**
+     * Packs this color into a color long. See the documentation of this class
+     * for a description of the color long format.
+     *
+     * @return A color long
+     *
+     * @throws IllegalArgumentException If this color's color space has the id
+     * {@link ColorSpace#MIN_ID} or if this color has more than 4 components
+     */
+    @ColorLong
+    public long pack() {
+        return pack(mComponents[0], mComponents[1], mComponents[2], mComponents[3], mColorSpace);
+    }
+
+    /**
+     * Converts this color from its color space to the specified color space.
+     * The conversion is done using the default rendering intent as specified
+     * by {@link ColorSpace#connect(ColorSpace, ColorSpace)}.
+     *
+     * @param colorSpace The destination color space, cannot be null
+     *
+     * @return A non-null color instance in the specified color space
+     */
+    @NonNull
+    public Color convert(@NonNull ColorSpace colorSpace) {
+        ColorSpace.Connector connector = ColorSpace.connect(mColorSpace, colorSpace);
+        float[] color = new float[] {
+                mComponents[0], mComponents[1], mComponents[2], mComponents[3]
+        };
+        connector.transform(color);
+        return new Color(color, colorSpace);
+    }
+
+    /**
+     * Converts this color to an ARGB color int. A color int is always in
+     * the {@link ColorSpace.Named#SRGB sRGB} color space. This implies
+     * a color space conversion is applied if needed.
+     *
+     * @return An ARGB color in the sRGB color space
+     */
+    @ColorInt
+    public int toArgb() {
+        if (mColorSpace.isSrgb()) {
+            return ((int) (mComponents[3] * 255.0f + 0.5f) << 24) |
+                   ((int) (mComponents[0] * 255.0f + 0.5f) << 16) |
+                   ((int) (mComponents[1] * 255.0f + 0.5f) <<  8) |
+                    (int) (mComponents[2] * 255.0f + 0.5f);
+        }
+
+        float[] color = new float[] {
+                mComponents[0], mComponents[1], mComponents[2], mComponents[3]
+        };
+        // The transformation saturates the output
+        ColorSpace.connect(mColorSpace).transform(color);
+
+        return ((int) (color[3] * 255.0f + 0.5f) << 24) |
+               ((int) (color[0] * 255.0f + 0.5f) << 16) |
+               ((int) (color[1] * 255.0f + 0.5f) <<  8) |
+                (int) (color[2] * 255.0f + 0.5f);
+    }
+
+    /**
+     * <p>Returns the value of the red component in the range defined by this
+     * color's color space (see {@link ColorSpace#getMinValue(int)} and
+     * {@link ColorSpace#getMaxValue(int)}).</p>
+     *
+     * <p>If this color's color model is not {@link ColorSpace.Model#RGB RGB},
+     * calling this method is equivalent to <code>getComponent(0)</code>.</p>
+     *
+     * @see #alpha()
+     * @see #red()
+     * @see #green
+     * @see #getComponents()
+     */
+    public float red() {
+        return mComponents[0];
+    }
+
+    /**
+     * <p>Returns the value of the green component in the range defined by this
+     * color's color space (see {@link ColorSpace#getMinValue(int)} and
+     * {@link ColorSpace#getMaxValue(int)}).</p>
+     *
+     * <p>If this color's color model is not {@link ColorSpace.Model#RGB RGB},
+     * calling this method is equivalent to <code>getComponent(1)</code>.</p>
+     *
+     * @see #alpha()
+     * @see #red()
+     * @see #green
+     * @see #getComponents()
+     */
+    public float green() {
+        return mComponents[1];
+    }
+
+    /**
+     * <p>Returns the value of the blue component in the range defined by this
+     * color's color space (see {@link ColorSpace#getMinValue(int)} and
+     * {@link ColorSpace#getMaxValue(int)}).</p>
+     *
+     * <p>If this color's color model is not {@link ColorSpace.Model#RGB RGB},
+     * calling this method is equivalent to <code>getComponent(2)</code>.</p>
+     *
+     * @see #alpha()
+     * @see #red()
+     * @see #green
+     * @see #getComponents()
+     */
+    public float blue() {
+        return mComponents[2];
+    }
+
+    /**
+     * Returns the value of the alpha component in the range \([0..1]\).
+     * Calling this method is equivalent to
+     * <code>getComponent(getComponentCount() - 1)</code>.
+     *
+     * @see #red()
+     * @see #green()
+     * @see #blue()
+     * @see #getComponents()
+     * @see #getComponent(int)
+     */
+    public float alpha() {
+        return mComponents[mComponents.length - 1];
+    }
+
+    /**
+     * Returns this color's components as a new array. The last element of the
+     * array is always the alpha component.
+     *
+     * @return A new, non-null array whose size is equal to {@link #getComponentCount()}
+     *
+     * @see #getComponent(int)
+     */
+    @NonNull
+    @Size(min = 4, max = 5)
+    public float[] getComponents() {
+        return Arrays.copyOf(mComponents, mComponents.length);
+    }
+
+    /**
+     * Copies this color's components in the supplied array. The last element of the
+     * array is always the alpha component.
+     *
+     * @param components An array of floats whose size must be at least
+     *                  {@link #getComponentCount()}, can be null
+     * @return The array passed as a parameter if not null, or a new array of length
+     *         {@link #getComponentCount()}
+     *
+     * @see #getComponent(int)
+     *
+     * @throws IllegalArgumentException If the specified array's length is less than
+     * {@link #getComponentCount()}
+     */
+    @NonNull
+    @Size(min = 4)
+    public float[] getComponents(@Nullable @Size(min = 4) float[] components) {
+        if (components == null) {
+            return Arrays.copyOf(mComponents, mComponents.length);
+        }
+
+        if (components.length < mComponents.length) {
+            throw new IllegalArgumentException("The specified array's length must be at "
+                    + "least " + mComponents.length);
+        }
+
+        System.arraycopy(mComponents, 0, components, 0, mComponents.length);
+        return components;
+    }
+
+    /**
+     * <p>Returns the value of the specified component in the range defined by
+     * this color's color space (see {@link ColorSpace#getMinValue(int)} and
+     * {@link ColorSpace#getMaxValue(int)}).</p>
+     *
+     * <p>If the requested component index is {@link #getComponentCount()},
+     * this method returns the alpha component, always in the range
+     * \([0..1]\).</p>
+     *
+     * @see #getComponents()
+     *
+     * @throws ArrayIndexOutOfBoundsException If the specified component index
+     * is < 0 or >= {@link #getComponentCount()}
+     */
+    public float getComponent(@IntRange(from = 0, to = 4) int component) {
+        return mComponents[component];
+    }
+
+    /**
+     * <p>Returns the relative luminance of this color.</p>
+     *
+     * <p>Based on the formula for relative luminance defined in WCAG 2.0,
+     * W3C Recommendation 11 December 2008.</p>
+     *
+     * @return A value between 0 (darkest black) and 1 (lightest white)
+     *
+     * @throws IllegalArgumentException If the this color's color space
+     * does not use the {@link ColorSpace.Model#RGB RGB} color model
+     */
+    public float luminance() {
+        if (mColorSpace.getModel() != ColorSpace.Model.RGB) {
+            throw new IllegalArgumentException("The specified color must be encoded in an RGB " +
+                    "color space. The supplied color space is " + mColorSpace.getModel());
+        }
+
+        DoubleUnaryOperator eotf = ((ColorSpace.Rgb) mColorSpace).getEotf();
+        double r = eotf.applyAsDouble(mComponents[0]);
+        double g = eotf.applyAsDouble(mComponents[1]);
+        double b = eotf.applyAsDouble(mComponents[2]);
+
+        return saturate((float) ((0.2126 * r) + (0.7152 * g) + (0.0722 * b)));
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        Color color = (Color) o;
+
+        //noinspection SimplifiableIfStatement
+        if (!Arrays.equals(mComponents, color.mComponents)) return false;
+        return mColorSpace.equals(color.mColorSpace);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = Arrays.hashCode(mComponents);
+        result = 31 * result + mColorSpace.hashCode();
+        return result;
+    }
+
+    /**
+     * <p>Returns a string representation of the object. This method returns
+     * a string equal to the value of:</p>
+     *
+     * <pre class="prettyprint">
+     * "Color(" + r + ", " + g + ", " + b + ", " + a +
+     *         ", " + getColorSpace().getName + ')'
+     * </pre>
+     *
+     * <p>For instance, the string representation of opaque black in the sRGB
+     * color space is equal to the following value:</p>
+     *
+     * <pre>
+     * Color(0.0, 0.0, 0.0, 1.0, sRGB IEC61966-2.1)
+     * </pre>
+     *
+     * @return A non-null string representation of the object
+     */
+    @Override
+    @NonNull
+    public String toString() {
+        StringBuilder b = new StringBuilder("Color(");
+        for (float c : mComponents) {
+            b.append(c).append(", ");
+        }
+        b.append(mColorSpace.getName());
+        b.append(')');
+        return b.toString();
+    }
+
+    /**
+     * Returns the color space encoded in the specified color long.
+     *
+     * @param color The color long whose color space to extract
+     * @return A non-null color space instance
+     * @throws IllegalArgumentException If the encoded color space is invalid or unknown
+     *
+     * @see #red(long)
+     * @see #green(long)
+     * @see #blue(long)
+     * @see #alpha(long)
+     */
+    @NonNull
+    public static ColorSpace colorSpace(@ColorLong long color) {
+        return ColorSpace.get((int) (color & 0x3fL));
+    }
+
+    /**
+     * Returns the red component encoded in the specified color long.
+     * The range of the returned value depends on the color space
+     * associated with the specified color. The color space can be
+     * queried by calling {@link #colorSpace(long)}.
+     *
+     * @param color The color long whose red channel to extract
+     * @return A float value with a range defined by the specified color's
+     * color space
+     *
+     * @see #colorSpace(long)
+     * @see #green(long)
+     * @see #blue(long)
+     * @see #alpha(long)
+     */
+    public static float red(@ColorLong long color) {
+        if ((color & 0x3fL) == 0L) return ((color >> 48) & 0xff) / 255.0f;
+        return Half.toFloat((short) ((color >> 48) & 0xffff));
+    }
+
+    /**
+     * Returns the green component encoded in the specified color long.
+     * The range of the returned value depends on the color space
+     * associated with the specified color. The color space can be
+     * queried by calling {@link #colorSpace(long)}.
+     *
+     * @param color The color long whose green channel to extract
+     * @return A float value with a range defined by the specified color's
+     * color space
+     *
+     * @see #colorSpace(long)
+     * @see #red(long)
+     * @see #blue(long)
+     * @see #alpha(long)
+     */
+    public static float green(@ColorLong long color) {
+        if ((color & 0x3fL) == 0L) return ((color >> 40) & 0xff) / 255.0f;
+        return Half.toFloat((short) ((color >> 32) & 0xffff));
+    }
+
+    /**
+     * Returns the blue component encoded in the specified color long.
+     * The range of the returned value depends on the color space
+     * associated with the specified color. The color space can be
+     * queried by calling {@link #colorSpace(long)}.
+     *
+     * @param color The color long whose blue channel to extract
+     * @return A float value with a range defined by the specified color's
+     * color space
+     *
+     * @see #colorSpace(long)
+     * @see #red(long)
+     * @see #green(long)
+     * @see #alpha(long)
+     */
+    public static float blue(@ColorLong long color) {
+        if ((color & 0x3fL) == 0L) return ((color >> 32) & 0xff) / 255.0f;
+        return Half.toFloat((short) ((color >> 16) & 0xffff));
+    }
+
+    /**
+     * Returns the alpha component encoded in the specified color long.
+     * The returned value is always in the range \([0..1]\).
+     *
+     * @param color The color long whose blue channel to extract
+     * @return A float value in the range \([0..1]\)
+     *
+     * @see #colorSpace(long)
+     * @see #red(long)
+     * @see #green(long)
+     * @see #blue(long)
+     */
+    public static float alpha(@ColorLong long color) {
+        if ((color & 0x3fL) == 0L) return ((color >> 56) & 0xff) / 255.0f;
+        return ((color >> 6) & 0x3ff) / 1023.0f;
+    }
+
+    /**
+     * Indicates whether the specified color is in the
+     * {@link ColorSpace.Named#SRGB sRGB} color space.
+     *
+     * @param color The color to test
+     * @return True if the color is in the sRGB color space, false otherwise
+     * @throws IllegalArgumentException If the encoded color space is invalid or unknown
+     *
+     * @see #isInColorSpace(long, ColorSpace)
+     * @see #isWideGamut(long)
+     */
+    public static boolean isSrgb(@ColorLong long color) {
+        return colorSpace(color).isSrgb();
+    }
+
+    /**
+     * Indicates whether the specified color is in a wide-gamut color space.
+     * See {@link ColorSpace#isWideGamut()} for a definition of a wide-gamut
+     * color space.
+     *
+     * @param color The color to test
+     * @return True if the color is in a wide-gamut color space, false otherwise
+     * @throws IllegalArgumentException If the encoded color space is invalid or unknown
+     *
+     * @see #isInColorSpace(long, ColorSpace)
+     * @see #isSrgb(long)
+     * @see ColorSpace#isWideGamut()
+     */
+    public static boolean isWideGamut(@ColorLong long color) {
+        return colorSpace(color).isWideGamut();
+    }
+
+    /**
+     * Indicates whether the specified color is in the specified color space.
+     *
+     * @param color The color to test
+     * @param colorSpace The color space to test against
+     * @return True if the color is in the specified color space, false otherwise
+     *
+     * @see #isSrgb(long)
+     * @see #isWideGamut(long)
+     */
+    public static boolean isInColorSpace(@ColorLong long color, @NonNull ColorSpace colorSpace) {
+        return (int) (color & 0x3fL) == colorSpace.getId();
+    }
+
+    /**
+     * Converts the specified color long to an ARGB color int. A color int is
+     * always in the {@link ColorSpace.Named#SRGB sRGB} color space. This implies
+     * a color space conversion is applied if needed.
+     *
+     * @return An ARGB color in the sRGB color space
+     * @throws IllegalArgumentException If the encoded color space is invalid or unknown
+     */
+    @ColorInt
+    public static int toArgb(@ColorLong long color) {
+        if ((color & 0x3fL) == 0L) return (int) (color >> 32);
+
+        float r = red(color);
+        float g = green(color);
+        float b = blue(color);
+        float a = alpha(color);
+
+        // The transformation saturates the output
+        float[] c = ColorSpace.connect(colorSpace(color)).transform(r, g, b);
+
+        return ((int) (a    * 255.0f + 0.5f) << 24) |
+               ((int) (c[0] * 255.0f + 0.5f) << 16) |
+               ((int) (c[1] * 255.0f + 0.5f) <<  8) |
+                (int) (c[2] * 255.0f + 0.5f);
+    }
+
+    /**
+     * Creates a new <code>Color</code> instance from an ARGB color int.
+     * The resulting color is in the {@link ColorSpace.Named#SRGB sRGB}
+     * color space.
+     *
+     * @param color The ARGB color int to create a <code>Color</code> from
+     * @return A non-null instance of {@link Color}
+     */
+    @NonNull
+    public static Color valueOf(@ColorInt int color) {
+        float r = ((color >> 16) & 0xff) / 255.0f;
+        float g = ((color >>  8) & 0xff) / 255.0f;
+        float b = ((color      ) & 0xff) / 255.0f;
+        float a = ((color >> 24) & 0xff) / 255.0f;
+        return new Color(r, g, b, a, ColorSpace.get(ColorSpace.Named.SRGB));
+    }
+
+    /**
+     * Creates a new <code>Color</code> instance from a color long.
+     * The resulting color is in the same color space as the specified color long.
+     *
+     * @param color The color long to create a <code>Color</code> from
+     * @return A non-null instance of {@link Color}
+     * @throws IllegalArgumentException If the encoded color space is invalid or unknown
+     */
+    @NonNull
+    public static Color valueOf(@ColorLong long color) {
+        return new Color(red(color), green(color), blue(color), alpha(color), colorSpace(color));
+    }
+
+    /**
+     * Creates a new opaque <code>Color</code> in the {@link ColorSpace.Named#SRGB sRGB}
+     * color space with the specified red, green and blue component values. The component
+     * values must be in the range \([0..1]\).
+     *
+     * @param r The red component of the opaque sRGB color to create, in \([0..1]\)
+     * @param g The green component of the opaque sRGB color to create, in \([0..1]\)
+     * @param b The blue component of the opaque sRGB color to create, in \([0..1]\)
+     * @return A non-null instance of {@link Color}
+     */
+    @NonNull
+    public static Color valueOf(float r, float g, float b) {
+        return new Color(r, g, b, 1.0f);
+    }
+
+    /**
+     * Creates a new <code>Color</code> in the {@link ColorSpace.Named#SRGB sRGB}
+     * color space with the specified red, green, blue and alpha component values.
+     * The component values must be in the range \([0..1]\).
+     *
+     * @param r The red component of the sRGB color to create, in \([0..1]\)
+     * @param g The green component of the sRGB color to create, in \([0..1]\)
+     * @param b The blue component of the sRGB color to create, in \([0..1]\)
+     * @param a The alpha component of the sRGB color to create, in \([0..1]\)
+     * @return A non-null instance of {@link Color}
+     */
+    @NonNull
+    public static Color valueOf(float r, float g, float b, float a) {
+        return new Color(saturate(r), saturate(g), saturate(b), saturate(a));
+    }
+
+    /**
+     * Creates a new <code>Color</code> in the specified color space with the
+     * specified red, green, blue and alpha component values. The range of the
+     * components is defined by {@link ColorSpace#getMinValue(int)} and
+     * {@link ColorSpace#getMaxValue(int)}. The values passed to this method
+     * must be in the proper range.
+     *
+     * @param r The red component of the color to create
+     * @param g The green component of the color to create
+     * @param b The blue component of the color to create
+     * @param a The alpha component of the color to create, in \([0..1]\)
+     * @param colorSpace The color space of the color to create
+     * @return A non-null instance of {@link Color}
+     *
+     * @throws IllegalArgumentException If the specified color space uses a
+     * color model with more than 3 components
+     */
+    @NonNull
+    public static Color valueOf(float r, float g, float b, float a, @NonNull ColorSpace colorSpace) {
+        if (colorSpace.getComponentCount() > 3) {
+            throw new IllegalArgumentException("The specified color space must use a color model " +
+                    "with at most 3 color components");
+        }
+        return new Color(r, g, b, a, colorSpace);
+    }
+
+    /**
+     * <p>Creates a new <code>Color</code> in the specified color space with the
+     * specified component values. The range of the components is defined by
+     * {@link ColorSpace#getMinValue(int)} and {@link ColorSpace#getMaxValue(int)}.
+     * The values passed to this method must be in the proper range. The alpha
+     * component is always in the range \([0..1]\).</p>
+     *
+     * <p>The length of the array of components must be at least
+     * <code>{@link ColorSpace#getComponentCount()} + 1</code>. The component at index
+     * {@link ColorSpace#getComponentCount()} is always alpha.</p>
+     *
+     * @param components The components of the color to create, with alpha as the last component
+     * @param colorSpace The color space of the color to create
+     * @return A non-null instance of {@link Color}
+     *
+     * @throws IllegalArgumentException If the array of components is smaller than
+     * required by the color space
+     */
+    @NonNull
+    public static Color valueOf(@NonNull @Size(min = 4, max = 5) float[] components,
+            @NonNull ColorSpace colorSpace) {
+        if (components.length < colorSpace.getComponentCount() + 1) {
+            throw new IllegalArgumentException("Received a component array of length " +
+                    components.length + " but the color model requires " +
+                    (colorSpace.getComponentCount() + 1) + " (including alpha)");
+        }
+        return new Color(Arrays.copyOf(components, colorSpace.getComponentCount() + 1), colorSpace);
+    }
+
+    /**
+     * Converts the specified ARGB color int to an RGBA color long in the sRGB
+     * color space. See the documentation of this class for a description of
+     * the color long format.
+     *
+     * @param color The ARGB color int to convert to an RGBA color long in sRGB
+     *
+     * @return A color long
+     */
+    @ColorLong
+    public static long pack(@ColorInt int color) {
+        return (color & 0xffffffffL) << 32;
+    }
+
+    /**
+     * Packs the sRGB color defined by the specified red, green and blue component
+     * values into an RGBA color long in the sRGB color space. The alpha component
+     * is set to 1.0. See the documentation of this class for a description of the
+     * color long format.
+     *
+     * @param red The red component of the sRGB color to create, in \([0..1]\)
+     * @param green The green component of the sRGB color to create, in \([0..1]\)
+     * @param blue The blue component of the sRGB color to create, in \([0..1]\)
+     *
+     * @return A color long
+     */
+    @ColorLong
+    public static long pack(float red, float green, float blue) {
+        return pack(red, green, blue, 1.0f, ColorSpace.get(ColorSpace.Named.SRGB));
+    }
+
+    /**
+     * Packs the sRGB color defined by the specified red, green, blue and alpha
+     * component values into an RGBA color long in the sRGB color space. See the
+     * documentation of this class for a description of the color long format.
+     *
+     * @param red The red component of the sRGB color to create, in \([0..1]\)
+     * @param green The green component of the sRGB color to create, in \([0..1]\)
+     * @param blue The blue component of the sRGB color to create, in \([0..1]\)
+     * @param alpha The alpha component of the sRGB color to create, in \([0..1]\)
+     *
+     * @return A color long
+     */
+    @ColorLong
+    public static long pack(float red, float green, float blue, float alpha) {
+        return pack(red, green, blue, alpha, ColorSpace.get(ColorSpace.Named.SRGB));
+    }
+
+    /**
+     * <p>Packs the 3 component color defined by the specified red, green, blue and
+     * alpha component values into a color long in the specified color space. See the
+     * documentation of this class for a description of the color long format.</p>
+     *
+     * <p>The red, green and blue components must be in the range defined by the
+     * specified color space. See {@link ColorSpace#getMinValue(int)} and
+     * {@link ColorSpace#getMaxValue(int)}.</p>
+     *
+     * @param red The red component of the color to create
+     * @param green The green component of the color to create
+     * @param blue The blue component of the color to create
+     * @param alpha The alpha component of the color to create, in \([0..1]\)
+     *
+     * @return A color long
+     *
+     * @throws IllegalArgumentException If the color space's id is {@link ColorSpace#MIN_ID}
+     * or if the color space's color model has more than 3 components
+     */
+    @ColorLong
+    public static long pack(float red, float green, float blue, float alpha,
+            @NonNull ColorSpace colorSpace) {
+        if (colorSpace.isSrgb()) {
+            int argb =
+                    ((int) (alpha * 255.0f + 0.5f) << 24) |
+                    ((int) (red   * 255.0f + 0.5f) << 16) |
+                    ((int) (green * 255.0f + 0.5f) <<  8) |
+                     (int) (blue  * 255.0f + 0.5f);
+            return (argb & 0xffffffffL) << 32;
+        }
+
+        int id = colorSpace.getId();
+        if (id == ColorSpace.MIN_ID) {
+            throw new IllegalArgumentException(
+                    "Unknown color space, please use a color space returned by ColorSpace.get()");
+        }
+        if (colorSpace.getComponentCount() > 3) {
+            throw new IllegalArgumentException(
+                    "The color space must use a color model with at most 3 components");
+        }
+
+        @HalfFloat short r = Half.toHalf(red);
+        @HalfFloat short g = Half.toHalf(green);
+        @HalfFloat short b = Half.toHalf(blue);
+
+        int a = (int) (Math.max(0.0f, Math.min(alpha, 1.0f)) * 1023.0f + 0.5f);
+
+        // Suppress sign extension
+        return  (r & 0xffffL) << 48 |
+                (g & 0xffffL) << 32 |
+                (b & 0xffffL) << 16 |
+                (a & 0x3ffL ) <<  6 |
+                id & 0x3fL;
+    }
+
+    /**
+     * Converts the specified ARGB color int from the {@link ColorSpace.Named#SRGB sRGB}
+     * color space into the specified destination color space. The resulting color is
+     * returned as a color long. See the documentation of this class for a description
+     * of the color long format.
+     *
+     * @param color The sRGB color int to convert
+     * @param colorSpace The destination color space
+     * @return A color long in the destination color space
+     */
+    @ColorLong
+    public static long convert(@ColorInt int color, @NonNull ColorSpace colorSpace) {
+        float r = ((color >> 16) & 0xff) / 255.0f;
+        float g = ((color >>  8) & 0xff) / 255.0f;
+        float b = ((color      ) & 0xff) / 255.0f;
+        float a = ((color >> 24) & 0xff) / 255.0f;
+        ColorSpace source = ColorSpace.get(ColorSpace.Named.SRGB);
+        return convert(r, g, b, a, source, colorSpace);
+    }
+
+    /**
+     * <p>Converts the specified color long from its color space into the specified
+     * destination color space. The resulting color is returned as a color long. See
+     * the documentation of this class for a description of the color long format.</p>
+     *
+     * <p>When converting several colors in a row, it is recommended to use
+     * {@link #convert(long, ColorSpace.Connector)} instead to
+     * avoid the creation of a {@link ColorSpace.Connector} on every invocation.</p>
+     *
+     * @param color The color long to convert
+     * @param colorSpace The destination color space
+     * @return A color long in the destination color space
+     * @throws IllegalArgumentException If the encoded color space is invalid or unknown
+     */
+    @ColorLong
+    public static long convert(@ColorLong long color, @NonNull ColorSpace colorSpace) {
+        float r = red(color);
+        float g = green(color);
+        float b = blue(color);
+        float a = alpha(color);
+        ColorSpace source = colorSpace(color);
+        return convert(r, g, b, a, source, colorSpace);
+    }
+
+    /**
+     * <p>Converts the specified 3 component color from the source color space to the
+     * destination color space. The resulting color is returned as a color long. See
+     * the documentation of this class for a description of the color long format.</p>
+     *
+     * <p>When converting multiple colors in a row, it is recommended to use
+     * {@link #convert(float, float, float, float, ColorSpace.Connector)} instead to
+     * avoid the creation of a {@link ColorSpace.Connector} on every invocation.</p>
+     *
+     * <p>The red, green and blue components must be in the range defined by the
+     * specified color space. See {@link ColorSpace#getMinValue(int)} and
+     * {@link ColorSpace#getMaxValue(int)}.</p>
+     *
+     * @param r The red component of the color to convert
+     * @param g The green component of the color to convert
+     * @param b The blue component of the color to convert
+     * @param a The alpha component of the color to convert, in \([0..1]\)
+     * @param source The source color space, cannot be null
+     * @param destination The destination color space, cannot be null
+     * @return A color long in the destination color space
+     *
+     * @see #convert(float, float, float, float, ColorSpace.Connector)
+     */
+    @ColorLong
+    public static long convert(float r, float g, float b, float a,
+            @NonNull ColorSpace source, @NonNull ColorSpace destination) {
+        float[] c = ColorSpace.connect(source, destination).transform(r, g, b);
+        return pack(c[0], c[1], c[2], a, destination);
+    }
+
+    /**
+     * <p>Converts the specified color long from a color space to another using the
+     * specified color space {@link ColorSpace.Connector connector}. The resulting
+     * color is returned as a color long. See the documentation of this class for a
+     * description of the color long format.</p>
+     *
+     * <p>When converting several colors in a row, this method is preferable to
+     * {@link #convert(long, ColorSpace)} as it prevents a new connector from being
+     * created on every invocation.</p>
+     *
+     * <p class="note">The connector's source color space should match the color long's
+     * color space.</p>
+     *
+     * @param color The color long to convert
+     * @param connector A color space connector, cannot be null
+     * @return A color long in the destination color space of the connector
+     */
+    @ColorLong
+    public static long convert(@ColorLong long color, @NonNull ColorSpace.Connector connector) {
+        float r = red(color);
+        float g = green(color);
+        float b = blue(color);
+        float a = alpha(color);
+        return convert(r, g, b, a, connector);
+    }
+
+    /**
+     * <p>Converts the specified 3 component color from a color space to another using
+     * the specified color space {@link ColorSpace.Connector connector}. The resulting
+     * color is returned as a color long. See the documentation of this class for a
+     * description of the color long format.</p>
+     *
+     * <p>When converting several colors in a row, this method is preferable to
+     * {@link #convert(float, float, float, float, ColorSpace, ColorSpace)} as
+     * it prevents a new connector from being created on every invocation.</p>
+     *
+     * <p>The red, green and blue components must be in the range defined by the
+     * source color space of the connector. See {@link ColorSpace#getMinValue(int)}
+     * and {@link ColorSpace#getMaxValue(int)}.</p>
+     *
+     * @param r The red component of the color to convert
+     * @param g The green component of the color to convert
+     * @param b The blue component of the color to convert
+     * @param a The alpha component of the color to convert, in \([0..1]\)
+     * @param connector A color space connector, cannot be null
+     * @return A color long in the destination color space of the connector
+     *
+     * @see #convert(float, float, float, float, ColorSpace, ColorSpace)
+     */
+    @ColorLong
+    public static long convert(float r, float g, float b, float a,
+            @NonNull ColorSpace.Connector connector) {
+        float[] c = connector.transform(r, g, b);
+        return pack(c[0], c[1], c[2], a, connector.getDestination());
+    }
+
+    /**
+     * <p>Returns the relative luminance of a color.</p>
+     *
+     * <p>Based on the formula for relative luminance defined in WCAG 2.0,
+     * W3C Recommendation 11 December 2008.</p>
+     *
+     * @return A value between 0 (darkest black) and 1 (lightest white)
+     *
+     * @throws IllegalArgumentException If the specified color's color space
+     * is unknown or does not use the {@link ColorSpace.Model#RGB RGB} color model
+     */
+    public static float luminance(@ColorLong long color) {
+        ColorSpace colorSpace = colorSpace(color);
+        if (colorSpace.getModel() != ColorSpace.Model.RGB) {
+            throw new IllegalArgumentException("The specified color must be encoded in an RGB " +
+                    "color space. The supplied color space is " + colorSpace.getModel());
+        }
+
+        DoubleUnaryOperator eotf = ((ColorSpace.Rgb) colorSpace).getEotf();
+        double r = eotf.applyAsDouble(red(color));
+        double g = eotf.applyAsDouble(green(color));
+        double b = eotf.applyAsDouble(blue(color));
+
+        return saturate((float) ((0.2126 * r) + (0.7152 * g) + (0.0722 * b)));
+    }
+
+    private static float saturate(float v) {
+        return v <= 0.0f ? 0.0f : (v >= 1.0f ? 1.0f : v);
+    }
+
+    /**
+     * Return the alpha component of a color int. This is the same as saying
+     * color >>> 24
+     */
+    @IntRange(from = 0, to = 255)
+    public static int alpha(int color) {
+        return color >>> 24;
+    }
+
+    /**
+     * Return the red component of a color int. This is the same as saying
+     * (color >> 16) & 0xFF
+     */
+    @IntRange(from = 0, to = 255)
+    public static int red(int color) {
+        return (color >> 16) & 0xFF;
+    }
+
+    /**
+     * Return the green component of a color int. This is the same as saying
+     * (color >> 8) & 0xFF
+     */
+    @IntRange(from = 0, to = 255)
+    public static int green(int color) {
+        return (color >> 8) & 0xFF;
+    }
+
+    /**
+     * Return the blue component of a color int. This is the same as saying
+     * color & 0xFF
+     */
+    @IntRange(from = 0, to = 255)
+    public static int blue(int color) {
+        return color & 0xFF;
+    }
+
+    /**
+     * Return a color-int from red, green, blue components.
+     * The alpha component is implicitly 255 (fully opaque).
+     * These component values should be \([0..255]\), but there is no
+     * range check performed, so if they are out of range, the
+     * returned color is undefined.
+     *
+     * @param red  Red component \([0..255]\) of the color
+     * @param green Green component \([0..255]\) of the color
+     * @param blue  Blue component \([0..255]\) of the color
+     */
+    @ColorInt
+    public static int rgb(
+            @IntRange(from = 0, to = 255) int red,
+            @IntRange(from = 0, to = 255) int green,
+            @IntRange(from = 0, to = 255) int blue) {
+        return 0xff000000 | (red << 16) | (green << 8) | blue;
+    }
+
+    /**
+     * Return a color-int from red, green, blue float components
+     * in the range \([0..1]\). The alpha component is implicitly
+     * 1.0 (fully opaque). If the components are out of range, the
+     * returned color is undefined.
+     *
+     * @param red Red component \([0..1]\) of the color
+     * @param green Green component \([0..1]\) of the color
+     * @param blue Blue component \([0..1]\) of the color
+     */
+    @ColorInt
+    public static int rgb(float red, float green, float blue) {
+        return 0xff000000 |
+               ((int) (red   * 255.0f + 0.5f) << 16) |
+               ((int) (green * 255.0f + 0.5f) <<  8) |
+                (int) (blue  * 255.0f + 0.5f);
+    }
+
+    /**
+     * Return a color-int from alpha, red, green, blue components.
+     * These component values should be \([0..255]\), but there is no
+     * range check performed, so if they are out of range, the
+     * returned color is undefined.
+     * @param alpha Alpha component \([0..255]\) of the color
+     * @param red Red component \([0..255]\) of the color
+     * @param green Green component \([0..255]\) of the color
+     * @param blue Blue component \([0..255]\) of the color
+     */
+    @ColorInt
+    public static int argb(
+            @IntRange(from = 0, to = 255) int alpha,
+            @IntRange(from = 0, to = 255) int red,
+            @IntRange(from = 0, to = 255) int green,
+            @IntRange(from = 0, to = 255) int blue) {
+        return (alpha << 24) | (red << 16) | (green << 8) | blue;
+    }
+
+    /**
+     * Return a color-int from alpha, red, green, blue float components
+     * in the range \([0..1]\). If the components are out of range, the
+     * returned color is undefined.
+     *
+     * @param alpha Alpha component \([0..1]\) of the color
+     * @param red Red component \([0..1]\) of the color
+     * @param green Green component \([0..1]\) of the color
+     * @param blue Blue component \([0..1]\) of the color
+     */
+    @ColorInt
+    public static int argb(float alpha, float red, float green, float blue) {
+        return ((int) (alpha * 255.0f + 0.5f) << 24) |
+               ((int) (red   * 255.0f + 0.5f) << 16) |
+               ((int) (green * 255.0f + 0.5f) <<  8) |
+                (int) (blue  * 255.0f + 0.5f);
+    }
+
+    /**
+     * Returns the relative luminance of a color.
+     * <p>
+     * Assumes sRGB encoding. Based on the formula for relative luminance
+     * defined in WCAG 2.0, W3C Recommendation 11 December 2008.
+     *
+     * @return a value between 0 (darkest black) and 1 (lightest white)
+     */
+    public static float luminance(@ColorInt int color) {
+        ColorSpace.Rgb cs = (ColorSpace.Rgb) ColorSpace.get(ColorSpace.Named.SRGB);
+        DoubleUnaryOperator eotf = cs.getEotf();
+
+        double r = eotf.applyAsDouble(red(color) / 255.0);
+        double g = eotf.applyAsDouble(green(color) / 255.0);
+        double b = eotf.applyAsDouble(blue(color) / 255.0);
+
+        return (float) ((0.2126 * r) + (0.7152 * g) + (0.0722 * b));
+    }
+
+    /**
+     * </p>Parse the color string, and return the corresponding color-int.
+     * If the string cannot be parsed, throws an IllegalArgumentException
+     * exception. Supported formats are:</p>
+     *
+     * <ul>
+     *   <li><code>#RRGGBB</code></li>
+     *   <li><code>#AARRGGBB</code></li>
+     * </ul>
+     *
+     * <p>The following names are also accepted: <code>red</code>, <code>blue</code>,
+     * <code>green</code>, <code>black</code>, <code>white</code>, <code>gray</code>,
+     * <code>cyan</code>, <code>magenta</code>, <code>yellow</code>, <code>lightgray</code>,
+     * <code>darkgray</code>, <code>grey</code>, <code>lightgrey</code>, <code>darkgrey</code>,
+     * <code>aqua</code>, <code>fuchsia</code>, <code>lime</code>, <code>maroon</code>,
+     * <code>navy</code>, <code>olive</code>, <code>purple</code>, <code>silver</code>,
+     * and <code>teal</code>.</p>
+     */
+    @ColorInt
+    public static int parseColor(@Size(min=1) String colorString) {
+        if (colorString.charAt(0) == '#') {
+            // Use a long to avoid rollovers on #ffXXXXXX
+            long color = Long.parseLong(colorString.substring(1), 16);
+            if (colorString.length() == 7) {
+                // Set the alpha value
+                color |= 0x00000000ff000000;
+            } else if (colorString.length() != 9) {
+                throw new IllegalArgumentException("Unknown color");
+            }
+            return (int)color;
+        } else {
+            Integer color = sColorNameMap.get(colorString.toLowerCase(Locale.ROOT));
+            if (color != null) {
+                return color;
+            }
+        }
+        throw new IllegalArgumentException("Unknown color");
+    }
+
+    /**
+     * Convert RGB components to HSV.
+     * <ul>
+     *   <li><code>hsv[0]</code> is Hue \([0..360[\)</li>
+     *   <li><code>hsv[1]</code> is Saturation \([0...1]\)</li>
+     *   <li><code>hsv[2]</code> is Value \([0...1]\)</li>
+     * </ul>
+     * @param red  red component value \([0..255]\)
+     * @param green  green component value \([0..255]\)
+     * @param blue  blue component value \([0..255]\)
+     * @param hsv  3 element array which holds the resulting HSV components.
+     */
+    public static void RGBToHSV(
+            @IntRange(from = 0, to = 255) int red,
+            @IntRange(from = 0, to = 255) int green,
+            @IntRange(from = 0, to = 255) int blue, @Size(3) float hsv[]) {
+        if (hsv.length < 3) {
+            throw new RuntimeException("3 components required for hsv");
+        }
+        nativeRGBToHSV(red, green, blue, hsv);
+    }
+
+    /**
+     * Convert the ARGB color to its HSV components.
+     * <ul>
+     *   <li><code>hsv[0]</code> is Hue \([0..360[\)</li>
+     *   <li><code>hsv[1]</code> is Saturation \([0...1]\)</li>
+     *   <li><code>hsv[2]</code> is Value \([0...1]\)</li>
+     * </ul>
+     * @param color the argb color to convert. The alpha component is ignored.
+     * @param hsv  3 element array which holds the resulting HSV components.
+     */
+    public static void colorToHSV(@ColorInt int color, @Size(3) float hsv[]) {
+        RGBToHSV((color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF, hsv);
+    }
+
+    /**
+     * Convert HSV components to an ARGB color. Alpha set to 0xFF.
+     * <ul>
+     *   <li><code>hsv[0]</code> is Hue \([0..360[\)</li>
+     *   <li><code>hsv[1]</code> is Saturation \([0...1]\)</li>
+     *   <li><code>hsv[2]</code> is Value \([0...1]\)</li>
+     * </ul>
+     * If hsv values are out of range, they are pinned.
+     * @param hsv  3 element array which holds the input HSV components.
+     * @return the resulting argb color
+    */
+    @ColorInt
+    public static int HSVToColor(@Size(3) float hsv[]) {
+        return HSVToColor(0xFF, hsv);
+    }
+
+    /**
+     * Convert HSV components to an ARGB color. The alpha component is passed
+     * through unchanged.
+     * <ul>
+     *   <li><code>hsv[0]</code> is Hue \([0..360[\)</li>
+     *   <li><code>hsv[1]</code> is Saturation \([0...1]\)</li>
+     *   <li><code>hsv[2]</code> is Value \([0...1]\)</li>
+     * </ul>
+     * If hsv values are out of range, they are pinned.
+     * @param alpha the alpha component of the returned argb color.
+     * @param hsv  3 element array which holds the input HSV components.
+     * @return the resulting argb color
+     */
+    @ColorInt
+    public static int HSVToColor(@IntRange(from = 0, to = 255) int alpha, @Size(3) float hsv[]) {
+        if (hsv.length < 3) {
+            throw new RuntimeException("3 components required for hsv");
+        }
+        return nativeHSVToColor(alpha, hsv);
+    }
+
+    private static native void nativeRGBToHSV(int red, int greed, int blue, float hsv[]);
+    private static native int nativeHSVToColor(int alpha, float hsv[]);
+
+    /**
+     * Converts an HTML color (named or numeric) to an integer RGB value.
+     *
+     * @param color Non-null color string.
+     *
+     * @return A color value, or {@code -1} if the color string could not be interpreted.
+     *
+     * @hide
+     */
+    @ColorInt
+    public static int getHtmlColor(@NonNull String color) {
+        Integer i = sColorNameMap.get(color.toLowerCase(Locale.ROOT));
+        if (i != null) {
+            return i;
+        } else {
+            try {
+                return XmlUtils.convertValueToInt(color, -1);
+            } catch (NumberFormatException nfe) {
+                return -1;
+            }
+        }
+    }
+
+    private static final HashMap<String, Integer> sColorNameMap;
+    static {
+        sColorNameMap = new HashMap<>();
+        sColorNameMap.put("black", BLACK);
+        sColorNameMap.put("darkgray", DKGRAY);
+        sColorNameMap.put("gray", GRAY);
+        sColorNameMap.put("lightgray", LTGRAY);
+        sColorNameMap.put("white", WHITE);
+        sColorNameMap.put("red", RED);
+        sColorNameMap.put("green", GREEN);
+        sColorNameMap.put("blue", BLUE);
+        sColorNameMap.put("yellow", YELLOW);
+        sColorNameMap.put("cyan", CYAN);
+        sColorNameMap.put("magenta", MAGENTA);
+        sColorNameMap.put("aqua", 0xFF00FFFF);
+        sColorNameMap.put("fuchsia", 0xFFFF00FF);
+        sColorNameMap.put("darkgrey", DKGRAY);
+        sColorNameMap.put("grey", GRAY);
+        sColorNameMap.put("lightgrey", LTGRAY);
+        sColorNameMap.put("lime", 0xFF00FF00);
+        sColorNameMap.put("maroon", 0xFF800000);
+        sColorNameMap.put("navy", 0xFF000080);
+        sColorNameMap.put("olive", 0xFF808000);
+        sColorNameMap.put("purple", 0xFF800080);
+        sColorNameMap.put("silver", 0xFFC0C0C0);
+        sColorNameMap.put("teal", 0xFF008080);
+
+    }
+}
diff --git a/android/graphics/ColorFilter.java b/android/graphics/ColorFilter.java
new file mode 100644
index 0000000..b24b988
--- /dev/null
+++ b/android/graphics/ColorFilter.java
@@ -0,0 +1,76 @@
+/*
+ * 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;
+
+import libcore.util.NativeAllocationRegistry;
+
+/**
+ * A color filter can be used with a {@link Paint} to modify the color of
+ * each pixel drawn with that paint. This is an abstract class that should
+ * never be used directly.
+ */
+public class ColorFilter {
+
+    private static class NoImagePreloadHolder {
+        public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+                ColorFilter.class.getClassLoader(), nativeGetFinalizer(), 50);
+    }
+
+    /**
+     * @deprecated Use subclass constructors directly instead.
+     */
+    @Deprecated
+    public ColorFilter() {}
+
+    /**
+     * Current native SkColorFilter instance.
+     */
+    private long mNativeInstance;
+    // Runnable to do immediate destruction
+    private Runnable mCleaner;
+
+    long createNativeInstance() {
+        return 0;
+    }
+
+    void discardNativeInstance() {
+        if (mNativeInstance != 0) {
+            mCleaner.run();
+            mCleaner = null;
+            mNativeInstance = 0;
+        }
+    }
+
+    /** @hide */
+    public long getNativeInstance() {
+        if (mNativeInstance == 0) {
+            mNativeInstance = createNativeInstance();
+
+            if (mNativeInstance != 0) {
+                // Note: we must check for null here, since it's possible for createNativeInstance()
+                // to return nullptr if the native SkColorFilter would be a no-op at draw time.
+                // See native implementations of subclass create methods for more info.
+                mCleaner = NoImagePreloadHolder.sRegistry.registerNativeAllocation(
+                        this, mNativeInstance);
+            }
+        }
+        return mNativeInstance;
+
+    }
+
+    private static native long nativeGetFinalizer();
+}
diff --git a/android/graphics/ColorFilter_Delegate.java b/android/graphics/ColorFilter_Delegate.java
new file mode 100644
index 0000000..84424bc
--- /dev/null
+++ b/android/graphics/ColorFilter_Delegate.java
@@ -0,0 +1,83 @@
+/*
+ * 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;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.awt.Graphics2D;
+
+import libcore.util.NativeAllocationRegistry_Delegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.ColorFilter
+ *
+ * Through the layoutlib_create tool, the original native methods of ColorFilter have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original ColorFilter class.
+ *
+ * This also serve as a base class for all ColorFilter delegate classes.
+ *
+ * @see DelegateManager
+ *
+ */
+public abstract class ColorFilter_Delegate {
+
+    // ---- delegate manager ----
+    protected static final DelegateManager<ColorFilter_Delegate> sManager =
+            new DelegateManager<ColorFilter_Delegate>(ColorFilter_Delegate.class);
+    private static long sFinalizer = -1;
+
+    // ---- delegate helper data ----
+
+    // ---- delegate data ----
+
+    // ---- Public Helper methods ----
+
+    public static ColorFilter_Delegate getDelegate(long nativeShader) {
+        return sManager.getDelegate(nativeShader);
+    }
+
+    public abstract String getSupportMessage();
+
+    public boolean isSupported() {
+        return false;
+    }
+
+    public void applyFilter(Graphics2D g, int width, int height) {
+        // This should never be called directly. If supported, the sub class should override this.
+        assert false;
+    }
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeGetFinalizer() {
+        synchronized (ColorFilter_Delegate.class) {
+            if (sFinalizer == -1) {
+                sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
+                        sManager::removeJavaReferenceFor);
+            }
+        }
+        return sFinalizer;
+    }
+
+    // ---- Private delegate/helper methods ----
+}
diff --git a/android/graphics/ColorMatrix.java b/android/graphics/ColorMatrix.java
new file mode 100644
index 0000000..6299b2c
--- /dev/null
+++ b/android/graphics/ColorMatrix.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2007 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;
+
+import java.util.Arrays;
+
+/**
+ * 4x5 matrix for transforming the color and alpha components of a Bitmap.
+ * The matrix can be passed as single array, and is treated as follows:
+ *
+ * <pre>
+ *  [ a, b, c, d, e,
+ *    f, g, h, i, j,
+ *    k, l, m, n, o,
+ *    p, q, r, s, t ]</pre>
+ *
+ * <p>
+ * When applied to a color <code>[R, G, B, A]</code>, the resulting color
+ * is computed as:
+ * </p>
+ *
+ * <pre>
+ *   R&rsquo; = a*R + b*G + c*B + d*A + e;
+ *   G&rsquo; = f*R + g*G + h*B + i*A + j;
+ *   B&rsquo; = k*R + l*G + m*B + n*A + o;
+ *   A&rsquo; = p*R + q*G + r*B + s*A + t;</pre>
+ *
+ * <p>
+ * That resulting color <code>[R&rsquo;, G&rsquo;, B&rsquo;, A&rsquo;]</code>
+ * then has each channel clamped to the <code>0</code> to <code>255</code>
+ * range.
+ * </p>
+ *
+ * <p>
+ * The sample ColorMatrix below inverts incoming colors by scaling each
+ * channel by <code>-1</code>, and then shifting the result up by
+ * <code>255</code> to remain in the standard color space.
+ * </p>
+ *
+ * <pre>
+ *   [ -1, 0, 0, 0, 255,
+ *     0, -1, 0, 0, 255,
+ *     0, 0, -1, 0, 255,
+ *     0, 0, 0, 1, 0 ]</pre>
+ */
+@SuppressWarnings({ "MismatchedReadAndWriteOfArray", "PointlessArithmeticExpression" })
+public class ColorMatrix {
+    private final float[] mArray = new float[20];
+
+    /**
+     * Create a new colormatrix initialized to identity (as if reset() had
+     * been called).
+     */
+    public ColorMatrix() {
+        reset();
+    }
+
+    /**
+     * Create a new colormatrix initialized with the specified array of values.
+     */
+    public ColorMatrix(float[] src) {
+        System.arraycopy(src, 0, mArray, 0, 20);
+    }
+
+    /**
+     * Create a new colormatrix initialized with the specified colormatrix.
+     */
+    public ColorMatrix(ColorMatrix src) {
+        System.arraycopy(src.mArray, 0, mArray, 0, 20);
+    }
+
+    /**
+     * Return the array of floats representing this colormatrix.
+     */
+    public final float[] getArray() { return mArray; }
+
+    /**
+     * Set this colormatrix to identity:
+     * <pre>
+     * [ 1 0 0 0 0   - red vector
+     *   0 1 0 0 0   - green vector
+     *   0 0 1 0 0   - blue vector
+     *   0 0 0 1 0 ] - alpha vector
+     * </pre>
+     */
+    public void reset() {
+        final float[] a = mArray;
+        Arrays.fill(a, 0);
+        a[0] = a[6] = a[12] = a[18] = 1;
+    }
+
+    /**
+     * Assign the src colormatrix into this matrix, copying all of its values.
+     */
+    public void set(ColorMatrix src) {
+        System.arraycopy(src.mArray, 0, mArray, 0, 20);
+    }
+
+    /**
+     * Assign the array of floats into this matrix, copying all of its values.
+     */
+    public void set(float[] src) {
+        System.arraycopy(src, 0, mArray, 0, 20);
+    }
+
+    /**
+     * Set this colormatrix to scale by the specified values.
+     */
+    public void setScale(float rScale, float gScale, float bScale,
+                         float aScale) {
+        final float[] a = mArray;
+
+        for (int i = 19; i > 0; --i) {
+            a[i] = 0;
+        }
+        a[0] = rScale;
+        a[6] = gScale;
+        a[12] = bScale;
+        a[18] = aScale;
+    }
+
+    /**
+     * Set the rotation on a color axis by the specified values.
+     * <p>
+     * <code>axis=0</code> correspond to a rotation around the RED color
+     * <code>axis=1</code> correspond to a rotation around the GREEN color
+     * <code>axis=2</code> correspond to a rotation around the BLUE color
+     * </p>
+     */
+    public void setRotate(int axis, float degrees) {
+        reset();
+        double radians = degrees * Math.PI / 180d;
+        float cosine = (float) Math.cos(radians);
+        float sine = (float) Math.sin(radians);
+        switch (axis) {
+        // Rotation around the red color
+        case 0:
+            mArray[6] = mArray[12] = cosine;
+            mArray[7] = sine;
+            mArray[11] = -sine;
+            break;
+        // Rotation around the green color
+        case 1:
+            mArray[0] = mArray[12] = cosine;
+            mArray[2] = -sine;
+            mArray[10] = sine;
+            break;
+        // Rotation around the blue color
+        case 2:
+            mArray[0] = mArray[6] = cosine;
+            mArray[1] = sine;
+            mArray[5] = -sine;
+            break;
+        default:
+            throw new RuntimeException();
+        }
+    }
+
+    /**
+     * Set this colormatrix to the concatenation of the two specified
+     * colormatrices, such that the resulting colormatrix has the same effect
+     * as applying matB and then applying matA.
+     * <p>
+     * It is legal for either matA or matB to be the same colormatrix as this.
+     * </p>
+     */
+    public void setConcat(ColorMatrix matA, ColorMatrix matB) {
+        float[] tmp;
+        if (matA == this || matB == this) {
+            tmp = new float[20];
+        } else {
+            tmp = mArray;
+        }
+
+        final float[] a = matA.mArray;
+        final float[] b = matB.mArray;
+        int index = 0;
+        for (int j = 0; j < 20; j += 5) {
+            for (int i = 0; i < 4; i++) {
+                tmp[index++] = a[j + 0] * b[i + 0] +  a[j + 1] * b[i + 5] +
+                               a[j + 2] * b[i + 10] + a[j + 3] * b[i + 15];
+            }
+            tmp[index++] = a[j + 0] * b[4] +  a[j + 1] * b[9] +
+                           a[j + 2] * b[14] + a[j + 3] * b[19] +
+                           a[j + 4];
+        }
+
+        if (tmp != mArray) {
+            System.arraycopy(tmp, 0, mArray, 0, 20);
+        }
+    }
+
+    /**
+     * Concat this colormatrix with the specified prematrix.
+     * <p>
+     * This is logically the same as calling setConcat(this, prematrix);
+     * </p>
+     */
+    public void preConcat(ColorMatrix prematrix) {
+        setConcat(this, prematrix);
+    }
+
+    /**
+     * Concat this colormatrix with the specified postmatrix.
+     * <p>
+     * This is logically the same as calling setConcat(postmatrix, this);
+     * </p>
+     */
+    public void postConcat(ColorMatrix postmatrix) {
+        setConcat(postmatrix, this);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Set the matrix to affect the saturation of colors.
+     *
+     * @param sat A value of 0 maps the color to gray-scale. 1 is identity.
+     */
+    public void setSaturation(float sat) {
+        reset();
+        float[] m = mArray;
+
+        final float invSat = 1 - sat;
+        final float R = 0.213f * invSat;
+        final float G = 0.715f * invSat;
+        final float B = 0.072f * invSat;
+
+        m[0] = R + sat; m[1] = G;       m[2] = B;
+        m[5] = R;       m[6] = G + sat; m[7] = B;
+        m[10] = R;      m[11] = G;      m[12] = B + sat;
+    }
+
+    /**
+     * Set the matrix to convert RGB to YUV
+     */
+    public void setRGB2YUV() {
+        reset();
+        float[] m = mArray;
+        // these coefficients match those in libjpeg
+        m[0]  = 0.299f;    m[1]  = 0.587f;    m[2]  = 0.114f;
+        m[5]  = -0.16874f; m[6]  = -0.33126f; m[7]  = 0.5f;
+        m[10] = 0.5f;      m[11] = -0.41869f; m[12] = -0.08131f;
+    }
+
+    /**
+     * Set the matrix to convert from YUV to RGB
+     */
+    public void setYUV2RGB() {
+        reset();
+        float[] m = mArray;
+        // these coefficients match those in libjpeg
+                                        m[2] = 1.402f;
+        m[5] = 1;   m[6] = -0.34414f;   m[7] = -0.71414f;
+        m[10] = 1;  m[11] = 1.772f;     m[12] = 0;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        // if (obj == this) return true; -- NaN value would mean matrix != itself
+        if (!(obj instanceof ColorMatrix)) {
+            return false;
+        }
+
+        // we don't use Arrays.equals(), since that considers NaN == NaN
+        final float[] other = ((ColorMatrix) obj).mArray;
+        for (int i = 0; i < 20; i++) {
+            if (other[i] != mArray[i]) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/android/graphics/ColorMatrixColorFilter.java b/android/graphics/ColorMatrixColorFilter.java
new file mode 100644
index 0000000..9201a2e
--- /dev/null
+++ b/android/graphics/ColorMatrixColorFilter.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2007 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * A color filter that transforms colors through a 4x5 color matrix. This filter
+ * can be used to change the saturation of pixels, convert from YUV to RGB, etc.
+ *
+ * @see ColorMatrix
+ */
+public class ColorMatrixColorFilter extends ColorFilter {
+    private final ColorMatrix mMatrix = new ColorMatrix();
+
+    /**
+     * Create a color filter that transforms colors through a 4x5 color matrix.
+     *
+     * @param matrix 4x5 matrix used to transform colors. It is copied into
+     *               the filter, so changes made to the matrix after the filter
+     *               is constructed will not be reflected in the filter.
+     */
+    public ColorMatrixColorFilter(@NonNull ColorMatrix matrix) {
+        mMatrix.set(matrix);
+    }
+
+    /**
+     * Create a color filter that transforms colors through a 4x5 color matrix.
+     *
+     * @param array Array of floats used to transform colors, treated as a 4x5
+     *              matrix. The first 20 entries of the array are copied into
+     *              the filter. See ColorMatrix.
+     */
+    public ColorMatrixColorFilter(@NonNull float[] array) {
+        if (array.length < 20) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        mMatrix.set(array);
+    }
+
+    /**
+     * Copies the ColorMatrix from the filter into the passed ColorMatrix.
+     *
+     * @param colorMatrix Set to the current value of the filter's ColorMatrix.
+     */
+    public void getColorMatrix(ColorMatrix colorMatrix) {
+        colorMatrix.set(mMatrix);
+    }
+
+    /**
+     * Copies the provided color matrix to be used by this filter.
+     *
+     * If the specified color matrix is null, this filter's color matrix will be reset to the
+     * identity matrix.
+     *
+     * @param matrix A {@link ColorMatrix} or null
+     *
+     * @see #getColorMatrix(ColorMatrix)
+     * @see #setColorMatrixArray(float[])
+     * @see ColorMatrix#reset()
+     *
+     * @hide
+     */
+    public void setColorMatrix(@Nullable ColorMatrix matrix) {
+        discardNativeInstance();
+        if (matrix == null) {
+            mMatrix.reset();
+        } else {
+            mMatrix.set(matrix);
+        }
+    }
+
+    /**
+     * Copies the provided color matrix to be used by this filter.
+     *
+     * If the specified color matrix is null, this filter's color matrix will be reset to the
+     * identity matrix.
+     *
+     * @param array Array of floats used to transform colors, treated as a 4x5
+     *              matrix. The first 20 entries of the array are copied into
+     *              the filter. See {@link ColorMatrix}.
+     *
+     * @see #getColorMatrix(ColorMatrix)
+     * @see #setColorMatrix(ColorMatrix)
+     * @see ColorMatrix#reset()
+     *
+     * @throws ArrayIndexOutOfBoundsException if the specified array's
+     *         length is < 20
+     *
+     * @hide
+     */
+    public void setColorMatrixArray(@Nullable float[] array) {
+        // called '...Array' so that passing null isn't ambiguous
+        discardNativeInstance();
+        if (array == null) {
+            mMatrix.reset();
+        } else {
+            if (array.length < 20) {
+                throw new ArrayIndexOutOfBoundsException();
+            }
+            mMatrix.set(array);
+        }
+    }
+
+    @Override
+    long createNativeInstance() {
+        return nativeColorMatrixFilter(mMatrix.getArray());
+    }
+
+    private static native long nativeColorMatrixFilter(float[] array);
+}
diff --git a/android/graphics/ColorMatrixColorFilter_Delegate.java b/android/graphics/ColorMatrixColorFilter_Delegate.java
new file mode 100644
index 0000000..6739484
--- /dev/null
+++ b/android/graphics/ColorMatrixColorFilter_Delegate.java
@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.ColorMatrixColorFilter
+ *
+ * Through the layoutlib_create tool, the original native methods of ColorMatrixColorFilter have
+ * been replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original ColorMatrixColorFilter class.
+ *
+ * Because this extends {@link ColorFilter_Delegate}, there's no need to use a
+ * {@link DelegateManager}, as all the Shader classes will be added to the manager
+ * owned by {@link ColorFilter_Delegate}.
+ *
+ * @see ColorFilter_Delegate
+ *
+ */
+public class ColorMatrixColorFilter_Delegate extends ColorFilter_Delegate {
+
+    // ---- delegate data ----
+
+    // ---- Public Helper methods ----
+
+    @Override
+    public String getSupportMessage() {
+        return "ColorMatrix Color Filters are not supported.";
+    }
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeColorMatrixFilter(float[] array) {
+        ColorMatrixColorFilter_Delegate newDelegate = new ColorMatrixColorFilter_Delegate();
+        return sManager.addNewDelegate(newDelegate);
+    }
+
+    // ---- Private delegate/helper methods ----
+}
diff --git a/android/graphics/ColorSpace.java b/android/graphics/ColorSpace.java
new file mode 100644
index 0000000..5814df5
--- /dev/null
+++ b/android/graphics/ColorSpace.java
@@ -0,0 +1,4460 @@
+/*
+ * Copyright (C) 2016 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;
+
+import android.annotation.AnyThread;
+import android.annotation.ColorInt;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.Size;
+import android.annotation.SuppressAutoDoc;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.DoubleUnaryOperator;
+
+/**
+ * {@usesMathJax}
+ *
+ * <p>A {@link ColorSpace} is used to identify a specific organization of colors.
+ * Each color space is characterized by a {@link Model color model} that defines
+ * how a color value is represented (for instance the {@link Model#RGB RGB} color
+ * model defines a color value as a triplet of numbers).</p>
+ *
+ * <p>Each component of a color must fall within a valid range, specific to each
+ * color space, defined by {@link #getMinValue(int)} and {@link #getMaxValue(int)}
+ * This range is commonly \([0..1]\). While it is recommended to use values in the
+ * valid range, a color space always clamps input and output values when performing
+ * operations such as converting to a different color space.</p>
+ *
+ * <h3>Using color spaces</h3>
+ *
+ * <p>This implementation provides a pre-defined set of common color spaces
+ * described in the {@link Named} enum. To obtain an instance of one of the
+ * pre-defined color spaces, simply invoke {@link #get(Named)}:</p>
+ *
+ * <pre class="prettyprint">
+ * ColorSpace sRgb = ColorSpace.get(ColorSpace.Named.SRGB);
+ * </pre>
+ *
+ * <p>The {@link #get(Named)} method always returns the same instance for a given
+ * name. Color spaces with an {@link Model#RGB RGB} color model can be safely
+ * cast to {@link Rgb}. Doing so gives you access to more APIs to query various
+ * properties of RGB color models: color gamut primaries, transfer functions,
+ * conversions to and from linear space, etc. Please refer to {@link Rgb} for
+ * more information.</p>
+ *
+ * <p>The documentation of {@link Named} provides a detailed description of the
+ * various characteristics of each available color space.</p>
+ *
+ * <h3>Color space conversions</h3>
+
+ * <p>To allow conversion between color spaces, this implementation uses the CIE
+ * XYZ profile connection space (PCS). Color values can be converted to and from
+ * this PCS using {@link #toXyz(float[])} and {@link #fromXyz(float[])}.</p>
+ *
+ * <p>For color space with a non-RGB color model, the white point of the PCS
+ * <em>must be</em> the CIE standard illuminant D50. RGB color spaces use their
+ * native white point (D65 for {@link Named#SRGB sRGB} for instance and must
+ * undergo {@link Adaptation chromatic adaptation} as necessary.</p>
+ *
+ * <p>Since the white point of the PCS is not defined for RGB color space, it is
+ * highly recommended to use the variants of the {@link #connect(ColorSpace, ColorSpace)}
+ * method to perform conversions between color spaces. A color space can be
+ * manually adapted to a specific white point using {@link #adapt(ColorSpace, float[])}.
+ * Please refer to the documentation of {@link Rgb RGB color spaces} for more
+ * information. Several common CIE standard illuminants are provided in this
+ * class as reference (see {@link #ILLUMINANT_D65} or {@link #ILLUMINANT_D50}
+ * for instance).</p>
+ *
+ * <p>Here is an example of how to convert from a color space to another:</p>
+ *
+ * <pre class="prettyprint">
+ * // Convert from DCI-P3 to Rec.2020
+ * ColorSpace.Connector connector = ColorSpace.connect(
+ *         ColorSpace.get(ColorSpace.Named.DCI_P3),
+ *         ColorSpace.get(ColorSpace.Named.BT2020));
+ *
+ * float[] bt2020 = connector.transform(p3r, p3g, p3b);
+ * </pre>
+ *
+ * <p>You can easily convert to {@link Named#SRGB sRGB} by omitting the second
+ * parameter:</p>
+ *
+ * <pre class="prettyprint">
+ * // Convert from DCI-P3 to sRGB
+ * ColorSpace.Connector connector = ColorSpace.connect(ColorSpace.get(ColorSpace.Named.DCI_P3));
+ *
+ * float[] sRGB = connector.transform(p3r, p3g, p3b);
+ * </pre>
+ *
+ * <p>Conversions also work between color spaces with different color models:</p>
+ *
+ * <pre class="prettyprint">
+ * // Convert from CIE L*a*b* (color model Lab) to Rec.709 (color model RGB)
+ * ColorSpace.Connector connector = ColorSpace.connect(
+ *         ColorSpace.get(ColorSpace.Named.CIE_LAB),
+ *         ColorSpace.get(ColorSpace.Named.BT709));
+ * </pre>
+ *
+ * <h3>Color spaces and multi-threading</h3>
+ *
+ * <p>Color spaces and other related classes ({@link Connector} for instance)
+ * are immutable and stateless. They can be safely used from multiple concurrent
+ * threads.</p>
+ *
+ * <p>Public static methods provided by this class, such as {@link #get(Named)}
+ * and {@link #connect(ColorSpace, ColorSpace)}, are also guaranteed to be
+ * thread-safe.</p>
+ *
+ * @see #get(Named)
+ * @see Named
+ * @see Model
+ * @see Connector
+ * @see Adaptation
+ */
+@AnyThread
+@SuppressWarnings("StaticInitializerReferencesSubClass")
+@SuppressAutoDoc
+public abstract class ColorSpace {
+    /**
+     * Standard CIE 1931 2° illuminant A, encoded in xyY.
+     * This illuminant has a color temperature of 2856K.
+     */
+    public static final float[] ILLUMINANT_A   = { 0.44757f, 0.40745f };
+    /**
+     * Standard CIE 1931 2° illuminant B, encoded in xyY.
+     * This illuminant has a color temperature of 4874K.
+     */
+    public static final float[] ILLUMINANT_B   = { 0.34842f, 0.35161f };
+    /**
+     * Standard CIE 1931 2° illuminant C, encoded in xyY.
+     * This illuminant has a color temperature of 6774K.
+     */
+    public static final float[] ILLUMINANT_C   = { 0.31006f, 0.31616f };
+    /**
+     * Standard CIE 1931 2° illuminant D50, encoded in xyY.
+     * This illuminant has a color temperature of 5003K. This illuminant
+     * is used by the profile connection space in ICC profiles.
+     */
+    public static final float[] ILLUMINANT_D50 = { 0.34567f, 0.35850f };
+    /**
+     * Standard CIE 1931 2° illuminant D55, encoded in xyY.
+     * This illuminant has a color temperature of 5503K.
+     */
+    public static final float[] ILLUMINANT_D55 = { 0.33242f, 0.34743f };
+    /**
+     * Standard CIE 1931 2° illuminant D60, encoded in xyY.
+     * This illuminant has a color temperature of 6004K.
+     */
+    public static final float[] ILLUMINANT_D60 = { 0.32168f, 0.33767f };
+    /**
+     * Standard CIE 1931 2° illuminant D65, encoded in xyY.
+     * This illuminant has a color temperature of 6504K. This illuminant
+     * is commonly used in RGB color spaces such as sRGB, BT.209, etc.
+     */
+    public static final float[] ILLUMINANT_D65 = { 0.31271f, 0.32902f };
+    /**
+     * Standard CIE 1931 2° illuminant D75, encoded in xyY.
+     * This illuminant has a color temperature of 7504K.
+     */
+    public static final float[] ILLUMINANT_D75 = { 0.29902f, 0.31485f };
+    /**
+     * Standard CIE 1931 2° illuminant E, encoded in xyY.
+     * This illuminant has a color temperature of 5454K.
+     */
+    public static final float[] ILLUMINANT_E   = { 0.33333f, 0.33333f };
+
+    /**
+     * The minimum ID value a color space can have.
+     *
+     * @see #getId()
+     */
+    public static final int MIN_ID = -1; // Do not change
+    /**
+     * The maximum ID value a color space can have.
+     *
+     * @see #getId()
+     */
+    public static final int MAX_ID = 63; // Do not change, used to encode in longs
+
+    private static final float[] SRGB_PRIMARIES = { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f };
+    private static final float[] NTSC_1953_PRIMARIES = { 0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f };
+    private static final float[] ILLUMINANT_D50_XYZ = { 0.964212f, 1.0f, 0.825188f };
+
+    // See static initialization block next to #get(Named)
+    private static final ColorSpace[] sNamedColorSpaces = new ColorSpace[Named.values().length];
+
+    @NonNull private final String mName;
+    @NonNull private final Model mModel;
+    @IntRange(from = MIN_ID, to = MAX_ID) private final int mId;
+
+    /**
+     * {@usesMathJax}
+     *
+     * <p>List of common, named color spaces. A corresponding instance of
+     * {@link ColorSpace} can be obtained by calling {@link ColorSpace#get(Named)}:</p>
+     *
+     * <pre class="prettyprint">
+     * ColorSpace cs = ColorSpace.get(ColorSpace.Named.DCI_P3);
+     * </pre>
+     *
+     * <p>The properties of each color space are described below (see {@link #SRGB sRGB}
+     * for instance). When applicable, the color gamut of each color space is compared
+     * to the color gamut of sRGB using a CIE 1931 xy chromaticity diagram. This diagram
+     * shows the location of the color space's primaries and white point.</p>
+     *
+     * @see ColorSpace#get(Named)
+     */
+    public enum Named {
+        // NOTE: Do NOT change the order of the enum
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space sRGB standardized as IEC 61966-2.1:1999.</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr>
+         *     <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">sRGB IEC61966-2.1</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function (OETF)</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{sRGB} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0031308 \\
+         *             1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \ge 0.0031308 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function (EOTF)</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{linear} = \begin{cases}\frac{C_{sRGB}}{12.92} & C_{sRGB} \lt 0.04045 \\
+         *             \left( \frac{C_{sRGB} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \ge 0.04045 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
+         * </table>
+         * <p>
+         *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_srgb.png" />
+         *     <figcaption style="text-align: center;">sRGB</figcaption>
+         * </p>
+         */
+        SRGB,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space sRGB standardized as IEC 61966-2.1:1999.</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr>
+         *     <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">sRGB IEC61966-2.1 (Linear)</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function (OETF)</td>
+         *         <td colspan="4">\(C_{sRGB} = C_{linear}\)</td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function (EOTF)</td>
+         *         <td colspan="4">\(C_{linear} = C_{sRGB}\)</td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
+         * </table>
+         * <p>
+         *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_srgb.png" />
+         *     <figcaption style="text-align: center;">sRGB</figcaption>
+         * </p>
+         */
+        LINEAR_SRGB,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space scRGB-nl standardized as IEC 61966-2-2:2003.</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr>
+         *     <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">scRGB-nl IEC 61966-2-2:2003</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function (OETF)</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{scRGB} = \begin{cases} sign(C_{linear}) 12.92 \times \left| C_{linear} \right| &
+         *                      \left| C_{linear} \right| \lt 0.0031308 \\
+         *             sign(C_{linear}) 1.055 \times \left| C_{linear} \right| ^{\frac{1}{2.4}} - 0.055 &
+         *                      \left| C_{linear} \right| \ge 0.0031308 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function (EOTF)</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{linear} = \begin{cases}sign(C_{scRGB}) \frac{\left| C_{scRGB} \right|}{12.92} &
+         *                  \left| C_{scRGB} \right| \lt 0.04045 \\
+         *             sign(C_{scRGB}) \left( \frac{\left| C_{scRGB} \right| + 0.055}{1.055} \right) ^{2.4} &
+         *                  \left| C_{scRGB} \right| \ge 0.04045 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([-0.799..2.399[\)</td></tr>
+         * </table>
+         * <p>
+         *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" />
+         *     <figcaption style="text-align: center;">Extended sRGB (orange) vs sRGB (white)</figcaption>
+         * </p>
+         */
+        EXTENDED_SRGB,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space scRGB standardized as IEC 61966-2-2:2003.</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr>
+         *     <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">scRGB IEC 61966-2-2:2003</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function (OETF)</td>
+         *         <td colspan="4">\(C_{scRGB} = C_{linear}\)</td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function (EOTF)</td>
+         *         <td colspan="4">\(C_{linear} = C_{scRGB}\)</td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([-0.5..7.499[\)</td></tr>
+         * </table>
+         * <p>
+         *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" />
+         *     <figcaption style="text-align: center;">Extended sRGB (orange) vs sRGB (white)</figcaption>
+         * </p>
+         */
+        LINEAR_EXTENDED_SRGB,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space BT.709 standardized as Rec. ITU-R BT.709-5.</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr>
+         *     <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">Rec. ITU-R BT.709-5</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function (OETF)</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\
+         *             1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function (EOTF)</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\
+         *             \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
+         * </table>
+         * <p>
+         *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_bt709.png" />
+         *     <figcaption style="text-align: center;">BT.709</figcaption>
+         * </p>
+         */
+        BT709,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space BT.2020 standardized as Rec. ITU-R BT.2020-1.</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.708</td><td>0.170</td><td>0.131</td><td>0.3127</td></tr>
+         *     <tr><td>y</td><td>0.292</td><td>0.797</td><td>0.046</td><td>0.3290</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">Rec. ITU-R BT.2020-1</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function (OETF)</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{BT2020} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.0181 \\
+         *             1.0993 \times C_{linear}^{\frac{1}{2.2}} - 0.0993 & C_{linear} \ge 0.0181 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function (EOTF)</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{linear} = \begin{cases}\frac{C_{BT2020}}{4.5} & C_{BT2020} \lt 0.08145 \\
+         *             \left( \frac{C_{BT2020} + 0.0993}{1.0993} \right) ^{2.2} & C_{BT2020} \ge 0.08145 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
+         * </table>
+         * <p>
+         *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_bt2020.png" />
+         *     <figcaption style="text-align: center;">BT.2020 (orange) vs sRGB (white)</figcaption>
+         * </p>
+         */
+        BT2020,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space DCI-P3 standardized as SMPTE RP 431-2-2007.</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.680</td><td>0.265</td><td>0.150</td><td>0.314</td></tr>
+         *     <tr><td>y</td><td>0.320</td><td>0.690</td><td>0.060</td><td>0.351</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">SMPTE RP 431-2-2007 DCI (P3)</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">N/A</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function (OETF)</td>
+         *         <td colspan="4">\(C_{P3} = C_{linear}^{\frac{1}{2.6}}\)</td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function (EOTF)</td>
+         *         <td colspan="4">\(C_{linear} = C_{P3}^{2.6}\)</td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
+         * </table>
+         * <p>
+         *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_dci_p3.png" />
+         *     <figcaption style="text-align: center;">DCI-P3 (orange) vs sRGB (white)</figcaption>
+         * </p>
+         */
+        DCI_P3,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space Display P3 based on SMPTE RP 431-2-2007 and IEC 61966-2.1:1999.</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.680</td><td>0.265</td><td>0.150</td><td>0.3127</td></tr>
+         *     <tr><td>y</td><td>0.320</td><td>0.690</td><td>0.060</td><td>0.3290</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">Display P3</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function (OETF)</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{DisplayP3} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0030186 \\
+         *             1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \ge 0.0030186 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function (EOTF)</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{linear} = \begin{cases}\frac{C_{DisplayP3}}{12.92} & C_{sRGB} \lt 0.039 \\
+         *             \left( \frac{C_{DisplayP3} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \ge 0.039 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
+         * </table>
+         * <p>
+         *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_display_p3.png" />
+         *     <figcaption style="text-align: center;">Display P3 (orange) vs sRGB (white)</figcaption>
+         * </p>
+         */
+        DISPLAY_P3,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space NTSC, 1953 standard.</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.67</td><td>0.21</td><td>0.14</td><td>0.310</td></tr>
+         *     <tr><td>y</td><td>0.33</td><td>0.71</td><td>0.08</td><td>0.316</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">NTSC (1953)</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">C</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function (OETF)</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\
+         *             1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function (EOTF)</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\
+         *             \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
+         * </table>
+         * <p>
+         *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_ntsc_1953.png" />
+         *     <figcaption style="text-align: center;">NTSC 1953 (orange) vs sRGB (white)</figcaption>
+         * </p>
+         */
+        NTSC_1953,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space SMPTE C.</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.630</td><td>0.310</td><td>0.155</td><td>0.3127</td></tr>
+         *     <tr><td>y</td><td>0.340</td><td>0.595</td><td>0.070</td><td>0.3290</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">SMPTE-C RGB</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function (OETF)</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\
+         *             1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function (EOTF)</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\
+         *             \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
+         * </table>
+         * <p>
+         *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_smpte_c.png" />
+         *     <figcaption style="text-align: center;">SMPTE-C (orange) vs sRGB (white)</figcaption>
+         * </p>
+         */
+        SMPTE_C,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space Adobe RGB (1998).</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.64</td><td>0.21</td><td>0.15</td><td>0.3127</td></tr>
+         *     <tr><td>y</td><td>0.33</td><td>0.71</td><td>0.06</td><td>0.3290</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">Adobe RGB (1998)</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function (OETF)</td>
+         *         <td colspan="4">\(C_{RGB} = C_{linear}^{\frac{1}{2.2}}\)</td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function (EOTF)</td>
+         *         <td colspan="4">\(C_{linear} = C_{RGB}^{2.2}\)</td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
+         * </table>
+         * <p>
+         *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_adobe_rgb.png" />
+         *     <figcaption style="text-align: center;">Adobe RGB (orange) vs sRGB (white)</figcaption>
+         * </p>
+         */
+        ADOBE_RGB,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space ProPhoto RGB standardized as ROMM RGB ISO 22028-2:2013.</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.7347</td><td>0.1596</td><td>0.0366</td><td>0.3457</td></tr>
+         *     <tr><td>y</td><td>0.2653</td><td>0.8404</td><td>0.0001</td><td>0.3585</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">ROMM RGB ISO 22028-2:2013</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D50</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function (OETF)</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{ROMM} = \begin{cases} 16 \times C_{linear} & C_{linear} \lt 0.001953 \\
+         *             C_{linear}^{\frac{1}{1.8}} & C_{linear} \ge 0.001953 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function (EOTF)</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{linear} = \begin{cases}\frac{C_{ROMM}}{16} & C_{ROMM} \lt 0.031248 \\
+         *             C_{ROMM}^{1.8} & C_{ROMM} \ge 0.031248 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
+         * </table>
+         * <p>
+         *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_pro_photo_rgb.png" />
+         *     <figcaption style="text-align: center;">ProPhoto RGB (orange) vs sRGB (white)</figcaption>
+         * </p>
+         */
+        PRO_PHOTO_RGB,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space ACES standardized as SMPTE ST 2065-1:2012.</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.73470</td><td>0.00000</td><td>0.00010</td><td>0.32168</td></tr>
+         *     <tr><td>y</td><td>0.26530</td><td>1.00000</td><td>-0.07700</td><td>0.33767</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">SMPTE ST 2065-1:2012 ACES</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D60</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function (OETF)</td>
+         *         <td colspan="4">\(C_{ACES} = C_{linear}\)</td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function (EOTF)</td>
+         *         <td colspan="4">\(C_{linear} = C_{ACES}\)</td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([-65504.0, 65504.0]\)</td></tr>
+         * </table>
+         * <p>
+         *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_aces.png" />
+         *     <figcaption style="text-align: center;">ACES (orange) vs sRGB (white)</figcaption>
+         * </p>
+         */
+        ACES,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space ACEScg standardized as Academy S-2014-004.</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.713</td><td>0.165</td><td>0.128</td><td>0.32168</td></tr>
+         *     <tr><td>y</td><td>0.293</td><td>0.830</td><td>0.044</td><td>0.33767</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">Academy S-2014-004 ACEScg</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D60</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function (OETF)</td>
+         *         <td colspan="4">\(C_{ACEScg} = C_{linear}\)</td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function (EOTF)</td>
+         *         <td colspan="4">\(C_{linear} = C_{ACEScg}\)</td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([-65504.0, 65504.0]\)</td></tr>
+         * </table>
+         * <p>
+         *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_acescg.png" />
+         *     <figcaption style="text-align: center;">ACEScg (orange) vs sRGB (white)</figcaption>
+         * </p>
+         */
+        ACESCG,
+        /**
+         * <p>{@link Model#XYZ XYZ} color space CIE XYZ. This color space assumes standard
+         * illuminant D50 as its white point.</p>
+         * <table summary="Color space definition">
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">Generic XYZ</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D50</td></tr>
+         *     <tr><td>Range</td><td colspan="4">\([-2.0, 2.0]\)</td></tr>
+         * </table>
+         */
+        CIE_XYZ,
+        /**
+         * <p>{@link Model#LAB Lab} color space CIE L*a*b*. This color space uses CIE XYZ D50
+         * as a profile conversion space.</p>
+         * <table summary="Color space definition">
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">Generic L*a*b*</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D50</td></tr>
+         *     <tr><td>Range</td><td colspan="4">\(L: [0.0, 100.0], a: [-128, 128], b: [-128, 128]\)</td></tr>
+         * </table>
+         */
+        CIE_LAB
+        // Update the initialization block next to #get(Named) when adding new values
+    }
+
+    /**
+     * <p>A render intent determines how a {@link ColorSpace.Connector connector}
+     * maps colors from one color space to another. The choice of mapping is
+     * important when the source color space has a larger color gamut than the
+     * destination color space.</p>
+     *
+     * @see ColorSpace#connect(ColorSpace, ColorSpace, RenderIntent)
+     */
+    public enum RenderIntent {
+        /**
+         * <p>Compresses the source gamut into the destination gamut.
+         * This render intent affects all colors, inside and outside
+         * of destination gamut. The goal of this render intent is
+         * to preserve the visual relationship between colors.</p>
+         *
+         * <p class="note">This render intent is currently not
+         * implemented and behaves like {@link #RELATIVE}.</p>
+         */
+        PERCEPTUAL,
+        /**
+         * Similar to the {@link #ABSOLUTE} render intent, this render
+         * intent matches the closest color in the destination gamut
+         * but makes adjustments for the destination white point.
+         */
+        RELATIVE,
+        /**
+         * <p>Attempts to maintain the relative saturation of colors
+         * from the source gamut to the destination gamut, to keep
+         * highly saturated colors as saturated as possible.</p>
+         *
+         * <p class="note">This render intent is currently not
+         * implemented and behaves like {@link #RELATIVE}.</p>
+         */
+        SATURATION,
+        /**
+         * Colors that are in the destination gamut are left unchanged.
+         * Colors that fall outside of the destination gamut are mapped
+         * to the closest possible color within the gamut of the destination
+         * color space (they are clipped).
+         */
+        ABSOLUTE
+    }
+
+    /**
+     * {@usesMathJax}
+     *
+     * <p>List of adaptation matrices that can be used for chromatic adaptation
+     * using the von Kries transform. These matrices are used to convert values
+     * in the CIE XYZ space to values in the LMS space (Long Medium Short).</p>
+     *
+     * <p>Given an adaptation matrix \(A\), the conversion from XYZ to
+     * LMS is straightforward:</p>
+     *
+     * $$\left[ \begin{array}{c} L\\ M\\ S \end{array} \right] =
+     * A \left[ \begin{array}{c} X\\ Y\\ Z \end{array} \right]$$
+     *
+     * <p>The complete von Kries transform \(T\) uses a diagonal matrix
+     * noted \(D\) to perform the adaptation in LMS space. In addition
+     * to \(A\) and \(D\), the source white point \(W1\) and the destination
+     * white point \(W2\) must be specified:</p>
+     *
+     * $$\begin{align*}
+     * \left[ \begin{array}{c} L_1\\ M_1\\ S_1 \end{array} \right] &=
+     *      A \left[ \begin{array}{c} W1_X\\ W1_Y\\ W1_Z \end{array} \right] \\
+     * \left[ \begin{array}{c} L_2\\ M_2\\ S_2 \end{array} \right] &=
+     *      A \left[ \begin{array}{c} W2_X\\ W2_Y\\ W2_Z \end{array} \right] \\
+     * D &= \left[ \begin{matrix} \frac{L_2}{L_1} & 0 & 0 \\
+     *      0 & \frac{M_2}{M_1} & 0 \\
+     *      0 & 0 & \frac{S_2}{S_1} \end{matrix} \right] \\
+     * T &= A^{-1}.D.A
+     * \end{align*}$$
+     *
+     * <p>As an example, the resulting matrix \(T\) can then be used to
+     * perform the chromatic adaptation of sRGB XYZ transform from D65
+     * to D50:</p>
+     *
+     * $$sRGB_{D50} = T.sRGB_{D65}$$
+     *
+     * @see ColorSpace.Connector
+     * @see ColorSpace#connect(ColorSpace, ColorSpace)
+     */
+    public enum Adaptation {
+        /**
+         * Bradford chromatic adaptation transform, as defined in the
+         * CIECAM97s color appearance model.
+         */
+        BRADFORD(new float[] {
+                 0.8951f, -0.7502f,  0.0389f,
+                 0.2664f,  1.7135f, -0.0685f,
+                -0.1614f,  0.0367f,  1.0296f
+        }),
+        /**
+         * von Kries chromatic adaptation transform.
+         */
+        VON_KRIES(new float[] {
+                 0.40024f, -0.22630f, 0.00000f,
+                 0.70760f,  1.16532f, 0.00000f,
+                -0.08081f,  0.04570f, 0.91822f
+        }),
+        /**
+         * CIECAT02 chromatic adaption transform, as defined in the
+         * CIECAM02 color appearance model.
+         */
+        CIECAT02(new float[] {
+                 0.7328f, -0.7036f,  0.0030f,
+                 0.4296f,  1.6975f,  0.0136f,
+                -0.1624f,  0.0061f,  0.9834f
+        });
+
+        final float[] mTransform;
+
+        Adaptation(@NonNull @Size(9) float[] transform) {
+            mTransform = transform;
+        }
+    }
+
+    /**
+     * A color model is required by a {@link ColorSpace} to describe the
+     * way colors can be represented as tuples of numbers. A common color
+     * model is the {@link #RGB RGB} color model which defines a color
+     * as represented by a tuple of 3 numbers (red, green and blue).
+     */
+    public enum Model {
+        /**
+         * The RGB model is a color model with 3 components that
+         * refer to the three additive primiaries: red, green
+         * andd blue.
+         */
+        RGB(3),
+        /**
+         * The XYZ model is a color model with 3 components that
+         * are used to model human color vision on a basic sensory
+         * level.
+         */
+        XYZ(3),
+        /**
+         * The Lab model is a color model with 3 components used
+         * to describe a color space that is more perceptually
+         * uniform than XYZ.
+         */
+        LAB(3),
+        /**
+         * The CMYK model is a color model with 4 components that
+         * refer to four inks used in color printing: cyan, magenta,
+         * yellow and black (or key). CMYK is a subtractive color
+         * model.
+         */
+        CMYK(4);
+
+        private final int mComponentCount;
+
+        Model(@IntRange(from = 1, to = 4) int componentCount) {
+            mComponentCount = componentCount;
+        }
+
+        /**
+         * Returns the number of components for this color model.
+         *
+         * @return An integer between 1 and 4
+         */
+        @IntRange(from = 1, to = 4)
+        public int getComponentCount() {
+            return mComponentCount;
+        }
+    }
+
+    private ColorSpace(
+            @NonNull String name,
+            @NonNull Model model,
+            @IntRange(from = MIN_ID, to = MAX_ID) int id) {
+
+        if (name == null || name.length() < 1) {
+            throw new IllegalArgumentException("The name of a color space cannot be null and " +
+                    "must contain at least 1 character");
+        }
+
+        if (model == null) {
+            throw new IllegalArgumentException("A color space must have a model");
+        }
+
+        if (id < MIN_ID || id > MAX_ID) {
+            throw new IllegalArgumentException("The id must be between " +
+                    MIN_ID + " and " + MAX_ID);
+        }
+
+        mName = name;
+        mModel = model;
+        mId = id;
+    }
+
+    /**
+     * <p>Returns the name of this color space. The name is never null
+     * and contains always at least 1 character.</p>
+     *
+     * <p>Color space names are recommended to be unique but are not
+     * guaranteed to be. There is no defined format but the name usually
+     * falls in one of the following categories:</p>
+     * <ul>
+     *     <li>Generic names used to identify color spaces in non-RGB
+     *     color models. For instance: {@link Named#CIE_LAB Generic L*a*b*}.</li>
+     *     <li>Names tied to a particular specification. For instance:
+     *     {@link Named#SRGB sRGB IEC61966-2.1} or
+     *     {@link Named#ACES SMPTE ST 2065-1:2012 ACES}.</li>
+     *     <li>Ad-hoc names, often generated procedurally or by the user
+     *     during a calibration workflow. These names often contain the
+     *     make and model of the display.</li>
+     * </ul>
+     *
+     * <p>Because the format of color space names is not defined, it is
+     * not recommended to programmatically identify a color space by its
+     * name alone. Names can be used as a first approximation.</p>
+     *
+     * <p>It is however perfectly acceptable to display color space names to
+     * users in a UI, or in debuggers and logs. When displaying a color space
+     * name to the user, it is recommended to add extra information to avoid
+     * ambiguities: color model, a representation of the color space's gamut,
+     * white point, etc.</p>
+     *
+     * @return A non-null String of length >= 1
+     */
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * Returns the ID of this color space. Positive IDs match the color
+     * spaces enumerated in {@link Named}. A negative ID indicates a
+     * color space created by calling one of the public constructors.
+     *
+     * @return An integer between {@link #MIN_ID} and {@link #MAX_ID}
+     */
+    @IntRange(from = MIN_ID, to = MAX_ID)
+    public int getId() {
+        return mId;
+    }
+
+    /**
+     * Return the color model of this color space.
+     *
+     * @return A non-null {@link Model}
+     *
+     * @see Model
+     * @see #getComponentCount()
+     */
+    @NonNull
+    public Model getModel() {
+        return mModel;
+    }
+
+    /**
+     * Returns the number of components that form a color value according
+     * to this color space's color model.
+     *
+     * @return An integer between 1 and 4
+     *
+     * @see Model
+     * @see #getModel()
+     */
+    @IntRange(from = 1, to = 4)
+    public int getComponentCount() {
+        return mModel.getComponentCount();
+    }
+
+    /**
+     * Returns whether this color space is a wide-gamut color space.
+     * An RGB color space is wide-gamut if its gamut entirely contains
+     * the {@link Named#SRGB sRGB} gamut and if the area of its gamut is
+     * 90% of greater than the area of the {@link Named#NTSC_1953 NTSC}
+     * gamut.
+     *
+     * @return True if this color space is a wide-gamut color space,
+     *         false otherwise
+     */
+    public abstract boolean isWideGamut();
+
+    /**
+     * <p>Indicates whether this color space is the sRGB color space or
+     * equivalent to the sRGB color space.</p>
+     * <p>A color space is considered sRGB if it meets all the following
+     * conditions:</p>
+     * <ul>
+     *     <li>Its color model is {@link Model#RGB}.</li>
+     *     <li>
+     *         Its primaries are within 1e-3 of the true
+     *         {@link Named#SRGB sRGB} primaries.
+     *     </li>
+     *     <li>
+     *         Its white point is withing 1e-3 of the CIE standard
+     *         illuminant {@link #ILLUMINANT_D65 D65}.
+     *     </li>
+     *     <li>Its opto-electronic transfer function is not linear.</li>
+     *     <li>Its electro-optical transfer function is not linear.</li>
+     *     <li>Its range is \([0..1]\).</li>
+     * </ul>
+     * <p>This method always returns true for {@link Named#SRGB}.</p>
+     *
+     * @return True if this color space is the sRGB color space (or a
+     *         close approximation), false otherwise
+     */
+    public boolean isSrgb() {
+        return false;
+    }
+
+    /**
+     * Returns the minimum valid value for the specified component of this
+     * color space's color model.
+     *
+     * @param component The index of the component
+     * @return A floating point value less than {@link #getMaxValue(int)}
+     *
+     * @see #getMaxValue(int)
+     * @see Model#getComponentCount()
+     */
+    public abstract float getMinValue(@IntRange(from = 0, to = 3) int component);
+
+    /**
+     * Returns the maximum valid value for the specified component of this
+     * color space's color model.
+     *
+     * @param component The index of the component
+     * @return A floating point value greater than {@link #getMinValue(int)}
+     *
+     * @see #getMinValue(int)
+     * @see Model#getComponentCount()
+     */
+    public abstract float getMaxValue(@IntRange(from = 0, to = 3) int component);
+
+    /**
+     * <p>Converts a color value from this color space's model to
+     * tristimulus CIE XYZ values. If the color model of this color
+     * space is not {@link Model#RGB RGB}, it is assumed that the
+     * target CIE XYZ space uses a {@link #ILLUMINANT_D50 D50}
+     * standard illuminant.</p>
+     *
+     * <p>This method is a convenience for color spaces with a model
+     * of 3 components ({@link Model#RGB RGB} or {@link Model#LAB}
+     * for instance). With color spaces using fewer or more components,
+     * use {@link #toXyz(float[])} instead</p>.
+     *
+     * @param r The first component of the value to convert from (typically R in RGB)
+     * @param g The second component of the value to convert from (typically G in RGB)
+     * @param b The third component of the value to convert from (typically B in RGB)
+     * @return A new array of 3 floats, containing tristimulus XYZ values
+     *
+     * @see #toXyz(float[])
+     * @see #fromXyz(float, float, float)
+     */
+    @NonNull
+    @Size(3)
+    public float[] toXyz(float r, float g, float b) {
+        return toXyz(new float[] { r, g, b });
+    }
+
+    /**
+     * <p>Converts a color value from this color space's model to
+     * tristimulus CIE XYZ values. If the color model of this color
+     * space is not {@link Model#RGB RGB}, it is assumed that the
+     * target CIE XYZ space uses a {@link #ILLUMINANT_D50 D50}
+     * standard illuminant.</p>
+     *
+     * <p class="note">The specified array's length  must be at least
+     * equal to to the number of color components as returned by
+     * {@link Model#getComponentCount()}.</p>
+     *
+     * @param v An array of color components containing the color space's
+     *          color value to convert to XYZ, and large enough to hold
+     *          the resulting tristimulus XYZ values
+     * @return The array passed in parameter
+     *
+     * @see #toXyz(float, float, float)
+     * @see #fromXyz(float[])
+     */
+    @NonNull
+    @Size(min = 3)
+    public abstract float[] toXyz(@NonNull @Size(min = 3) float[] v);
+
+    /**
+     * <p>Converts tristimulus values from the CIE XYZ space to this
+     * color space's color model.</p>
+     *
+     * @param x The X component of the color value
+     * @param y The Y component of the color value
+     * @param z The Z component of the color value
+     * @return A new array whose size is equal to the number of color
+     *         components as returned by {@link Model#getComponentCount()}
+     *
+     * @see #fromXyz(float[])
+     * @see #toXyz(float, float, float)
+     */
+    @NonNull
+    @Size(min = 3)
+    public float[] fromXyz(float x, float y, float z) {
+        float[] xyz = new float[mModel.getComponentCount()];
+        xyz[0] = x;
+        xyz[1] = y;
+        xyz[2] = z;
+        return fromXyz(xyz);
+    }
+
+    /**
+     * <p>Converts tristimulus values from the CIE XYZ space to this color
+     * space's color model. The resulting value is passed back in the specified
+     * array.</p>
+     *
+     * <p class="note">The specified array's length  must be at least equal to
+     * to the number of color components as returned by
+     * {@link Model#getComponentCount()}, and its first 3 values must
+     * be the XYZ components to convert from.</p>
+     *
+     * @param v An array of color components containing the XYZ values
+     *          to convert from, and large enough to hold the number
+     *          of components of this color space's model
+     * @return The array passed in parameter
+     *
+     * @see #fromXyz(float, float, float)
+     * @see #toXyz(float[])
+     */
+    @NonNull
+    @Size(min = 3)
+    public abstract float[] fromXyz(@NonNull @Size(min = 3) float[] v);
+
+    /**
+     * <p>Returns a string representation of the object. This method returns
+     * a string equal to the value of:</p>
+     *
+     * <pre class="prettyprint">
+     * getName() + "(id=" + getId() + ", model=" + getModel() + ")"
+     * </pre>
+     *
+     * <p>For instance, the string representation of the {@link Named#SRGB sRGB}
+     * color space is equal to the following value:</p>
+     *
+     * <pre>
+     * sRGB IEC61966-2.1 (id=0, model=RGB)
+     * </pre>
+     *
+     * @return A string representation of the object
+     */
+    @Override
+    @NonNull
+    public String toString() {
+        return mName + " (id=" + mId + ", model=" + mModel + ")";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        ColorSpace that = (ColorSpace) o;
+
+        if (mId != that.mId) return false;
+        //noinspection SimplifiableIfStatement
+        if (!mName.equals(that.mName)) return false;
+        return mModel == that.mModel;
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mName.hashCode();
+        result = 31 * result + mModel.hashCode();
+        result = 31 * result + mId;
+        return result;
+    }
+
+    /**
+     * <p>Connects two color spaces to allow conversion from the source color
+     * space to the destination color space. If the source and destination
+     * color spaces do not have the same profile connection space (CIE XYZ
+     * with the same white point), they are chromatically adapted to use the
+     * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p>
+     *
+     * <p>If the source and destination are the same, an optimized connector
+     * is returned to avoid unnecessary computations and loss of precision.</p>
+     *
+     * <p>Colors are mapped from the source color space to the destination color
+     * space using the {@link RenderIntent#PERCEPTUAL perceptual} render intent.</p>
+     *
+     * @param source The color space to convert colors from
+     * @param destination The color space to convert colors to
+     * @return A non-null connector between the two specified color spaces
+     *
+     * @see #connect(ColorSpace)
+     * @see #connect(ColorSpace, RenderIntent)
+     * @see #connect(ColorSpace, ColorSpace, RenderIntent)
+     */
+    @NonNull
+    public static Connector connect(@NonNull ColorSpace source, @NonNull ColorSpace destination) {
+        return connect(source, destination, RenderIntent.PERCEPTUAL);
+    }
+
+    /**
+     * <p>Connects two color spaces to allow conversion from the source color
+     * space to the destination color space. If the source and destination
+     * color spaces do not have the same profile connection space (CIE XYZ
+     * with the same white point), they are chromatically adapted to use the
+     * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p>
+     *
+     * <p>If the source and destination are the same, an optimized connector
+     * is returned to avoid unnecessary computations and loss of precision.</p>
+     *
+     * @param source The color space to convert colors from
+     * @param destination The color space to convert colors to
+     * @param intent The render intent to map colors from the source to the destination
+     * @return A non-null connector between the two specified color spaces
+     *
+     * @see #connect(ColorSpace)
+     * @see #connect(ColorSpace, RenderIntent)
+     * @see #connect(ColorSpace, ColorSpace)
+     */
+    @NonNull
+    @SuppressWarnings("ConstantConditions")
+    public static Connector connect(@NonNull ColorSpace source, @NonNull ColorSpace destination,
+            @NonNull RenderIntent intent) {
+        if (source.equals(destination)) return Connector.identity(source);
+
+        if (source.getModel() == Model.RGB && destination.getModel() == Model.RGB) {
+            return new Connector.Rgb((Rgb) source, (Rgb) destination, intent);
+        }
+
+        return new Connector(source, destination, intent);
+    }
+
+    /**
+     * <p>Connects the specified color spaces to sRGB.
+     * If the source color space does not use CIE XYZ D65 as its profile
+     * connection space, the two spaces are chromatically adapted to use the
+     * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p>
+     *
+     * <p>If the source is the sRGB color space, an optimized connector
+     * is returned to avoid unnecessary computations and loss of precision.</p>
+     *
+     * <p>Colors are mapped from the source color space to the destination color
+     * space using the {@link RenderIntent#PERCEPTUAL perceptual} render intent.</p>
+     *
+     * @param source The color space to convert colors from
+     * @return A non-null connector between the specified color space and sRGB
+     *
+     * @see #connect(ColorSpace, RenderIntent)
+     * @see #connect(ColorSpace, ColorSpace)
+     * @see #connect(ColorSpace, ColorSpace, RenderIntent)
+     */
+    @NonNull
+    public static Connector connect(@NonNull ColorSpace source) {
+        return connect(source, RenderIntent.PERCEPTUAL);
+    }
+
+    /**
+     * <p>Connects the specified color spaces to sRGB.
+     * If the source color space does not use CIE XYZ D65 as its profile
+     * connection space, the two spaces are chromatically adapted to use the
+     * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p>
+     *
+     * <p>If the source is the sRGB color space, an optimized connector
+     * is returned to avoid unnecessary computations and loss of precision.</p>
+     *
+     * @param source The color space to convert colors from
+     * @param intent The render intent to map colors from the source to the destination
+     * @return A non-null connector between the specified color space and sRGB
+     *
+     * @see #connect(ColorSpace)
+     * @see #connect(ColorSpace, ColorSpace)
+     * @see #connect(ColorSpace, ColorSpace, RenderIntent)
+     */
+    @NonNull
+    public static Connector connect(@NonNull ColorSpace source, @NonNull RenderIntent intent) {
+        if (source.isSrgb()) return Connector.identity(source);
+
+        if (source.getModel() == Model.RGB) {
+            return new Connector.Rgb((Rgb) source, (Rgb) get(Named.SRGB), intent);
+        }
+
+        return new Connector(source, get(Named.SRGB), intent);
+    }
+
+    /**
+     * <p>Performs the chromatic adaptation of a color space from its native
+     * white point to the specified white point.</p>
+     *
+     * <p>The chromatic adaptation is performed using the
+     * {@link Adaptation#BRADFORD} matrix.</p>
+     *
+     * <p class="note">The color space returned by this method always has
+     * an ID of {@link #MIN_ID}.</p>
+     *
+     * @param colorSpace The color space to chromatically adapt
+     * @param whitePoint The new white point
+     * @return A {@link ColorSpace} instance with the same name, primaries,
+     *         transfer functions and range as the specified color space
+     *
+     * @see Adaptation
+     * @see #adapt(ColorSpace, float[], Adaptation)
+     */
+    @NonNull
+    public static ColorSpace adapt(@NonNull ColorSpace colorSpace,
+            @NonNull @Size(min = 2, max = 3) float[] whitePoint) {
+        return adapt(colorSpace, whitePoint, Adaptation.BRADFORD);
+    }
+
+    /**
+     * <p>Performs the chromatic adaptation of a color space from its native
+     * white point to the specified white point. If the specified color space
+     * does not have an {@link Model#RGB RGB} color model, or if the color
+     * space already has the target white point, the color space is returned
+     * unmodified.</p>
+     *
+     * <p>The chromatic adaptation is performed using the von Kries method
+     * described in the documentation of {@link Adaptation}.</p>
+     *
+     * <p class="note">The color space returned by this method always has
+     * an ID of {@link #MIN_ID}.</p>
+     *
+     * @param colorSpace The color space to chromatically adapt
+     * @param whitePoint The new white point
+     * @param adaptation The adaptation matrix
+     * @return A new color space if the specified color space has an RGB
+     *         model and a white point different from the specified white
+     *         point; the specified color space otherwise
+     *
+     * @see Adaptation
+     * @see #adapt(ColorSpace, float[])
+     */
+    @NonNull
+    public static ColorSpace adapt(@NonNull ColorSpace colorSpace,
+            @NonNull @Size(min = 2, max = 3) float[] whitePoint,
+            @NonNull Adaptation adaptation) {
+        if (colorSpace.getModel() == Model.RGB) {
+            ColorSpace.Rgb rgb = (ColorSpace.Rgb) colorSpace;
+            if (compare(rgb.mWhitePoint, whitePoint)) return colorSpace;
+
+            float[] xyz = whitePoint.length == 3 ?
+                    Arrays.copyOf(whitePoint, 3) : xyYToXyz(whitePoint);
+            float[] adaptationTransform = chromaticAdaptation(adaptation.mTransform,
+                    xyYToXyz(rgb.getWhitePoint()), xyz);
+            float[] transform = mul3x3(adaptationTransform, rgb.mTransform);
+
+            return new ColorSpace.Rgb(rgb, transform, whitePoint);
+        }
+        return colorSpace;
+    }
+
+    /**
+     * <p>Returns an instance of {@link ColorSpace} whose ID matches the
+     * specified ID.</p>
+     *
+     * <p>This method always returns the same instance for a given ID.</p>
+     *
+     * <p>This method is thread-safe.</p>
+     *
+     * @param index An integer ID between {@link #MIN_ID} and {@link #MAX_ID}
+     * @return A non-null {@link ColorSpace} instance
+     * @throws IllegalArgumentException If the ID does not match the ID of one of the
+     *         {@link Named named color spaces}
+     */
+    @NonNull
+    static ColorSpace get(@IntRange(from = MIN_ID, to = MAX_ID) int index) {
+        if (index < 0 || index > Named.values().length) {
+            throw new IllegalArgumentException("Invalid ID, must be in the range [0.." +
+                    Named.values().length + "]");
+        }
+        return sNamedColorSpaces[index];
+    }
+
+    /**
+     * <p>Returns an instance of {@link ColorSpace} identified by the specified
+     * name. The list of names provided in the {@link Named} enum gives access
+     * to a variety of common RGB color spaces.</p>
+     *
+     * <p>This method always returns the same instance for a given name.</p>
+     *
+     * <p>This method is thread-safe.</p>
+     *
+     * @param name The name of the color space to get an instance of
+     * @return A non-null {@link ColorSpace} instance
+     */
+    @NonNull
+    public static ColorSpace get(@NonNull Named name) {
+        return sNamedColorSpaces[name.ordinal()];
+    }
+
+    /**
+     * <p>Returns a {@link Named} instance of {@link ColorSpace} that matches
+     * the specified RGB to CIE XYZ transform and transfer functions. If no
+     * instance can be found, this method returns null.</p>
+     *
+     * <p>The color transform matrix is assumed to target the CIE XYZ space
+     * a {@link #ILLUMINANT_D50 D50} standard illuminant.</p>
+     *
+     * @param toXYZD50 3x3 column-major transform matrix from RGB to the profile
+     *                 connection space CIE XYZ as an array of 9 floats, cannot be null
+     * @param function Parameters for the transfer functions
+     * @return A non-null {@link ColorSpace} if a match is found, null otherwise
+     */
+    @Nullable
+    public static ColorSpace match(
+            @NonNull @Size(9) float[] toXYZD50,
+            @NonNull Rgb.TransferParameters function) {
+
+        for (ColorSpace colorSpace : sNamedColorSpaces) {
+            if (colorSpace.getModel() == Model.RGB) {
+                ColorSpace.Rgb rgb = (ColorSpace.Rgb) adapt(colorSpace, ILLUMINANT_D50_XYZ);
+                if (compare(toXYZD50, rgb.mTransform) &&
+                        compare(function, rgb.mTransferParameters)) {
+                    return colorSpace;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * <p>Creates a new {@link Renderer} that can be used to visualize and
+     * debug color spaces. See the documentation of {@link Renderer} for
+     * more information.</p>
+     *
+     * @return A new non-null {@link Renderer} instance
+     *
+     * @see Renderer
+     *
+     * @hide
+     */
+    @NonNull
+    public static Renderer createRenderer() {
+        return new Renderer();
+    }
+
+    static {
+        sNamedColorSpaces[Named.SRGB.ordinal()] = new ColorSpace.Rgb(
+                "sRGB IEC61966-2.1",
+                SRGB_PRIMARIES,
+                ILLUMINANT_D65,
+                new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4),
+                Named.SRGB.ordinal()
+        );
+        sNamedColorSpaces[Named.LINEAR_SRGB.ordinal()] = new ColorSpace.Rgb(
+                "sRGB IEC61966-2.1 (Linear)",
+                SRGB_PRIMARIES,
+                ILLUMINANT_D65,
+                1.0,
+                0.0f, 1.0f,
+                Named.LINEAR_SRGB.ordinal()
+        );
+        sNamedColorSpaces[Named.EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb(
+                "scRGB-nl IEC 61966-2-2:2003",
+                SRGB_PRIMARIES,
+                ILLUMINANT_D65,
+                x -> absRcpResponse(x, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4),
+                x -> absResponse(x, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4),
+                -0.799f, 2.399f,
+                Named.EXTENDED_SRGB.ordinal()
+        );
+        sNamedColorSpaces[Named.LINEAR_EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb(
+                "scRGB IEC 61966-2-2:2003",
+                SRGB_PRIMARIES,
+                ILLUMINANT_D65,
+                1.0,
+                -0.5f, 7.499f,
+                Named.LINEAR_EXTENDED_SRGB.ordinal()
+        );
+        sNamedColorSpaces[Named.BT709.ordinal()] = new ColorSpace.Rgb(
+                "Rec. ITU-R BT.709-5",
+                new float[] { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f },
+                ILLUMINANT_D65,
+                new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45),
+                Named.BT709.ordinal()
+        );
+        sNamedColorSpaces[Named.BT2020.ordinal()] = new ColorSpace.Rgb(
+                "Rec. ITU-R BT.2020-1",
+                new float[] { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f },
+                ILLUMINANT_D65,
+                new Rgb.TransferParameters(1 / 1.0993, 0.0993 / 1.0993, 1 / 4.5, 0.08145, 1 / 0.45),
+                Named.BT2020.ordinal()
+        );
+        sNamedColorSpaces[Named.DCI_P3.ordinal()] = new ColorSpace.Rgb(
+                "SMPTE RP 431-2-2007 DCI (P3)",
+                new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
+                new float[] { 0.314f, 0.351f },
+                2.6,
+                0.0f, 1.0f,
+                Named.DCI_P3.ordinal()
+        );
+        sNamedColorSpaces[Named.DISPLAY_P3.ordinal()] = new ColorSpace.Rgb(
+                "Display P3",
+                new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
+                ILLUMINANT_D65,
+                new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.039, 2.4),
+                Named.DISPLAY_P3.ordinal()
+        );
+        sNamedColorSpaces[Named.NTSC_1953.ordinal()] = new ColorSpace.Rgb(
+                "NTSC (1953)",
+                NTSC_1953_PRIMARIES,
+                ILLUMINANT_C,
+                new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45),
+                Named.NTSC_1953.ordinal()
+        );
+        sNamedColorSpaces[Named.SMPTE_C.ordinal()] = new ColorSpace.Rgb(
+                "SMPTE-C RGB",
+                new float[] { 0.630f, 0.340f, 0.310f, 0.595f, 0.155f, 0.070f },
+                ILLUMINANT_D65,
+                new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45),
+                Named.SMPTE_C.ordinal()
+        );
+        sNamedColorSpaces[Named.ADOBE_RGB.ordinal()] = new ColorSpace.Rgb(
+                "Adobe RGB (1998)",
+                new float[] { 0.64f, 0.33f, 0.21f, 0.71f, 0.15f, 0.06f },
+                ILLUMINANT_D65,
+                2.2,
+                0.0f, 1.0f,
+                Named.ADOBE_RGB.ordinal()
+        );
+        sNamedColorSpaces[Named.PRO_PHOTO_RGB.ordinal()] = new ColorSpace.Rgb(
+                "ROMM RGB ISO 22028-2:2013",
+                new float[] { 0.7347f, 0.2653f, 0.1596f, 0.8404f, 0.0366f, 0.0001f },
+                ILLUMINANT_D50,
+                new Rgb.TransferParameters(1.0, 0.0, 1 / 16.0, 0.031248, 1.8),
+                Named.PRO_PHOTO_RGB.ordinal()
+        );
+        sNamedColorSpaces[Named.ACES.ordinal()] = new ColorSpace.Rgb(
+                "SMPTE ST 2065-1:2012 ACES",
+                new float[] { 0.73470f, 0.26530f, 0.0f, 1.0f, 0.00010f, -0.0770f },
+                ILLUMINANT_D60,
+                1.0,
+                -65504.0f, 65504.0f,
+                Named.ACES.ordinal()
+        );
+        sNamedColorSpaces[Named.ACESCG.ordinal()] = new ColorSpace.Rgb(
+                "Academy S-2014-004 ACEScg",
+                new float[] { 0.713f, 0.293f, 0.165f, 0.830f, 0.128f, 0.044f },
+                ILLUMINANT_D60,
+                1.0,
+                -65504.0f, 65504.0f,
+                Named.ACESCG.ordinal()
+        );
+        sNamedColorSpaces[Named.CIE_XYZ.ordinal()] = new Xyz(
+                "Generic XYZ",
+                Named.CIE_XYZ.ordinal()
+        );
+        sNamedColorSpaces[Named.CIE_LAB.ordinal()] = new ColorSpace.Lab(
+                "Generic L*a*b*",
+                Named.CIE_LAB.ordinal()
+        );
+    }
+
+    // Reciprocal piecewise gamma response
+    private static double rcpResponse(double x, double a, double b, double c, double d, double g) {
+        return x >= d * c ? (Math.pow(x, 1.0 / g) - b) / a : x / c;
+    }
+
+    // Piecewise gamma response
+    private static double response(double x, double a, double b, double c, double d, double g) {
+        return x >= d ? Math.pow(a * x + b, g) : c * x;
+    }
+
+    // Reciprocal piecewise gamma response
+    private static double rcpResponse(double x, double a, double b, double c, double d,
+            double e, double f, double g) {
+        return x >= d * c ? (Math.pow(x - e, 1.0 / g) - b) / a : (x - f) / c;
+    }
+
+    // Piecewise gamma response
+    private static double response(double x, double a, double b, double c, double d,
+            double e, double f, double g) {
+        return x >= d ? Math.pow(a * x + b, g) + e : c * x + f;
+    }
+
+    // Reciprocal piecewise gamma response, encoded as sign(x).f(abs(x)) for color
+    // spaces that allow negative values
+    @SuppressWarnings("SameParameterValue")
+    private static double absRcpResponse(double x, double a, double b, double c, double d, double g) {
+        return Math.copySign(rcpResponse(x < 0.0 ? -x : x, a, b, c, d, g), x);
+    }
+
+    // Piecewise gamma response, encoded as sign(x).f(abs(x)) for color spaces that
+    // allow negative values
+    @SuppressWarnings("SameParameterValue")
+    private static double absResponse(double x, double a, double b, double c, double d, double g) {
+        return Math.copySign(response(x < 0.0 ? -x : x, a, b, c, d, g), x);
+    }
+
+    /**
+     * Compares two sets of parametric transfer functions parameters with a precision of 1e-3.
+     *
+     * @param a The first set of parameters to compare
+     * @param b The second set of parameters to compare
+     * @return True if the two sets are equal, false otherwise
+     */
+    private static boolean compare(
+            @Nullable Rgb.TransferParameters a,
+            @Nullable Rgb.TransferParameters b) {
+        //noinspection SimplifiableIfStatement
+        if (a == null && b == null) return true;
+        return a != null && b != null &&
+                Math.abs(a.a - b.a) < 1e-3 &&
+                Math.abs(a.b - b.b) < 1e-3 &&
+                Math.abs(a.c - b.c) < 1e-3 &&
+                Math.abs(a.d - b.d) < 2e-3 && // Special case for variations in sRGB OETF/EOTF
+                Math.abs(a.e - b.e) < 1e-3 &&
+                Math.abs(a.f - b.f) < 1e-3 &&
+                Math.abs(a.g - b.g) < 1e-3;
+    }
+
+    /**
+     * Compares two arrays of float with a precision of 1e-3.
+     *
+     * @param a The first array to compare
+     * @param b The second array to compare
+     * @return True if the two arrays are equal, false otherwise
+     */
+    private static boolean compare(@NonNull float[] a, @NonNull float[] b) {
+        if (a == b) return true;
+        for (int i = 0; i < a.length; i++) {
+            if (Float.compare(a[i], b[i]) != 0 && Math.abs(a[i] - b[i]) > 1e-3f) return false;
+        }
+        return true;
+    }
+
+    /**
+     * Inverts a 3x3 matrix. This method assumes the matrix is invertible.
+     *
+     * @param m A 3x3 matrix as a non-null array of 9 floats
+     * @return A new array of 9 floats containing the inverse of the input matrix
+     */
+    @NonNull
+    @Size(9)
+    private static float[] inverse3x3(@NonNull @Size(9) float[] m) {
+        float a = m[0];
+        float b = m[3];
+        float c = m[6];
+        float d = m[1];
+        float e = m[4];
+        float f = m[7];
+        float g = m[2];
+        float h = m[5];
+        float i = m[8];
+
+        float A = e * i - f * h;
+        float B = f * g - d * i;
+        float C = d * h - e * g;
+
+        float det = a * A + b * B + c * C;
+
+        float inverted[] = new float[m.length];
+        inverted[0] = A / det;
+        inverted[1] = B / det;
+        inverted[2] = C / det;
+        inverted[3] = (c * h - b * i) / det;
+        inverted[4] = (a * i - c * g) / det;
+        inverted[5] = (b * g - a * h) / det;
+        inverted[6] = (b * f - c * e) / det;
+        inverted[7] = (c * d - a * f) / det;
+        inverted[8] = (a * e - b * d) / det;
+        return inverted;
+    }
+
+    /**
+     * Multiplies two 3x3 matrices, represented as non-null arrays of 9 floats.
+     *
+     * @param lhs 3x3 matrix, as a non-null array of 9 floats
+     * @param rhs 3x3 matrix, as a non-null array of 9 floats
+     * @return A new array of 9 floats containing the result of the multiplication
+     *         of rhs by lhs
+     */
+    @NonNull
+    @Size(9)
+    private static float[] mul3x3(@NonNull @Size(9) float[] lhs, @NonNull @Size(9) float[] rhs) {
+        float[] r = new float[9];
+        r[0] = lhs[0] * rhs[0] + lhs[3] * rhs[1] + lhs[6] * rhs[2];
+        r[1] = lhs[1] * rhs[0] + lhs[4] * rhs[1] + lhs[7] * rhs[2];
+        r[2] = lhs[2] * rhs[0] + lhs[5] * rhs[1] + lhs[8] * rhs[2];
+        r[3] = lhs[0] * rhs[3] + lhs[3] * rhs[4] + lhs[6] * rhs[5];
+        r[4] = lhs[1] * rhs[3] + lhs[4] * rhs[4] + lhs[7] * rhs[5];
+        r[5] = lhs[2] * rhs[3] + lhs[5] * rhs[4] + lhs[8] * rhs[5];
+        r[6] = lhs[0] * rhs[6] + lhs[3] * rhs[7] + lhs[6] * rhs[8];
+        r[7] = lhs[1] * rhs[6] + lhs[4] * rhs[7] + lhs[7] * rhs[8];
+        r[8] = lhs[2] * rhs[6] + lhs[5] * rhs[7] + lhs[8] * rhs[8];
+        return r;
+    }
+
+    /**
+     * Multiplies a vector of 3 components by a 3x3 matrix and stores the
+     * result in the input vector.
+     *
+     * @param lhs 3x3 matrix, as a non-null array of 9 floats
+     * @param rhs Vector of 3 components, as a non-null array of 3 floats
+     * @return The array of 3 passed as the rhs parameter
+     */
+    @NonNull
+    @Size(min = 3)
+    private static float[] mul3x3Float3(
+            @NonNull @Size(9) float[] lhs, @NonNull @Size(min = 3) float[] rhs) {
+        float r0 = rhs[0];
+        float r1 = rhs[1];
+        float r2 = rhs[2];
+        rhs[0] = lhs[0] * r0 + lhs[3] * r1 + lhs[6] * r2;
+        rhs[1] = lhs[1] * r0 + lhs[4] * r1 + lhs[7] * r2;
+        rhs[2] = lhs[2] * r0 + lhs[5] * r1 + lhs[8] * r2;
+        return rhs;
+    }
+
+    /**
+     * Multiplies a diagonal 3x3 matrix lhs, represented as an array of 3 floats,
+     * by a 3x3 matrix represented as an array of 9 floats.
+     *
+     * @param lhs Diagonal 3x3 matrix, as a non-null array of 3 floats
+     * @param rhs 3x3 matrix, as a non-null array of 9 floats
+     * @return A new array of 9 floats containing the result of the multiplication
+     *         of rhs by lhs
+     */
+    @NonNull
+    @Size(9)
+    private static float[] mul3x3Diag(
+            @NonNull @Size(3) float[] lhs, @NonNull @Size(9) float[] rhs) {
+        return new float[] {
+                lhs[0] * rhs[0], lhs[1] * rhs[1], lhs[2] * rhs[2],
+                lhs[0] * rhs[3], lhs[1] * rhs[4], lhs[2] * rhs[5],
+                lhs[0] * rhs[6], lhs[1] * rhs[7], lhs[2] * rhs[8]
+        };
+    }
+
+    /**
+     * Converts a value from CIE xyY to CIE XYZ. Y is assumed to be 1 so the
+     * input xyY array only contains the x and y components.
+     *
+     * @param xyY The xyY value to convert to XYZ, cannot be null, length must be 2
+     * @return A new float array of length 3 containing XYZ values
+     */
+    @NonNull
+    @Size(3)
+    private static float[] xyYToXyz(@NonNull @Size(2) float[] xyY) {
+        return new float[] { xyY[0] / xyY[1], 1.0f, (1 - xyY[0] - xyY[1]) / xyY[1] };
+    }
+
+    /**
+     * Converts values from CIE xyY to CIE L*u*v*. Y is assumed to be 1 so the
+     * input xyY array only contains the x and y components. After this method
+     * returns, the xyY array contains the converted u and v components.
+     *
+     * @param xyY The xyY value to convert to XYZ, cannot be null,
+     *            length must be a multiple of 2
+     */
+    private static void xyYToUv(@NonNull @Size(multiple = 2) float[] xyY) {
+        for (int i = 0; i < xyY.length; i += 2) {
+            float x = xyY[i];
+            float y = xyY[i + 1];
+
+            float d = -2.0f * x + 12.0f * y + 3;
+            float u = (4.0f * x) / d;
+            float v = (9.0f * y) / d;
+
+            xyY[i] = u;
+            xyY[i + 1] = v;
+        }
+    }
+
+    /**
+     * <p>Computes the chromatic adaptation transform from the specified
+     * source white point to the specified destination white point.</p>
+     *
+     * <p>The transform is computed using the von Kries method, described
+     * in more details in the documentation of {@link Adaptation}. The
+     * {@link Adaptation} enum provides different matrices that can be
+     * used to perform the adaptation.</p>
+     *
+     * @param matrix The adaptation matrix
+     * @param srcWhitePoint The white point to adapt from, *will be modified*
+     * @param dstWhitePoint The white point to adapt to, *will be modified*
+     * @return A 3x3 matrix as a non-null array of 9 floats
+     */
+    @NonNull
+    @Size(9)
+    private static float[] chromaticAdaptation(@NonNull @Size(9) float[] matrix,
+            @NonNull @Size(3) float[] srcWhitePoint, @NonNull @Size(3) float[] dstWhitePoint) {
+        float[] srcLMS = mul3x3Float3(matrix, srcWhitePoint);
+        float[] dstLMS = mul3x3Float3(matrix, dstWhitePoint);
+        // LMS is a diagonal matrix stored as a float[3]
+        float[] LMS = { dstLMS[0] / srcLMS[0], dstLMS[1] / srcLMS[1], dstLMS[2] / srcLMS[2] };
+        return mul3x3(inverse3x3(matrix), mul3x3Diag(LMS, matrix));
+    }
+
+    /**
+     * Implementation of the CIE XYZ color space. Assumes the white point is D50.
+     */
+    @AnyThread
+    private static final class Xyz extends ColorSpace {
+        private Xyz(@NonNull String name, @IntRange(from = MIN_ID, to = MAX_ID) int id) {
+            super(name, Model.XYZ, id);
+        }
+
+        @Override
+        public boolean isWideGamut() {
+            return true;
+        }
+
+        @Override
+        public float getMinValue(@IntRange(from = 0, to = 3) int component) {
+            return -2.0f;
+        }
+
+        @Override
+        public float getMaxValue(@IntRange(from = 0, to = 3) int component) {
+            return 2.0f;
+        }
+
+        @Override
+        public float[] toXyz(@NonNull @Size(min = 3) float[] v) {
+            v[0] = clamp(v[0]);
+            v[1] = clamp(v[1]);
+            v[2] = clamp(v[2]);
+            return v;
+        }
+
+        @Override
+        public float[] fromXyz(@NonNull @Size(min = 3) float[] v) {
+            v[0] = clamp(v[0]);
+            v[1] = clamp(v[1]);
+            v[2] = clamp(v[2]);
+            return v;
+        }
+
+        private static float clamp(float x) {
+            return x < -2.0f ? -2.0f : x > 2.0f ? 2.0f : x;
+        }
+    }
+
+    /**
+     * Implementation of the CIE L*a*b* color space. Its PCS is CIE XYZ
+     * with a white point of D50.
+     */
+    @AnyThread
+    private static final class Lab extends ColorSpace {
+        private static final float A = 216.0f / 24389.0f;
+        private static final float B = 841.0f / 108.0f;
+        private static final float C = 4.0f / 29.0f;
+        private static final float D = 6.0f / 29.0f;
+
+        private Lab(@NonNull String name, @IntRange(from = MIN_ID, to = MAX_ID) int id) {
+            super(name, Model.LAB, id);
+        }
+
+        @Override
+        public boolean isWideGamut() {
+            return true;
+        }
+
+        @Override
+        public float getMinValue(@IntRange(from = 0, to = 3) int component) {
+            return component == 0 ? 0.0f : -128.0f;
+        }
+
+        @Override
+        public float getMaxValue(@IntRange(from = 0, to = 3) int component) {
+            return component == 0 ? 100.0f : 128.0f;
+        }
+
+        @Override
+        public float[] toXyz(@NonNull @Size(min = 3) float[] v) {
+            v[0] = clamp(v[0], 0.0f, 100.0f);
+            v[1] = clamp(v[1], -128.0f, 128.0f);
+            v[2] = clamp(v[2], -128.0f, 128.0f);
+
+            float fy = (v[0] + 16.0f) / 116.0f;
+            float fx = fy + (v[1] * 0.002f);
+            float fz = fy - (v[2] * 0.005f);
+            float X = fx > D ? fx * fx * fx : (1.0f / B) * (fx - C);
+            float Y = fy > D ? fy * fy * fy : (1.0f / B) * (fy - C);
+            float Z = fz > D ? fz * fz * fz : (1.0f / B) * (fz - C);
+
+            v[0] = X * ILLUMINANT_D50_XYZ[0];
+            v[1] = Y * ILLUMINANT_D50_XYZ[1];
+            v[2] = Z * ILLUMINANT_D50_XYZ[2];
+
+            return v;
+        }
+
+        @Override
+        public float[] fromXyz(@NonNull @Size(min = 3) float[] v) {
+            float X = v[0] / ILLUMINANT_D50_XYZ[0];
+            float Y = v[1] / ILLUMINANT_D50_XYZ[1];
+            float Z = v[2] / ILLUMINANT_D50_XYZ[2];
+
+            float fx = X > A ? (float) Math.pow(X, 1.0 / 3.0) : B * X + C;
+            float fy = Y > A ? (float) Math.pow(Y, 1.0 / 3.0) : B * Y + C;
+            float fz = Z > A ? (float) Math.pow(Z, 1.0 / 3.0) : B * Z + C;
+
+            float L = 116.0f * fy - 16.0f;
+            float a = 500.0f * (fx - fy);
+            float b = 200.0f * (fy - fz);
+
+            v[0] = clamp(L, 0.0f, 100.0f);
+            v[1] = clamp(a, -128.0f, 128.0f);
+            v[2] = clamp(b, -128.0f, 128.0f);
+
+            return v;
+        }
+
+        private static float clamp(float x, float min, float max) {
+            return x < min ? min : x > max ? max : x;
+        }
+    }
+
+    /**
+     * {@usesMathJax}
+     *
+     * <p>An RGB color space is an additive color space using the
+     * {@link Model#RGB RGB} color model (a color is therefore represented
+     * by a tuple of 3 numbers).</p>
+     *
+     * <p>A specific RGB color space is defined by the following properties:</p>
+     * <ul>
+     *     <li>Three chromaticities of the red, green and blue primaries, which
+     *     define the gamut of the color space.</li>
+     *     <li>A white point chromaticity that defines the stimulus to which
+     *     color space values are normalized (also just called "white").</li>
+     *     <li>An opto-electronic transfer function, also called opto-electronic
+     *     conversion function or often, and approximately, gamma function.</li>
+     *     <li>An electro-optical transfer function, also called electo-optical
+     *     conversion function or often, and approximately, gamma function.</li>
+     *     <li>A range of valid RGB values (most commonly \([0..1]\)).</li>
+     * </ul>
+     *
+     * <p>The most commonly used RGB color space is {@link Named#SRGB sRGB}.</p>
+     *
+     * <h3>Primaries and white point chromaticities</h3>
+     * <p>In this implementation, the chromaticity of the primaries and the white
+     * point of an RGB color space is defined in the CIE xyY color space. This
+     * color space separates the chromaticity of a color, the x and y components,
+     * and its luminance, the Y component. Since the primaries and the white
+     * point have full brightness, the Y component is assumed to be 1 and only
+     * the x and y components are needed to encode them.</p>
+     * <p>For convenience, this implementation also allows to define the
+     * primaries and white point in the CIE XYZ space. The tristimulus XYZ values
+     * are internally converted to xyY.</p>
+     *
+     * <p>
+     *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_srgb.png" />
+     *     <figcaption style="text-align: center;">sRGB primaries and white point</figcaption>
+     * </p>
+     *
+     * <h3>Transfer functions</h3>
+     * <p>A transfer function is a color component conversion function, defined as
+     * a single variable, monotonic mathematical function. It is applied to each
+     * individual component of a color. They are used to perform the mapping
+     * between linear tristimulus values and non-linear electronic signal value.</p>
+     * <p>The <em>opto-electronic transfer function</em> (OETF or OECF) encodes
+     * tristimulus values in a scene to a non-linear electronic signal value.
+     * An OETF is often expressed as a power function with an exponent between
+     * 0.38 and 0.55 (the reciprocal of 1.8 to 2.6).</p>
+     * <p>The <em>electro-optical transfer function</em> (EOTF or EOCF) decodes
+     * a non-linear electronic signal value to a tristimulus value at the display.
+     * An EOTF is often expressed as a power function with an exponent between
+     * 1.8 and 2.6.</p>
+     * <p>Transfer functions are used as a compression scheme. For instance,
+     * linear sRGB values would normally require 11 to 12 bits of precision to
+     * store all values that can be perceived by the human eye. When encoding
+     * sRGB values using the appropriate OETF (see {@link Named#SRGB sRGB} for
+     * an exact mathematical description of that OETF), the values can be
+     * compressed to only 8 bits precision.</p>
+     * <p>When manipulating RGB values, particularly sRGB values, it is safe
+     * to assume that these values have been encoded with the appropriate
+     * OETF (unless noted otherwise). Encoded values are often said to be in
+     * "gamma space". They are therefore defined in a non-linear space. This
+     * in turns means that any linear operation applied to these values is
+     * going to yield mathematically incorrect results (any linear interpolation
+     * such as gradient generation for instance, most image processing functions
+     * such as blurs, etc.).</p>
+     * <p>To properly process encoded RGB values you must first apply the
+     * EOTF to decode the value into linear space. After processing, the RGB
+     * value must be encoded back to non-linear ("gamma") space. Here is a
+     * formal description of the process, where \(f\) is the processing
+     * function to apply:</p>
+     *
+     * $$RGB_{out} = OETF(f(EOTF(RGB_{in})))$$
+     *
+     * <p>If the transfer functions of the color space can be expressed as an
+     * ICC parametric curve as defined in ICC.1:2004-10, the numeric parameters
+     * can be retrieved by calling {@link #getTransferParameters()}. This can
+     * be useful to match color spaces for instance.</p>
+     *
+     * <p class="note">Some RGB color spaces, such as {@link Named#ACES} and
+     * {@link Named#LINEAR_EXTENDED_SRGB scRGB}, are said to be linear because
+     * their transfer functions are the identity function: \(f(x) = x\).
+     * If the source and/or destination are known to be linear, it is not
+     * necessary to invoke the transfer functions.</p>
+     *
+     * <h3>Range</h3>
+     * <p>Most RGB color spaces allow RGB values in the range \([0..1]\). There
+     * are however a few RGB color spaces that allow much larger ranges. For
+     * instance, {@link Named#EXTENDED_SRGB scRGB} is used to manipulate the
+     * range \([-0.5..7.5]\) while {@link Named#ACES ACES} can be used throughout
+     * the range \([-65504, 65504]\).</p>
+     *
+     * <p>
+     *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" />
+     *     <figcaption style="text-align: center;">Extended sRGB and its large range</figcaption>
+     * </p>
+     *
+     * <h3>Converting between RGB color spaces</h3>
+     * <p>Conversion between two color spaces is achieved by using an intermediate
+     * color space called the profile connection space (PCS). The PCS used by
+     * this implementation is CIE XYZ. The conversion operation is defined
+     * as such:</p>
+     *
+     * $$RGB_{out} = OETF(T_{dst}^{-1} \cdot T_{src} \cdot EOTF(RGB_{in}))$$
+     *
+     * <p>Where \(T_{src}\) is the {@link #getTransform() RGB to XYZ transform}
+     * of the source color space and \(T_{dst}^{-1}\) the {@link #getInverseTransform()
+     * XYZ to RGB transform} of the destination color space.</p>
+     * <p>Many RGB color spaces commonly used with electronic devices use the
+     * standard illuminant {@link #ILLUMINANT_D65 D65}. Care must be take however
+     * when converting between two RGB color spaces if their white points do not
+     * match. This can be achieved by either calling
+     * {@link #adapt(ColorSpace, float[])} to adapt one or both color spaces to
+     * a single common white point. This can be achieved automatically by calling
+     * {@link ColorSpace#connect(ColorSpace, ColorSpace)}, which also handles
+     * non-RGB color spaces.</p>
+     * <p>To learn more about the white point adaptation process, refer to the
+     * documentation of {@link Adaptation}.</p>
+     */
+    @AnyThread
+    public static class Rgb extends ColorSpace {
+        /**
+         * {@usesMathJax}
+         *
+         * <p>Defines the parameters for the ICC parametric curve type 4, as
+         * defined in ICC.1:2004-10, section 10.15.</p>
+         *
+         * <p>The EOTF is of the form:</p>
+         *
+         * \(\begin{equation}
+         * Y = \begin{cases}c X + f & X \lt d \\
+         * \left( a X + b \right) ^{g} + e & X \ge d \end{cases}
+         * \end{equation}\)
+         *
+         * <p>The corresponding OETF is simply the inverse function.</p>
+         *
+         * <p>The parameters defined by this class form a valid transfer
+         * function only if all the following conditions are met:</p>
+         * <ul>
+         *     <li>No parameter is a {@link Double#isNaN(double) Not-a-Number}</li>
+         *     <li>\(d\) is in the range \([0..1]\)</li>
+         *     <li>The function is not constant</li>
+         *     <li>The function is positive and increasing</li>
+         * </ul>
+         */
+        public static class TransferParameters {
+            /** Variable \(a\) in the equation of the EOTF described above. */
+            public final double a;
+            /** Variable \(b\) in the equation of the EOTF described above. */
+            public final double b;
+            /** Variable \(c\) in the equation of the EOTF described above. */
+            public final double c;
+            /** Variable \(d\) in the equation of the EOTF described above. */
+            public final double d;
+            /** Variable \(e\) in the equation of the EOTF described above. */
+            public final double e;
+            /** Variable \(f\) in the equation of the EOTF described above. */
+            public final double f;
+            /** Variable \(g\) in the equation of the EOTF described above. */
+            public final double g;
+
+            /**
+             * <p>Defines the parameters for the ICC parametric curve type 3, as
+             * defined in ICC.1:2004-10, section 10.15.</p>
+             *
+             * <p>The EOTF is of the form:</p>
+             *
+             * \(\begin{equation}
+             * Y = \begin{cases}c X & X \lt d \\
+             * \left( a X + b \right) ^{g} & X \ge d \end{cases}
+             * \end{equation}\)
+             *
+             * <p>This constructor is equivalent to setting  \(e\) and \(f\) to 0.</p>
+             *
+             * @param a The value of \(a\) in the equation of the EOTF described above
+             * @param b The value of \(b\) in the equation of the EOTF described above
+             * @param c The value of \(c\) in the equation of the EOTF described above
+             * @param d The value of \(d\) in the equation of the EOTF described above
+             * @param g The value of \(g\) in the equation of the EOTF described above
+             *
+             * @throws IllegalArgumentException If the parameters form an invalid transfer function
+             */
+            public TransferParameters(double a, double b, double c, double d, double g) {
+                this(a, b, c, d, 0.0, 0.0, g);
+            }
+
+            /**
+             * <p>Defines the parameters for the ICC parametric curve type 4, as
+             * defined in ICC.1:2004-10, section 10.15.</p>
+             *
+             * @param a The value of \(a\) in the equation of the EOTF described above
+             * @param b The value of \(b\) in the equation of the EOTF described above
+             * @param c The value of \(c\) in the equation of the EOTF described above
+             * @param d The value of \(d\) in the equation of the EOTF described above
+             * @param e The value of \(e\) in the equation of the EOTF described above
+             * @param f The value of \(f\) in the equation of the EOTF described above
+             * @param g The value of \(g\) in the equation of the EOTF described above
+             *
+             * @throws IllegalArgumentException If the parameters form an invalid transfer function
+             */
+            public TransferParameters(double a, double b, double c, double d, double e,
+                    double f, double g) {
+
+                if (Double.isNaN(a) || Double.isNaN(b) || Double.isNaN(c) ||
+                        Double.isNaN(d) || Double.isNaN(e) || Double.isNaN(f) ||
+                        Double.isNaN(g)) {
+                    throw new IllegalArgumentException("Parameters cannot be NaN");
+                }
+
+                // Next representable float after 1.0
+                // We use doubles here but the representation inside our native code is often floats
+                if (!(d >= 0.0 && d <= 1.0f + Math.ulp(1.0f))) {
+                    throw new IllegalArgumentException("Parameter d must be in the range [0..1], " +
+                            "was " + d);
+                }
+
+                if (d == 0.0 && (a == 0.0 || g == 0.0)) {
+                    throw new IllegalArgumentException(
+                            "Parameter a or g is zero, the transfer function is constant");
+                }
+
+                if (d >= 1.0 && c == 0.0) {
+                    throw new IllegalArgumentException(
+                            "Parameter c is zero, the transfer function is constant");
+                }
+
+                if ((a == 0.0 || g == 0.0) && c == 0.0) {
+                    throw new IllegalArgumentException("Parameter a or g is zero," +
+                            " and c is zero, the transfer function is constant");
+                }
+
+                if (c < 0.0) {
+                    throw new IllegalArgumentException("The transfer function must be increasing");
+                }
+
+                if (a < 0.0 || g < 0.0) {
+                    throw new IllegalArgumentException("The transfer function must be " +
+                            "positive or increasing");
+                }
+
+                this.a = a;
+                this.b = b;
+                this.c = c;
+                this.d = d;
+                this.e = e;
+                this.f = f;
+                this.g = g;
+            }
+
+            @SuppressWarnings("SimplifiableIfStatement")
+            @Override
+            public boolean equals(Object o) {
+                if (this == o) return true;
+                if (o == null || getClass() != o.getClass()) return false;
+
+                TransferParameters that = (TransferParameters) o;
+
+                if (Double.compare(that.a, a) != 0) return false;
+                if (Double.compare(that.b, b) != 0) return false;
+                if (Double.compare(that.c, c) != 0) return false;
+                if (Double.compare(that.d, d) != 0) return false;
+                if (Double.compare(that.e, e) != 0) return false;
+                if (Double.compare(that.f, f) != 0) return false;
+                return Double.compare(that.g, g) == 0;
+            }
+
+            @Override
+            public int hashCode() {
+                int result;
+                long temp;
+                temp = Double.doubleToLongBits(a);
+                result = (int) (temp ^ (temp >>> 32));
+                temp = Double.doubleToLongBits(b);
+                result = 31 * result + (int) (temp ^ (temp >>> 32));
+                temp = Double.doubleToLongBits(c);
+                result = 31 * result + (int) (temp ^ (temp >>> 32));
+                temp = Double.doubleToLongBits(d);
+                result = 31 * result + (int) (temp ^ (temp >>> 32));
+                temp = Double.doubleToLongBits(e);
+                result = 31 * result + (int) (temp ^ (temp >>> 32));
+                temp = Double.doubleToLongBits(f);
+                result = 31 * result + (int) (temp ^ (temp >>> 32));
+                temp = Double.doubleToLongBits(g);
+                result = 31 * result + (int) (temp ^ (temp >>> 32));
+                return result;
+            }
+        }
+
+        @NonNull private final float[] mWhitePoint;
+        @NonNull private final float[] mPrimaries;
+        @NonNull private final float[] mTransform;
+        @NonNull private final float[] mInverseTransform;
+
+        @NonNull private final DoubleUnaryOperator mOetf;
+        @NonNull private final DoubleUnaryOperator mEotf;
+        @NonNull private final DoubleUnaryOperator mClampedOetf;
+        @NonNull private final DoubleUnaryOperator mClampedEotf;
+
+        private final float mMin;
+        private final float mMax;
+
+        private final boolean mIsWideGamut;
+        private final boolean mIsSrgb;
+
+        @Nullable private TransferParameters mTransferParameters;
+
+        /**
+         * <p>Creates a new RGB color space using a 3x3 column-major transform matrix.
+         * The transform matrix must convert from the RGB space to the profile connection
+         * space CIE XYZ.</p>
+         *
+         * <p class="note">The range of the color space is imposed to be \([0..1]\).</p>
+         *
+         * @param name Name of the color space, cannot be null, its length must be >= 1
+         * @param toXYZ 3x3 column-major transform matrix from RGB to the profile
+         *              connection space CIE XYZ as an array of 9 floats, cannot be null
+         * @param oetf Opto-electronic transfer function, cannot be null
+         * @param eotf Electro-optical transfer function, cannot be null
+         *
+         * @throws IllegalArgumentException If any of the following conditions is met:
+         * <ul>
+         *     <li>The name is null or has a length of 0.</li>
+         *     <li>The OETF is null or the EOTF is null.</li>
+         *     <li>The minimum valid value is >= the maximum valid value.</li>
+         * </ul>
+         *
+         * @see #get(Named)
+         */
+        public Rgb(
+                @NonNull @Size(min = 1) String name,
+                @NonNull @Size(9) float[] toXYZ,
+                @NonNull DoubleUnaryOperator oetf,
+                @NonNull DoubleUnaryOperator eotf) {
+            this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ),
+                    oetf, eotf, 0.0f, 1.0f, MIN_ID);
+        }
+
+        /**
+         * <p>Creates a new RGB color space using a specified set of primaries
+         * and a specified white point.</p>
+         *
+         * <p>The primaries and white point can be specified in the CIE xyY space
+         * or in CIE XYZ. The length of the arrays depends on the chosen space:</p>
+         *
+         * <table summary="Parameters length">
+         *     <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr>
+         *     <tr><td>xyY</td><td>6</td><td>2</td></tr>
+         *     <tr><td>XYZ</td><td>9</td><td>3</td></tr>
+         * </table>
+         *
+         * <p>When the primaries and/or white point are specified in xyY, the Y component
+         * does not need to be specified and is assumed to be 1.0. Only the xy components
+         * are required.</p>
+         *
+         * <p class="note">The ID, areturned by {@link #getId()}, of an object created by
+         * this constructor is always {@link #MIN_ID}.</p>
+         *
+         * @param name Name of the color space, cannot be null, its length must be >= 1
+         * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
+         * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats
+         * @param oetf Opto-electronic transfer function, cannot be null
+         * @param eotf Electro-optical transfer function, cannot be null
+         * @param min The minimum valid value in this color space's RGB range
+         * @param max The maximum valid value in this color space's RGB range
+         *
+         * @throws IllegalArgumentException <p>If any of the following conditions is met:</p>
+         * <ul>
+         *     <li>The name is null or has a length of 0.</li>
+         *     <li>The primaries array is null or has a length that is neither 6 or 9.</li>
+         *     <li>The white point array is null or has a length that is neither 2 or 3.</li>
+         *     <li>The OETF is null or the EOTF is null.</li>
+         *     <li>The minimum valid value is >= the maximum valid value.</li>
+         * </ul>
+         *
+         * @see #get(Named)
+         */
+        public Rgb(
+                @NonNull @Size(min = 1) String name,
+                @NonNull @Size(min = 6, max = 9) float[] primaries,
+                @NonNull @Size(min = 2, max = 3) float[] whitePoint,
+                @NonNull DoubleUnaryOperator oetf,
+                @NonNull DoubleUnaryOperator eotf,
+                float min,
+                float max) {
+            this(name, primaries, whitePoint, oetf, eotf, min, max, MIN_ID);
+        }
+
+        /**
+         * <p>Creates a new RGB color space using a 3x3 column-major transform matrix.
+         * The transform matrix must convert from the RGB space to the profile connection
+         * space CIE XYZ.</p>
+         *
+         * <p class="note">The range of the color space is imposed to be \([0..1]\).</p>
+         *
+         * @param name Name of the color space, cannot be null, its length must be >= 1
+         * @param toXYZ 3x3 column-major transform matrix from RGB to the profile
+         *              connection space CIE XYZ as an array of 9 floats, cannot be null
+         * @param function Parameters for the transfer functions
+         *
+         * @throws IllegalArgumentException If any of the following conditions is met:
+         * <ul>
+         *     <li>The name is null or has a length of 0.</li>
+         *     <li>Gamma is negative.</li>
+         * </ul>
+         *
+         * @see #get(Named)
+         */
+        public Rgb(
+                @NonNull @Size(min = 1) String name,
+                @NonNull @Size(9) float[] toXYZ,
+                @NonNull TransferParameters function) {
+            this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ), function, MIN_ID);
+        }
+
+        /**
+         * <p>Creates a new RGB color space using a specified set of primaries
+         * and a specified white point.</p>
+         *
+         * <p>The primaries and white point can be specified in the CIE xyY space
+         * or in CIE XYZ. The length of the arrays depends on the chosen space:</p>
+         *
+         * <table summary="Parameters length">
+         *     <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr>
+         *     <tr><td>xyY</td><td>6</td><td>2</td></tr>
+         *     <tr><td>XYZ</td><td>9</td><td>3</td></tr>
+         * </table>
+         *
+         * <p>When the primaries and/or white point are specified in xyY, the Y component
+         * does not need to be specified and is assumed to be 1.0. Only the xy components
+         * are required.</p>
+         *
+         * @param name Name of the color space, cannot be null, its length must be >= 1
+         * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
+         * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats
+         * @param function Parameters for the transfer functions
+         *
+         * @throws IllegalArgumentException If any of the following conditions is met:
+         * <ul>
+         *     <li>The name is null or has a length of 0.</li>
+         *     <li>The primaries array is null or has a length that is neither 6 or 9.</li>
+         *     <li>The white point array is null or has a length that is neither 2 or 3.</li>
+         *     <li>The transfer parameters are invalid.</li>
+         * </ul>
+         *
+         * @see #get(Named)
+         */
+        public Rgb(
+                @NonNull @Size(min = 1) String name,
+                @NonNull @Size(min = 6, max = 9) float[] primaries,
+                @NonNull @Size(min = 2, max = 3) float[] whitePoint,
+                @NonNull TransferParameters function) {
+            this(name, primaries, whitePoint, function, MIN_ID);
+        }
+
+        /**
+         * <p>Creates a new RGB color space using a specified set of primaries
+         * and a specified white point.</p>
+         *
+         * <p>The primaries and white point can be specified in the CIE xyY space
+         * or in CIE XYZ. The length of the arrays depends on the chosen space:</p>
+         *
+         * <table summary="Parameters length">
+         *     <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr>
+         *     <tr><td>xyY</td><td>6</td><td>2</td></tr>
+         *     <tr><td>XYZ</td><td>9</td><td>3</td></tr>
+         * </table>
+         *
+         * <p>When the primaries and/or white point are specified in xyY, the Y component
+         * does not need to be specified and is assumed to be 1.0. Only the xy components
+         * are required.</p>
+         *
+         * @param name Name of the color space, cannot be null, its length must be >= 1
+         * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
+         * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats
+         * @param function Parameters for the transfer functions
+         * @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID}
+         *
+         * @throws IllegalArgumentException If any of the following conditions is met:
+         * <ul>
+         *     <li>The name is null or has a length of 0.</li>
+         *     <li>The primaries array is null or has a length that is neither 6 or 9.</li>
+         *     <li>The white point array is null or has a length that is neither 2 or 3.</li>
+         *     <li>The ID is not between {@link #MIN_ID} and {@link #MAX_ID}.</li>
+         *     <li>The transfer parameters are invalid.</li>
+         * </ul>
+         *
+         * @see #get(Named)
+         */
+        private Rgb(
+                @NonNull @Size(min = 1) String name,
+                @NonNull @Size(min = 6, max = 9) float[] primaries,
+                @NonNull @Size(min = 2, max = 3) float[] whitePoint,
+                @NonNull TransferParameters function,
+                @IntRange(from = MIN_ID, to = MAX_ID) int id) {
+            this(name, primaries, whitePoint,
+                    function.e == 0.0 && function.f == 0.0 ?
+                            x -> rcpResponse(x, function.a, function.b,
+                                    function.c, function.d, function.g) :
+                            x -> rcpResponse(x, function.a, function.b, function.c,
+                                    function.d, function.e, function.f, function.g),
+                    function.e == 0.0 && function.f == 0.0 ?
+                            x -> response(x, function.a, function.b,
+                                    function.c, function.d, function.g) :
+                            x -> response(x, function.a, function.b, function.c,
+                                    function.d, function.e, function.f, function.g),
+                    0.0f, 1.0f, id);
+            mTransferParameters = function;
+        }
+
+        /**
+         * <p>Creates a new RGB color space using a 3x3 column-major transform matrix.
+         * The transform matrix must convert from the RGB space to the profile connection
+         * space CIE XYZ.</p>
+         *
+         * <p class="note">The range of the color space is imposed to be \([0..1]\).</p>
+         *
+         * @param name Name of the color space, cannot be null, its length must be >= 1
+         * @param toXYZ 3x3 column-major transform matrix from RGB to the profile
+         *              connection space CIE XYZ as an array of 9 floats, cannot be null
+         * @param gamma Gamma to use as the transfer function
+         *
+         * @throws IllegalArgumentException If any of the following conditions is met:
+         * <ul>
+         *     <li>The name is null or has a length of 0.</li>
+         *     <li>Gamma is negative.</li>
+         * </ul>
+         *
+         * @see #get(Named)
+         */
+        public Rgb(
+                @NonNull @Size(min = 1) String name,
+                @NonNull @Size(9) float[] toXYZ,
+                double gamma) {
+            this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ), gamma, 0.0f, 1.0f, MIN_ID);
+        }
+
+        /**
+         * <p>Creates a new RGB color space using a specified set of primaries
+         * and a specified white point.</p>
+         *
+         * <p>The primaries and white point can be specified in the CIE xyY space
+         * or in CIE XYZ. The length of the arrays depends on the chosen space:</p>
+         *
+         * <table summary="Parameters length">
+         *     <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr>
+         *     <tr><td>xyY</td><td>6</td><td>2</td></tr>
+         *     <tr><td>XYZ</td><td>9</td><td>3</td></tr>
+         * </table>
+         *
+         * <p>When the primaries and/or white point are specified in xyY, the Y component
+         * does not need to be specified and is assumed to be 1.0. Only the xy components
+         * are required.</p>
+         *
+         * @param name Name of the color space, cannot be null, its length must be >= 1
+         * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
+         * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats
+         * @param gamma Gamma to use as the transfer function
+         *
+         * @throws IllegalArgumentException If any of the following conditions is met:
+         * <ul>
+         *     <li>The name is null or has a length of 0.</li>
+         *     <li>The primaries array is null or has a length that is neither 6 or 9.</li>
+         *     <li>The white point array is null or has a length that is neither 2 or 3.</li>
+         *     <li>Gamma is negative.</li>
+         * </ul>
+         *
+         * @see #get(Named)
+         */
+        public Rgb(
+                @NonNull @Size(min = 1) String name,
+                @NonNull @Size(min = 6, max = 9) float[] primaries,
+                @NonNull @Size(min = 2, max = 3) float[] whitePoint,
+                double gamma) {
+            this(name, primaries, whitePoint, gamma, 0.0f, 1.0f, MIN_ID);
+        }
+
+        /**
+         * <p>Creates a new RGB color space using a specified set of primaries
+         * and a specified white point.</p>
+         *
+         * <p>The primaries and white point can be specified in the CIE xyY space
+         * or in CIE XYZ. The length of the arrays depends on the chosen space:</p>
+         *
+         * <table summary="Parameters length">
+         *     <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr>
+         *     <tr><td>xyY</td><td>6</td><td>2</td></tr>
+         *     <tr><td>XYZ</td><td>9</td><td>3</td></tr>
+         * </table>
+         *
+         * <p>When the primaries and/or white point are specified in xyY, the Y component
+         * does not need to be specified and is assumed to be 1.0. Only the xy components
+         * are required.</p>
+         *
+         * @param name Name of the color space, cannot be null, its length must be >= 1
+         * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
+         * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats
+         * @param gamma Gamma to use as the transfer function
+         * @param min The minimum valid value in this color space's RGB range
+         * @param max The maximum valid value in this color space's RGB range
+         * @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID}
+         *
+         * @throws IllegalArgumentException If any of the following conditions is met:
+         * <ul>
+         *     <li>The name is null or has a length of 0.</li>
+         *     <li>The primaries array is null or has a length that is neither 6 or 9.</li>
+         *     <li>The white point array is null or has a length that is neither 2 or 3.</li>
+         *     <li>The minimum valid value is >= the maximum valid value.</li>
+         *     <li>The ID is not between {@link #MIN_ID} and {@link #MAX_ID}.</li>
+         *     <li>Gamma is negative.</li>
+         * </ul>
+         *
+         * @see #get(Named)
+         */
+        private Rgb(
+                @NonNull @Size(min = 1) String name,
+                @NonNull @Size(min = 6, max = 9) float[] primaries,
+                @NonNull @Size(min = 2, max = 3) float[] whitePoint,
+                double gamma,
+                float min,
+                float max,
+                @IntRange(from = MIN_ID, to = MAX_ID) int id) {
+            this(name, primaries, whitePoint,
+                    gamma == 1.0 ? DoubleUnaryOperator.identity() :
+                            x -> Math.pow(x < 0.0 ? 0.0 : x, 1 / gamma),
+                    gamma == 1.0 ? DoubleUnaryOperator.identity() :
+                            x -> Math.pow(x < 0.0 ? 0.0 : x, gamma),
+                    min, max, id);
+            mTransferParameters = gamma == 1.0 ?
+                    new TransferParameters(0.0, 0.0, 1.0, 1.0 + Math.ulp(1.0f), gamma) :
+                    new TransferParameters(1.0, 0.0, 0.0, 0.0, gamma);
+        }
+
+        /**
+         * <p>Creates a new RGB color space using a specified set of primaries
+         * and a specified white point.</p>
+         *
+         * <p>The primaries and white point can be specified in the CIE xyY space
+         * or in CIE XYZ. The length of the arrays depends on the chosen space:</p>
+         *
+         * <table summary="Parameters length">
+         *     <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr>
+         *     <tr><td>xyY</td><td>6</td><td>2</td></tr>
+         *     <tr><td>XYZ</td><td>9</td><td>3</td></tr>
+         * </table>
+         *
+         * <p>When the primaries and/or white point are specified in xyY, the Y component
+         * does not need to be specified and is assumed to be 1.0. Only the xy components
+         * are required.</p>
+         *
+         * @param name Name of the color space, cannot be null, its length must be >= 1
+         * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
+         * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats
+         * @param oetf Opto-electronic transfer function, cannot be null
+         * @param eotf Electro-optical transfer function, cannot be null
+         * @param min The minimum valid value in this color space's RGB range
+         * @param max The maximum valid value in this color space's RGB range
+         * @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID}
+         *
+         * @throws IllegalArgumentException If any of the following conditions is met:
+         * <ul>
+         *     <li>The name is null or has a length of 0.</li>
+         *     <li>The primaries array is null or has a length that is neither 6 or 9.</li>
+         *     <li>The white point array is null or has a length that is neither 2 or 3.</li>
+         *     <li>The OETF is null or the EOTF is null.</li>
+         *     <li>The minimum valid value is >= the maximum valid value.</li>
+         *     <li>The ID is not between {@link #MIN_ID} and {@link #MAX_ID}.</li>
+         * </ul>
+         *
+         * @see #get(Named)
+         */
+        private Rgb(
+                @NonNull @Size(min = 1) String name,
+                @NonNull @Size(min = 6, max = 9) float[] primaries,
+                @NonNull @Size(min = 2, max = 3) float[] whitePoint,
+                @NonNull DoubleUnaryOperator oetf,
+                @NonNull DoubleUnaryOperator eotf,
+                float min,
+                float max,
+                @IntRange(from = MIN_ID, to = MAX_ID) int id) {
+
+            super(name, Model.RGB, id);
+
+            if (primaries == null || (primaries.length != 6 && primaries.length != 9)) {
+                throw new IllegalArgumentException("The color space's primaries must be " +
+                        "defined as an array of 6 floats in xyY or 9 floats in XYZ");
+            }
+
+            if (whitePoint == null || (whitePoint.length != 2 && whitePoint.length != 3)) {
+                throw new IllegalArgumentException("The color space's white point must be " +
+                        "defined as an array of 2 floats in xyY or 3 float in XYZ");
+            }
+
+            if (oetf == null || eotf == null) {
+                throw new IllegalArgumentException("The transfer functions of a color space " +
+                        "cannot be null");
+            }
+
+            if (min >= max) {
+                throw new IllegalArgumentException("Invalid range: min=" + min + ", max=" + max +
+                        "; min must be strictly < max");
+            }
+
+            mWhitePoint = xyWhitePoint(whitePoint);
+            mPrimaries =  xyPrimaries(primaries);
+
+            mTransform = computeXYZMatrix(mPrimaries, mWhitePoint);
+            mInverseTransform = inverse3x3(mTransform);
+
+            mOetf = oetf;
+            mEotf = eotf;
+
+            mMin = min;
+            mMax = max;
+
+            DoubleUnaryOperator clamp = this::clamp;
+            mClampedOetf = oetf.andThen(clamp);
+            mClampedEotf = clamp.andThen(eotf);
+
+            // A color space is wide-gamut if its area is >90% of NTSC 1953 and
+            // if it entirely contains the Color space definition in xyY
+            mIsWideGamut = isWideGamut(mPrimaries, min, max);
+            mIsSrgb = isSrgb(mPrimaries, mWhitePoint, oetf, eotf, min, max, id);
+        }
+
+        /**
+         * Creates a copy of the specified color space with a new transform.
+         *
+         * @param colorSpace The color space to create a copy of
+         */
+        private Rgb(Rgb colorSpace,
+                @NonNull @Size(9) float[] transform,
+                @NonNull @Size(min = 2, max = 3) float[] whitePoint) {
+            super(colorSpace.getName(), Model.RGB, -1);
+
+            mWhitePoint = xyWhitePoint(whitePoint);
+            mPrimaries = colorSpace.mPrimaries;
+
+            mTransform = transform;
+            mInverseTransform = inverse3x3(transform);
+
+            mMin = colorSpace.mMin;
+            mMax = colorSpace.mMax;
+
+            mOetf = colorSpace.mOetf;
+            mEotf = colorSpace.mEotf;
+
+            mClampedOetf = colorSpace.mClampedOetf;
+            mClampedEotf = colorSpace.mClampedEotf;
+
+            mIsWideGamut = colorSpace.mIsWideGamut;
+            mIsSrgb = colorSpace.mIsSrgb;
+
+            mTransferParameters = colorSpace.mTransferParameters;
+        }
+
+        /**
+         * Copies the non-adapted CIE xyY white point of this color space in
+         * specified array. The Y component is assumed to be 1 and is therefore
+         * not copied into the destination. The x and y components are written
+         * in the array at positions 0 and 1 respectively.
+         *
+         * @param whitePoint The destination array, cannot be null, its length
+         *                   must be >= 2
+         *
+         * @return The destination array passed as a parameter
+         *
+         * @see #getWhitePoint(float[])
+         */
+        @NonNull
+        @Size(min = 2)
+        public float[] getWhitePoint(@NonNull @Size(min = 2) float[] whitePoint) {
+            whitePoint[0] = mWhitePoint[0];
+            whitePoint[1] = mWhitePoint[1];
+            return whitePoint;
+        }
+
+        /**
+         * Returns the non-adapted CIE xyY white point of this color space as
+         * a new array of 2 floats. The Y component is assumed to be 1 and is
+         * therefore not copied into the destination. The x and y components
+         * are written in the array at positions 0 and 1 respectively.
+         *
+         * @return A new non-null array of 2 floats
+         *
+         * @see #getWhitePoint()
+         */
+        @NonNull
+        @Size(2)
+        public float[] getWhitePoint() {
+            return Arrays.copyOf(mWhitePoint, mWhitePoint.length);
+        }
+
+        /**
+         * Copies the primaries of this color space in specified array. The Y
+         * component is assumed to be 1 and is therefore not copied into the
+         * destination. The x and y components of the first primary are written
+         * in the array at positions 0 and 1 respectively.
+         *
+         * @param primaries The destination array, cannot be null, its length
+         *                  must be >= 6
+         *
+         * @return The destination array passed as a parameter
+         *
+         * @see #getPrimaries(float[])
+         */
+        @NonNull
+        @Size(min = 6)
+        public float[] getPrimaries(@NonNull @Size(min = 6) float[] primaries) {
+            System.arraycopy(mPrimaries, 0, primaries, 0, mPrimaries.length);
+            return primaries;
+        }
+
+        /**
+         * Returns the primaries of this color space as a new array of 6 floats.
+         * The Y component is assumed to be 1 and is therefore not copied into
+         * the destination. The x and y components of the first primary are
+         * written in the array at positions 0 and 1 respectively.
+         *
+         * @return A new non-null array of 2 floats
+         *
+         * @see #getWhitePoint()
+         */
+        @NonNull
+        @Size(6)
+        public float[] getPrimaries() {
+            return Arrays.copyOf(mPrimaries, mPrimaries.length);
+        }
+
+        /**
+         * <p>Copies the transform of this color space in specified array. The
+         * transform is used to convert from RGB to XYZ (with the same white
+         * point as this color space). To connect color spaces, you must first
+         * {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them to the
+         * same white point.</p>
+         * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)}
+         * to convert between color spaces.</p>
+         *
+         * @param transform The destination array, cannot be null, its length
+         *                  must be >= 9
+         *
+         * @return The destination array passed as a parameter
+         *
+         * @see #getInverseTransform()
+         */
+        @NonNull
+        @Size(min = 9)
+        public float[] getTransform(@NonNull @Size(min = 9) float[] transform) {
+            System.arraycopy(mTransform, 0, transform, 0, mTransform.length);
+            return transform;
+        }
+
+        /**
+         * <p>Returns the transform of this color space as a new array. The
+         * transform is used to convert from RGB to XYZ (with the same white
+         * point as this color space). To connect color spaces, you must first
+         * {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them to the
+         * same white point.</p>
+         * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)}
+         * to convert between color spaces.</p>
+         *
+         * @return A new array of 9 floats
+         *
+         * @see #getInverseTransform(float[])
+         */
+        @NonNull
+        @Size(9)
+        public float[] getTransform() {
+            return Arrays.copyOf(mTransform, mTransform.length);
+        }
+
+        /**
+         * <p>Copies the inverse transform of this color space in specified array.
+         * The inverse transform is used to convert from XYZ to RGB (with the
+         * same white point as this color space). To connect color spaces, you
+         * must first {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them
+         * to the same white point.</p>
+         * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)}
+         * to convert between color spaces.</p>
+         *
+         * @param inverseTransform The destination array, cannot be null, its length
+         *                  must be >= 9
+         *
+         * @return The destination array passed as a parameter
+         *
+         * @see #getTransform()
+         */
+        @NonNull
+        @Size(min = 9)
+        public float[] getInverseTransform(@NonNull @Size(min = 9) float[] inverseTransform) {
+            System.arraycopy(mInverseTransform, 0, inverseTransform, 0, mInverseTransform.length);
+            return inverseTransform;
+        }
+
+        /**
+         * <p>Returns the inverse transform of this color space as a new array.
+         * The inverse transform is used to convert from XYZ to RGB (with the
+         * same white point as this color space). To connect color spaces, you
+         * must first {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them
+         * to the same white point.</p>
+         * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)}
+         * to convert between color spaces.</p>
+         *
+         * @return A new array of 9 floats
+         *
+         * @see #getTransform(float[])
+         */
+        @NonNull
+        @Size(9)
+        public float[] getInverseTransform() {
+            return Arrays.copyOf(mInverseTransform, mInverseTransform.length);
+        }
+
+        /**
+         * <p>Returns the opto-electronic transfer function (OETF) of this color space.
+         * The inverse function is the electro-optical transfer function (EOTF) returned
+         * by {@link #getEotf()}. These functions are defined to satisfy the following
+         * equality for \(x \in [0..1]\):</p>
+         *
+         * $$OETF(EOTF(x)) = EOTF(OETF(x)) = x$$
+         *
+         * <p>For RGB colors, this function can be used to convert from linear space
+         * to "gamma space" (gamma encoded). The terms gamma space and gamma encoded
+         * are frequently used because many OETFs can be closely approximated using
+         * a simple power function of the form \(x^{\frac{1}{\gamma}}\) (the
+         * approximation of the {@link Named#SRGB sRGB} OETF uses \(\gamma=2.2\)
+         * for instance).</p>
+         *
+         * @return A transfer function that converts from linear space to "gamma space"
+         *
+         * @see #getEotf()
+         * @see #getTransferParameters()
+         */
+        @NonNull
+        public DoubleUnaryOperator getOetf() {
+            return mClampedOetf;
+        }
+
+        /**
+         * <p>Returns the electro-optical transfer function (EOTF) of this color space.
+         * The inverse function is the opto-electronic transfer function (OETF)
+         * returned by {@link #getOetf()}. These functions are defined to satisfy the
+         * following equality for \(x \in [0..1]\):</p>
+         *
+         * $$OETF(EOTF(x)) = EOTF(OETF(x)) = x$$
+         *
+         * <p>For RGB colors, this function can be used to convert from "gamma space"
+         * (gamma encoded) to linear space. The terms gamma space and gamma encoded
+         * are frequently used because many EOTFs can be closely approximated using
+         * a simple power function of the form \(x^\gamma\) (the approximation of the
+         * {@link Named#SRGB sRGB} EOTF uses \(\gamma=2.2\) for instance).</p>
+         *
+         * @return A transfer function that converts from "gamma space" to linear space
+         *
+         * @see #getOetf()
+         * @see #getTransferParameters()
+         */
+        @NonNull
+        public DoubleUnaryOperator getEotf() {
+            return mClampedEotf;
+        }
+
+        /**
+         * <p>Returns the parameters used by the {@link #getEotf() electro-optical}
+         * and {@link #getOetf() opto-electronic} transfer functions. If the transfer
+         * functions do not match the ICC parametric curves defined in ICC.1:2004-10
+         * (section 10.15), this method returns null.</p>
+         *
+         * <p>See {@link TransferParameters} for a full description of the transfer
+         * functions.</p>
+         *
+         * @return An instance of {@link TransferParameters} or null if this color
+         *         space's transfer functions do not match the equation defined in
+         *         {@link TransferParameters}
+         */
+        @Nullable
+        public TransferParameters getTransferParameters() {
+            return mTransferParameters;
+        }
+
+        @Override
+        public boolean isSrgb() {
+            return mIsSrgb;
+        }
+
+        @Override
+        public boolean isWideGamut() {
+            return mIsWideGamut;
+        }
+
+        @Override
+        public float getMinValue(int component) {
+            return mMin;
+        }
+
+        @Override
+        public float getMaxValue(int component) {
+            return mMax;
+        }
+
+        /**
+         * <p>Decodes an RGB value to linear space. This is achieved by
+         * applying this color space's electro-optical transfer function
+         * to the supplied values.</p>
+         *
+         * <p>Refer to the documentation of {@link ColorSpace.Rgb} for
+         * more information about transfer functions and their use for
+         * encoding and decoding RGB values.</p>
+         *
+         * @param r The red component to decode to linear space
+         * @param g The green component to decode to linear space
+         * @param b The blue component to decode to linear space
+         * @return A new array of 3 floats containing linear RGB values
+         *
+         * @see #toLinear(float[])
+         * @see #fromLinear(float, float, float)
+         */
+        @NonNull
+        @Size(3)
+        public float[] toLinear(float r, float g, float b) {
+            return toLinear(new float[] { r, g, b });
+        }
+
+        /**
+         * <p>Decodes an RGB value to linear space. This is achieved by
+         * applying this color space's electro-optical transfer function
+         * to the first 3 values of the supplied array. The result is
+         * stored back in the input array.</p>
+         *
+         * <p>Refer to the documentation of {@link ColorSpace.Rgb} for
+         * more information about transfer functions and their use for
+         * encoding and decoding RGB values.</p>
+         *
+         * @param v A non-null array of non-linear RGB values, its length
+         *          must be at least 3
+         * @return The specified array
+         *
+         * @see #toLinear(float, float, float)
+         * @see #fromLinear(float[])
+         */
+        @NonNull
+        @Size(min = 3)
+        public float[] toLinear(@NonNull @Size(min = 3) float[] v) {
+            v[0] = (float) mClampedEotf.applyAsDouble(v[0]);
+            v[1] = (float) mClampedEotf.applyAsDouble(v[1]);
+            v[2] = (float) mClampedEotf.applyAsDouble(v[2]);
+            return v;
+        }
+
+        /**
+         * <p>Encodes an RGB value from linear space to this color space's
+         * "gamma space". This is achieved by applying this color space's
+         * opto-electronic transfer function to the supplied values.</p>
+         *
+         * <p>Refer to the documentation of {@link ColorSpace.Rgb} for
+         * more information about transfer functions and their use for
+         * encoding and decoding RGB values.</p>
+         *
+         * @param r The red component to encode from linear space
+         * @param g The green component to encode from linear space
+         * @param b The blue component to encode from linear space
+         * @return A new array of 3 floats containing non-linear RGB values
+         *
+         * @see #fromLinear(float[])
+         * @see #toLinear(float, float, float)
+         */
+        @NonNull
+        @Size(3)
+        public float[] fromLinear(float r, float g, float b) {
+            return fromLinear(new float[] { r, g, b });
+        }
+
+        /**
+         * <p>Encodes an RGB value from linear space to this color space's
+         * "gamma space". This is achieved by applying this color space's
+         * opto-electronic transfer function to the first 3 values of the
+         * supplied array. The result is stored back in the input array.</p>
+         *
+         * <p>Refer to the documentation of {@link ColorSpace.Rgb} for
+         * more information about transfer functions and their use for
+         * encoding and decoding RGB values.</p>
+         *
+         * @param v A non-null array of linear RGB values, its length
+         *          must be at least 3
+         * @return A new array of 3 floats containing non-linear RGB values
+         *
+         * @see #fromLinear(float[])
+         * @see #toLinear(float, float, float)
+         */
+        @NonNull
+        @Size(min = 3)
+        public float[] fromLinear(@NonNull @Size(min = 3) float[] v) {
+            v[0] = (float) mClampedOetf.applyAsDouble(v[0]);
+            v[1] = (float) mClampedOetf.applyAsDouble(v[1]);
+            v[2] = (float) mClampedOetf.applyAsDouble(v[2]);
+            return v;
+        }
+
+        @Override
+        @NonNull
+        @Size(min = 3)
+        public float[] toXyz(@NonNull @Size(min = 3) float[] v) {
+            v[0] = (float) mClampedEotf.applyAsDouble(v[0]);
+            v[1] = (float) mClampedEotf.applyAsDouble(v[1]);
+            v[2] = (float) mClampedEotf.applyAsDouble(v[2]);
+            return mul3x3Float3(mTransform, v);
+        }
+
+        @Override
+        @NonNull
+        @Size(min = 3)
+        public float[] fromXyz(@NonNull @Size(min = 3) float[] v) {
+            mul3x3Float3(mInverseTransform, v);
+            v[0] = (float) mClampedOetf.applyAsDouble(v[0]);
+            v[1] = (float) mClampedOetf.applyAsDouble(v[1]);
+            v[2] = (float) mClampedOetf.applyAsDouble(v[2]);
+            return v;
+        }
+
+        private double clamp(double x) {
+            return x < mMin ? mMin : x > mMax ? mMax : x;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            if (!super.equals(o)) return false;
+
+            Rgb rgb = (Rgb) o;
+
+            if (Float.compare(rgb.mMin, mMin) != 0) return false;
+            if (Float.compare(rgb.mMax, mMax) != 0) return false;
+            if (!Arrays.equals(mWhitePoint, rgb.mWhitePoint)) return false;
+            if (!Arrays.equals(mPrimaries, rgb.mPrimaries)) return false;
+            if (mTransferParameters != null) {
+                return mTransferParameters.equals(rgb.mTransferParameters);
+            } else if (rgb.mTransferParameters == null) {
+                return true;
+            }
+            //noinspection SimplifiableIfStatement
+            if (!mOetf.equals(rgb.mOetf)) return false;
+            return mEotf.equals(rgb.mEotf);
+        }
+
+        @Override
+        public int hashCode() {
+            int result = super.hashCode();
+            result = 31 * result + Arrays.hashCode(mWhitePoint);
+            result = 31 * result + Arrays.hashCode(mPrimaries);
+            result = 31 * result + (mMin != +0.0f ? Float.floatToIntBits(mMin) : 0);
+            result = 31 * result + (mMax != +0.0f ? Float.floatToIntBits(mMax) : 0);
+            result = 31 * result +
+                    (mTransferParameters != null ? mTransferParameters.hashCode() : 0);
+            if (mTransferParameters == null) {
+                result = 31 * result + mOetf.hashCode();
+                result = 31 * result + mEotf.hashCode();
+            }
+            return result;
+        }
+
+        /**
+         * Computes whether a color space is the sRGB color space or at least
+         * a close approximation.
+         *
+         * @param primaries The set of RGB primaries in xyY as an array of 6 floats
+         * @param whitePoint The white point in xyY as an array of 2 floats
+         * @param OETF The opto-electronic transfer function
+         * @param EOTF The electro-optical transfer function
+         * @param min The minimum value of the color space's range
+         * @param max The minimum value of the color space's range
+         * @param id The ID of the color space
+         * @return True if the color space can be considered as the sRGB color space
+         *
+         * @see #isSrgb()
+         */
+        @SuppressWarnings("RedundantIfStatement")
+        private static boolean isSrgb(
+                @NonNull @Size(6) float[] primaries,
+                @NonNull @Size(2) float[] whitePoint,
+                @NonNull DoubleUnaryOperator OETF,
+                @NonNull DoubleUnaryOperator EOTF,
+                float min,
+                float max,
+                @IntRange(from = MIN_ID, to = MAX_ID) int id) {
+            if (id == 0) return true;
+            if (!compare(primaries, SRGB_PRIMARIES)) {
+                return false;
+            }
+            if (!compare(whitePoint, ILLUMINANT_D65)) {
+                return false;
+            }
+            if (OETF.applyAsDouble(0.5) < 0.5001) return false;
+            if (EOTF.applyAsDouble(0.5) > 0.5001) return false;
+            if (min != 0.0f) return false;
+            if (max != 1.0f) return false;
+            return true;
+        }
+
+        /**
+         * Computes whether the specified CIE xyY or XYZ primaries (with Y set to 1) form
+         * a wide color gamut. A color gamut is considered wide if its area is &gt; 90%
+         * of the area of NTSC 1953 and if it contains the sRGB color gamut entirely.
+         * If the conditions above are not met, the color space is considered as having
+         * a wide color gamut if its range is larger than [0..1].
+         *
+         * @param primaries RGB primaries in CIE xyY as an array of 6 floats
+         * @param min The minimum value of the color space's range
+         * @param max The minimum value of the color space's range
+         * @return True if the color space has a wide gamut, false otherwise
+         *
+         * @see #isWideGamut()
+         * @see #area(float[])
+         */
+        private static boolean isWideGamut(@NonNull @Size(6) float[] primaries,
+                float min, float max) {
+            return (area(primaries) / area(NTSC_1953_PRIMARIES) > 0.9f &&
+                            contains(primaries, SRGB_PRIMARIES)) || (min < 0.0f && max > 1.0f);
+        }
+
+        /**
+         * Computes the area of the triangle represented by a set of RGB primaries
+         * in the CIE xyY space.
+         *
+         * @param primaries The triangle's vertices, as RGB primaries in an array of 6 floats
+         * @return The area of the triangle
+         *
+         * @see #isWideGamut(float[], float, float)
+         */
+        private static float area(@NonNull @Size(6) float[] primaries) {
+            float Rx = primaries[0];
+            float Ry = primaries[1];
+            float Gx = primaries[2];
+            float Gy = primaries[3];
+            float Bx = primaries[4];
+            float By = primaries[5];
+            float det = Rx * Gy + Ry * Bx + Gx * By - Gy * Bx - Ry * Gx - Rx * By;
+            float r = 0.5f * det;
+            return r < 0.0f ? -r : r;
+        }
+
+        /**
+         * Computes the cross product of two 2D vectors.
+         *
+         * @param ax The x coordinate of the first vector
+         * @param ay The y coordinate of the first vector
+         * @param bx The x coordinate of the second vector
+         * @param by The y coordinate of the second vector
+         * @return The result of a x b
+         */
+        private static float cross(float ax, float ay, float bx, float by) {
+            return ax * by - ay * bx;
+        }
+
+        /**
+         * Decides whether a 2D triangle, identified by the 6 coordinates of its
+         * 3 vertices, is contained within another 2D triangle, also identified
+         * by the 6 coordinates of its 3 vertices.
+         *
+         * In the illustration below, we want to test whether the RGB triangle
+         * is contained within the triangle XYZ formed by the 3 vertices at
+         * the "+" locations.
+         *
+         *                                     Y     .
+         *                                 .   +    .
+         *                                  .     ..
+         *                                   .   .
+         *                                    . .
+         *                                     .  G
+         *                                     *
+         *                                    * *
+         *                                  **   *
+         *                                 *      **
+         *                                *         *
+         *                              **           *
+         *                             *              *
+         *                            *                *
+         *                          **                  *
+         *                         *                     *
+         *                        *                       **
+         *                      **                          *   R    ...
+         *                     *                             *  .....
+         *                    *                         ***** ..
+         *                  **              ************       .   +
+         *              B  *    ************                    .   X
+         *           ......*****                                 .
+         *     ......    .                                        .
+         *             ..
+         *        +   .
+         *      Z    .
+         *
+         * RGB is contained within XYZ if all the following conditions are true
+         * (with "x" the cross product operator):
+         *
+         *   -->  -->
+         *   GR x RX >= 0
+         *   -->  -->
+         *   RX x BR >= 0
+         *   -->  -->
+         *   RG x GY >= 0
+         *   -->  -->
+         *   GY x RG >= 0
+         *   -->  -->
+         *   RB x BZ >= 0
+         *   -->  -->
+         *   BZ x GB >= 0
+         *
+         * @param p1 The enclosing triangle
+         * @param p2 The enclosed triangle
+         * @return True if the triangle p1 contains the triangle p2
+         *
+         * @see #isWideGamut(float[], float, float)
+         */
+        @SuppressWarnings("RedundantIfStatement")
+        private static boolean contains(@NonNull @Size(6) float[] p1, @NonNull @Size(6) float[] p2) {
+            // Translate the vertices p1 in the coordinates system
+            // with the vertices p2 as the origin
+            float[] p0 = new float[] {
+                    p1[0] - p2[0], p1[1] - p2[1],
+                    p1[2] - p2[2], p1[3] - p2[3],
+                    p1[4] - p2[4], p1[5] - p2[5],
+            };
+            // Check the first vertex of p1
+            if (cross(p0[0], p0[1], p2[0] - p2[4], p2[1] - p2[5]) < 0 ||
+                    cross(p2[0] - p2[2], p2[1] - p2[3], p0[0], p0[1]) < 0) {
+                return false;
+            }
+            // Check the second vertex of p1
+            if (cross(p0[2], p0[3], p2[2] - p2[0], p2[3] - p2[1]) < 0 ||
+                    cross(p2[2] - p2[4], p2[3] - p2[5], p0[2], p0[3]) < 0) {
+                return false;
+            }
+            // Check the third vertex of p1
+            if (cross(p0[4], p0[5], p2[4] - p2[2], p2[5] - p2[3]) < 0 ||
+                    cross(p2[4] - p2[0], p2[5] - p2[1], p0[4], p0[5]) < 0) {
+                return false;
+            }
+            return true;
+        }
+
+        /**
+         * Computes the primaries  of a color space identified only by
+         * its RGB->XYZ transform matrix. This method assumes that the
+         * range of the color space is [0..1].
+         *
+         * @param toXYZ The color space's 3x3 transform matrix to XYZ
+         * @return A new array of 6 floats containing the color space's
+         *         primaries in CIE xyY
+         */
+        @NonNull
+        @Size(6)
+        private static float[] computePrimaries(@NonNull @Size(9) float[] toXYZ) {
+            float[] r = mul3x3Float3(toXYZ, new float[] { 1.0f, 0.0f, 0.0f });
+            float[] g = mul3x3Float3(toXYZ, new float[] { 0.0f, 1.0f, 0.0f });
+            float[] b = mul3x3Float3(toXYZ, new float[] { 0.0f, 0.0f, 1.0f });
+
+            float rSum = r[0] + r[1] + r[2];
+            float gSum = g[0] + g[1] + g[2];
+            float bSum = b[0] + b[1] + b[2];
+
+            return new float[] {
+                    r[0] / rSum, r[1] / rSum,
+                    g[0] / gSum, g[1] / gSum,
+                    b[0] / bSum, b[1] / bSum,
+            };
+        }
+
+        /**
+         * Computes the white point of a color space identified only by
+         * its RGB->XYZ transform matrix. This method assumes that the
+         * range of the color space is [0..1].
+         *
+         * @param toXYZ The color space's 3x3 transform matrix to XYZ
+         * @return A new array of 2 floats containing the color space's
+         *         white point in CIE xyY
+         */
+        @NonNull
+        @Size(2)
+        private static float[] computeWhitePoint(@NonNull @Size(9) float[] toXYZ) {
+            float[] w = mul3x3Float3(toXYZ, new float[] { 1.0f, 1.0f, 1.0f });
+            float sum = w[0] + w[1] + w[2];
+            return new float[] { w[0] / sum, w[1] / sum };
+        }
+
+        /**
+         * Converts the specified RGB primaries point to xyY if needed. The primaries
+         * can be specified as an array of 6 floats (in CIE xyY) or 9 floats
+         * (in CIE XYZ). If no conversion is needed, the input array is copied.
+         *
+         * @param primaries The primaries in xyY or XYZ
+         * @return A new array of 6 floats containing the primaries in xyY
+         */
+        @NonNull
+        @Size(6)
+        private static float[] xyPrimaries(@NonNull @Size(min = 6, max = 9) float[] primaries) {
+            float[] xyPrimaries = new float[6];
+
+            // XYZ to xyY
+            if (primaries.length == 9) {
+                float sum;
+
+                sum = primaries[0] + primaries[1] + primaries[2];
+                xyPrimaries[0] = primaries[0] / sum;
+                xyPrimaries[1] = primaries[1] / sum;
+
+                sum = primaries[3] + primaries[4] + primaries[5];
+                xyPrimaries[2] = primaries[3] / sum;
+                xyPrimaries[3] = primaries[4] / sum;
+
+                sum = primaries[6] + primaries[7] + primaries[8];
+                xyPrimaries[4] = primaries[6] / sum;
+                xyPrimaries[5] = primaries[7] / sum;
+            } else {
+                System.arraycopy(primaries, 0, xyPrimaries, 0, 6);
+            }
+
+            return xyPrimaries;
+        }
+
+        /**
+         * Converts the specified white point to xyY if needed. The white point
+         * can be specified as an array of 2 floats (in CIE xyY) or 3 floats
+         * (in CIE XYZ). If no conversion is needed, the input array is copied.
+         *
+         * @param whitePoint The white point in xyY or XYZ
+         * @return A new array of 2 floats containing the white point in xyY
+         */
+        @NonNull
+        @Size(2)
+        private static float[] xyWhitePoint(@Size(min = 2, max = 3) float[] whitePoint) {
+            float[] xyWhitePoint = new float[2];
+
+            // XYZ to xyY
+            if (whitePoint.length == 3) {
+                float sum = whitePoint[0] + whitePoint[1] + whitePoint[2];
+                xyWhitePoint[0] = whitePoint[0] / sum;
+                xyWhitePoint[1] = whitePoint[1] / sum;
+            } else {
+                System.arraycopy(whitePoint, 0, xyWhitePoint, 0, 2);
+            }
+
+            return xyWhitePoint;
+        }
+
+        /**
+         * Computes the matrix that converts from RGB to XYZ based on RGB
+         * primaries and a white point, both specified in the CIE xyY space.
+         * The Y component of the primaries and white point is implied to be 1.
+         *
+         * @param primaries The RGB primaries in xyY, as an array of 6 floats
+         * @param whitePoint The white point in xyY, as an array of 2 floats
+         * @return A 3x3 matrix as a new array of 9 floats
+         */
+        @NonNull
+        @Size(9)
+        private static float[] computeXYZMatrix(
+                @NonNull @Size(6) float[] primaries,
+                @NonNull @Size(2) float[] whitePoint) {
+            float Rx = primaries[0];
+            float Ry = primaries[1];
+            float Gx = primaries[2];
+            float Gy = primaries[3];
+            float Bx = primaries[4];
+            float By = primaries[5];
+            float Wx = whitePoint[0];
+            float Wy = whitePoint[1];
+
+            float oneRxRy = (1 - Rx) / Ry;
+            float oneGxGy = (1 - Gx) / Gy;
+            float oneBxBy = (1 - Bx) / By;
+            float oneWxWy = (1 - Wx) / Wy;
+
+            float RxRy = Rx / Ry;
+            float GxGy = Gx / Gy;
+            float BxBy = Bx / By;
+            float WxWy = Wx / Wy;
+
+            float BY =
+                    ((oneWxWy - oneRxRy) * (GxGy - RxRy) - (WxWy - RxRy) * (oneGxGy - oneRxRy)) /
+                    ((oneBxBy - oneRxRy) * (GxGy - RxRy) - (BxBy - RxRy) * (oneGxGy - oneRxRy));
+            float GY = (WxWy - RxRy - BY * (BxBy - RxRy)) / (GxGy - RxRy);
+            float RY = 1 - GY - BY;
+
+            float RYRy = RY / Ry;
+            float GYGy = GY / Gy;
+            float BYBy = BY / By;
+
+            return new float[] {
+                    RYRy * Rx, RY, RYRy * (1 - Rx - Ry),
+                    GYGy * Gx, GY, GYGy * (1 - Gx - Gy),
+                    BYBy * Bx, BY, BYBy * (1 - Bx - By)
+            };
+        }
+    }
+
+    /**
+     * {@usesMathJax}
+     *
+     * <p>A connector transforms colors from a source color space to a destination
+     * color space.</p>
+     *
+     * <p>A source color space is connected to a destination color space using the
+     * color transform \(C\) computed from their respective transforms noted
+     * \(T_{src}\) and \(T_{dst}\) in the following equation:</p>
+     *
+     * $$C = T^{-1}_{dst} . T_{src}$$
+     *
+     * <p>The transform \(C\) shown above is only valid when the source and
+     * destination color spaces have the same profile connection space (PCS).
+     * We know that instances of {@link ColorSpace} always use CIE XYZ as their
+     * PCS but their white points might differ. When they do, we must perform
+     * a chromatic adaptation of the color spaces' transforms. To do so, we
+     * use the von Kries method described in the documentation of {@link Adaptation},
+     * using the CIE standard illuminant {@link ColorSpace#ILLUMINANT_D50 D50}
+     * as the target white point.</p>
+     *
+     * <p>Example of conversion from {@link Named#SRGB sRGB} to
+     * {@link Named#DCI_P3 DCI-P3}:</p>
+     *
+     * <pre class="prettyprint">
+     * ColorSpace.Connector connector = ColorSpace.connect(
+     *         ColorSpace.get(ColorSpace.Named.SRGB),
+     *         ColorSpace.get(ColorSpace.Named.DCI_P3));
+     * float[] p3 = connector.transform(1.0f, 0.0f, 0.0f);
+     * // p3 contains { 0.9473, 0.2740, 0.2076 }
+     * </pre>
+     *
+     * @see Adaptation
+     * @see ColorSpace#adapt(ColorSpace, float[], Adaptation)
+     * @see ColorSpace#adapt(ColorSpace, float[])
+     * @see ColorSpace#connect(ColorSpace, ColorSpace, RenderIntent)
+     * @see ColorSpace#connect(ColorSpace, ColorSpace)
+     * @see ColorSpace#connect(ColorSpace, RenderIntent)
+     * @see ColorSpace#connect(ColorSpace)
+     */
+    @AnyThread
+    public static class Connector {
+        @NonNull private final ColorSpace mSource;
+        @NonNull private final ColorSpace mDestination;
+        @NonNull private final ColorSpace mTransformSource;
+        @NonNull private final ColorSpace mTransformDestination;
+        @NonNull private final RenderIntent mIntent;
+        @NonNull @Size(3) private final float[] mTransform;
+
+        /**
+         * Creates a new connector between a source and a destination color space.
+         *
+         * @param source The source color space, cannot be null
+         * @param destination The destination color space, cannot be null
+         * @param intent The render intent to use when compressing gamuts
+         */
+        Connector(@NonNull ColorSpace source, @NonNull ColorSpace destination,
+                @NonNull RenderIntent intent) {
+            this(source, destination,
+                    source.getModel() == Model.RGB ? adapt(source, ILLUMINANT_D50_XYZ) : source,
+                    destination.getModel() == Model.RGB ?
+                            adapt(destination, ILLUMINANT_D50_XYZ) : destination,
+                    intent, computeTransform(source, destination, intent));
+        }
+
+        /**
+         * To connect between color spaces, we might need to use adapted transforms.
+         * This should be transparent to the user so this constructor takes the
+         * original source and destinations (returned by the getters), as well as
+         * possibly adapted color spaces used by transform().
+         */
+        private Connector(
+                @NonNull ColorSpace source, @NonNull ColorSpace destination,
+                @NonNull ColorSpace transformSource, @NonNull ColorSpace transformDestination,
+                @NonNull RenderIntent intent, @Nullable @Size(3) float[] transform) {
+            mSource = source;
+            mDestination = destination;
+            mTransformSource = transformSource;
+            mTransformDestination = transformDestination;
+            mIntent = intent;
+            mTransform = transform;
+        }
+
+        /**
+         * Computes an extra transform to apply in XYZ space depending on the
+         * selected rendering intent.
+         */
+        @Nullable
+        private static float[] computeTransform(@NonNull ColorSpace source,
+                @NonNull ColorSpace destination, @NonNull RenderIntent intent) {
+            if (intent != RenderIntent.ABSOLUTE) return null;
+
+            boolean srcRGB = source.getModel() == Model.RGB;
+            boolean dstRGB = destination.getModel() == Model.RGB;
+
+            if (srcRGB && dstRGB) return null;
+
+            if (srcRGB || dstRGB) {
+                ColorSpace.Rgb rgb = (ColorSpace.Rgb) (srcRGB ? source : destination);
+                float[] srcXYZ = srcRGB ? xyYToXyz(rgb.mWhitePoint) : ILLUMINANT_D50_XYZ;
+                float[] dstXYZ = dstRGB ? xyYToXyz(rgb.mWhitePoint) : ILLUMINANT_D50_XYZ;
+                return new float[] {
+                        srcXYZ[0] / dstXYZ[0],
+                        srcXYZ[1] / dstXYZ[1],
+                        srcXYZ[2] / dstXYZ[2],
+                };
+            }
+
+            return null;
+        }
+
+        /**
+         * Returns the source color space this connector will convert from.
+         *
+         * @return A non-null instance of {@link ColorSpace}
+         *
+         * @see #getDestination()
+         */
+        @NonNull
+        public ColorSpace getSource() {
+            return mSource;
+        }
+
+        /**
+         * Returns the destination color space this connector will convert to.
+         *
+         * @return A non-null instance of {@link ColorSpace}
+         *
+         * @see #getSource()
+         */
+        @NonNull
+        public ColorSpace getDestination() {
+            return mDestination;
+        }
+
+        /**
+         * Returns the render intent this connector will use when mapping the
+         * source color space to the destination color space.
+         *
+         * @return A non-null {@link RenderIntent}
+         *
+         * @see RenderIntent
+         */
+        public RenderIntent getRenderIntent() {
+            return mIntent;
+        }
+
+        /**
+         * <p>Transforms the specified color from the source color space
+         * to a color in the destination color space. This convenience
+         * method assumes a source color model with 3 components
+         * (typically RGB). To transform from color models with more than
+         * 3 components, such as {@link Model#CMYK CMYK}, use
+         * {@link #transform(float[])} instead.</p>
+         *
+         * @param r The red component of the color to transform
+         * @param g The green component of the color to transform
+         * @param b The blue component of the color to transform
+         * @return A new array of 3 floats containing the specified color
+         *         transformed from the source space to the destination space
+         *
+         * @see #transform(float[])
+         */
+        @NonNull
+        @Size(3)
+        public float[] transform(float r, float g, float b) {
+            return transform(new float[] { r, g, b });
+        }
+
+        /**
+         * <p>Transforms the specified color from the source color space
+         * to a color in the destination color space.</p>
+         *
+         * @param v A non-null array of 3 floats containing the value to transform
+         *            and that will hold the result of the transform
+         * @return The v array passed as a parameter, containing the specified color
+         *         transformed from the source space to the destination space
+         *
+         * @see #transform(float, float, float)
+         */
+        @NonNull
+        @Size(min = 3)
+        public float[] transform(@NonNull @Size(min = 3) float[] v) {
+            float[] xyz = mTransformSource.toXyz(v);
+            if (mTransform != null) {
+                xyz[0] *= mTransform[0];
+                xyz[1] *= mTransform[1];
+                xyz[2] *= mTransform[2];
+            }
+            return mTransformDestination.fromXyz(xyz);
+        }
+
+        /**
+         * Optimized connector for RGB->RGB conversions.
+         */
+        private static class Rgb extends Connector {
+            @NonNull private final ColorSpace.Rgb mSource;
+            @NonNull private final ColorSpace.Rgb mDestination;
+            @NonNull private final float[] mTransform;
+
+            Rgb(@NonNull ColorSpace.Rgb source, @NonNull ColorSpace.Rgb destination,
+                    @NonNull RenderIntent intent) {
+                super(source, destination, source, destination, intent, null);
+                mSource = source;
+                mDestination = destination;
+                mTransform = computeTransform(source, destination, intent);
+            }
+
+            @Override
+            public float[] transform(@NonNull @Size(min = 3) float[] rgb) {
+                rgb[0] = (float) mSource.mClampedEotf.applyAsDouble(rgb[0]);
+                rgb[1] = (float) mSource.mClampedEotf.applyAsDouble(rgb[1]);
+                rgb[2] = (float) mSource.mClampedEotf.applyAsDouble(rgb[2]);
+                mul3x3Float3(mTransform, rgb);
+                rgb[0] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[0]);
+                rgb[1] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[1]);
+                rgb[2] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[2]);
+                return rgb;
+            }
+
+            /**
+             * <p>Computes the color transform that connects two RGB color spaces.</p>
+             *
+             * <p>We can only connect color spaces if they use the same profile
+             * connection space. We assume the connection space is always
+             * CIE XYZ but we maye need to perform a chromatic adaptation to
+             * match the white points. If an adaptation is needed, we use the
+             * CIE standard illuminant D50. The unmatched color space is adapted
+             * using the von Kries transform and the {@link Adaptation#BRADFORD}
+             * matrix.</p>
+             *
+             * @param source The source color space, cannot be null
+             * @param destination The destination color space, cannot be null
+             * @param intent The render intent to use when compressing gamuts
+             * @return An array of 9 floats containing the 3x3 matrix transform
+             */
+            @NonNull
+            @Size(9)
+            private static float[] computeTransform(
+                    @NonNull ColorSpace.Rgb source,
+                    @NonNull ColorSpace.Rgb destination,
+                    @NonNull RenderIntent intent) {
+                if (compare(source.mWhitePoint, destination.mWhitePoint)) {
+                    // RGB->RGB using the PCS of both color spaces since they have the same
+                    return mul3x3(destination.mInverseTransform, source.mTransform);
+                } else {
+                    // RGB->RGB using CIE XYZ D50 as the PCS
+                    float[] transform = source.mTransform;
+                    float[] inverseTransform = destination.mInverseTransform;
+
+                    float[] srcXYZ = xyYToXyz(source.mWhitePoint);
+                    float[] dstXYZ = xyYToXyz(destination.mWhitePoint);
+
+                    if (!compare(source.mWhitePoint, ILLUMINANT_D50)) {
+                        float[] srcAdaptation = chromaticAdaptation(
+                                Adaptation.BRADFORD.mTransform, srcXYZ,
+                                Arrays.copyOf(ILLUMINANT_D50_XYZ, 3));
+                        transform = mul3x3(srcAdaptation, source.mTransform);
+                    }
+
+                    if (!compare(destination.mWhitePoint, ILLUMINANT_D50)) {
+                        float[] dstAdaptation = chromaticAdaptation(
+                                Adaptation.BRADFORD.mTransform, dstXYZ,
+                                Arrays.copyOf(ILLUMINANT_D50_XYZ, 3));
+                        inverseTransform = inverse3x3(mul3x3(dstAdaptation, destination.mTransform));
+                    }
+
+                    if (intent == RenderIntent.ABSOLUTE) {
+                        transform = mul3x3Diag(
+                                new float[] {
+                                        srcXYZ[0] / dstXYZ[0],
+                                        srcXYZ[1] / dstXYZ[1],
+                                        srcXYZ[2] / dstXYZ[2],
+                                }, transform);
+                    }
+
+                    return mul3x3(inverseTransform, transform);
+                }
+            }
+        }
+
+        /**
+         * Returns the identity connector for a given color space.
+         *
+         * @param source The source and destination color space
+         * @return A non-null connector that does not perform any transform
+         *
+         * @see ColorSpace#connect(ColorSpace, ColorSpace)
+         */
+        static Connector identity(ColorSpace source) {
+            return new Connector(source, source, RenderIntent.RELATIVE) {
+                @Override
+                public float[] transform(@NonNull @Size(min = 3) float[] v) {
+                    return v;
+                }
+            };
+        }
+    }
+
+    /**
+     * <p>A color space renderer can be used to visualize and compare the gamut and
+     * white point of one or more color spaces. The output is an sRGB {@link Bitmap}
+     * showing a CIE 1931 xyY or a CIE 1976 UCS chromaticity diagram.</p>
+     *
+     * <p>The following code snippet shows how to compare the {@link Named#SRGB}
+     * and {@link Named#DCI_P3} color spaces in a CIE 1931 diagram:</p>
+     *
+     * <pre class="prettyprint">
+     * Bitmap bitmap = ColorSpace.createRenderer()
+     *     .size(768)
+     *     .clip(true)
+     *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
+     *     .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
+     *     .render();
+     * </pre>
+     * <p>
+     *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_clipped.png" />
+     *     <figcaption style="text-align: center;">sRGB vs DCI-P3</figcaption>
+     * </p>
+     *
+     * <p>A renderer can also be used to show the location of specific colors,
+     * associated with a color space, in the CIE 1931 xyY chromaticity diagram.
+     * See {@link #add(ColorSpace, float, float, float, int)} for more information.</p>
+     *
+     * @see ColorSpace#createRenderer()
+     *
+     * @hide
+     */
+    public static class Renderer {
+        private static final int NATIVE_SIZE = 1440;
+        private static final float UCS_SCALE = 9.0f / 6.0f;
+
+        // Number of subdivision of the inside of the spectral locus
+        private static final int CHROMATICITY_RESOLUTION = 32;
+        private static final double ONE_THIRD = 1.0 / 3.0;
+
+        @IntRange(from = 128, to = Integer.MAX_VALUE)
+        private int mSize = 1024;
+
+        private boolean mShowWhitePoint = true;
+        private boolean mClip = false;
+        private boolean mUcs = false;
+
+        private final List<Pair<ColorSpace, Integer>> mColorSpaces = new ArrayList<>(2);
+        private final List<Point> mPoints = new ArrayList<>(0);
+
+        private Renderer() {
+        }
+
+        /**
+         * <p>Defines whether the chromaticity diagram should be clipped by the first
+         * registered color space. The default value is false.</p>
+         *
+         * <p>The following code snippet and image show the default behavior:</p>
+         * <pre class="prettyprint">
+         * Bitmap bitmap = ColorSpace.createRenderer()
+         *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
+         *     .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
+         *     .render();
+         * </pre>
+         * <p>
+         *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_comparison.png" />
+         *     <figcaption style="text-align: center;">Clipping disabled</figcaption>
+         * </p>
+         *
+         * <p>Here is the same example with clipping enabled:</p>
+         * <pre class="prettyprint">
+         * Bitmap bitmap = ColorSpace.createRenderer()
+         *     .clip(true)
+         *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
+         *     .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
+         *     .render();
+         * </pre>
+         * <p>
+         *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_clipped.png" />
+         *     <figcaption style="text-align: center;">Clipping enabled</figcaption>
+         * </p>
+         *
+         * @param clip True to clip the chromaticity diagram to the first registered color space,
+         *             false otherwise
+         * @return This instance of {@link Renderer}
+         */
+        @NonNull
+        public Renderer clip(boolean clip) {
+            mClip = clip;
+            return this;
+        }
+
+        /**
+         * <p>Defines whether the chromaticity diagram should use the uniform
+         * chromaticity scale (CIE 1976 UCS). When the uniform chromaticity scale
+         * is used, the distance between two points on the diagram is approximately
+         * proportional to the perceived color difference.</p>
+         *
+         * <p>The following code snippet shows how to enable the uniform chromaticity
+         * scale. The image below shows the result:</p>
+         * <pre class="prettyprint">
+         * Bitmap bitmap = ColorSpace.createRenderer()
+         *     .uniformChromaticityScale(true)
+         *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
+         *     .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
+         *     .render();
+         * </pre>
+         * <p>
+         *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_ucs.png" />
+         *     <figcaption style="text-align: center;">CIE 1976 UCS diagram</figcaption>
+         * </p>
+         *
+         * @param ucs True to render the chromaticity diagram as the CIE 1976 UCS diagram
+         * @return This instance of {@link Renderer}
+         */
+        @NonNull
+        public Renderer uniformChromaticityScale(boolean ucs) {
+            mUcs = ucs;
+            return this;
+        }
+
+        /**
+         * Sets the dimensions (width and height) in pixels of the output bitmap.
+         * The size must be at least 128px and defaults to 1024px.
+         *
+         * @param size The size in pixels of the output bitmap
+         * @return This instance of {@link Renderer}
+         */
+        @NonNull
+        public Renderer size(@IntRange(from = 128, to = Integer.MAX_VALUE) int size) {
+            mSize = Math.max(128, size);
+            return this;
+        }
+
+        /**
+         * Shows or hides the white point of each color space in the output bitmap.
+         * The default is true.
+         *
+         * @param show True to show the white point of each color space, false
+         *             otherwise
+         * @return This instance of {@link Renderer}
+         */
+        @NonNull
+        public Renderer showWhitePoint(boolean show) {
+            mShowWhitePoint = show;
+            return this;
+        }
+
+        /**
+         * <p>Adds a color space to represent on the output CIE 1931 chromaticity
+         * diagram. The color space is represented as a triangle showing the
+         * footprint of its color gamut and, optionally, the location of its
+         * white point.</p>
+         *
+         * <p class="note">Color spaces with a color model that is not RGB are
+         * accepted but ignored.</p>
+         *
+         * <p>The following code snippet and image show an example of calling this
+         * method to compare {@link Named#SRGB sRGB} and {@link Named#DCI_P3 DCI-P3}:</p>
+         * <pre class="prettyprint">
+         * Bitmap bitmap = ColorSpace.createRenderer()
+         *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
+         *     .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
+         *     .render();
+         * </pre>
+         * <p>
+         *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_comparison.png" />
+         *     <figcaption style="text-align: center;">sRGB vs DCI-P3</figcaption>
+         * </p>
+         *
+         * <p>Adding a color space extending beyond the boundaries of the
+         * spectral locus will alter the size of the diagram within the output
+         * bitmap as shown in this example:</p>
+         * <pre class="prettyprint">
+         * Bitmap bitmap = ColorSpace.createRenderer()
+         *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
+         *     .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
+         *     .add(ColorSpace.get(ColorSpace.Named.ACES), 0xff097ae9)
+         *     .add(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), 0xff000000)
+         *     .render();
+         * </pre>
+         * <p>
+         *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_comparison2.png" />
+         *     <figcaption style="text-align: center;">sRGB, DCI-P3, ACES and scRGB</figcaption>
+         * </p>
+         *
+         * @param colorSpace The color space whose gamut to render on the diagram
+         * @param color The sRGB color to use to render the color space's gamut and white point
+         * @return This instance of {@link Renderer}
+         *
+         * @see #clip(boolean)
+         * @see #showWhitePoint(boolean)
+         */
+        @NonNull
+        public Renderer add(@NonNull ColorSpace colorSpace, @ColorInt int color) {
+            mColorSpaces.add(new Pair<>(colorSpace, color));
+            return this;
+        }
+
+        /**
+         * <p>Adds a color to represent as a point on the chromaticity diagram.
+         * The color is associated with a color space which will be used to
+         * perform the conversion to CIE XYZ and compute the location of the point
+         * on the diagram. The point is rendered as a colored circle.</p>
+         *
+         * <p>The following code snippet and image show an example of calling this
+         * method to render the location of several sRGB colors as white circles:</p>
+         * <pre class="prettyprint">
+         * Bitmap bitmap = ColorSpace.createRenderer()
+         *     .clip(true)
+         *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
+         *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.0f, 0.1f, 0xffffffff)
+         *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.1f, 0.1f, 0xffffffff)
+         *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.2f, 0.1f, 0xffffffff)
+         *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.3f, 0.1f, 0xffffffff)
+         *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.4f, 0.1f, 0xffffffff)
+         *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.5f, 0.1f, 0xffffffff)
+         *     .render();
+         * </pre>
+         * <p>
+         *     <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_points.png" />
+         *     <figcaption style="text-align: center;">
+         *         Locating colors on the chromaticity diagram
+         *     </figcaption>
+         * </p>
+         *
+         * @param colorSpace The color space of the color to locate on the diagram
+         * @param r The first component of the color to locate on the diagram
+         * @param g The second component of the color to locate on the diagram
+         * @param b The third component of the color to locate on the diagram
+         * @param pointColor The sRGB color to use to render the point on the diagram
+         * @return This instance of {@link Renderer}
+         */
+        @NonNull
+        public Renderer add(@NonNull ColorSpace colorSpace, float r, float g, float b,
+                @ColorInt int pointColor) {
+            mPoints.add(new Point(colorSpace, new float[] { r, g, b }, pointColor));
+            return this;
+        }
+
+        /**
+         * <p>Renders the {@link #add(ColorSpace, int) color spaces} and
+         * {@link #add(ColorSpace, float, float, float, int) points} registered
+         * with this renderer. The output bitmap is an sRGB image with the
+         * dimensions specified by calling {@link #size(int)} (1204x1024px by
+         * default).</p>
+         *
+         * @return A new non-null {@link Bitmap} with the dimensions specified
+         *        by {@link #size(int)} (1024x1024 by default)
+         */
+        @NonNull
+        public Bitmap render() {
+            Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+            Bitmap bitmap = Bitmap.createBitmap(mSize, mSize, Bitmap.Config.ARGB_8888);
+            Canvas canvas = new Canvas(bitmap);
+
+            float[] primaries = new float[6];
+            float[] whitePoint = new float[2];
+
+            int width = NATIVE_SIZE;
+            int height = NATIVE_SIZE;
+
+            Path path = new Path();
+
+            setTransform(canvas, width, height, primaries);
+            drawBox(canvas, width, height, paint, path);
+            setUcsTransform(canvas, height);
+            drawLocus(canvas, width, height, paint, path, primaries);
+            drawGamuts(canvas, width, height, paint, path, primaries, whitePoint);
+            drawPoints(canvas, width, height, paint);
+
+            return bitmap;
+        }
+
+        /**
+         * Draws registered points at their correct position in the xyY coordinates.
+         * Each point is positioned according to its associated color space.
+         *
+         * @param canvas The canvas to transform
+         * @param width Width in pixel of the final image
+         * @param height Height in pixel of the final image
+         * @param paint A pre-allocated paint used to avoid temporary allocations
+         */
+        private void drawPoints(@NonNull Canvas canvas, int width, int height,
+                @NonNull Paint paint) {
+
+            paint.setStyle(Paint.Style.FILL);
+
+            float radius = 4.0f / (mUcs ? UCS_SCALE : 1.0f);
+
+            float[] v = new float[3];
+            float[] xy = new float[2];
+
+            for (final Point point : mPoints) {
+                v[0] = point.mRgb[0];
+                v[1] = point.mRgb[1];
+                v[2] = point.mRgb[2];
+                point.mColorSpace.toXyz(v);
+
+                paint.setColor(point.mColor);
+
+                // XYZ to xyY, assuming Y=1.0, then to L*u*v* if needed
+                float sum = v[0] + v[1] + v[2];
+                xy[0] = v[0] / sum;
+                xy[1] = v[1] / sum;
+                if (mUcs) xyYToUv(xy);
+
+                canvas.drawCircle(width * xy[0], height - height * xy[1], radius, paint);
+            }
+        }
+
+        /**
+         * Draws the color gamuts and white points of all the registered color
+         * spaces. Only color spaces with an RGB color model are rendered, the
+         * others are ignored.
+         *
+         * @param canvas The canvas to transform
+         * @param width Width in pixel of the final image
+         * @param height Height in pixel of the final image
+         * @param paint A pre-allocated paint used to avoid temporary allocations
+         * @param path A pre-allocated path used to avoid temporary allocations
+         * @param primaries A pre-allocated array of 6 floats to avoid temporary allocations
+         * @param whitePoint A pre-allocated array of 2 floats to avoid temporary allocations
+         */
+        private void drawGamuts(
+                @NonNull Canvas canvas, int width, int height,
+                @NonNull Paint paint, @NonNull Path path,
+                @NonNull @Size(6) float[] primaries, @NonNull @Size(2) float[] whitePoint) {
+
+            float radius = 4.0f / (mUcs ? UCS_SCALE : 1.0f);
+
+            for (final Pair<ColorSpace, Integer> item : mColorSpaces) {
+                ColorSpace colorSpace = item.first;
+                int color = item.second;
+
+                if (colorSpace.getModel() != Model.RGB) continue;
+
+                Rgb rgb = (Rgb) colorSpace;
+                getPrimaries(rgb, primaries, mUcs);
+
+                path.rewind();
+                path.moveTo(width * primaries[0], height - height * primaries[1]);
+                path.lineTo(width * primaries[2], height - height * primaries[3]);
+                path.lineTo(width * primaries[4], height - height * primaries[5]);
+                path.close();
+
+                paint.setStyle(Paint.Style.STROKE);
+                paint.setColor(color);
+                canvas.drawPath(path, paint);
+
+                // Draw the white point
+                if (mShowWhitePoint) {
+                    rgb.getWhitePoint(whitePoint);
+                    if (mUcs) xyYToUv(whitePoint);
+
+                    paint.setStyle(Paint.Style.FILL);
+                    paint.setColor(color);
+                    canvas.drawCircle(
+                            width * whitePoint[0], height - height * whitePoint[1], radius, paint);
+                }
+            }
+        }
+
+        /**
+         * Returns the primaries of the specified RGB color space. This method handles
+         * the special case of the {@link Named#EXTENDED_SRGB} family of color spaces.
+         *
+         * @param rgb The color space whose primaries to extract
+         * @param primaries A pre-allocated array of 6 floats that will hold the result
+         * @param asUcs True if the primaries should be returned in Luv, false for xyY
+         */
+        @NonNull
+        @Size(6)
+        private static void getPrimaries(@NonNull Rgb rgb,
+                @NonNull @Size(6) float[] primaries, boolean asUcs) {
+            // TODO: We should find a better way to handle these cases
+            if (rgb.equals(ColorSpace.get(Named.EXTENDED_SRGB)) ||
+                    rgb.equals(ColorSpace.get(Named.LINEAR_EXTENDED_SRGB))) {
+                primaries[0] = 1.41f;
+                primaries[1] = 0.33f;
+                primaries[2] = 0.27f;
+                primaries[3] = 1.24f;
+                primaries[4] = -0.23f;
+                primaries[5] = -0.57f;
+            } else {
+                rgb.getPrimaries(primaries);
+            }
+            if (asUcs) xyYToUv(primaries);
+        }
+
+        /**
+         * Draws the CIE 1931 chromaticity diagram: the spectral locus and its inside.
+         * This method respect the clip parameter.
+         *
+         * @param canvas The canvas to transform
+         * @param width Width in pixel of the final image
+         * @param height Height in pixel of the final image
+         * @param paint A pre-allocated paint used to avoid temporary allocations
+         * @param path A pre-allocated path used to avoid temporary allocations
+         * @param primaries A pre-allocated array of 6 floats to avoid temporary allocations
+         */
+        private void drawLocus(
+                @NonNull Canvas canvas, int width, int height, @NonNull Paint paint,
+                @NonNull Path path, @NonNull @Size(6) float[] primaries) {
+
+            int vertexCount = SPECTRUM_LOCUS_X.length * CHROMATICITY_RESOLUTION * 6;
+            float[] vertices = new float[vertexCount * 2];
+            int[] colors = new int[vertices.length];
+            computeChromaticityMesh(vertices, colors);
+
+            if (mUcs) xyYToUv(vertices);
+            for (int i = 0; i < vertices.length; i += 2) {
+                vertices[i] *= width;
+                vertices[i + 1] = height - vertices[i + 1] * height;
+            }
+
+            // Draw the spectral locus
+            if (mClip && mColorSpaces.size() > 0) {
+                for (final Pair<ColorSpace, Integer> item : mColorSpaces) {
+                    ColorSpace colorSpace = item.first;
+                    if (colorSpace.getModel() != Model.RGB) continue;
+
+                    Rgb rgb = (Rgb) colorSpace;
+                    getPrimaries(rgb, primaries, mUcs);
+
+                    break;
+                }
+
+                path.rewind();
+                path.moveTo(width * primaries[0], height - height * primaries[1]);
+                path.lineTo(width * primaries[2], height - height * primaries[3]);
+                path.lineTo(width * primaries[4], height - height * primaries[5]);
+                path.close();
+
+                int[] solid = new int[colors.length];
+                Arrays.fill(solid, 0xff6c6c6c);
+                canvas.drawVertices(Canvas.VertexMode.TRIANGLES, vertices.length, vertices, 0,
+                        null, 0, solid, 0, null, 0, 0, paint);
+
+                canvas.save();
+                canvas.clipPath(path);
+
+                canvas.drawVertices(Canvas.VertexMode.TRIANGLES, vertices.length, vertices, 0,
+                        null, 0, colors, 0, null, 0, 0, paint);
+
+                canvas.restore();
+            } else {
+                canvas.drawVertices(Canvas.VertexMode.TRIANGLES, vertices.length, vertices, 0,
+                        null, 0, colors, 0, null, 0, 0, paint);
+            }
+
+            // Draw the non-spectral locus
+            int index = (CHROMATICITY_RESOLUTION - 1) * 12;
+            path.reset();
+            path.moveTo(vertices[index], vertices[index + 1]);
+            for (int x = 2; x < SPECTRUM_LOCUS_X.length; x++) {
+                index += CHROMATICITY_RESOLUTION * 12;
+                path.lineTo(vertices[index], vertices[index + 1]);
+            }
+            path.close();
+
+            paint.setStrokeWidth(4.0f / (mUcs ? UCS_SCALE : 1.0f));
+            paint.setStyle(Paint.Style.STROKE);
+            paint.setColor(0xff000000);
+            canvas.drawPath(path, paint);
+        }
+
+        /**
+         * Draws the diagram box, including borders, tick marks, grid lines
+         * and axis labels.
+         *
+         * @param canvas The canvas to transform
+         * @param width Width in pixel of the final image
+         * @param height Height in pixel of the final image
+         * @param paint A pre-allocated paint used to avoid temporary allocations
+         * @param path A pre-allocated path used to avoid temporary allocations
+         */
+        private void drawBox(@NonNull Canvas canvas, int width, int height, @NonNull Paint paint,
+                @NonNull Path path) {
+
+            int lineCount = 10;
+            float scale = 1.0f;
+            if (mUcs) {
+                lineCount = 7;
+                scale = UCS_SCALE;
+            }
+
+            // Draw the unit grid
+            paint.setStyle(Paint.Style.STROKE);
+            paint.setStrokeWidth(2.0f);
+            paint.setColor(0xffc0c0c0);
+
+            for (int i = 1; i < lineCount - 1; i++) {
+                float v = i / 10.0f;
+                float x = (width * v) * scale;
+                float y = height - (height * v) * scale;
+
+                canvas.drawLine(0.0f, y, 0.9f * width, y, paint);
+                canvas.drawLine(x, height, x, 0.1f * height, paint);
+            }
+
+            // Draw tick marks
+            paint.setStrokeWidth(4.0f);
+            paint.setColor(0xff000000);
+            for (int i = 1; i < lineCount - 1; i++) {
+                float v = i / 10.0f;
+                float x = (width * v) * scale;
+                float y = height - (height * v) * scale;
+
+                canvas.drawLine(0.0f, y, width / 100.0f, y, paint);
+                canvas.drawLine(x, height, x, height - (height / 100.0f), paint);
+            }
+
+            // Draw the axis labels
+            paint.setStyle(Paint.Style.FILL);
+            paint.setTextSize(36.0f);
+            paint.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL));
+
+            Rect bounds = new Rect();
+            for (int i = 1; i < lineCount - 1; i++) {
+                String text = "0." + i;
+                paint.getTextBounds(text, 0, text.length(), bounds);
+
+                float v = i / 10.0f;
+                float x = (width * v) * scale;
+                float y = height - (height * v) * scale;
+
+                canvas.drawText(text, -0.05f * width + 10, y + bounds.height() / 2.0f, paint);
+                canvas.drawText(text, x - bounds.width() / 2.0f,
+                        height + bounds.height() + 16, paint);
+            }
+            paint.setStyle(Paint.Style.STROKE);
+
+            // Draw the diagram box
+            path.moveTo(0.0f, height);
+            path.lineTo(0.9f * width, height);
+            path.lineTo(0.9f * width, 0.1f * height);
+            path.lineTo(0.0f, 0.1f * height);
+            path.close();
+            canvas.drawPath(path, paint);
+        }
+
+        /**
+         * Computes and applies the Canvas transforms required to make the color
+         * gamut of each color space visible in the final image.
+         *
+         * @param canvas The canvas to transform
+         * @param width Width in pixel of the final image
+         * @param height Height in pixel of the final image
+         * @param primaries Array of 6 floats used to avoid temporary allocations
+         */
+        private void setTransform(@NonNull Canvas canvas, int width, int height,
+                @NonNull @Size(6) float[] primaries) {
+
+            RectF primariesBounds = new RectF();
+            for (final Pair<ColorSpace, Integer> item : mColorSpaces) {
+                ColorSpace colorSpace = item.first;
+                if (colorSpace.getModel() != Model.RGB) continue;
+
+                Rgb rgb = (Rgb) colorSpace;
+                getPrimaries(rgb, primaries, mUcs);
+
+                primariesBounds.left = Math.min(primariesBounds.left, primaries[4]);
+                primariesBounds.top = Math.min(primariesBounds.top, primaries[5]);
+                primariesBounds.right = Math.max(primariesBounds.right, primaries[0]);
+                primariesBounds.bottom = Math.max(primariesBounds.bottom, primaries[3]);
+            }
+
+            float max = mUcs ? 0.6f : 0.9f;
+
+            primariesBounds.left = Math.min(0.0f, primariesBounds.left);
+            primariesBounds.top = Math.min(0.0f, primariesBounds.top);
+            primariesBounds.right = Math.max(max, primariesBounds.right);
+            primariesBounds.bottom = Math.max(max, primariesBounds.bottom);
+
+            float scaleX = max / primariesBounds.width();
+            float scaleY = max / primariesBounds.height();
+            float scale = Math.min(scaleX, scaleY);
+
+            canvas.scale(mSize / (float) NATIVE_SIZE, mSize / (float) NATIVE_SIZE);
+            canvas.scale(scale, scale);
+            canvas.translate(
+                    (primariesBounds.width() - max) * width / 2.0f,
+                    (primariesBounds.height() - max) * height / 2.0f);
+
+            // The spectrum extends ~0.85 vertically and ~0.65 horizontally
+            // We shift the canvas a little bit to get nicer margins
+            canvas.translate(0.05f * width, -0.05f * height);
+        }
+
+        /**
+         * Computes and applies the Canvas transforms required to render the CIE
+         * 197 UCS chromaticity diagram.
+         *
+         * @param canvas The canvas to transform
+         * @param height Height in pixel of the final image
+         */
+        private void setUcsTransform(@NonNull Canvas canvas, int height) {
+            if (mUcs) {
+                canvas.translate(0.0f, (height - height * UCS_SCALE));
+                canvas.scale(UCS_SCALE, UCS_SCALE);
+            }
+        }
+
+        // X coordinates of the spectral locus in CIE 1931
+        private static final float[] SPECTRUM_LOCUS_X = {
+                0.175596f, 0.172787f, 0.170806f, 0.170085f, 0.160343f,
+                0.146958f, 0.139149f, 0.133536f, 0.126688f, 0.115830f,
+                0.109616f, 0.099146f, 0.091310f, 0.078130f, 0.068717f,
+                0.054675f, 0.040763f, 0.027497f, 0.016270f, 0.008169f,
+                0.004876f, 0.003983f, 0.003859f, 0.004646f, 0.007988f,
+                0.013870f, 0.022244f, 0.027273f, 0.032820f, 0.038851f,
+                0.045327f, 0.052175f, 0.059323f, 0.066713f, 0.074299f,
+                0.089937f, 0.114155f, 0.138695f, 0.154714f, 0.192865f,
+                0.229607f, 0.265760f, 0.301588f, 0.337346f, 0.373083f,
+                0.408717f, 0.444043f, 0.478755f, 0.512467f, 0.544767f,
+                0.575132f, 0.602914f, 0.627018f, 0.648215f, 0.665746f,
+                0.680061f, 0.691487f, 0.700589f, 0.707901f, 0.714015f,
+                0.719017f, 0.723016f, 0.734674f, 0.717203f, 0.699732f,
+                0.682260f, 0.664789f, 0.647318f, 0.629847f, 0.612376f,
+                0.594905f, 0.577433f, 0.559962f, 0.542491f, 0.525020f,
+                0.507549f, 0.490077f, 0.472606f, 0.455135f, 0.437664f,
+                0.420193f, 0.402721f, 0.385250f, 0.367779f, 0.350308f,
+                0.332837f, 0.315366f, 0.297894f, 0.280423f, 0.262952f,
+                0.245481f, 0.228010f, 0.210538f, 0.193067f, 0.175596f
+        };
+        // Y coordinates of the spectral locus in CIE 1931
+        private static final float[] SPECTRUM_LOCUS_Y = {
+                0.005295f, 0.004800f, 0.005472f, 0.005976f, 0.014496f,
+                0.026643f, 0.035211f, 0.042704f, 0.053441f, 0.073601f,
+                0.086866f, 0.112037f, 0.132737f, 0.170464f, 0.200773f,
+                0.254155f, 0.317049f, 0.387997f, 0.463035f, 0.538504f,
+                0.587196f, 0.610526f, 0.654897f, 0.675970f, 0.715407f,
+                0.750246f, 0.779682f, 0.792153f, 0.802971f, 0.812059f,
+                0.819430f, 0.825200f, 0.829460f, 0.832306f, 0.833833f,
+                0.833316f, 0.826231f, 0.814796f, 0.805884f, 0.781648f,
+                0.754347f, 0.724342f, 0.692326f, 0.658867f, 0.624470f,
+                0.589626f, 0.554734f, 0.520222f, 0.486611f, 0.454454f,
+                0.424252f, 0.396516f, 0.372510f, 0.351413f, 0.334028f,
+                0.319765f, 0.308359f, 0.299317f, 0.292044f, 0.285945f,
+                0.280951f, 0.276964f, 0.265326f, 0.257200f, 0.249074f,
+                0.240948f, 0.232822f, 0.224696f, 0.216570f, 0.208444f,
+                0.200318f, 0.192192f, 0.184066f, 0.175940f, 0.167814f,
+                0.159688f, 0.151562f, 0.143436f, 0.135311f, 0.127185f,
+                0.119059f, 0.110933f, 0.102807f, 0.094681f, 0.086555f,
+                0.078429f, 0.070303f, 0.062177f, 0.054051f, 0.045925f,
+                0.037799f, 0.029673f, 0.021547f, 0.013421f, 0.005295f
+        };
+
+        /**
+         * Computes a 2D mesh representation of the CIE 1931 chromaticity
+         * diagram.
+         *
+         * @param vertices Array of floats that will hold the mesh vertices
+         * @param colors Array of floats that will hold the mesh colors
+         */
+        private static void computeChromaticityMesh(@NonNull float[] vertices,
+                @NonNull int[] colors) {
+
+            ColorSpace colorSpace = get(Named.SRGB);
+
+            float[] color = new float[3];
+
+            int vertexIndex = 0;
+            int colorIndex = 0;
+
+            for (int x = 0; x < SPECTRUM_LOCUS_X.length; x++) {
+                int nextX = (x % (SPECTRUM_LOCUS_X.length - 1)) + 1;
+
+                float a1 = (float) Math.atan2(
+                        SPECTRUM_LOCUS_Y[x] - ONE_THIRD,
+                        SPECTRUM_LOCUS_X[x] - ONE_THIRD);
+                float a2 = (float) Math.atan2(
+                        SPECTRUM_LOCUS_Y[nextX] - ONE_THIRD,
+                        SPECTRUM_LOCUS_X[nextX] - ONE_THIRD);
+
+                float radius1 = (float) Math.pow(
+                        sqr(SPECTRUM_LOCUS_X[x] - ONE_THIRD) +
+                                sqr(SPECTRUM_LOCUS_Y[x] - ONE_THIRD),
+                        0.5);
+                float radius2 = (float) Math.pow(
+                        sqr(SPECTRUM_LOCUS_X[nextX] - ONE_THIRD) +
+                                sqr(SPECTRUM_LOCUS_Y[nextX] - ONE_THIRD),
+                        0.5);
+
+                // Compute patches; each patch is a quad with a different
+                // color associated with each vertex
+                for (int c = 1; c <= CHROMATICITY_RESOLUTION; c++) {
+                    float f1 = c / (float) CHROMATICITY_RESOLUTION;
+                    float f2 = (c - 1) / (float) CHROMATICITY_RESOLUTION;
+
+                    double cr1 = radius1 * Math.cos(a1);
+                    double sr1 = radius1 * Math.sin(a1);
+                    double cr2 = radius2 * Math.cos(a2);
+                    double sr2 = radius2 * Math.sin(a2);
+
+                    // Compute the XYZ coordinates of the 4 vertices of the patch
+                    float v1x = (float) (ONE_THIRD + cr1 * f1);
+                    float v1y = (float) (ONE_THIRD + sr1 * f1);
+                    float v1z = 1 - v1x - v1y;
+
+                    float v2x = (float) (ONE_THIRD + cr1 * f2);
+                    float v2y = (float) (ONE_THIRD + sr1 * f2);
+                    float v2z = 1 - v2x - v2y;
+
+                    float v3x = (float) (ONE_THIRD + cr2 * f2);
+                    float v3y = (float) (ONE_THIRD + sr2 * f2);
+                    float v3z = 1 - v3x - v3y;
+
+                    float v4x = (float) (ONE_THIRD + cr2 * f1);
+                    float v4y = (float) (ONE_THIRD + sr2 * f1);
+                    float v4z = 1 - v4x - v4y;
+
+                    // Compute the sRGB representation of each XYZ coordinate of the patch
+                    colors[colorIndex    ] = computeColor(color, v1x, v1y, v1z, colorSpace);
+                    colors[colorIndex + 1] = computeColor(color, v2x, v2y, v2z, colorSpace);
+                    colors[colorIndex + 2] = computeColor(color, v3x, v3y, v3z, colorSpace);
+                    colors[colorIndex + 3] = colors[colorIndex];
+                    colors[colorIndex + 4] = colors[colorIndex + 2];
+                    colors[colorIndex + 5] = computeColor(color, v4x, v4y, v4z, colorSpace);
+                    colorIndex += 6;
+
+                    // Flip the mesh upside down to match Canvas' coordinates system
+                    vertices[vertexIndex++] = v1x;
+                    vertices[vertexIndex++] = v1y;
+                    vertices[vertexIndex++] = v2x;
+                    vertices[vertexIndex++] = v2y;
+                    vertices[vertexIndex++] = v3x;
+                    vertices[vertexIndex++] = v3y;
+                    vertices[vertexIndex++] = v1x;
+                    vertices[vertexIndex++] = v1y;
+                    vertices[vertexIndex++] = v3x;
+                    vertices[vertexIndex++] = v3y;
+                    vertices[vertexIndex++] = v4x;
+                    vertices[vertexIndex++] = v4y;
+                }
+            }
+        }
+
+        @ColorInt
+        private static int computeColor(@NonNull @Size(3) float[] color,
+                float x, float y, float z, @NonNull ColorSpace cs) {
+            color[0] = x;
+            color[1] = y;
+            color[2] = z;
+            cs.fromXyz(color);
+            return 0xff000000 |
+                    (((int) (color[0] * 255.0f) & 0xff) << 16) |
+                    (((int) (color[1] * 255.0f) & 0xff) <<  8) |
+                    (((int) (color[2] * 255.0f) & 0xff)      );
+        }
+
+        private static double sqr(double v) {
+            return v * v;
+        }
+
+        private static class Point {
+            @NonNull final ColorSpace mColorSpace;
+            @NonNull final float[] mRgb;
+            final int mColor;
+
+            Point(@NonNull ColorSpace colorSpace,
+                    @NonNull @Size(3) float[] rgb, @ColorInt int color) {
+                mColorSpace = colorSpace;
+                mRgb = rgb;
+                mColor = color;
+            }
+        }
+    }
+}
diff --git a/android/graphics/ComposePathEffect.java b/android/graphics/ComposePathEffect.java
new file mode 100644
index 0000000..3fc9eb5
--- /dev/null
+++ b/android/graphics/ComposePathEffect.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2007 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;
+
+public class ComposePathEffect extends PathEffect {
+
+    /**
+     * Construct a PathEffect whose effect is to apply first the inner effect
+     * and the the outer pathEffect (e.g. outer(inner(path))).
+     */
+    public ComposePathEffect(PathEffect outerpe, PathEffect innerpe) {
+        native_instance = nativeCreate(outerpe.native_instance,
+                                       innerpe.native_instance);
+    }
+    
+    private static native long nativeCreate(long nativeOuterpe,
+                                            long nativeInnerpe);
+}
+
diff --git a/android/graphics/ComposePathEffect_Delegate.java b/android/graphics/ComposePathEffect_Delegate.java
new file mode 100644
index 0000000..bc3df7d
--- /dev/null
+++ b/android/graphics/ComposePathEffect_Delegate.java
@@ -0,0 +1,71 @@
+/*
+ * 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;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.awt.Stroke;
+
+/**
+ * Delegate implementing the native methods of android.graphics.ComposePathEffect
+ *
+ * Through the layoutlib_create tool, the original native methods of ComposePathEffect have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original ComposePathEffect class.
+ *
+ * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager},
+ * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}.
+ *
+ * @see PathEffect_Delegate
+ *
+ */
+public class ComposePathEffect_Delegate extends PathEffect_Delegate {
+
+    // ---- delegate data ----
+
+    // ---- Public Helper methods ----
+
+    @Override
+    public Stroke getStroke(Paint_Delegate paint) {
+        // FIXME
+        return null;
+    }
+
+    @Override
+    public boolean isSupported() {
+        return false;
+    }
+
+    @Override
+    public String getSupportMessage() {
+        return "Compose Path Effects are not supported in Layout Preview mode.";
+    }
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeCreate(long outerpe, long innerpe) {
+        ComposePathEffect_Delegate newDelegate = new ComposePathEffect_Delegate();
+        return sManager.addNewDelegate(newDelegate);
+    }
+
+    // ---- Private delegate/helper methods ----
+}
diff --git a/android/graphics/ComposeShader.java b/android/graphics/ComposeShader.java
new file mode 100644
index 0000000..70a5f53
--- /dev/null
+++ b/android/graphics/ComposeShader.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2007 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;
+
+
+import android.annotation.NonNull;
+
+/** A subclass of shader that returns the composition of two other shaders, combined by
+    an {@link android.graphics.Xfermode} subclass.
+*/
+public class ComposeShader extends Shader {
+
+    Shader mShaderA;
+    private long mNativeInstanceShaderA;
+    Shader mShaderB;
+    private long mNativeInstanceShaderB;
+    private int mPorterDuffMode;
+
+    /**
+     * Create a new compose shader, given shaders A, B, and a combining mode.
+     * When the mode is applied, it will be given the result from shader A as its
+     * "dst", and the result from shader B as its "src".
+     *
+     * @param shaderA  The colors from this shader are seen as the "dst" by the mode
+     * @param shaderB  The colors from this shader are seen as the "src" by the mode
+     * @param mode     The mode that combines the colors from the two shaders. If mode
+     *                 is null, then SRC_OVER is assumed.
+    */
+    public ComposeShader(@NonNull Shader shaderA, @NonNull Shader shaderB, @NonNull Xfermode mode) {
+        this(shaderA, shaderB, mode.porterDuffMode);
+    }
+
+    /**
+     * Create a new compose shader, given shaders A, B, and a combining PorterDuff mode.
+     * When the mode is applied, it will be given the result from shader A as its
+     * "dst", and the result from shader B as its "src".
+     *
+     * @param shaderA  The colors from this shader are seen as the "dst" by the mode
+     * @param shaderB  The colors from this shader are seen as the "src" by the mode
+     * @param mode     The PorterDuff mode that combines the colors from the two shaders.
+    */
+    public ComposeShader(@NonNull Shader shaderA, @NonNull Shader shaderB,
+            @NonNull PorterDuff.Mode mode) {
+        this(shaderA, shaderB, mode.nativeInt);
+    }
+
+    private ComposeShader(Shader shaderA, Shader shaderB, int nativeMode) {
+        if (shaderA == null || shaderB == null) {
+            throw new IllegalArgumentException("Shader parameters must not be null");
+        }
+
+        mShaderA = shaderA;
+        mShaderB = shaderB;
+        mPorterDuffMode = nativeMode;
+    }
+
+    @Override
+    long createNativeInstance(long nativeMatrix) {
+        mNativeInstanceShaderA = mShaderA.getNativeInstance();
+        mNativeInstanceShaderB = mShaderB.getNativeInstance();
+        return nativeCreate(nativeMatrix,
+                mShaderA.getNativeInstance(), mShaderB.getNativeInstance(), mPorterDuffMode);
+    }
+
+    /** @hide */
+    @Override
+    protected void verifyNativeInstance() {
+        if (mShaderA.getNativeInstance() != mNativeInstanceShaderA
+                || mShaderB.getNativeInstance() != mNativeInstanceShaderB) {
+            // Child shader native instance has been updated,
+            // so our cached native instance is no longer valid - discard it
+            discardNativeInstance();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    protected Shader copy() {
+        final ComposeShader copy = new ComposeShader(
+                mShaderA.copy(), mShaderB.copy(), mPorterDuffMode);
+        copyLocalMatrix(copy);
+        return copy;
+    }
+
+    private static native long nativeCreate(long nativeMatrix,
+            long nativeShaderA, long nativeShaderB, int porterDuffMode);
+}
diff --git a/android/graphics/ComposeShader_Delegate.java b/android/graphics/ComposeShader_Delegate.java
new file mode 100644
index 0000000..ab37968
--- /dev/null
+++ b/android/graphics/ComposeShader_Delegate.java
@@ -0,0 +1,79 @@
+/*
+ * 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;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.awt.Paint;
+
+/**
+ * Delegate implementing the native methods of android.graphics.ComposeShader
+ *
+ * Through the layoutlib_create tool, the original native methods of ComposeShader have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original ComposeShader class.
+ *
+ * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager},
+ * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}.
+ *
+ * @see Shader_Delegate
+ *
+ */
+public class ComposeShader_Delegate extends Shader_Delegate {
+
+    // ---- delegate data ----
+
+    // ---- Public Helper methods ----
+
+    @Override
+    public Paint getJavaPaint() {
+        // FIXME
+        return null;
+    }
+
+    @Override
+    public boolean isSupported() {
+        return false;
+    }
+
+    @Override
+    public String getSupportMessage() {
+        return "Compose Shaders are not supported in Layout Preview mode.";
+    }
+
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeCreate(long nativeMatrix, long native_shaderA,
+            long native_shaderB, int native_mode) {
+        // FIXME not supported yet.
+        ComposeShader_Delegate newDelegate = new ComposeShader_Delegate(nativeMatrix);
+        return sManager.addNewDelegate(newDelegate);
+    }
+
+
+    // ---- Private delegate/helper methods ----
+
+    private ComposeShader_Delegate(long nativeMatrix) {
+        super(nativeMatrix);
+    }
+}
diff --git a/android/graphics/CornerPathEffect.java b/android/graphics/CornerPathEffect.java
new file mode 100644
index 0000000..8f4d7d9
--- /dev/null
+++ b/android/graphics/CornerPathEffect.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2007 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;
+
+public class CornerPathEffect extends PathEffect {
+
+    /**
+     * Transforms geometries that are drawn (either STROKE or FILL styles) by
+     * replacing any sharp angles between line segments into rounded angles of
+     * the specified radius.
+     * @param radius Amount to round sharp angles between line segments.
+     */
+    public CornerPathEffect(float radius) {
+        native_instance = nativeCreate(radius);
+    }
+    
+    private static native long nativeCreate(float radius);
+}
+
diff --git a/android/graphics/CornerPathEffect_Delegate.java b/android/graphics/CornerPathEffect_Delegate.java
new file mode 100644
index 0000000..73745c3
--- /dev/null
+++ b/android/graphics/CornerPathEffect_Delegate.java
@@ -0,0 +1,71 @@
+/*
+ * 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;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.awt.Stroke;
+
+/**
+ * Delegate implementing the native methods of android.graphics.CornerPathEffect
+ *
+ * Through the layoutlib_create tool, the original native methods of CornerPathEffect have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original CornerPathEffect class.
+ *
+ * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager},
+ * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}.
+ *
+ * @see PathEffect_Delegate
+ *
+ */
+public class CornerPathEffect_Delegate extends PathEffect_Delegate {
+
+    // ---- delegate data ----
+
+    // ---- Public Helper methods ----
+
+    @Override
+    public Stroke getStroke(Paint_Delegate paint) {
+        // FIXME
+        return null;
+    }
+
+    @Override
+    public boolean isSupported() {
+        return false;
+    }
+
+    @Override
+    public String getSupportMessage() {
+        return "Corner Path Effects are not supported in Layout Preview mode.";
+    }
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeCreate(float radius) {
+        CornerPathEffect_Delegate newDelegate = new CornerPathEffect_Delegate();
+        return sManager.addNewDelegate(newDelegate);
+    }
+
+    // ---- Private delegate/helper methods ----
+}
diff --git a/android/graphics/DashPathEffect.java b/android/graphics/DashPathEffect.java
new file mode 100644
index 0000000..ef3ebe8
--- /dev/null
+++ b/android/graphics/DashPathEffect.java
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+public class DashPathEffect extends PathEffect {
+
+    /**
+     * The intervals array must contain an even number of entries (>=2), with
+     * the even indices specifying the "on" intervals, and the odd indices
+     * specifying the "off" intervals. phase is an offset into the intervals
+     * array (mod the sum of all of the intervals). The intervals array
+     * controls the length of the dashes. The paint's strokeWidth controls the
+     * thickness of the dashes.
+     * Note: this patheffect only affects drawing with the paint's style is set
+     * to STROKE or FILL_AND_STROKE. It is ignored if the drawing is done with
+     * style == FILL.
+     * @param intervals array of ON and OFF distances
+     * @param phase offset into the intervals array
+     */
+    public DashPathEffect(float intervals[], float phase) {
+        if (intervals.length < 2) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        native_instance = nativeCreate(intervals, phase);
+    }
+    
+    private static native long nativeCreate(float intervals[], float phase);
+}
+
diff --git a/android/graphics/DashPathEffect_Delegate.java b/android/graphics/DashPathEffect_Delegate.java
new file mode 100644
index 0000000..881afde
--- /dev/null
+++ b/android/graphics/DashPathEffect_Delegate.java
@@ -0,0 +1,89 @@
+/*
+ * 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;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.awt.BasicStroke;
+import java.awt.Stroke;
+
+/**
+ * Delegate implementing the native methods of android.graphics.DashPathEffect
+ *
+ * Through the layoutlib_create tool, the original native methods of DashPathEffect have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original DashPathEffect class.
+ *
+ * Because this extends {@link PathEffect_Delegate}, there's no need to use a
+ * {@link DelegateManager}, as all the PathEffect classes will be added to the manager owned by
+ * {@link PathEffect_Delegate}.
+ *
+ * @see PathEffect_Delegate
+ *
+ */
+public final class DashPathEffect_Delegate extends PathEffect_Delegate {
+
+    // ---- delegate data ----
+
+    private final float[] mIntervals;
+    private final float mPhase;
+
+    // ---- Public Helper methods ----
+
+    @Override
+    public Stroke getStroke(Paint_Delegate paint) {
+        return new BasicStroke(
+                paint.getStrokeWidth(),
+                paint.getJavaCap(),
+                paint.getJavaJoin(),
+                paint.getJavaStrokeMiter(),
+                mIntervals,
+                mPhase);
+    }
+
+    @Override
+    public boolean isSupported() {
+        return true;
+    }
+
+    @Override
+    public String getSupportMessage() {
+        // no message since isSupported returns true;
+        return null;
+    }
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeCreate(float intervals[], float phase) {
+        DashPathEffect_Delegate newDelegate = new DashPathEffect_Delegate(intervals, phase);
+        return sManager.addNewDelegate(newDelegate);
+    }
+
+    // ---- Private delegate/helper methods ----
+
+    private DashPathEffect_Delegate(float intervals[], float phase) {
+        mIntervals = new float[intervals.length];
+        System.arraycopy(intervals, 0, mIntervals, 0, intervals.length);
+        mPhase = phase;
+    }
+}
+
diff --git a/android/graphics/DiscretePathEffect.java b/android/graphics/DiscretePathEffect.java
new file mode 100644
index 0000000..3b3c9c9
--- /dev/null
+++ b/android/graphics/DiscretePathEffect.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2008 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;
+
+public class DiscretePathEffect extends PathEffect {
+
+    /**
+     * Chop the path into lines of segmentLength, randomly deviating from the
+     * original path by deviation.
+     */
+    public DiscretePathEffect(float segmentLength, float deviation) {
+        native_instance = nativeCreate(segmentLength, deviation);
+    }
+    
+    private static native long nativeCreate(float length, float deviation);
+}
+
diff --git a/android/graphics/DiscretePathEffect_Delegate.java b/android/graphics/DiscretePathEffect_Delegate.java
new file mode 100644
index 0000000..46109f3
--- /dev/null
+++ b/android/graphics/DiscretePathEffect_Delegate.java
@@ -0,0 +1,71 @@
+/*
+ * 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;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.awt.Stroke;
+
+/**
+ * Delegate implementing the native methods of android.graphics.DiscretePathEffect
+ *
+ * Through the layoutlib_create tool, the original native methods of DiscretePathEffect have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original DiscretePathEffect class.
+ *
+ * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager},
+ * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}.
+ *
+ * @see PathEffect_Delegate
+ *
+ */
+public class DiscretePathEffect_Delegate extends PathEffect_Delegate {
+
+    // ---- delegate data ----
+
+    // ---- Public Helper methods ----
+
+    @Override
+    public Stroke getStroke(Paint_Delegate paint) {
+        // FIXME
+        return null;
+    }
+
+    @Override
+    public boolean isSupported() {
+        return false;
+    }
+
+    @Override
+    public String getSupportMessage() {
+        return "Discrete Path Effects are not supported in Layout Preview mode.";
+    }
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeCreate(float length, float deviation) {
+        DiscretePathEffect_Delegate newDelegate = new DiscretePathEffect_Delegate();
+        return sManager.addNewDelegate(newDelegate);
+    }
+
+    // ---- Private delegate/helper methods ----
+}
diff --git a/android/graphics/DrawFilter.java b/android/graphics/DrawFilter.java
new file mode 100644
index 0000000..c7fdcb2
--- /dev/null
+++ b/android/graphics/DrawFilter.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2008 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;
+
+/**
+ * A DrawFilter subclass can be installed in a Canvas. When it is present, it
+ * can modify the paint that is used to draw (temporarily). With this, a filter
+ * can disable/enable antialiasing, or change the color for everything this is
+ * drawn.
+ */
+public class DrawFilter {
+
+    /**
+     * this is set by subclasses
+     * @hide
+     */
+    public long mNativeInt;
+
+    protected void finalize() throws Throwable {
+        try {
+            nativeDestructor(mNativeInt);
+            mNativeInt = 0;
+        } finally {
+            super.finalize();
+        }
+    }
+    
+    private static native void nativeDestructor(long nativeDrawFilter);
+}
+
diff --git a/android/graphics/DrawFilter_Delegate.java b/android/graphics/DrawFilter_Delegate.java
new file mode 100644
index 0000000..2e10740
--- /dev/null
+++ b/android/graphics/DrawFilter_Delegate.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.DrawFilter
+ *
+ * Through the layoutlib_create tool, the original native methods of DrawFilter have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original DrawFilter class.
+ *
+ * This also serve as a base class for all DrawFilter delegate classes.
+ *
+ * @see DelegateManager
+ *
+ */
+public abstract class DrawFilter_Delegate {
+
+    // ---- delegate manager ----
+    protected static final DelegateManager<DrawFilter_Delegate> sManager =
+            new DelegateManager<DrawFilter_Delegate>(DrawFilter_Delegate.class);
+
+    // ---- delegate helper data ----
+
+    // ---- delegate data ----
+
+    // ---- Public Helper methods ----
+
+    public static DrawFilter_Delegate getDelegate(long nativeDrawFilter) {
+        return sManager.getDelegate(nativeDrawFilter);
+    }
+
+    public abstract boolean isSupported();
+    public abstract String getSupportMessage();
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static void nativeDestructor(long nativeDrawFilter) {
+        sManager.removeJavaReferenceFor(nativeDrawFilter);
+    }
+
+    // ---- Private delegate/helper methods ----
+}
diff --git a/android/graphics/EmbossMaskFilter.java b/android/graphics/EmbossMaskFilter.java
new file mode 100644
index 0000000..a9e180f
--- /dev/null
+++ b/android/graphics/EmbossMaskFilter.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+public class EmbossMaskFilter extends MaskFilter {
+    /**
+     * Create an emboss maskfilter
+     *
+     * @param direction  array of 3 scalars [x, y, z] specifying the direction of the light source
+     * @param ambient    0...1 amount of ambient light
+     * @param specular   coefficient for specular highlights (e.g. 8)
+     * @param blurRadius amount to blur before applying lighting (e.g. 3)
+     * @return           the emboss maskfilter
+     */
+    public EmbossMaskFilter(float[] direction, float ambient, float specular, float blurRadius) {
+        if (direction.length < 3) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        native_instance = nativeConstructor(direction, ambient, specular, blurRadius);
+    }
+
+    private static native long nativeConstructor(float[] direction, float ambient, float specular, float blurRadius);
+}
+
diff --git a/android/graphics/EmbossMaskFilter_Delegate.java b/android/graphics/EmbossMaskFilter_Delegate.java
new file mode 100644
index 0000000..e5040cc
--- /dev/null
+++ b/android/graphics/EmbossMaskFilter_Delegate.java
@@ -0,0 +1,65 @@
+/*
+ * 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;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.EmbossMaskFilter
+ *
+ * Through the layoutlib_create tool, the original native methods of EmbossMaskFilter have
+ * been replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original EmbossMaskFilter class.
+ *
+ * Because this extends {@link MaskFilter_Delegate}, there's no need to use a
+ * {@link DelegateManager}, as all the Shader classes will be added to the manager
+ * owned by {@link MaskFilter_Delegate}.
+ *
+ * @see MaskFilter_Delegate
+ *
+ */
+public class EmbossMaskFilter_Delegate extends MaskFilter_Delegate {
+
+    // ---- delegate data ----
+
+    // ---- Public Helper methods ----
+
+    @Override
+    public boolean isSupported() {
+        return false;
+    }
+
+    @Override
+    public String getSupportMessage() {
+        return "Emboss Mask Filters are not supported.";
+    }
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeConstructor(float[] direction, float ambient,
+            float specular, float blurRadius) {
+        EmbossMaskFilter_Delegate newDelegate = new EmbossMaskFilter_Delegate();
+        return sManager.addNewDelegate(newDelegate);
+    }
+
+    // ---- Private delegate/helper methods ----
+}
diff --git a/android/graphics/FontFamily.java b/android/graphics/FontFamily.java
new file mode 100644
index 0000000..d77e601
--- /dev/null
+++ b/android/graphics/FontFamily.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2014 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;
+
+import android.annotation.Nullable;
+import android.content.res.AssetManager;
+import android.graphics.fonts.FontVariationAxis;
+import android.text.FontConfig;
+import android.text.TextUtils;
+import android.util.Log;
+import dalvik.annotation.optimization.CriticalNative;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+
+/**
+ * A family of typefaces with different styles.
+ *
+ * @hide
+ */
+public class FontFamily {
+
+    private static String TAG = "FontFamily";
+
+    /**
+     * @hide
+     */
+    public long mNativePtr;
+
+    // Points native font family builder. Must be zero after freezing this family.
+    private long mBuilderPtr;
+
+    public FontFamily() {
+        mBuilderPtr = nInitBuilder(null, 0);
+    }
+
+    public FontFamily(@Nullable String[] langs, int variant) {
+        final String langsString;
+        if (langs == null || langs.length == 0) {
+            langsString = null;
+        } else if (langs.length == 1) {
+            langsString = langs[0];
+        } else {
+            langsString = TextUtils.join(",", langs);
+        }
+        mBuilderPtr = nInitBuilder(langsString, variant);
+    }
+
+    /**
+     * Finalize the FontFamily creation.
+     *
+     * @return boolean returns false if some error happens in native code, e.g. broken font file is
+     *                 passed, etc.
+     */
+    public boolean freeze() {
+        if (mBuilderPtr == 0) {
+            throw new IllegalStateException("This FontFamily is already frozen");
+        }
+        mNativePtr = nCreateFamily(mBuilderPtr);
+        mBuilderPtr = 0;
+        return mNativePtr != 0;
+    }
+
+    public void abortCreation() {
+        if (mBuilderPtr == 0) {
+            throw new IllegalStateException("This FontFamily is already frozen or abandoned");
+        }
+        nAbort(mBuilderPtr);
+        mBuilderPtr = 0;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mNativePtr != 0) {
+                nUnrefFamily(mNativePtr);
+            }
+            if (mBuilderPtr != 0) {
+                nAbort(mBuilderPtr);
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
+    public boolean addFont(String path, int ttcIndex, FontVariationAxis[] axes, int weight,
+            int italic) {
+        if (mBuilderPtr == 0) {
+            throw new IllegalStateException("Unable to call addFont after freezing.");
+        }
+        try (FileInputStream file = new FileInputStream(path)) {
+            FileChannel fileChannel = file.getChannel();
+            long fontSize = fileChannel.size();
+            ByteBuffer fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
+            if (axes != null) {
+                for (FontVariationAxis axis : axes) {
+                    nAddAxisValue(mBuilderPtr, axis.getOpenTypeTagValue(), axis.getStyleValue());
+                }
+            }
+            return nAddFont(mBuilderPtr, fontBuffer, ttcIndex, weight, italic);
+        } catch (IOException e) {
+            Log.e(TAG, "Error mapping font file " + path);
+            return false;
+        }
+    }
+
+    public boolean addFontFromBuffer(ByteBuffer font, int ttcIndex, FontVariationAxis[] axes,
+            int weight, int italic) {
+        if (mBuilderPtr == 0) {
+            throw new IllegalStateException("Unable to call addFontWeightStyle after freezing.");
+        }
+        if (axes != null) {
+            for (FontVariationAxis axis : axes) {
+                nAddAxisValue(mBuilderPtr, axis.getOpenTypeTagValue(), axis.getStyleValue());
+            }
+        }
+        return nAddFontWeightStyle(mBuilderPtr, font, ttcIndex, weight, italic);
+    }
+
+    /**
+     * @param mgr The AssetManager to use for this context.
+     * @param path The path to the font file to load.
+     * @param cookie If available, the resource cookie given by Resources.
+     * @param isAsset {@code true} if this is from the assets/ folder, {@code false} if from
+     *            resources
+     * @param weight The weight of the font. If 0 is given, the weight and italic will be resolved
+     *            using the OS/2 table in the font.
+     * @param isItalic Whether this font is italic. If the weight is set to 0, this will be resolved
+     *            using the OS/2 table in the font.
+     * @return
+     */
+    public boolean addFontFromAssetManager(AssetManager mgr, String path, int cookie,
+            boolean isAsset, int ttcIndex, int weight, int isItalic,
+            FontVariationAxis[] axes) {
+        if (mBuilderPtr == 0) {
+            throw new IllegalStateException("Unable to call addFontFromAsset after freezing.");
+        }
+        if (axes != null) {
+            for (FontVariationAxis axis : axes) {
+                nAddAxisValue(mBuilderPtr, axis.getOpenTypeTagValue(), axis.getStyleValue());
+            }
+        }
+        return nAddFontFromAssetManager(mBuilderPtr, mgr, path, cookie, isAsset, ttcIndex, weight,
+                isItalic);
+    }
+
+    /**
+     * Allow creating unsupported FontFamily.
+     *
+     * For compatibility reasons, we still need to create a FontFamily object even if Minikin failed
+     * to find any usable 'cmap' table for some reasons, e.g. broken 'cmap' table, no 'cmap' table
+     * encoded with Unicode code points, etc. Without calling this method, the freeze() method will
+     * return null if Minikin fails to find any usable 'cmap' table. By calling this method, the
+     * freeze() won't fail and will create an empty FontFamily. This empty FontFamily is placed at
+     * the top of the fallback chain but is never used. if we don't create this empty FontFamily
+     * and put it at top, bad things (performance regressions, unexpected glyph selection) will
+     * happen.
+     */
+    public void allowUnsupportedFont() {
+        if (mBuilderPtr == 0) {
+            throw new IllegalStateException("Unable to allow unsupported font.");
+        }
+        nAllowUnsupportedFont(mBuilderPtr);
+    }
+
+    // TODO: Remove once internal user stop using private API.
+    private static boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex) {
+        return nAddFont(builderPtr, font, ttcIndex, -1, -1);
+    }
+
+    private static native long nInitBuilder(String langs, int variant);
+
+    @CriticalNative
+    private static native long nCreateFamily(long mBuilderPtr);
+
+    @CriticalNative
+    private static native void nAllowUnsupportedFont(long builderPtr);
+
+    @CriticalNative
+    private static native void nAbort(long mBuilderPtr);
+
+    @CriticalNative
+    private static native void nUnrefFamily(long nativePtr);
+    // By passing -1 to weigth argument, the weight value is resolved by OS/2 table in the font.
+    // By passing -1 to italic argument, the italic value is resolved by OS/2 table in the font.
+    private static native boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex,
+            int weight, int isItalic);
+    private static native boolean nAddFontWeightStyle(long builderPtr, ByteBuffer font,
+            int ttcIndex, int weight, int isItalic);
+    private static native boolean nAddFontFromAssetManager(long builderPtr, AssetManager mgr,
+            String path, int cookie, boolean isAsset, int ttcIndex, int weight, int isItalic);
+
+    // The added axis values are only valid for the next nAddFont* method call.
+    @CriticalNative
+    private static native void nAddAxisValue(long builderPtr, int tag, float value);
+}
diff --git a/android/graphics/FontFamily_Delegate.java b/android/graphics/FontFamily_Delegate.java
new file mode 100644
index 0000000..1e3ebd7
--- /dev/null
+++ b/android/graphics/FontFamily_Delegate.java
@@ -0,0 +1,557 @@
+/*
+ * Copyright (C) 2014 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;
+
+import com.android.ide.common.rendering.api.AssetRepository;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.AssetManager;
+import android.content.res.BridgeAssetManager;
+import android.graphics.fonts.FontVariationAxis;
+import android.text.FontConfig;
+
+import java.awt.Font;
+import java.awt.FontFormatException;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Scanner;
+import java.util.Set;
+
+import static android.graphics.Typeface.RESOLVE_BY_FONT_TABLE;
+import static android.graphics.Typeface_Delegate.SYSTEM_FONTS;
+
+/**
+ * Delegate implementing the native methods of android.graphics.FontFamily
+ *
+ * Through the layoutlib_create tool, the original native methods of FontFamily have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original FontFamily class.
+ *
+ * @see DelegateManager
+ */
+public class FontFamily_Delegate {
+
+    public static final int DEFAULT_FONT_WEIGHT = 400;
+    public static final int BOLD_FONT_WEIGHT_DELTA = 300;
+    public static final int BOLD_FONT_WEIGHT = 700;
+
+    private static final String FONT_SUFFIX_ITALIC = "Italic.ttf";
+    private static final String FN_ALL_FONTS_LIST = "fontsInSdk.txt";
+    private static final String EXTENSION_OTF = ".otf";
+
+    private static final int CACHE_SIZE = 10;
+    // The cache has a drawback that if the font file changed after the font object was created,
+    // we will not update it.
+    private static final Map<String, FontInfo> sCache =
+            new LinkedHashMap<String, FontInfo>(CACHE_SIZE) {
+        @Override
+        protected boolean removeEldestEntry(Map.Entry<String, FontInfo> eldest) {
+            return size() > CACHE_SIZE;
+        }
+
+        @Override
+        public FontInfo put(String key, FontInfo value) {
+            // renew this entry.
+            FontInfo removed = remove(key);
+            super.put(key, value);
+            return removed;
+        }
+    };
+
+    /**
+     * A class associating {@link Font} with its metadata.
+     */
+    private static final class FontInfo {
+        @Nullable
+        Font mFont;
+        int mWeight;
+        boolean mIsItalic;
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            FontInfo fontInfo = (FontInfo) o;
+            return mWeight == fontInfo.mWeight && mIsItalic == fontInfo.mIsItalic;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mWeight, mIsItalic);
+        }
+
+        @Override
+        public String toString() {
+            return "FontInfo{" + "mWeight=" + mWeight + ", mIsItalic=" + mIsItalic + '}';
+        }
+    }
+
+    // ---- delegate manager ----
+    private static final DelegateManager<FontFamily_Delegate> sManager =
+            new DelegateManager<FontFamily_Delegate>(FontFamily_Delegate.class);
+
+    // ---- delegate helper data ----
+    private static String sFontLocation;
+    private static final List<FontFamily_Delegate> sPostInitDelegate = new
+            ArrayList<FontFamily_Delegate>();
+    private static Set<String> SDK_FONTS;
+
+
+    // ---- delegate data ----
+
+    // Order does not really matter but we use a LinkedHashMap to get reproducible results across
+    // render calls
+    private Map<FontInfo, Font> mFonts = new LinkedHashMap<>();
+
+    /**
+     * The variant of the Font Family - compact or elegant.
+     * <p/>
+     * 0 is unspecified, 1 is compact and 2 is elegant. This needs to be kept in sync with values in
+     * android.graphics.FontFamily
+     *
+     * @see Paint#setElegantTextHeight(boolean)
+     */
+    private FontVariant mVariant;
+    // List of runnables to process fonts after sFontLoader is initialized.
+    private List<Runnable> mPostInitRunnables = new ArrayList<Runnable>();
+    /** @see #isValid() */
+    private boolean mValid = false;
+
+
+    // ---- Public helper class ----
+
+    public enum FontVariant {
+        // The order needs to be kept in sync with android.graphics.FontFamily.
+        NONE, COMPACT, ELEGANT
+    }
+
+    // ---- Public Helper methods ----
+
+    public static FontFamily_Delegate getDelegate(long nativeFontFamily) {
+        return sManager.getDelegate(nativeFontFamily);
+    }
+
+    public static synchronized void setFontLocation(String fontLocation) {
+        sFontLocation = fontLocation;
+        // init list of bundled fonts.
+        File allFonts = new File(fontLocation, FN_ALL_FONTS_LIST);
+        // Current number of fonts is 103. Use the next round number to leave scope for more fonts
+        // in the future.
+        Set<String> allFontsList = new HashSet<>(128);
+        Scanner scanner = null;
+        try {
+            scanner = new Scanner(allFonts);
+            while (scanner.hasNext()) {
+                String name = scanner.next();
+                // Skip font configuration files.
+                if (!name.endsWith(".xml")) {
+                    allFontsList.add(name);
+                }
+            }
+        } catch (FileNotFoundException e) {
+            Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+                    "Unable to load the list of fonts. Try re-installing the SDK Platform from the SDK Manager.",
+                    e, null);
+        } finally {
+            if (scanner != null) {
+                scanner.close();
+            }
+        }
+        SDK_FONTS = Collections.unmodifiableSet(allFontsList);
+        for (FontFamily_Delegate fontFamily : sPostInitDelegate) {
+            fontFamily.init();
+        }
+        sPostInitDelegate.clear();
+    }
+
+    @Nullable
+    public Font getFont(int desiredWeight, boolean isItalic) {
+        FontInfo desiredStyle = new FontInfo();
+        desiredStyle.mWeight = desiredWeight;
+        desiredStyle.mIsItalic = isItalic;
+
+        Font cachedFont = mFonts.get(desiredStyle);
+        if (cachedFont != null) {
+            return cachedFont;
+        }
+
+        FontInfo bestFont = null;
+
+        if (mFonts.size() == 1) {
+            // No need to compute the match since we only have one candidate
+            bestFont = mFonts.keySet().iterator().next();
+        } else {
+            int bestMatch = Integer.MAX_VALUE;
+
+            //noinspection ForLoopReplaceableByForEach (avoid iterator instantiation)
+            for (FontInfo font : mFonts.keySet()) {
+                int match = computeMatch(font, desiredStyle);
+                if (match < bestMatch) {
+                    bestMatch = match;
+                    bestFont = font;
+                }
+            }
+
+            // This would mean that we already had the font so it should be in the set
+            assert bestMatch != 0;
+        }
+
+        if (bestFont == null) {
+            return null;
+        }
+
+
+        // Derive the font as required and add it to the list of Fonts.
+        deriveFont(bestFont, desiredStyle);
+        addFont(desiredStyle);
+        return desiredStyle.mFont;
+    }
+
+    public FontVariant getVariant() {
+        return mVariant;
+    }
+
+    /**
+     * Returns if the FontFamily should contain any fonts. If this returns true and
+     * {@link #getFont(int, boolean)} returns an empty list, it means that an error occurred while
+     * loading the fonts. However, some fonts are deliberately skipped, for example they are not
+     * bundled with the SDK. In such a case, this method returns false.
+     */
+    public boolean isValid() {
+        return mValid;
+    }
+
+    private static Font loadFont(String path) {
+        if (path.startsWith(SYSTEM_FONTS) ) {
+            String relativePath = path.substring(SYSTEM_FONTS.length());
+            File f = new File(sFontLocation, relativePath);
+
+            try {
+                return Font.createFont(Font.TRUETYPE_FONT, f);
+            } catch (Exception e) {
+                if (path.endsWith(EXTENSION_OTF) && e instanceof FontFormatException) {
+                    // If we aren't able to load an Open Type font, don't log a warning just yet.
+                    // We wait for a case where font is being used. Only then we try to log the
+                    // warning.
+                    return null;
+                }
+                Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN,
+                        String.format("Unable to load font %1$s", relativePath),
+                        e, null);
+            }
+        } else {
+            Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                    "Only platform fonts located in " + SYSTEM_FONTS + "can be loaded.",
+                    null, null);
+        }
+
+        return null;
+    }
+
+    @Nullable
+    /*package*/ static String getFontLocation() {
+        return sFontLocation;
+    }
+
+    // ---- delegate methods ----
+    @LayoutlibDelegate
+    /*package*/ static boolean addFont(FontFamily thisFontFamily, String path, int ttcIndex,
+            FontVariationAxis[] axes, int weight, int italic) {
+        if (thisFontFamily.mBuilderPtr == 0) {
+            assert false : "Unable to call addFont after freezing.";
+            return false;
+        }
+        final FontFamily_Delegate delegate = getDelegate(thisFontFamily.mBuilderPtr);
+        return delegate != null && delegate.addFont(path, ttcIndex, weight, italic);
+    }
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static long nInitBuilder(String lang, int variant) {
+        // TODO: support lang. This is required for japanese locale.
+        FontFamily_Delegate delegate = new FontFamily_Delegate();
+        // variant can be 0, 1 or 2.
+        assert variant < 3;
+        delegate.mVariant = FontVariant.values()[variant];
+        if (sFontLocation != null) {
+            delegate.init();
+        } else {
+            sPostInitDelegate.add(delegate);
+        }
+        return sManager.addNewDelegate(delegate);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nCreateFamily(long builderPtr) {
+        return builderPtr;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nUnrefFamily(long nativePtr) {
+        // Removing the java reference for the object doesn't mean that it's freed for garbage
+        // collection. Typeface_Delegate may still hold a reference for it.
+        sManager.removeJavaReferenceFor(nativePtr);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex,
+            int weight, int isItalic) {
+        assert false : "The only client of this method has been overridden.";
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nAddFontWeightStyle(long builderPtr, ByteBuffer font,
+            int ttcIndex, int weight, int isItalic) {
+        assert false : "The only client of this method has been overridden.";
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nAddAxisValue(long builderPtr, int tag, float value) {
+        assert false : "The only client of this method has been overridden.";
+    }
+
+    static boolean addFont(long builderPtr, final String path, final int weight,
+            final boolean isItalic) {
+        final FontFamily_Delegate delegate = getDelegate(builderPtr);
+        int italic = isItalic ? 1 : 0;
+        if (delegate != null) {
+            if (sFontLocation == null) {
+                delegate.mPostInitRunnables.add(() -> delegate.addFont(path, weight, italic));
+                return true;
+            }
+            return delegate.addFont(path, weight, italic);
+        }
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nAddFontFromAssetManager(long builderPtr, AssetManager mgr, String path,
+            int cookie, boolean isAsset, int ttcIndex, int weight, int isItalic) {
+        FontFamily_Delegate ffd = sManager.getDelegate(builderPtr);
+        if (ffd == null) {
+            return false;
+        }
+        ffd.mValid = true;
+        if (mgr == null) {
+            return false;
+        }
+        if (mgr instanceof BridgeAssetManager) {
+            InputStream fontStream = null;
+            try {
+                AssetRepository assetRepository = ((BridgeAssetManager) mgr).getAssetRepository();
+                if (assetRepository == null) {
+                    Bridge.getLog().error(LayoutLog.TAG_MISSING_ASSET, "Asset not found: " + path,
+                            null);
+                    return false;
+                }
+                if (!assetRepository.isSupported()) {
+                    // Don't log any warnings on unsupported IDEs.
+                    return false;
+                }
+                // Check cache
+                FontInfo fontInfo = sCache.get(path);
+                if (fontInfo != null) {
+                    // renew the font's lease.
+                    sCache.put(path, fontInfo);
+                    ffd.addFont(fontInfo);
+                    return true;
+                }
+                fontStream = isAsset ?
+                        assetRepository.openAsset(path, AssetManager.ACCESS_STREAMING) :
+                        assetRepository.openNonAsset(cookie, path, AssetManager.ACCESS_STREAMING);
+                if (fontStream == null) {
+                    Bridge.getLog().error(LayoutLog.TAG_MISSING_ASSET, "Asset not found: " + path,
+                            path);
+                    return false;
+                }
+                Font font = Font.createFont(Font.TRUETYPE_FONT, fontStream);
+                fontInfo = new FontInfo();
+                fontInfo.mFont = font;
+                if (weight == RESOLVE_BY_FONT_TABLE) {
+                    fontInfo.mWeight = font.isBold() ? BOLD_FONT_WEIGHT : DEFAULT_FONT_WEIGHT;
+                } else {
+                    fontInfo.mWeight = weight;
+                }
+                fontInfo.mIsItalic = isItalic == RESOLVE_BY_FONT_TABLE ? font.isItalic() :
+                        isItalic == 1;
+                ffd.addFont(fontInfo);
+                return true;
+            } catch (IOException e) {
+                Bridge.getLog().error(LayoutLog.TAG_MISSING_ASSET, "Unable to load font " + path, e,
+                        path);
+            } catch (FontFormatException e) {
+                if (path.endsWith(EXTENSION_OTF)) {
+                    // otf fonts are not supported on the user's config (JRE version + OS)
+                    Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                            "OpenType fonts are not supported yet: " + path, null, path);
+                } else {
+                    Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+                            "Unable to load font " + path, e, path);
+                }
+            } finally {
+                if (fontStream != null) {
+                    try {
+                        fontStream.close();
+                    } catch (IOException ignored) {
+                    }
+                }
+            }
+            return false;
+        }
+        // This should never happen. AssetManager is a final class (from user's perspective), and
+        // we've replaced every creation of AssetManager with our implementation. We create an
+        // exception and log it, but continue with rest of the rendering, without loading this font.
+        Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+                "You have found a bug in the rendering library. Please file a bug at b.android.com.",
+                new RuntimeException("Asset Manager is not an instance of BridgeAssetManager"),
+                null);
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nAbort(long builderPtr) {
+        sManager.removeJavaReferenceFor(builderPtr);
+    }
+
+    /**
+     * @see FontFamily#allowUnsupportedFont
+     */
+    @LayoutlibDelegate
+    /*package*/ static void nAllowUnsupportedFont(long builderPtr) {
+        // Do nothing here as this is used for Minikin fonts
+    }
+
+    // ---- private helper methods ----
+
+    private void init() {
+        for (Runnable postInitRunnable : mPostInitRunnables) {
+            postInitRunnable.run();
+        }
+        mPostInitRunnables = null;
+    }
+
+    private boolean addFont(final String path, int ttcIndex, int weight, int italic) {
+        // FIXME: support ttc fonts. Hack JRE??
+        if (sFontLocation == null) {
+            mPostInitRunnables.add(() -> addFont(path, weight, italic));
+            return true;
+        }
+        return addFont(path, weight, italic);
+    }
+
+     private boolean addFont(@NonNull String path) {
+         return addFont(path, DEFAULT_FONT_WEIGHT, path.endsWith(FONT_SUFFIX_ITALIC) ? 1 : RESOLVE_BY_FONT_TABLE);
+     }
+
+    private boolean addFont(@NonNull String path, int weight, int italic) {
+        if (path.startsWith(SYSTEM_FONTS) &&
+                !SDK_FONTS.contains(path.substring(SYSTEM_FONTS.length()))) {
+            return mValid = false;
+        }
+        // Set valid to true, even if the font fails to load.
+        mValid = true;
+        Font font = loadFont(path);
+        if (font == null) {
+            return false;
+        }
+        FontInfo fontInfo = new FontInfo();
+        fontInfo.mFont = font;
+        fontInfo.mWeight = weight;
+        fontInfo.mIsItalic = italic == RESOLVE_BY_FONT_TABLE ? font.isItalic() : italic == 1;
+        addFont(fontInfo);
+        return true;
+    }
+
+    private boolean addFont(@NonNull FontInfo fontInfo) {
+        return mFonts.putIfAbsent(fontInfo, fontInfo.mFont) == null;
+    }
+
+    /**
+     * Compute matching metric between two styles - 0 is an exact match.
+     */
+    private static int computeMatch(@NonNull FontInfo font1, @NonNull FontInfo font2) {
+        int score = Math.abs(font1.mWeight - font2.mWeight);
+        if (font1.mIsItalic != font2.mIsItalic) {
+            score += 200;
+        }
+        return score;
+    }
+
+    /**
+     * Try to derive a font from {@code srcFont} for the style in {@code outFont}.
+     * <p/>
+     * {@code outFont} is updated to reflect the style of the derived font.
+     * @param srcFont the source font
+     * @param outFont contains the desired font style. Updated to contain the derived font and
+     *                its style
+     * @return outFont
+     */
+    private void deriveFont(@NonNull FontInfo srcFont, @NonNull FontInfo outFont) {
+        int desiredWeight = outFont.mWeight;
+        int srcWeight = srcFont.mWeight;
+        assert srcFont.mFont != null;
+        Font derivedFont = srcFont.mFont;
+        int derivedStyle = 0;
+        // Embolden the font if required.
+        if (desiredWeight >= BOLD_FONT_WEIGHT && desiredWeight - srcWeight > BOLD_FONT_WEIGHT_DELTA / 2) {
+            derivedStyle |= Font.BOLD;
+            srcWeight += BOLD_FONT_WEIGHT_DELTA;
+        }
+        // Italicize the font if required.
+        if (outFont.mIsItalic && !srcFont.mIsItalic) {
+            derivedStyle |= Font.ITALIC;
+        } else if (outFont.mIsItalic != srcFont.mIsItalic) {
+            // The desired font is plain, but the src font is italics. We can't convert it back. So
+            // we update the value to reflect the true style of the font we're deriving.
+            outFont.mIsItalic = srcFont.mIsItalic;
+        }
+
+        if (derivedStyle != 0) {
+            derivedFont = derivedFont.deriveFont(derivedStyle);
+        }
+
+        outFont.mFont = derivedFont;
+        outFont.mWeight = srcWeight;
+        // No need to update mIsItalics, as it's already been handled above.
+    }
+}
diff --git a/android/graphics/FontListParser.java b/android/graphics/FontListParser.java
new file mode 100644
index 0000000..9f672e3
--- /dev/null
+++ b/android/graphics/FontListParser.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2014 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;
+
+import android.text.FontConfig;
+import android.graphics.fonts.FontVariationAxis;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Parser for font config files.
+ *
+ * @hide
+ */
+public class FontListParser {
+
+    /* Parse fallback list (no names) */
+    public static FontConfig parse(InputStream in) throws XmlPullParserException, IOException {
+        try {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(in, null);
+            parser.nextTag();
+            return readFamilies(parser);
+        } finally {
+            in.close();
+        }
+    }
+
+    private static FontConfig readFamilies(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        List<FontConfig.Family> families = new ArrayList<>();
+        List<FontConfig.Alias> aliases = new ArrayList<>();
+
+        parser.require(XmlPullParser.START_TAG, null, "familyset");
+        while (parser.next() != XmlPullParser.END_TAG) {
+            if (parser.getEventType() != XmlPullParser.START_TAG) continue;
+            String tag = parser.getName();
+            if (tag.equals("family")) {
+                families.add(readFamily(parser));
+            } else if (tag.equals("alias")) {
+                aliases.add(readAlias(parser));
+            } else {
+                skip(parser);
+            }
+        }
+        return new FontConfig(families.toArray(new FontConfig.Family[families.size()]),
+                aliases.toArray(new FontConfig.Alias[aliases.size()]));
+    }
+
+    private static FontConfig.Family readFamily(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        final String name = parser.getAttributeValue(null, "name");
+        final String lang = parser.getAttributeValue(null, "lang");
+        final String[] langs = lang == null ? null : lang.split("\\s+");
+        final String variant = parser.getAttributeValue(null, "variant");
+        final List<FontConfig.Font> fonts = new ArrayList<FontConfig.Font>();
+        while (parser.next() != XmlPullParser.END_TAG) {
+            if (parser.getEventType() != XmlPullParser.START_TAG) continue;
+            final String tag = parser.getName();
+            if (tag.equals("font")) {
+                fonts.add(readFont(parser));
+            } else {
+                skip(parser);
+            }
+        }
+        int intVariant = FontConfig.Family.VARIANT_DEFAULT;
+        if (variant != null) {
+            if (variant.equals("compact")) {
+                intVariant = FontConfig.Family.VARIANT_COMPACT;
+            } else if (variant.equals("elegant")) {
+                intVariant = FontConfig.Family.VARIANT_ELEGANT;
+            }
+        }
+        return new FontConfig.Family(name, fonts.toArray(new FontConfig.Font[fonts.size()]), langs,
+                intVariant);
+    }
+
+    /** Matches leading and trailing XML whitespace. */
+    private static final Pattern FILENAME_WHITESPACE_PATTERN =
+            Pattern.compile("^[ \\n\\r\\t]+|[ \\n\\r\\t]+$");
+
+    private static FontConfig.Font readFont(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        String indexStr = parser.getAttributeValue(null, "index");
+        int index = indexStr == null ? 0 : Integer.parseInt(indexStr);
+        List<FontVariationAxis> axes = new ArrayList<FontVariationAxis>();
+        String weightStr = parser.getAttributeValue(null, "weight");
+        int weight = weightStr == null ? 400 : Integer.parseInt(weightStr);
+        boolean isItalic = "italic".equals(parser.getAttributeValue(null, "style"));
+        String fallbackFor = parser.getAttributeValue(null, "fallbackFor");
+        StringBuilder filename = new StringBuilder();
+        while (parser.next() != XmlPullParser.END_TAG) {
+            if (parser.getEventType() == XmlPullParser.TEXT) {
+                filename.append(parser.getText());
+            }
+            if (parser.getEventType() != XmlPullParser.START_TAG) continue;
+            String tag = parser.getName();
+            if (tag.equals("axis")) {
+                axes.add(readAxis(parser));
+            } else {
+                skip(parser);
+            }
+        }
+        String sanitizedName = FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll("");
+        return new FontConfig.Font(sanitizedName, index,
+                axes.toArray(new FontVariationAxis[axes.size()]), weight, isItalic, fallbackFor);
+    }
+
+    private static FontVariationAxis readAxis(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        String tagStr = parser.getAttributeValue(null, "tag");
+        String styleValueStr = parser.getAttributeValue(null, "stylevalue");
+        skip(parser);  // axis tag is empty, ignore any contents and consume end tag
+        return new FontVariationAxis(tagStr, Float.parseFloat(styleValueStr));
+    }
+
+    private static FontConfig.Alias readAlias(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        String name = parser.getAttributeValue(null, "name");
+        String toName = parser.getAttributeValue(null, "to");
+        String weightStr = parser.getAttributeValue(null, "weight");
+        int weight;
+        if (weightStr == null) {
+            weight = 400;
+        } else {
+            weight = Integer.parseInt(weightStr);
+        }
+        skip(parser);  // alias tag is empty, ignore any contents and consume end tag
+        return new FontConfig.Alias(name, toName, weight);
+    }
+
+    private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
+        int depth = 1;
+        while (depth > 0) {
+            switch (parser.next()) {
+            case XmlPullParser.START_TAG:
+                depth++;
+                break;
+            case XmlPullParser.END_TAG:
+                depth--;
+                break;
+            }
+        }
+    }
+}
diff --git a/android/graphics/Gradient_Delegate.java b/android/graphics/Gradient_Delegate.java
new file mode 100644
index 0000000..64410e4
--- /dev/null
+++ b/android/graphics/Gradient_Delegate.java
@@ -0,0 +1,212 @@
+/*
+ * 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;
+
+import android.graphics.Shader.TileMode;
+
+/**
+ * Base class for true Gradient shader delegate.
+ */
+public abstract class Gradient_Delegate extends Shader_Delegate {
+
+    protected final int[] mColors;
+    protected final float[] mPositions;
+
+    @Override
+    public boolean isSupported() {
+        // all gradient shaders are supported.
+        return true;
+    }
+
+    @Override
+    public String getSupportMessage() {
+        // all gradient shaders are supported, no need for a gradient support
+        return null;
+    }
+
+    /**
+     * Creates the base shader and do some basic test on the parameters.
+     *
+     * @param nativeMatrix reference to the shader's native transformation matrix
+     * @param colors The colors to be distributed along the gradient line
+     * @param positions May be null. The relative positions [0..1] of each
+     *            corresponding color in the colors array. If this is null, the
+     *            the colors are distributed evenly along the gradient line.
+     */
+    protected Gradient_Delegate(long nativeMatrix, int colors[], float positions[]) {
+        super(nativeMatrix);
+        assert colors.length >= 2 : "needs >= 2 number of colors";
+
+        if (positions == null) {
+            float spacing = 1.f / (colors.length - 1);
+            positions = new float[colors.length];
+            positions[0] = 0.f;
+            positions[colors.length - 1] = 1.f;
+            for (int i = 1; i < colors.length - 1; i++) {
+                positions[i] = spacing * i;
+            }
+        } else {
+            assert colors.length == positions.length :
+                    "color and position " + "arrays must be of equal length";
+        }
+
+        mColors = colors;
+        mPositions = positions;
+    }
+
+    /**
+     * Base class for (Java) Gradient Paints. This handles computing the gradient colors based
+     * on the color and position lists, as well as the {@link TileMode}
+     *
+     */
+    protected abstract static class GradientPaint implements java.awt.Paint {
+        private final static int GRADIENT_SIZE = 100;
+
+        private final int[] mColors;
+        private final float[] mPositions;
+        private final TileMode mTileMode;
+        private int[] mGradient;
+
+        protected GradientPaint(int[] colors, float[] positions, TileMode tileMode) {
+            mColors = colors;
+            mPositions = positions;
+            mTileMode = tileMode;
+        }
+
+        @Override
+        public int getTransparency() {
+            return java.awt.Paint.TRANSLUCENT;
+        }
+
+        /**
+         * Pre-computes the colors for the gradient. This must be called once before any call
+         * to {@link #getGradientColor(float)}
+         */
+        protected void precomputeGradientColors() {
+            if (mGradient == null) {
+                // actually create an array with an extra size, so that we can really go
+                // from 0 to SIZE (100%), or currentPos in the loop below will never equal 1.0
+                mGradient = new int[GRADIENT_SIZE+1];
+
+                int prevPos = 0;
+                int nextPos = 1;
+                for (int i  = 0 ; i <= GRADIENT_SIZE ; i++) {
+                    // compute current position
+                    float currentPos = (float)i/GRADIENT_SIZE;
+                    while (currentPos > mPositions[nextPos]) {
+                        prevPos = nextPos++;
+                    }
+
+                    float percent = (currentPos - mPositions[prevPos]) /
+                            (mPositions[nextPos] - mPositions[prevPos]);
+
+                    mGradient[i] = computeColor(mColors[prevPos], mColors[nextPos], percent);
+                }
+            }
+        }
+
+        /**
+         * Returns the color based on the position in the gradient.
+         * <var>pos</var> can be anything, even &lt; 0 or &gt; > 1, as the gradient
+         * will use {@link TileMode} value to convert it into a [0,1] value.
+         */
+        protected int getGradientColor(float pos) {
+            if (pos < 0.f) {
+                if (mTileMode != null) {
+                    switch (mTileMode) {
+                        case CLAMP:
+                            pos = 0.f;
+                            break;
+                        case REPEAT:
+                            // remove the integer part to stay in the [0,1] range.
+                            // we also need to invert the value from [-1,0] to [0, 1]
+                            pos = pos - (float)Math.floor(pos);
+                            break;
+                        case MIRROR:
+                            // this is the same as the positive side, just make the value positive
+                            // first.
+                            pos = Math.abs(pos);
+
+                            // get the integer and the decimal part
+                            int intPart = (int)Math.floor(pos);
+                            pos = pos - intPart;
+                            // 0 -> 1 : normal order
+                            // 1 -> 2: mirrored
+                            // etc..
+                            // this means if the intpart is odd we invert
+                            if ((intPart % 2) == 1) {
+                                pos = 1.f - pos;
+                            }
+                            break;
+                    }
+                } else {
+                    pos = 0.0f;
+                }
+            } else if (pos > 1f) {
+                if (mTileMode != null) {
+                    switch (mTileMode) {
+                        case CLAMP:
+                            pos = 1.f;
+                            break;
+                        case REPEAT:
+                            // remove the integer part to stay in the [0,1] range
+                            pos = pos - (float)Math.floor(pos);
+                            break;
+                        case MIRROR:
+                            // get the integer and the decimal part
+                            int intPart = (int)Math.floor(pos);
+                            pos = pos - intPart;
+                            // 0 -> 1 : normal order
+                            // 1 -> 2: mirrored
+                            // etc..
+                            // this means if the intpart is odd we invert
+                            if ((intPart % 2) == 1) {
+                                pos = 1.f - pos;
+                            }
+                            break;
+                    }
+                } else {
+                    pos = 1.0f;
+                }
+            }
+
+            int index = (int)((pos * GRADIENT_SIZE) + .5);
+
+            return mGradient[index];
+        }
+
+        /**
+         * Returns the color between c1, and c2, based on the percent of the distance
+         * between c1 and c2.
+         */
+        private int computeColor(int c1, int c2, float percent) {
+            int a = computeChannel((c1 >> 24) & 0xFF, (c2 >> 24) & 0xFF, percent);
+            int r = computeChannel((c1 >> 16) & 0xFF, (c2 >> 16) & 0xFF, percent);
+            int g = computeChannel((c1 >>  8) & 0xFF, (c2 >>  8) & 0xFF, percent);
+            int b = computeChannel((c1      ) & 0xFF, (c2      ) & 0xFF, percent);
+            return a << 24 | r << 16 | g << 8 | b;
+        }
+
+        /**
+         * Returns the channel value between 2 values based on the percent of the distance between
+         * the 2 values..
+         */
+        private int computeChannel(int c1, int c2, float percent) {
+            return c1 + (int)((percent * (c2-c1)) + .5);
+        }
+    }
+}
diff --git a/android/graphics/GraphicBuffer.java b/android/graphics/GraphicBuffer.java
new file mode 100644
index 0000000..53d2177
--- /dev/null
+++ b/android/graphics/GraphicBuffer.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2013 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;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Simple wrapper for the native GraphicBuffer class.
+ *
+ * @hide
+ */
+@SuppressWarnings("UnusedDeclaration")
+public class GraphicBuffer implements Parcelable {
+    // Note: keep usage flags in sync with GraphicBuffer.h and gralloc.h
+    public static final int USAGE_SW_READ_NEVER = 0x0;
+    public static final int USAGE_SW_READ_RARELY = 0x2;
+    public static final int USAGE_SW_READ_OFTEN = 0x3;
+    public static final int USAGE_SW_READ_MASK = 0xF;
+
+    public static final int USAGE_SW_WRITE_NEVER = 0x0;
+    public static final int USAGE_SW_WRITE_RARELY = 0x20;
+    public static final int USAGE_SW_WRITE_OFTEN = 0x30;
+    public static final int USAGE_SW_WRITE_MASK = 0xF0;
+
+    public static final int USAGE_SOFTWARE_MASK = USAGE_SW_READ_MASK | USAGE_SW_WRITE_MASK;
+
+    public static final int USAGE_PROTECTED = 0x4000;
+
+    public static final int USAGE_HW_TEXTURE = 0x100;
+    public static final int USAGE_HW_RENDER = 0x200;
+    public static final int USAGE_HW_2D = 0x400;
+    public static final int USAGE_HW_COMPOSER = 0x800;
+    public static final int USAGE_HW_VIDEO_ENCODER = 0x10000;
+    public static final int USAGE_HW_MASK = 0x71F00;
+
+    private final int mWidth;
+    private final int mHeight;
+    private final int mFormat;
+    private final int mUsage;
+    // Note: do not rename, this field is used by native code
+    private final long mNativeObject;
+
+    // These two fields are only used by lock/unlockCanvas()
+    private Canvas mCanvas;
+    private int mSaveCount;
+
+    // If set to true, this GraphicBuffer instance cannot be used anymore
+    private boolean mDestroyed;
+
+    /**
+     * Creates new <code>GraphicBuffer</code> instance. This method will return null
+     * if the buffer cannot be created.
+     *
+     * @param width The width in pixels of the buffer
+     * @param height The height in pixels of the buffer
+     * @param format The format of each pixel as specified in {@link PixelFormat}
+     * @param usage Hint indicating how the buffer will be used
+     *
+     * @return A <code>GraphicBuffer</code> instance or null
+     */
+    public static GraphicBuffer create(int width, int height, int format, int usage) {
+        long nativeObject = nCreateGraphicBuffer(width, height, format, usage);
+        if (nativeObject != 0) {
+            return new GraphicBuffer(width, height, format, usage, nativeObject);
+        }
+        return null;
+    }
+
+    /**
+     * Private use only. See {@link #create(int, int, int, int)}.
+     */
+    private GraphicBuffer(int width, int height, int format, int usage, long nativeObject) {
+        mWidth = width;
+        mHeight = height;
+        mFormat = format;
+        mUsage = usage;
+        mNativeObject = nativeObject;
+    }
+
+    /**
+     * For SurfaceControl JNI.
+     * @hide
+     */
+    public static GraphicBuffer createFromExisting(int width, int height,
+            int format, int usage, long unwrappedNativeObject) {
+        long nativeObject = nWrapGraphicBuffer(unwrappedNativeObject);
+        if (nativeObject != 0) {
+            return new GraphicBuffer(width, height, format, usage, nativeObject);
+        }
+        return null;
+    }
+
+    /**
+     * Returns the width of this buffer in pixels.
+     */
+    public int getWidth() {
+        return mWidth;
+    }
+
+    /**
+     * Returns the height of this buffer in pixels.
+     */
+    public int getHeight() {
+        return mHeight;
+    }
+
+    /**
+     * Returns the pixel format of this buffer. The pixel format must be one of
+     * the formats defined in {@link PixelFormat}.
+     */
+    public int getFormat() {
+        return mFormat;
+    }
+
+    /**
+     * Returns the usage hint set on this buffer.
+     */
+    public int getUsage() {
+        return mUsage;
+    }
+
+    /**
+     * <p>Start editing the pixels in the buffer. A null is returned if the buffer
+     * cannot be locked for editing.</p>
+     *
+     * <p>The content of the buffer is preserved between unlockCanvas()
+     * and lockCanvas().</p>
+     *
+     * <p>If this method is called after {@link #destroy()}, the return value will
+     * always be null.</p>
+     *
+     * @return A Canvas used to draw into the buffer, or null.
+     *
+     * @see #lockCanvas(android.graphics.Rect)
+     * @see #unlockCanvasAndPost(android.graphics.Canvas)
+     * @see #isDestroyed()
+     */
+    public Canvas lockCanvas() {
+        return lockCanvas(null);
+    }
+
+    /**
+     * Just like {@link #lockCanvas()} but allows specification of a dirty
+     * rectangle.
+     *
+     * <p>If this method is called after {@link #destroy()}, the return value will
+     * always be null.</p>
+     *
+     * @param dirty Area of the buffer that may be modified.
+
+     * @return A Canvas used to draw into the surface, or null.
+     *
+     * @see #lockCanvas()
+     * @see #unlockCanvasAndPost(android.graphics.Canvas)
+     * @see #isDestroyed()
+     */
+    public Canvas lockCanvas(Rect dirty) {
+        if (mDestroyed) {
+            return null;
+        }
+
+        if (mCanvas == null) {
+            mCanvas = new Canvas();
+        }
+
+        if (nLockCanvas(mNativeObject, mCanvas, dirty)) {
+            mSaveCount = mCanvas.save();
+            return mCanvas;
+        }
+
+        return null;
+    }
+
+    /**
+     * Finish editing pixels in the buffer.
+     *
+     * <p>This method doesn't do anything if {@link #destroy()} was
+     * previously called.</p>
+     *
+     * @param canvas The Canvas previously returned by lockCanvas()
+     *
+     * @see #lockCanvas()
+     * @see #lockCanvas(android.graphics.Rect)
+     * @see #isDestroyed()
+     */
+    public void unlockCanvasAndPost(Canvas canvas) {
+        if (!mDestroyed && mCanvas != null && canvas == mCanvas) {
+            canvas.restoreToCount(mSaveCount);
+            mSaveCount = 0;
+
+            nUnlockCanvasAndPost(mNativeObject, mCanvas);
+        }
+    }
+
+    /**
+     * Destroyes this buffer immediately. Calling this method frees up any
+     * underlying native resources. After calling this method, this buffer
+     * must not be used in any way ({@link #lockCanvas()} must not be called,
+     * etc.)
+     *
+     * @see #isDestroyed()
+     */
+    public void destroy() {
+        if (!mDestroyed) {
+            mDestroyed = true;
+            nDestroyGraphicBuffer(mNativeObject);
+        }
+    }
+
+    /**
+     * Indicates whether this buffer has been destroyed. A destroyed buffer
+     * cannot be used in any way: locking a Canvas will return null, the buffer
+     * cannot be written to a parcel, etc.
+     *
+     * @return True if this <code>GraphicBuffer</code> is in a destroyed state,
+     *         false otherwise.
+     *
+     * @see #destroy()
+     */
+    public boolean isDestroyed() {
+        return mDestroyed;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (!mDestroyed) nDestroyGraphicBuffer(mNativeObject);
+        } finally {
+            super.finalize();
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Flatten this object in to a Parcel.
+     *
+     * <p>Calling this method will throw an <code>IllegalStateException</code> if
+     * {@link #destroy()} has been previously called.</p>
+     *
+     * @param dest The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written.
+     *              May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        if (mDestroyed) {
+            throw new IllegalStateException("This GraphicBuffer has been destroyed and cannot be "
+                    + "written to a parcel.");
+        }
+
+        dest.writeInt(mWidth);
+        dest.writeInt(mHeight);
+        dest.writeInt(mFormat);
+        dest.writeInt(mUsage);
+        nWriteGraphicBufferToParcel(mNativeObject, dest);
+    }
+
+    public static final Parcelable.Creator<GraphicBuffer> CREATOR =
+            new Parcelable.Creator<GraphicBuffer>() {
+        public GraphicBuffer createFromParcel(Parcel in) {
+            int width = in.readInt();
+            int height = in.readInt();
+            int format = in.readInt();
+            int usage = in.readInt();
+            long nativeObject = nReadGraphicBufferFromParcel(in);
+            if (nativeObject != 0) {
+                return new GraphicBuffer(width, height, format, usage, nativeObject);
+            }
+            return null;
+        }
+
+        public GraphicBuffer[] newArray(int size) {
+            return new GraphicBuffer[size];
+        }
+    };
+
+    private static native long nCreateGraphicBuffer(int width, int height, int format, int usage);
+    private static native void nDestroyGraphicBuffer(long nativeObject);
+    private static native void nWriteGraphicBufferToParcel(long nativeObject, Parcel dest);
+    private static native long nReadGraphicBufferFromParcel(Parcel in);
+    private static native boolean nLockCanvas(long nativeObject, Canvas canvas, Rect dirty);
+    private static native boolean nUnlockCanvasAndPost(long nativeObject, Canvas canvas);
+    private static native long nWrapGraphicBuffer(long nativeObject);
+}
diff --git a/android/graphics/ImageFormat.java b/android/graphics/ImageFormat.java
new file mode 100644
index 0000000..e3527e3
--- /dev/null
+++ b/android/graphics/ImageFormat.java
@@ -0,0 +1,784 @@
+/*
+ * 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;
+
+public class ImageFormat {
+    /*
+     * these constants are chosen to be binary compatible with their previous
+     * location in PixelFormat.java
+     */
+
+    public static final int UNKNOWN = 0;
+
+    /**
+     * RGB format used for pictures encoded as RGB_565. See
+     * {@link android.hardware.Camera.Parameters#setPictureFormat(int)}.
+     */
+    public static final int RGB_565 = 4;
+
+    /**
+     * <p>Android YUV format.</p>
+     *
+     * <p>This format is exposed to software decoders and applications.</p>
+     *
+     * <p>YV12 is a 4:2:0 YCrCb planar format comprised of a WxH Y plane followed
+     * by (W/2) x (H/2) Cr and Cb planes.</p>
+     *
+     * <p>This format assumes
+     * <ul>
+     * <li>an even width</li>
+     * <li>an even height</li>
+     * <li>a horizontal stride multiple of 16 pixels</li>
+     * <li>a vertical stride equal to the height</li>
+     * </ul>
+     * </p>
+     *
+     * <pre> y_size = stride * height
+     * c_stride = ALIGN(stride/2, 16)
+     * c_size = c_stride * height/2
+     * size = y_size + c_size * 2
+     * cr_offset = y_size
+     * cb_offset = y_size + c_size</pre>
+     *
+     * <p>For the {@link android.hardware.camera2} API, the {@link #YUV_420_888} format is
+     * recommended for YUV output instead.</p>
+     *
+     * <p>For the older camera API, this format is guaranteed to be supported for
+     * {@link android.hardware.Camera} preview images since API level 12; for earlier API versions,
+     * check {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.
+     *
+     * <p>Note that for camera preview callback use (see
+     * {@link android.hardware.Camera#setPreviewCallback}), the
+     * <var>stride</var> value is the smallest possible; that is, it is equal
+     * to:
+     *
+     * <pre>stride = ALIGN(width, 16)</pre>
+     *
+     * @see android.hardware.Camera.Parameters#setPreviewCallback
+     * @see android.hardware.Camera.Parameters#setPreviewFormat
+     * @see android.hardware.Camera.Parameters#getSupportedPreviewFormats
+     * </p>
+     */
+    public static final int YV12 = 0x32315659;
+
+    /**
+     * <p>Android Y8 format.</p>
+     *
+     * <p>Y8 is a YUV planar format comprised of a WxH Y plane only, with each pixel
+     * being represented by 8 bits. It is equivalent to just the Y plane from {@link #YV12}
+     * format.</p>
+     *
+     * <p>This format assumes
+     * <ul>
+     * <li>an even width</li>
+     * <li>an even height</li>
+     * <li>a horizontal stride multiple of 16 pixels</li>
+     * </ul>
+     * </p>
+     *
+     * <pre> y_size = stride * height </pre>
+     *
+     * <p>For example, the {@link android.media.Image} object can provide data
+     * in this format from a {@link android.hardware.camera2.CameraDevice}
+     * through a {@link android.media.ImageReader} object if this format is
+     * supported by {@link android.hardware.camera2.CameraDevice}.</p>
+     *
+     * @see android.media.Image
+     * @see android.media.ImageReader
+     * @see android.hardware.camera2.CameraDevice
+     *
+     * @hide
+     */
+    public static final int Y8 = 0x20203859;
+
+    /**
+     * <p>Android Y16 format.</p>
+     *
+     * Y16 is a YUV planar format comprised of a WxH Y plane, with each pixel
+     * being represented by 16 bits. It is just like {@link #Y8}, but has 16
+     * bits per pixel (little endian).</p>
+     *
+     * <p>This format assumes
+     * <ul>
+     * <li>an even width</li>
+     * <li>an even height</li>
+     * <li>a horizontal stride multiple of 16 pixels</li>
+     * </ul>
+     * </p>
+     *
+     * <pre> y_size = stride * height </pre>
+     *
+     * <p>For example, the {@link android.media.Image} object can provide data
+     * in this format from a {@link android.hardware.camera2.CameraDevice}
+     * through a {@link android.media.ImageReader} object if this format is
+     * supported by {@link android.hardware.camera2.CameraDevice}.</p>
+     *
+     * @see android.media.Image
+     * @see android.media.ImageReader
+     * @see android.hardware.camera2.CameraDevice
+     *
+     * @hide
+     */
+    public static final int Y16 = 0x20363159;
+
+    /**
+     * YCbCr format, used for video.
+     *
+     * <p>For the {@link android.hardware.camera2} API, the {@link #YUV_420_888} format is
+     * recommended for YUV output instead.</p>
+     *
+     * <p>Whether this format is supported by the old camera API can be determined by
+     * {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.</p>
+     *
+     */
+    public static final int NV16 = 0x10;
+
+    /**
+     * YCrCb format used for images, which uses the NV21 encoding format.
+     *
+     * <p>This is the default format
+     * for {@link android.hardware.Camera} preview images, when not otherwise set with
+     * {@link android.hardware.Camera.Parameters#setPreviewFormat(int)}.</p>
+     *
+     * <p>For the {@link android.hardware.camera2} API, the {@link #YUV_420_888} format is
+     * recommended for YUV output instead.</p>
+     */
+    public static final int NV21 = 0x11;
+
+    /**
+     * YCbCr format used for images, which uses YUYV (YUY2) encoding format.
+     *
+     * <p>For the {@link android.hardware.camera2} API, the {@link #YUV_420_888} format is
+     * recommended for YUV output instead.</p>
+     *
+     * <p>This is an alternative format for {@link android.hardware.Camera} preview images. Whether
+     * this format is supported by the camera hardware can be determined by
+     * {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.</p>
+     */
+    public static final int YUY2 = 0x14;
+
+    /**
+     * Compressed JPEG format.
+     *
+     * <p>This format is always supported as an output format for the
+     * {@link android.hardware.camera2} API, and as a picture format for the older
+     * {@link android.hardware.Camera} API</p>
+     */
+    public static final int JPEG = 0x100;
+
+    /**
+     * <p>Multi-plane Android YUV 420 format</p>
+     *
+     * <p>This format is a generic YCbCr format, capable of describing any 4:2:0
+     * chroma-subsampled planar or semiplanar buffer (but not fully interleaved),
+     * with 8 bits per color sample.</p>
+     *
+     * <p>Images in this format are always represented by three separate buffers
+     * of data, one for each color plane. Additional information always
+     * accompanies the buffers, describing the row stride and the pixel stride
+     * for each plane.</p>
+     *
+     * <p>The order of planes in the array returned by
+     * {@link android.media.Image#getPlanes() Image#getPlanes()} is guaranteed such that
+     * plane #0 is always Y, plane #1 is always U (Cb), and plane #2 is always V (Cr).</p>
+     *
+     * <p>The Y-plane is guaranteed not to be interleaved with the U/V planes
+     * (in particular, pixel stride is always 1 in
+     * {@link android.media.Image.Plane#getPixelStride() yPlane.getPixelStride()}).</p>
+     *
+     * <p>The U/V planes are guaranteed to have the same row stride and pixel stride
+     * (in particular,
+     * {@link android.media.Image.Plane#getRowStride() uPlane.getRowStride()}
+     * == {@link android.media.Image.Plane#getRowStride() vPlane.getRowStride()} and
+     * {@link android.media.Image.Plane#getPixelStride() uPlane.getPixelStride()}
+     * == {@link android.media.Image.Plane#getPixelStride() vPlane.getPixelStride()};
+     * ).</p>
+     *
+     * <p>For example, the {@link android.media.Image} object can provide data
+     * in this format from a {@link android.hardware.camera2.CameraDevice}
+     * through a {@link android.media.ImageReader} object.</p>
+     *
+     * @see android.media.Image
+     * @see android.media.ImageReader
+     * @see android.hardware.camera2.CameraDevice
+     */
+    public static final int YUV_420_888 = 0x23;
+
+    /**
+     * <p>Multi-plane Android YUV 422 format</p>
+     *
+     * <p>This format is a generic YCbCr format, capable of describing any 4:2:2
+     * chroma-subsampled (planar, semiplanar or interleaved) format,
+     * with 8 bits per color sample.</p>
+     *
+     * <p>Images in this format are always represented by three separate buffers
+     * of data, one for each color plane. Additional information always
+     * accompanies the buffers, describing the row stride and the pixel stride
+     * for each plane.</p>
+     *
+     * <p>The order of planes in the array returned by
+     * {@link android.media.Image#getPlanes() Image#getPlanes()} is guaranteed such that
+     * plane #0 is always Y, plane #1 is always U (Cb), and plane #2 is always V (Cr).</p>
+     *
+     * <p>In contrast to the {@link #YUV_420_888} format, the Y-plane may have a pixel
+     * stride greater than 1 in
+     * {@link android.media.Image.Plane#getPixelStride() yPlane.getPixelStride()}.</p>
+     *
+     * <p>The U/V planes are guaranteed to have the same row stride and pixel stride
+     * (in particular,
+     * {@link android.media.Image.Plane#getRowStride() uPlane.getRowStride()}
+     * == {@link android.media.Image.Plane#getRowStride() vPlane.getRowStride()} and
+     * {@link android.media.Image.Plane#getPixelStride() uPlane.getPixelStride()}
+     * == {@link android.media.Image.Plane#getPixelStride() vPlane.getPixelStride()};
+     * ).</p>
+     *
+     * <p>For example, the {@link android.media.Image} object can provide data
+     * in this format from a {@link android.media.MediaCodec}
+     * through {@link android.media.MediaCodec#getOutputImage} object.</p>
+     *
+     * @see android.media.Image
+     * @see android.media.MediaCodec
+     */
+    public static final int YUV_422_888 = 0x27;
+
+    /**
+     * <p>Multi-plane Android YUV 444 format</p>
+     *
+     * <p>This format is a generic YCbCr format, capable of describing any 4:4:4
+     * (planar, semiplanar or interleaved) format,
+     * with 8 bits per color sample.</p>
+     *
+     * <p>Images in this format are always represented by three separate buffers
+     * of data, one for each color plane. Additional information always
+     * accompanies the buffers, describing the row stride and the pixel stride
+     * for each plane.</p>
+     *
+     * <p>The order of planes in the array returned by
+     * {@link android.media.Image#getPlanes() Image#getPlanes()} is guaranteed such that
+     * plane #0 is always Y, plane #1 is always U (Cb), and plane #2 is always V (Cr).</p>
+     *
+     * <p>In contrast to the {@link #YUV_420_888} format, the Y-plane may have a pixel
+     * stride greater than 1 in
+     * {@link android.media.Image.Plane#getPixelStride() yPlane.getPixelStride()}.</p>
+     *
+     * <p>The U/V planes are guaranteed to have the same row stride and pixel stride
+     * (in particular,
+     * {@link android.media.Image.Plane#getRowStride() uPlane.getRowStride()}
+     * == {@link android.media.Image.Plane#getRowStride() vPlane.getRowStride()} and
+     * {@link android.media.Image.Plane#getPixelStride() uPlane.getPixelStride()}
+     * == {@link android.media.Image.Plane#getPixelStride() vPlane.getPixelStride()};
+     * ).</p>
+     *
+     * <p>For example, the {@link android.media.Image} object can provide data
+     * in this format from a {@link android.media.MediaCodec}
+     * through {@link android.media.MediaCodec#getOutputImage} object.</p>
+     *
+     * @see android.media.Image
+     * @see android.media.MediaCodec
+     */
+    public static final int YUV_444_888 = 0x28;
+
+    /**
+     * <p>Multi-plane Android RGB format</p>
+     *
+     * <p>This format is a generic RGB format, capable of describing most RGB formats,
+     * with 8 bits per color sample.</p>
+     *
+     * <p>Images in this format are always represented by three separate buffers
+     * of data, one for each color plane. Additional information always
+     * accompanies the buffers, describing the row stride and the pixel stride
+     * for each plane.</p>
+     *
+     * <p>The order of planes in the array returned by
+     * {@link android.media.Image#getPlanes() Image#getPlanes()} is guaranteed such that
+     * plane #0 is always R (red), plane #1 is always G (green), and plane #2 is always B
+     * (blue).</p>
+     *
+     * <p>All three planes are guaranteed to have the same row strides and pixel strides.</p>
+     *
+     * <p>For example, the {@link android.media.Image} object can provide data
+     * in this format from a {@link android.media.MediaCodec}
+     * through {@link android.media.MediaCodec#getOutputImage} object.</p>
+     *
+     * @see android.media.Image
+     * @see android.media.MediaCodec
+     */
+    public static final int FLEX_RGB_888 = 0x29;
+
+    /**
+     * <p>Multi-plane Android RGBA format</p>
+     *
+     * <p>This format is a generic RGBA format, capable of describing most RGBA formats,
+     * with 8 bits per color sample.</p>
+     *
+     * <p>Images in this format are always represented by four separate buffers
+     * of data, one for each color plane. Additional information always
+     * accompanies the buffers, describing the row stride and the pixel stride
+     * for each plane.</p>
+     *
+     * <p>The order of planes in the array returned by
+     * {@link android.media.Image#getPlanes() Image#getPlanes()} is guaranteed such that
+     * plane #0 is always R (red), plane #1 is always G (green), plane #2 is always B (blue),
+     * and plane #3 is always A (alpha). This format may represent pre-multiplied or
+     * non-premultiplied alpha.</p>
+     *
+     * <p>All four planes are guaranteed to have the same row strides and pixel strides.</p>
+     *
+     * <p>For example, the {@link android.media.Image} object can provide data
+     * in this format from a {@link android.media.MediaCodec}
+     * through {@link android.media.MediaCodec#getOutputImage} object.</p>
+     *
+     * @see android.media.Image
+     * @see android.media.MediaCodec
+     */
+    public static final int FLEX_RGBA_8888 = 0x2A;
+
+    /**
+     * <p>General raw camera sensor image format, usually representing a
+     * single-channel Bayer-mosaic image. Each pixel color sample is stored with
+     * 16 bits of precision.</p>
+     *
+     * <p>The layout of the color mosaic, the maximum and minimum encoding
+     * values of the raw pixel data, the color space of the image, and all other
+     * needed information to interpret a raw sensor image must be queried from
+     * the {@link android.hardware.camera2.CameraDevice} which produced the
+     * image.</p>
+     */
+    public static final int RAW_SENSOR = 0x20;
+
+    /**
+     * <p>Private raw camera sensor image format, a single channel image with
+     * implementation depedent pixel layout.</p>
+     *
+     * <p>RAW_PRIVATE is a format for unprocessed raw image buffers coming from an
+     * image sensor. The actual structure of buffers of this format is
+     * implementation-dependent.</p>
+     *
+     */
+    public static final int RAW_PRIVATE = 0x24;
+
+    /**
+     * <p>
+     * Android 10-bit raw format
+     * </p>
+     * <p>
+     * This is a single-plane, 10-bit per pixel, densely packed (in each row),
+     * unprocessed format, usually representing raw Bayer-pattern images coming
+     * from an image sensor.
+     * </p>
+     * <p>
+     * In an image buffer with this format, starting from the first pixel of
+     * each row, each 4 consecutive pixels are packed into 5 bytes (40 bits).
+     * Each one of the first 4 bytes contains the top 8 bits of each pixel, The
+     * fifth byte contains the 2 least significant bits of the 4 pixels, the
+     * exact layout data for each 4 consecutive pixels is illustrated below
+     * ({@code Pi[j]} stands for the jth bit of the ith pixel):
+     * </p>
+     * <table>
+     * <thead>
+     * <tr>
+     * <th align="center"></th>
+     * <th align="center">bit 7</th>
+     * <th align="center">bit 6</th>
+     * <th align="center">bit 5</th>
+     * <th align="center">bit 4</th>
+     * <th align="center">bit 3</th>
+     * <th align="center">bit 2</th>
+     * <th align="center">bit 1</th>
+     * <th align="center">bit 0</th>
+     * </tr>
+     * </thead> <tbody>
+     * <tr>
+     * <td align="center">Byte 0:</td>
+     * <td align="center">P0[9]</td>
+     * <td align="center">P0[8]</td>
+     * <td align="center">P0[7]</td>
+     * <td align="center">P0[6]</td>
+     * <td align="center">P0[5]</td>
+     * <td align="center">P0[4]</td>
+     * <td align="center">P0[3]</td>
+     * <td align="center">P0[2]</td>
+     * </tr>
+     * <tr>
+     * <td align="center">Byte 1:</td>
+     * <td align="center">P1[9]</td>
+     * <td align="center">P1[8]</td>
+     * <td align="center">P1[7]</td>
+     * <td align="center">P1[6]</td>
+     * <td align="center">P1[5]</td>
+     * <td align="center">P1[4]</td>
+     * <td align="center">P1[3]</td>
+     * <td align="center">P1[2]</td>
+     * </tr>
+     * <tr>
+     * <td align="center">Byte 2:</td>
+     * <td align="center">P2[9]</td>
+     * <td align="center">P2[8]</td>
+     * <td align="center">P2[7]</td>
+     * <td align="center">P2[6]</td>
+     * <td align="center">P2[5]</td>
+     * <td align="center">P2[4]</td>
+     * <td align="center">P2[3]</td>
+     * <td align="center">P2[2]</td>
+     * </tr>
+     * <tr>
+     * <td align="center">Byte 3:</td>
+     * <td align="center">P3[9]</td>
+     * <td align="center">P3[8]</td>
+     * <td align="center">P3[7]</td>
+     * <td align="center">P3[6]</td>
+     * <td align="center">P3[5]</td>
+     * <td align="center">P3[4]</td>
+     * <td align="center">P3[3]</td>
+     * <td align="center">P3[2]</td>
+     * </tr>
+     * <tr>
+     * <td align="center">Byte 4:</td>
+     * <td align="center">P3[1]</td>
+     * <td align="center">P3[0]</td>
+     * <td align="center">P2[1]</td>
+     * <td align="center">P2[0]</td>
+     * <td align="center">P1[1]</td>
+     * <td align="center">P1[0]</td>
+     * <td align="center">P0[1]</td>
+     * <td align="center">P0[0]</td>
+     * </tr>
+     * </tbody>
+     * </table>
+     * <p>
+     * This format assumes
+     * <ul>
+     * <li>a width multiple of 4 pixels</li>
+     * <li>an even height</li>
+     * </ul>
+     * </p>
+     *
+     * <pre>size = row stride * height</pre> where the row stride is in <em>bytes</em>,
+     * not pixels.
+     *
+     * <p>
+     * Since this is a densely packed format, the pixel stride is always 0. The
+     * application must use the pixel data layout defined in above table to
+     * access each row data. When row stride is equal to {@code width * (10 / 8)}, there
+     * will be no padding bytes at the end of each row, the entire image data is
+     * densely packed. When stride is larger than {@code width * (10 / 8)}, padding
+     * bytes will be present at the end of each row.
+     * </p>
+     * <p>
+     * For example, the {@link android.media.Image} object can provide data in
+     * this format from a {@link android.hardware.camera2.CameraDevice} (if
+     * supported) through a {@link android.media.ImageReader} object. The
+     * {@link android.media.Image#getPlanes() Image#getPlanes()} will return a
+     * single plane containing the pixel data. The pixel stride is always 0 in
+     * {@link android.media.Image.Plane#getPixelStride()}, and the
+     * {@link android.media.Image.Plane#getRowStride()} describes the vertical
+     * neighboring pixel distance (in bytes) between adjacent rows.
+     * </p>
+     *
+     * @see android.media.Image
+     * @see android.media.ImageReader
+     * @see android.hardware.camera2.CameraDevice
+     */
+    public static final int RAW10 = 0x25;
+
+    /**
+     * <p>
+     * Android 12-bit raw format
+     * </p>
+     * <p>
+     * This is a single-plane, 12-bit per pixel, densely packed (in each row),
+     * unprocessed format, usually representing raw Bayer-pattern images coming
+     * from an image sensor.
+     * </p>
+     * <p>
+     * In an image buffer with this format, starting from the first pixel of each
+     * row, each two consecutive pixels are packed into 3 bytes (24 bits). The first
+     * and second byte contains the top 8 bits of first and second pixel. The third
+     * byte contains the 4 least significant bits of the two pixels, the exact layout
+     * data for each two consecutive pixels is illustrated below (Pi[j] stands for
+     * the jth bit of the ith pixel):
+     * </p>
+     * <table>
+     * <thead>
+     * <tr>
+     * <th align="center"></th>
+     * <th align="center">bit 7</th>
+     * <th align="center">bit 6</th>
+     * <th align="center">bit 5</th>
+     * <th align="center">bit 4</th>
+     * <th align="center">bit 3</th>
+     * <th align="center">bit 2</th>
+     * <th align="center">bit 1</th>
+     * <th align="center">bit 0</th>
+     * </tr>
+     * </thead> <tbody>
+     * <tr>
+     * <td align="center">Byte 0:</td>
+     * <td align="center">P0[11]</td>
+     * <td align="center">P0[10]</td>
+     * <td align="center">P0[ 9]</td>
+     * <td align="center">P0[ 8]</td>
+     * <td align="center">P0[ 7]</td>
+     * <td align="center">P0[ 6]</td>
+     * <td align="center">P0[ 5]</td>
+     * <td align="center">P0[ 4]</td>
+     * </tr>
+     * <tr>
+     * <td align="center">Byte 1:</td>
+     * <td align="center">P1[11]</td>
+     * <td align="center">P1[10]</td>
+     * <td align="center">P1[ 9]</td>
+     * <td align="center">P1[ 8]</td>
+     * <td align="center">P1[ 7]</td>
+     * <td align="center">P1[ 6]</td>
+     * <td align="center">P1[ 5]</td>
+     * <td align="center">P1[ 4]</td>
+     * </tr>
+     * <tr>
+     * <td align="center">Byte 2:</td>
+     * <td align="center">P1[ 3]</td>
+     * <td align="center">P1[ 2]</td>
+     * <td align="center">P1[ 1]</td>
+     * <td align="center">P1[ 0]</td>
+     * <td align="center">P0[ 3]</td>
+     * <td align="center">P0[ 2]</td>
+     * <td align="center">P0[ 1]</td>
+     * <td align="center">P0[ 0]</td>
+     * </tr>
+     * </tbody>
+     * </table>
+     * <p>
+     * This format assumes
+     * <ul>
+     * <li>a width multiple of 4 pixels</li>
+     * <li>an even height</li>
+     * </ul>
+     * </p>
+     *
+     * <pre>size = row stride * height</pre> where the row stride is in <em>bytes</em>,
+     * not pixels.
+     *
+     * <p>
+     * Since this is a densely packed format, the pixel stride is always 0. The
+     * application must use the pixel data layout defined in above table to
+     * access each row data. When row stride is equal to {@code width * (12 / 8)}, there
+     * will be no padding bytes at the end of each row, the entire image data is
+     * densely packed. When stride is larger than {@code width * (12 / 8)}, padding
+     * bytes will be present at the end of each row.
+     * </p>
+     * <p>
+     * For example, the {@link android.media.Image} object can provide data in
+     * this format from a {@link android.hardware.camera2.CameraDevice} (if
+     * supported) through a {@link android.media.ImageReader} object. The
+     * {@link android.media.Image#getPlanes() Image#getPlanes()} will return a
+     * single plane containing the pixel data. The pixel stride is always 0 in
+     * {@link android.media.Image.Plane#getPixelStride()}, and the
+     * {@link android.media.Image.Plane#getRowStride()} describes the vertical
+     * neighboring pixel distance (in bytes) between adjacent rows.
+     * </p>
+     *
+     * @see android.media.Image
+     * @see android.media.ImageReader
+     * @see android.hardware.camera2.CameraDevice
+     */
+    public static final int RAW12 = 0x26;
+
+    /**
+     * <p>Android dense depth image format.</p>
+     *
+     * <p>Each pixel is 16 bits, representing a depth ranging measurement from a depth camera or
+     * similar sensor. The 16-bit sample consists of a confidence value and the actual ranging
+     * measurement.</p>
+     *
+     * <p>The confidence value is an estimate of correctness for this sample.  It is encoded in the
+     * 3 most significant bits of the sample, with a value of 0 representing 100% confidence, a
+     * value of 1 representing 0% confidence, a value of 2 representing 1/7, a value of 3
+     * representing 2/7, and so on.</p>
+     *
+     * <p>As an example, the following sample extracts the range and confidence from the first pixel
+     * of a DEPTH16-format {@link android.media.Image}, and converts the confidence to a
+     * floating-point value between 0 and 1.f inclusive, with 1.f representing maximum confidence:
+     *
+     * <pre>
+     *    ShortBuffer shortDepthBuffer = img.getPlanes()[0].getBuffer().asShortBuffer();
+     *    short depthSample = shortDepthBuffer.get()
+     *    short depthRange = (short) (depthSample & 0x1FFF);
+     *    short depthConfidence = (short) ((depthSample >> 13) & 0x7);
+     *    float depthPercentage = depthConfidence == 0 ? 1.f : (depthConfidence - 1) / 7.f;
+     * </pre>
+     * </p>
+     *
+     * <p>This format assumes
+     * <ul>
+     * <li>an even width</li>
+     * <li>an even height</li>
+     * <li>a horizontal stride multiple of 16 pixels</li>
+     * </ul>
+     * </p>
+     *
+     * <pre> y_size = stride * height </pre>
+     *
+     * When produced by a camera, the units for the range are millimeters.
+     */
+    public static final int DEPTH16 = 0x44363159;
+
+    /**
+     * Android sparse depth point cloud format.
+     *
+     * <p>A variable-length list of 3D points plus a confidence value, with each point represented
+     * by four floats; first the X, Y, Z position coordinates, and then the confidence value.</p>
+     *
+     * <p>The number of points is {@code (size of the buffer in bytes) / 16}.
+     *
+     * <p>The coordinate system and units of the position values depend on the source of the point
+     * cloud data. The confidence value is between 0.f and 1.f, inclusive, with 0 representing 0%
+     * confidence and 1.f representing 100% confidence in the measured position values.</p>
+     *
+     * <p>As an example, the following code extracts the first depth point in a DEPTH_POINT_CLOUD
+     * format {@link android.media.Image}:
+     * <pre>
+     *    FloatBuffer floatDepthBuffer = img.getPlanes()[0].getBuffer().asFloatBuffer();
+     *    float x = floatDepthBuffer.get();
+     *    float y = floatDepthBuffer.get();
+     *    float z = floatDepthBuffer.get();
+     *    float confidence = floatDepthBuffer.get();
+     * </pre>
+     *
+     */
+    public static final int DEPTH_POINT_CLOUD = 0x101;
+
+    /**
+     * Unprocessed implementation-dependent raw
+     * depth measurements, opaque with 16 bit
+     * samples.
+     *
+     * @hide
+     */
+    public static final int RAW_DEPTH = 0x1002;
+
+    /**
+     * Android private opaque image format.
+     * <p>
+     * The choices of the actual format and pixel data layout are entirely up to
+     * the device-specific and framework internal implementations, and may vary
+     * depending on use cases even for the same device. The buffers of this
+     * format can be produced by components like
+     * {@link android.media.ImageWriter ImageWriter} , and interpreted correctly
+     * by consumers like {@link android.hardware.camera2.CameraDevice
+     * CameraDevice} based on the device/framework private information. However,
+     * these buffers are not directly accessible to the application.
+     * </p>
+     * <p>
+     * When an {@link android.media.Image Image} of this format is obtained from
+     * an {@link android.media.ImageReader ImageReader} or
+     * {@link android.media.ImageWriter ImageWriter}, the
+     * {@link android.media.Image#getPlanes() getPlanes()} method will return an
+     * empty {@link android.media.Image.Plane Plane} array.
+     * </p>
+     * <p>
+     * If a buffer of this format is to be used as an OpenGL ES texture, the
+     * framework will assume that sampling the texture will always return an
+     * alpha value of 1.0 (i.e. the buffer contains only opaque pixel values).
+     * </p>
+     */
+    public static final int PRIVATE = 0x22;
+
+    /**
+     * Use this function to retrieve the number of bits per pixel of an
+     * ImageFormat.
+     *
+     * @param format
+     * @return the number of bits per pixel of the given format or -1 if the
+     *         format doesn't exist or is not supported.
+     */
+    public static int getBitsPerPixel(int format) {
+        switch (format) {
+            case RGB_565:
+                return 16;
+            case NV16:
+                return 16;
+            case YUY2:
+                return 16;
+            case YV12:
+                return 12;
+            case Y8:
+                return 8;
+            case Y16:
+            case DEPTH16:
+                return 16;
+            case NV21:
+                return 12;
+            case YUV_420_888:
+                return 12;
+            case YUV_422_888:
+                return 16;
+            case YUV_444_888:
+                return 24;
+            case FLEX_RGB_888:
+                return 24;
+            case FLEX_RGBA_8888:
+                return 32;
+            case RAW_DEPTH:
+            case RAW_SENSOR:
+                return 16;
+            case RAW10:
+                return 10;
+            case RAW12:
+                return 12;
+        }
+        return -1;
+    }
+
+    /**
+     * Determine whether or not this is a public-visible {@code format}.
+     *
+     * <p>In particular, {@code @hide} formats will return {@code false}.</p>
+     *
+     * <p>Any other formats (including UNKNOWN) will return {@code false}.</p>
+     *
+     * @param format an integer format
+     * @return a boolean
+     *
+     * @hide
+     */
+    public static boolean isPublicFormat(int format) {
+        switch (format) {
+            case RGB_565:
+            case NV16:
+            case YUY2:
+            case YV12:
+            case JPEG:
+            case NV21:
+            case YUV_420_888:
+            case YUV_422_888:
+            case YUV_444_888:
+            case FLEX_RGB_888:
+            case FLEX_RGBA_8888:
+            case RAW_SENSOR:
+            case RAW_PRIVATE:
+            case RAW10:
+            case RAW12:
+            case DEPTH16:
+            case DEPTH_POINT_CLOUD:
+            case PRIVATE:
+            case RAW_DEPTH:
+                return true;
+        }
+
+        return false;
+    }
+}
diff --git a/android/graphics/Insets.java b/android/graphics/Insets.java
new file mode 100644
index 0000000..156f990
--- /dev/null
+++ b/android/graphics/Insets.java
@@ -0,0 +1,114 @@
+/*
+ * 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;
+
+/**
+ * An Insets instance holds four integer offsets which describe changes to the four
+ * edges of a Rectangle. By convention, positive values move edges towards the
+ * centre of the rectangle.
+ * <p>
+ * Insets are immutable so may be treated as values.
+ *
+ * @hide
+ */
+public class Insets {
+    public static final Insets NONE = new Insets(0, 0, 0, 0);
+
+    public final int left;
+    public final int top;
+    public final int right;
+    public final int bottom;
+
+    private Insets(int left, int top, int right, int bottom) {
+        this.left = left;
+        this.top = top;
+        this.right = right;
+        this.bottom = bottom;
+    }
+
+    // Factory methods
+
+    /**
+     * Return an Insets instance with the appropriate values.
+     *
+     * @param left the left inset
+     * @param top the top inset
+     * @param right the right inset
+     * @param bottom the bottom inset
+     *
+     * @return Insets instance with the appropriate values
+     */
+    public static Insets of(int left, int top, int right, int bottom) {
+        if (left == 0 && top == 0 && right == 0 && bottom == 0) {
+            return NONE;
+        }
+        return new Insets(left, top, right, bottom);
+    }
+
+    /**
+     * Return an Insets instance with the appropriate values.
+     *
+     * @param r the rectangle from which to take the values
+     *
+     * @return an Insets instance with the appropriate values
+     */
+    public static Insets of(Rect r) {
+        return (r == null) ? NONE : of(r.left, r.top, r.right, r.bottom);
+    }
+
+    /**
+     * Two Insets instances are equal iff they belong to the same class and their fields are
+     * pairwise equal.
+     *
+     * @param o the object to compare this instance with.
+     *
+     * @return true iff this object is equal {@code o}
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        Insets insets = (Insets) o;
+
+        if (bottom != insets.bottom) return false;
+        if (left != insets.left) return false;
+        if (right != insets.right) return false;
+        if (top != insets.top) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = left;
+        result = 31 * result + top;
+        result = 31 * result + right;
+        result = 31 * result + bottom;
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "Insets{" +
+                "left=" + left +
+                ", top=" + top +
+                ", right=" + right +
+                ", bottom=" + bottom +
+                '}';
+    }
+}
diff --git a/android/graphics/Interpolator.java b/android/graphics/Interpolator.java
new file mode 100644
index 0000000..1045464
--- /dev/null
+++ b/android/graphics/Interpolator.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2007 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;
+
+import android.os.SystemClock;
+
+public class Interpolator {
+
+    public Interpolator(int valueCount) {
+        mValueCount = valueCount;
+        mFrameCount = 2;
+        native_instance = nativeConstructor(valueCount, 2);
+    }
+    
+    public Interpolator(int valueCount, int frameCount) {
+        mValueCount = valueCount;
+        mFrameCount = frameCount;
+        native_instance = nativeConstructor(valueCount, frameCount);
+    }
+    
+    /**
+     * Reset the Interpolator to have the specified number of values and an
+     * implicit keyFrame count of 2 (just a start and end). After this call the
+     * values for each keyFrame must be assigned using setKeyFrame().
+     */
+    public void reset(int valueCount) {
+        reset(valueCount, 2);
+    }
+    
+    /**
+     * Reset the Interpolator to have the specified number of values and
+     * keyFrames. After this call the values for each keyFrame must be assigned
+     * using setKeyFrame().
+     */
+    public void reset(int valueCount, int frameCount) {
+        mValueCount = valueCount;
+        mFrameCount = frameCount;
+        nativeReset(native_instance, valueCount, frameCount);
+    }
+    
+    public final int getKeyFrameCount() {
+        return mFrameCount;
+    }
+    
+    public final int getValueCount() {
+        return mValueCount;
+    }
+    
+    /**
+     * Assign the keyFrame (specified by index) a time value and an array of key
+     * values (with an implicity blend array of [0, 0, 1, 1] giving linear
+     * transition to the next set of key values).
+     * 
+     * @param index The index of the key frame to assign
+     * @param msec The time (in mililiseconds) for this key frame. Based on the
+     *        SystemClock.uptimeMillis() clock
+     * @param values Array of values associated with theis key frame
+     */
+    public void setKeyFrame(int index, int msec, float[] values) {
+        setKeyFrame(index, msec, values, null);
+    }
+
+    /**
+     * Assign the keyFrame (specified by index) a time value and an array of key
+     * values and blend array.
+     * 
+     * @param index The index of the key frame to assign
+     * @param msec The time (in mililiseconds) for this key frame. Based on the
+     *        SystemClock.uptimeMillis() clock
+     * @param values Array of values associated with theis key frame
+     * @param blend (may be null) Optional array of 4 blend values
+     */
+    public void setKeyFrame(int index, int msec, float[] values, float[] blend) {
+        if (index < 0 || index >= mFrameCount) {
+            throw new IndexOutOfBoundsException();
+        }
+        if (values.length < mValueCount) {
+            throw new ArrayStoreException();
+        }
+        if (blend != null && blend.length < 4) {
+            throw new ArrayStoreException();
+        }
+        nativeSetKeyFrame(native_instance, index, msec, values, blend);
+    }
+    
+    /**
+     * Set a repeat count (which may be fractional) for the interpolator, and
+     * whether the interpolator should mirror its repeats. The default settings
+     * are repeatCount = 1, and mirror = false.
+     */
+    public void setRepeatMirror(float repeatCount, boolean mirror) {
+        if (repeatCount >= 0) {
+            nativeSetRepeatMirror(native_instance, repeatCount, mirror);
+        }
+    }
+    
+    public enum Result {
+        NORMAL,
+        FREEZE_START,
+        FREEZE_END
+    }
+
+    /**
+     * Calls timeToValues(msec, values) with the msec set to now (by calling
+     * (int)SystemClock.uptimeMillis().)
+     */
+    public Result timeToValues(float[] values) {
+        return timeToValues((int)SystemClock.uptimeMillis(), values);
+    }
+
+    /**
+     * Given a millisecond time value (msec), return the interpolated values and
+     * return whether the specified time was within the range of key times
+     * (NORMAL), was before the first key time (FREEZE_START) or after the last
+     * key time (FREEZE_END). In any event, computed values are always returned.
+     * 
+     * @param msec The time (in milliseconds) used to sample into the
+     *        Interpolator. Based on the SystemClock.uptimeMillis() clock
+     * @param values Where to write the computed values (may be NULL).
+     * @return how the values were computed (even if values == null)
+     */
+    public Result timeToValues(int msec, float[] values) {
+        if (values != null && values.length < mValueCount) {
+            throw new ArrayStoreException();
+        }
+        switch (nativeTimeToValues(native_instance, msec, values)) {
+            case 0: return Result.NORMAL;
+            case 1: return Result.FREEZE_START;
+            default: return Result.FREEZE_END;
+        }
+    }
+    
+    @Override
+    protected void finalize() throws Throwable {
+        nativeDestructor(native_instance);
+        native_instance = 0;  // Other finalizers can still call us.
+    }
+    
+    private int mValueCount;
+    private int mFrameCount;
+    private long native_instance;
+
+    private static native long nativeConstructor(int valueCount, int frameCount);
+    private static native void nativeDestructor(long native_instance);
+    private static native void nativeReset(long native_instance, int valueCount, int frameCount);
+    private static native void nativeSetKeyFrame(long native_instance, int index, int msec, float[] values, float[] blend);
+    private static native void nativeSetRepeatMirror(long native_instance, float repeatCount, boolean mirror);
+    private static native int  nativeTimeToValues(long native_instance, int msec, float[] values);
+}
+
diff --git a/android/graphics/LargeBitmap.java b/android/graphics/LargeBitmap.java
new file mode 100644
index 0000000..936c338
--- /dev/null
+++ b/android/graphics/LargeBitmap.java
@@ -0,0 +1,118 @@
+/*
+ * 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;
+
+/**
+ * LargeBitmap can be used to decode a rectangle region from an image.
+ * LargeBimap is particularly useful when an original image is large and
+ * you only need parts of the image.
+ *
+ * To create a LargeBitmap, call BitmapFactory.createLargeBitmap().
+ * Given a LargeBitmap, users can call decodeRegion() repeatedly
+ * to get a decoded Bitmap of the specified region.
+ * @hide
+ */
+public final class LargeBitmap {
+    private long mNativeLargeBitmap;
+    private boolean mRecycled;
+
+    /*  Private constructor that must received an already allocated native
+        large bitmap int (pointer).
+
+        This can be called from JNI code.
+    */
+    private LargeBitmap(long nativeLbm) {
+        mNativeLargeBitmap = nativeLbm;
+        mRecycled = false;
+    }
+
+    /**
+     * Decodes a rectangle region in the image specified by rect.
+     *
+     * @param rect The rectangle that specified the region to be decode.
+     * @param opts null-ok; Options that control downsampling.
+     *             inPurgeable is not supported.
+     * @return The decoded bitmap, or null if the image data could not be
+     *         decoded.
+     */
+    public Bitmap decodeRegion(Rect rect, BitmapFactory.Options options) {
+        checkRecycled("decodeRegion called on recycled large bitmap");
+        if (rect.left < 0 || rect.top < 0 || rect.right > getWidth() || rect.bottom > getHeight())
+            throw new IllegalArgumentException("rectangle is not inside the image");
+        return nativeDecodeRegion(mNativeLargeBitmap, rect.left, rect.top,
+                rect.right - rect.left, rect.bottom - rect.top, options);
+    }
+
+    /** Returns the original image's width */
+    public int getWidth() {
+        checkRecycled("getWidth called on recycled large bitmap");
+        return nativeGetWidth(mNativeLargeBitmap);
+    }
+
+    /** Returns the original image's height */
+    public int getHeight() {
+        checkRecycled("getHeight called on recycled large bitmap");
+        return nativeGetHeight(mNativeLargeBitmap);
+    }
+
+    /**
+     * Frees up the memory associated with this large bitmap, and mark the
+     * large bitmap as "dead", meaning it will throw an exception if decodeRegion(),
+     * getWidth() or getHeight() is called.
+     * This operation cannot be reversed, so it should only be called if you are
+     * sure there are no further uses for the large bitmap. This is an advanced call,
+     * and normally need not be called, since the normal GC process will free up this
+     * memory when there are no more references to this bitmap.
+     */
+    public void recycle() {
+        if (!mRecycled) {
+            nativeClean(mNativeLargeBitmap);
+            mRecycled = true;
+        }
+    }
+
+    /**
+     * Returns true if this large bitmap has been recycled.
+     * If so, then it is an error to try use its method.
+     *
+     * @return true if the large bitmap has been recycled
+     */
+    public final boolean isRecycled() {
+        return mRecycled;
+    }
+
+    /**
+     * Called by methods that want to throw an exception if the bitmap
+     * has already been recycled.
+     */
+    private void checkRecycled(String errorMessage) {
+        if (mRecycled) {
+            throw new IllegalStateException(errorMessage);
+        }
+    }
+
+    protected void finalize() {
+        recycle();
+    }
+
+    private static native Bitmap nativeDecodeRegion(long nativeLbm,
+            int start_x, int start_y, int width, int height,
+            BitmapFactory.Options options);
+    private static native int nativeGetWidth(long nativeLbm);
+    private static native int nativeGetHeight(long nativeLbm);
+    private static native void nativeClean(long nativeLbm);
+}
diff --git a/android/graphics/LayerRasterizer.java b/android/graphics/LayerRasterizer.java
new file mode 100644
index 0000000..25155ab
--- /dev/null
+++ b/android/graphics/LayerRasterizer.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+/**
+ * @removed feature is not supported by hw-accerlerated or PDF backends
+ */
+@Deprecated
+public class LayerRasterizer extends Rasterizer {
+    public LayerRasterizer() { }
+
+    /** Add a new layer (above any previous layers) to the rasterizer.
+        The layer will extract those fields that affect the mask from
+        the specified paint, but will not retain a reference to the paint
+        object itself, so it may be reused without danger of side-effects.
+    */
+    public void addLayer(Paint paint, float dx, float dy) { }
+
+    public void addLayer(Paint paint) { }
+}
+
diff --git a/android/graphics/LeakyTypefaceStorage.java b/android/graphics/LeakyTypefaceStorage.java
new file mode 100644
index 0000000..618e60d
--- /dev/null
+++ b/android/graphics/LeakyTypefaceStorage.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017 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;
+
+import com.android.internal.annotations.GuardedBy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Process;
+import android.util.ArrayMap;
+
+import java.util.ArrayList;
+
+/**
+ * This class is used for Parceling Typeface object.
+ * Note: Typeface object can not be passed over the process boundary.
+ *
+ * @hide
+ */
+public class LeakyTypefaceStorage {
+    private static final Object sLock = new Object();
+
+    @GuardedBy("sLock")
+    private static final ArrayList<Typeface> sStorage = new ArrayList<>();
+    @GuardedBy("sLock")
+    private static final ArrayMap<Typeface, Integer> sTypefaceMap = new ArrayMap<>();
+
+    /**
+     * Write typeface to parcel.
+     *
+     * You can't transfer Typeface to a different process. {@link readTypefaceFromParcel} will
+     * return {@code null} if the {@link readTypefaceFromParcel} is called in a different process.
+     *
+     * @param typeface A {@link Typeface} to be written.
+     * @param parcel A {@link Parcel} object.
+     */
+    public static void writeTypefaceToParcel(@Nullable Typeface typeface, @NonNull Parcel parcel) {
+        parcel.writeInt(Process.myPid());
+        synchronized (sLock) {
+            final int id;
+            final Integer i = sTypefaceMap.get(typeface);
+            if (i != null) {
+                id = i.intValue();
+            } else {
+                id = sStorage.size();
+                sStorage.add(typeface);
+                sTypefaceMap.put(typeface, id);
+            }
+            parcel.writeInt(id);
+        }
+    }
+
+    /**
+     * Read typeface from parcel.
+     *
+     * If the {@link Typeface} was created in another process, this method returns null.
+     *
+     * @param parcel A {@link Parcel} object
+     * @return A {@link Typeface} object.
+     */
+    public static @Nullable Typeface readTypefaceFromParcel(@NonNull Parcel parcel) {
+        final int pid = parcel.readInt();
+        final int typefaceId = parcel.readInt();
+        if (pid != Process.myPid()) {
+            return null;  // The Typeface was created and written in another process.
+        }
+        synchronized (sLock) {
+            return sStorage.get(typefaceId);
+        }
+    }
+}
diff --git a/android/graphics/LightingColorFilter.java b/android/graphics/LightingColorFilter.java
new file mode 100644
index 0000000..1578ffb
--- /dev/null
+++ b/android/graphics/LightingColorFilter.java
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+
+// This file was generated from the C++ include file: SkColorFilter.h
+// Any changes made to this file will be discarded by the build.
+// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, 
+// or one of the auxilary file specifications in device/tools/gluemaker.
+
+package android.graphics;
+
+import android.annotation.ColorInt;
+
+/**
+ * A color filter that can be used to simulate simple lighting effects.
+ * A <code>LightingColorFilter</code> is defined by two parameters, one
+ * used to multiply the source color (called <code>colorMultiply</code>)
+ * and one used to add to the source color (called <code>colorAdd</code>).
+ * The alpha channel is left untouched by this color filter.
+ *
+ * Given a source color RGB, the resulting R'G'B' color is computed thusly:
+ * <pre>
+ * R' = R * colorMultiply.R + colorAdd.R
+ * G' = G * colorMultiply.G + colorAdd.G
+ * B' = B * colorMultiply.B + colorAdd.B
+ * </pre>
+ * The result is pinned to the <code>[0..255]</code> range for each channel.
+ */
+public class LightingColorFilter extends ColorFilter {
+    @ColorInt
+    private int mMul;
+    @ColorInt
+    private int mAdd;
+
+    /**
+     * Create a colorfilter that multiplies the RGB channels by one color,
+     * and then adds a second color. The alpha components of the mul and add
+     * arguments are ignored.
+     */
+    public LightingColorFilter(@ColorInt int mul, @ColorInt int add) {
+        mMul = mul;
+        mAdd = add;
+    }
+
+    /**
+     * Returns the RGB color used to multiply the source color when the
+     * color filter is applied.
+     */
+    @ColorInt
+    public int getColorMultiply() {
+        return mMul;
+    }
+
+    /**
+     * Specifies the RGB color used to multiply the source color when the
+     * color filter is applied.
+     * The alpha channel of this color is ignored.
+     *
+     * @see #getColorMultiply()
+     *
+     * @hide
+     */
+    public void setColorMultiply(@ColorInt int mul) {
+        if (mMul != mul) {
+            mMul = mul;
+            discardNativeInstance();
+        }
+    }
+
+    /**
+     * Returns the RGB color that will be added to the source color
+     * when the color filter is applied.
+     */
+    @ColorInt
+    public int getColorAdd() {
+        return mAdd;
+    }
+
+    /**
+     * Specifies the RGB that will be added to the source color when
+     * the color filter is applied.
+     * The alpha channel of this color is ignored.
+     *
+     * @see #getColorAdd()
+     *
+     * @hide
+     */
+    public void setColorAdd(@ColorInt int add) {
+        if (mAdd != add) {
+            mAdd = add;
+            discardNativeInstance();
+        }
+    }
+
+    @Override
+    long createNativeInstance() {
+        return native_CreateLightingFilter(mMul, mAdd);
+    }
+
+    private static native long native_CreateLightingFilter(int mul, int add);
+}
diff --git a/android/graphics/LightingColorFilter_Delegate.java b/android/graphics/LightingColorFilter_Delegate.java
new file mode 100644
index 0000000..0dd9703
--- /dev/null
+++ b/android/graphics/LightingColorFilter_Delegate.java
@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.LightingColorFilter
+ *
+ * Through the layoutlib_create tool, the original native methods of LightingColorFilter have
+ * been replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original LightingColorFilter class.
+ *
+ * Because this extends {@link ColorFilter_Delegate}, there's no need to use a
+ * {@link DelegateManager}, as all the Shader classes will be added to the manager
+ * owned by {@link ColorFilter_Delegate}.
+ *
+ * @see ColorFilter_Delegate
+ *
+ */
+public class LightingColorFilter_Delegate extends ColorFilter_Delegate {
+
+    // ---- delegate data ----
+
+    // ---- Public Helper methods ----
+
+    @Override
+    public String getSupportMessage() {
+        return "Lighting Color Filters are not supported.";
+    }
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static long native_CreateLightingFilter(int mul, int add) {
+        LightingColorFilter_Delegate newDelegate = new LightingColorFilter_Delegate();
+        return sManager.addNewDelegate(newDelegate);
+    }
+
+    // ---- Private delegate/helper methods ----
+}
diff --git a/android/graphics/LinearGradient.java b/android/graphics/LinearGradient.java
new file mode 100644
index 0000000..7139efe
--- /dev/null
+++ b/android/graphics/LinearGradient.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2007 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;
+
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+public class LinearGradient extends Shader {
+
+    private static final int TYPE_COLORS_AND_POSITIONS = 1;
+    private static final int TYPE_COLOR_START_AND_COLOR_END = 2;
+
+    /**
+     * Type of the LinearGradient: can be either TYPE_COLORS_AND_POSITIONS or
+     * TYPE_COLOR_START_AND_COLOR_END.
+     */
+    private int mType;
+
+    private float mX0;
+    private float mY0;
+    private float mX1;
+    private float mY1;
+    private int[] mColors;
+    private float[] mPositions;
+    private int mColor0;
+    private int mColor1;
+
+    private TileMode mTileMode;
+
+    /**
+     * Create a shader that draws a linear gradient along a line.
+     *
+     * @param x0           The x-coordinate for the start of the gradient line
+     * @param y0           The y-coordinate for the start of the gradient line
+     * @param x1           The x-coordinate for the end of the gradient line
+     * @param y1           The y-coordinate for the end of the gradient line
+     * @param colors       The colors to be distributed along the gradient line
+     * @param positions    May be null. The relative positions [0..1] of
+     *                     each corresponding color in the colors array. If this is null,
+     *                     the the colors are distributed evenly along the gradient line.
+     * @param tile         The Shader tiling mode
+    */
+    public LinearGradient(float x0, float y0, float x1, float y1, @NonNull @ColorInt int colors[],
+            @Nullable float positions[], @NonNull TileMode tile) {
+        if (colors.length < 2) {
+            throw new IllegalArgumentException("needs >= 2 number of colors");
+        }
+        if (positions != null && colors.length != positions.length) {
+            throw new IllegalArgumentException("color and position arrays must be of equal length");
+        }
+        mType = TYPE_COLORS_AND_POSITIONS;
+        mX0 = x0;
+        mY0 = y0;
+        mX1 = x1;
+        mY1 = y1;
+        mColors = colors.clone();
+        mPositions = positions != null ? positions.clone() : null;
+        mTileMode = tile;
+    }
+
+    /**
+     * Create a shader that draws a linear gradient along a line.
+     *
+     * @param x0       The x-coordinate for the start of the gradient line
+     * @param y0       The y-coordinate for the start of the gradient line
+     * @param x1       The x-coordinate for the end of the gradient line
+     * @param y1       The y-coordinate for the end of the gradient line
+     * @param color0   The color at the start of the gradient line.
+     * @param color1   The color at the end of the gradient line.
+     * @param tile     The Shader tiling mode
+    */
+    public LinearGradient(float x0, float y0, float x1, float y1,
+            @ColorInt int color0, @ColorInt int color1,
+            @NonNull TileMode tile) {
+        mType = TYPE_COLOR_START_AND_COLOR_END;
+        mX0 = x0;
+        mY0 = y0;
+        mX1 = x1;
+        mY1 = y1;
+        mColor0 = color0;
+        mColor1 = color1;
+        mColors = null;
+        mPositions = null;
+        mTileMode = tile;
+    }
+
+    @Override
+    long createNativeInstance(long nativeMatrix) {
+        if (mType == TYPE_COLORS_AND_POSITIONS) {
+            return nativeCreate1(nativeMatrix, mX0, mY0, mX1, mY1,
+                    mColors, mPositions, mTileMode.nativeInt);
+        } else { // TYPE_COLOR_START_AND_COLOR_END
+            return nativeCreate2(nativeMatrix, mX0, mY0, mX1, mY1,
+                    mColor0, mColor1, mTileMode.nativeInt);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    protected Shader copy() {
+        final LinearGradient copy;
+        if (mType == TYPE_COLORS_AND_POSITIONS) {
+            copy = new LinearGradient(mX0, mY0, mX1, mY1, mColors.clone(),
+                    mPositions != null ? mPositions.clone() : null, mTileMode);
+        } else { // TYPE_COLOR_START_AND_COLOR_END
+            copy = new LinearGradient(mX0, mY0, mX1, mY1, mColor0, mColor1, mTileMode);
+        }
+        copyLocalMatrix(copy);
+        return copy;
+    }
+
+    private native long nativeCreate1(long matrix, float x0, float y0, float x1, float y1,
+            int colors[], float positions[], int tileMode);
+    private native long nativeCreate2(long matrix, float x0, float y0, float x1, float y1,
+            int color0, int color1, int tileMode);
+}
diff --git a/android/graphics/LinearGradient_Delegate.java b/android/graphics/LinearGradient_Delegate.java
new file mode 100644
index 0000000..477705c
--- /dev/null
+++ b/android/graphics/LinearGradient_Delegate.java
@@ -0,0 +1,226 @@
+/*
+ * 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;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.graphics.Shader.TileMode;
+
+import java.awt.image.ColorModel;
+import java.awt.image.DataBufferInt;
+import java.awt.image.Raster;
+import java.awt.image.SampleModel;
+
+/**
+ * Delegate implementing the native methods of android.graphics.LinearGradient
+ *
+ * Through the layoutlib_create tool, the original native methods of LinearGradient have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original LinearGradient class.
+ *
+ * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager},
+ * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}.
+ *
+ * @see Shader_Delegate
+ *
+ */
+public final class LinearGradient_Delegate extends Gradient_Delegate {
+
+    // ---- delegate data ----
+    private java.awt.Paint mJavaPaint;
+
+    // ---- Public Helper methods ----
+
+    @Override
+    public java.awt.Paint getJavaPaint() {
+        return mJavaPaint;
+    }
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeCreate1(LinearGradient thisGradient, long matrix,
+            float x0, float y0, float x1, float y1,
+            int colors[], float positions[], int tileMode) {
+        LinearGradient_Delegate newDelegate = new LinearGradient_Delegate(matrix, x0, y0,
+                x1, y1, colors, positions, Shader_Delegate.getTileMode(tileMode));
+        return sManager.addNewDelegate(newDelegate);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeCreate2(LinearGradient thisGradient, long matrix,
+            float x0, float y0, float x1, float y1,
+            int color0, int color1, int tileMode) {
+        return nativeCreate1(thisGradient, matrix, x0, y0, x1, y1, new int[] { color0, color1},
+                null /*positions*/, tileMode);
+    }
+
+    // ---- Private delegate/helper methods ----
+
+    /**
+     * Create a shader that draws a linear gradient along a line.
+     *
+     * @param nativeMatrix reference to the shader's native transformation matrix
+     * @param x0 The x-coordinate for the start of the gradient line
+     * @param y0 The y-coordinate for the start of the gradient line
+     * @param x1 The x-coordinate for the end of the gradient line
+     * @param y1 The y-coordinate for the end of the gradient line
+     * @param colors The colors to be distributed along the gradient line
+     * @param positions May be null. The relative positions [0..1] of each
+     *            corresponding color in the colors array. If this is null, the
+     *            the colors are distributed evenly along the gradient line.
+     * @param tile The Shader tiling mode
+     */
+    private LinearGradient_Delegate(long nativeMatrix, float x0, float y0, float x1,
+            float y1, int colors[], float positions[], TileMode tile) {
+        super(nativeMatrix, colors, positions);
+        mJavaPaint = new LinearGradientPaint(x0, y0, x1, y1, mColors, mPositions, tile);
+    }
+
+    // ---- Custom Java Paint ----
+    /**
+     * Linear Gradient (Java) Paint able to handle more than 2 points, as
+     * {@link java.awt.GradientPaint} only supports 2 points and does not support Android's tile
+     * modes.
+     */
+    private class LinearGradientPaint extends GradientPaint {
+
+        private final float mX0;
+        private final float mY0;
+        private final float mDx;
+        private final float mDy;
+        private final float mDSize2;
+
+        public LinearGradientPaint(float x0, float y0, float x1, float y1, int colors[],
+                float positions[], TileMode tile) {
+            super(colors, positions, tile);
+            mX0 = x0;
+            mY0 = y0;
+            mDx = x1 - x0;
+            mDy = y1 - y0;
+            mDSize2 = mDx * mDx + mDy * mDy;
+        }
+
+        @Override
+        public java.awt.PaintContext createContext(
+                java.awt.image.ColorModel      colorModel,
+                java.awt.Rectangle             deviceBounds,
+                java.awt.geom.Rectangle2D      userBounds,
+                java.awt.geom.AffineTransform  xform,
+                java.awt.RenderingHints        hints) {
+            precomputeGradientColors();
+
+            java.awt.geom.AffineTransform canvasMatrix;
+            try {
+                canvasMatrix = xform.createInverse();
+            } catch (java.awt.geom.NoninvertibleTransformException e) {
+                Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE,
+                        "Unable to inverse matrix in LinearGradient", e, null /*data*/);
+                canvasMatrix = new java.awt.geom.AffineTransform();
+            }
+
+            java.awt.geom.AffineTransform localMatrix = getLocalMatrix();
+            try {
+                localMatrix = localMatrix.createInverse();
+            } catch (java.awt.geom.NoninvertibleTransformException e) {
+                Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE,
+                        "Unable to inverse matrix in LinearGradient", e, null /*data*/);
+                localMatrix = new java.awt.geom.AffineTransform();
+            }
+
+            return new LinearGradientPaintContext(canvasMatrix, localMatrix, colorModel);
+        }
+
+        private class LinearGradientPaintContext implements java.awt.PaintContext {
+
+            private final java.awt.geom.AffineTransform mCanvasMatrix;
+            private final java.awt.geom.AffineTransform mLocalMatrix;
+            private final java.awt.image.ColorModel mColorModel;
+
+            private LinearGradientPaintContext(
+                    java.awt.geom.AffineTransform canvasMatrix,
+                    java.awt.geom.AffineTransform localMatrix,
+                    java.awt.image.ColorModel colorModel) {
+                mCanvasMatrix = canvasMatrix;
+                mLocalMatrix = localMatrix;
+                mColorModel = colorModel.hasAlpha() ? colorModel : ColorModel.getRGBdefault();
+            }
+
+            @Override
+            public void dispose() {
+            }
+
+            @Override
+            public java.awt.image.ColorModel getColorModel() {
+                return mColorModel;
+            }
+
+            @Override
+            public java.awt.image.Raster getRaster(int x, int y, int w, int h) {
+                int[] data = new int[w*h];
+
+                int index = 0;
+                float[] pt1 = new float[2];
+                float[] pt2 = new float[2];
+                for (int iy = 0 ; iy < h ; iy++) {
+                    for (int ix = 0 ; ix < w ; ix++) {
+                        // handle the canvas transform
+                        pt1[0] = x + ix;
+                        pt1[1] = y + iy;
+                        mCanvasMatrix.transform(pt1, 0, pt2, 0, 1);
+
+                        // handle the local matrix.
+                        pt1[0] = pt2[0];
+                        pt1[1] = pt2[1];
+                        mLocalMatrix.transform(pt1, 0, pt2, 0, 1);
+
+                        data[index++] = getColor(pt2[0], pt2[1]);
+                    }
+                }
+
+                DataBufferInt dataBuffer = new DataBufferInt(data, data.length);
+                SampleModel colorModel = mColorModel.createCompatibleSampleModel(w, h);
+                return Raster.createWritableRaster(colorModel, dataBuffer, null);
+            }
+        }
+
+        /**
+         * Returns a color for an arbitrary point.
+         */
+        private int getColor(float x, float y) {
+            float pos;
+            if (mDx == 0) {
+                pos = (y - mY0) / mDy;
+            } else if (mDy == 0) {
+                pos = (x - mX0) / mDx;
+            } else {
+                // find the x position on the gradient vector.
+                float _x = (mDx*mDy*(y-mY0) + mDy*mDy*mX0 + mDx*mDx*x) / mDSize2;
+                // from it get the position relative to the vector
+                pos = (_x - mX0) / mDx;
+            }
+
+            return getGradientColor(pos);
+        }
+    }
+}
diff --git a/android/graphics/MaskFilter.java b/android/graphics/MaskFilter.java
new file mode 100644
index 0000000..d474315
--- /dev/null
+++ b/android/graphics/MaskFilter.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+/**
+ * MaskFilter is the base class for object that perform transformations on
+ * an alpha-channel mask before drawing it. A subclass of MaskFilter may be
+ * installed into a Paint. Blur and emboss are implemented as subclasses of MaskFilter.
+ */
+public class MaskFilter {
+
+    protected void finalize() throws Throwable {
+        nativeDestructor(native_instance);
+        native_instance = 0;  // Other finalizers can still call us.
+    }
+
+    private static native void nativeDestructor(long native_filter);
+    long native_instance;
+}
diff --git a/android/graphics/MaskFilter_Delegate.java b/android/graphics/MaskFilter_Delegate.java
new file mode 100644
index 0000000..e726c59
--- /dev/null
+++ b/android/graphics/MaskFilter_Delegate.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.MaskFilter
+ *
+ * Through the layoutlib_create tool, the original native methods of MaskFilter have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original MaskFilter class.
+ *
+ * This also serve as a base class for all MaskFilter delegate classes.
+ *
+ * @see DelegateManager
+ *
+ */
+public abstract class MaskFilter_Delegate {
+
+    // ---- delegate manager ----
+    protected static final DelegateManager<MaskFilter_Delegate> sManager =
+            new DelegateManager<MaskFilter_Delegate>(MaskFilter_Delegate.class);
+
+    // ---- delegate helper data ----
+
+    // ---- delegate data ----
+
+    // ---- Public Helper methods ----
+
+    public static MaskFilter_Delegate getDelegate(long nativeShader) {
+        return sManager.getDelegate(nativeShader);
+    }
+
+    public abstract boolean isSupported();
+    public abstract String getSupportMessage();
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static void nativeDestructor(long native_filter) {
+        sManager.removeJavaReferenceFor(native_filter);
+    }
+
+    // ---- Private delegate/helper methods ----
+}
diff --git a/android/graphics/Matrix.java b/android/graphics/Matrix.java
new file mode 100644
index 0000000..486070c
--- /dev/null
+++ b/android/graphics/Matrix.java
@@ -0,0 +1,943 @@
+/*
+ * 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;
+
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.io.PrintWriter;
+
+/**
+ * The Matrix class holds a 3x3 matrix for transforming coordinates.
+ */
+public class Matrix {
+
+    public static final int MSCALE_X = 0;   //!< use with getValues/setValues
+    public static final int MSKEW_X  = 1;   //!< use with getValues/setValues
+    public static final int MTRANS_X = 2;   //!< use with getValues/setValues
+    public static final int MSKEW_Y  = 3;   //!< use with getValues/setValues
+    public static final int MSCALE_Y = 4;   //!< use with getValues/setValues
+    public static final int MTRANS_Y = 5;   //!< use with getValues/setValues
+    public static final int MPERSP_0 = 6;   //!< use with getValues/setValues
+    public static final int MPERSP_1 = 7;   //!< use with getValues/setValues
+    public static final int MPERSP_2 = 8;   //!< use with getValues/setValues
+
+    /** @hide */
+    public final static Matrix IDENTITY_MATRIX = new Matrix() {
+        void oops() {
+            throw new IllegalStateException("Matrix can not be modified");
+        }
+
+        @Override
+        public void set(Matrix src) {
+            oops();
+        }
+
+        @Override
+        public void reset() {
+            oops();
+        }
+
+        @Override
+        public void setTranslate(float dx, float dy) {
+            oops();
+        }
+
+        @Override
+        public void setScale(float sx, float sy, float px, float py) {
+            oops();
+        }
+
+        @Override
+        public void setScale(float sx, float sy) {
+            oops();
+        }
+
+        @Override
+        public void setRotate(float degrees, float px, float py) {
+            oops();
+        }
+
+        @Override
+        public void setRotate(float degrees) {
+            oops();
+        }
+
+        @Override
+        public void setSinCos(float sinValue, float cosValue, float px, float py) {
+            oops();
+        }
+
+        @Override
+        public void setSinCos(float sinValue, float cosValue) {
+            oops();
+        }
+
+        @Override
+        public void setSkew(float kx, float ky, float px, float py) {
+            oops();
+        }
+
+        @Override
+        public void setSkew(float kx, float ky) {
+            oops();
+        }
+
+        @Override
+        public boolean setConcat(Matrix a, Matrix b) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean preTranslate(float dx, float dy) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean preScale(float sx, float sy, float px, float py) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean preScale(float sx, float sy) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean preRotate(float degrees, float px, float py) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean preRotate(float degrees) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean preSkew(float kx, float ky, float px, float py) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean preSkew(float kx, float ky) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean preConcat(Matrix other) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean postTranslate(float dx, float dy) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean postScale(float sx, float sy, float px, float py) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean postScale(float sx, float sy) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean postRotate(float degrees, float px, float py) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean postRotate(float degrees) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean postSkew(float kx, float ky, float px, float py) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean postSkew(float kx, float ky) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean postConcat(Matrix other) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean setRectToRect(RectF src, RectF dst, ScaleToFit stf) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean setPolyToPoly(float[] src, int srcIndex, float[] dst, int dstIndex,
+                int pointCount) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public void setValues(float[] values) {
+            oops();
+        }
+    };
+
+    // sizeof(SkMatrix) is 9 * sizeof(float) + uint32_t
+    private static final long NATIVE_ALLOCATION_SIZE = 40;
+
+    private static class NoImagePreloadHolder {
+        public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+                Matrix.class.getClassLoader(), nGetNativeFinalizer(), NATIVE_ALLOCATION_SIZE);
+    }
+
+    /**
+     * @hide
+     */
+    public final long native_instance;
+
+    /**
+     * Create an identity matrix
+     */
+    public Matrix() {
+        native_instance = nCreate(0);
+        NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, native_instance);
+    }
+
+    /**
+     * Create a matrix that is a (deep) copy of src
+     *
+     * @param src The matrix to copy into this matrix
+     */
+    public Matrix(Matrix src) {
+        native_instance = nCreate(src != null ? src.native_instance : 0);
+        NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, native_instance);
+    }
+
+    /**
+     * Returns true if the matrix is identity. This maybe faster than testing if (getType() == 0)
+     */
+    public boolean isIdentity() {
+        return nIsIdentity(native_instance);
+    }
+
+    /**
+     * Gets whether this matrix is affine. An affine matrix preserves straight lines and has no
+     * perspective.
+     *
+     * @return Whether the matrix is affine.
+     */
+    public boolean isAffine() {
+        return nIsAffine(native_instance);
+    }
+
+    /**
+     * Returns true if will map a rectangle to another rectangle. This can be true if the matrix is
+     * identity, scale-only, or rotates a multiple of 90 degrees.
+     */
+    public boolean rectStaysRect() {
+        return nRectStaysRect(native_instance);
+    }
+
+    /**
+     * (deep) copy the src matrix into this matrix. If src is null, reset this matrix to the
+     * identity matrix.
+     */
+    public void set(Matrix src) {
+        if (src == null) {
+            reset();
+        } else {
+            nSet(native_instance, src.native_instance);
+        }
+    }
+
+    /**
+     * Returns true iff obj is a Matrix and its values equal our values.
+     */
+    @Override
+    public boolean equals(Object obj) {
+        // if (obj == this) return true; -- NaN value would mean matrix != itself
+        if (!(obj instanceof Matrix)) {
+            return false;
+        }
+        return nEquals(native_instance, ((Matrix) obj).native_instance);
+    }
+
+    @Override
+    public int hashCode() {
+        // This should generate the hash code by performing some arithmetic operation on all
+        // the matrix elements -- our equals() does an element-by-element comparison, and we
+        // need to ensure that the hash code for two equal objects is the same. We're not
+        // really using this at the moment, so we take the easy way out.
+        return 44;
+    }
+
+    /** Set the matrix to identity */
+    public void reset() {
+        nReset(native_instance);
+    }
+
+    /** Set the matrix to translate by (dx, dy). */
+    public void setTranslate(float dx, float dy) {
+        nSetTranslate(native_instance, dx, dy);
+    }
+
+    /**
+     * Set the matrix to scale by sx and sy, with a pivot point at (px, py). The pivot point is the
+     * coordinate that should remain unchanged by the specified transformation.
+     */
+    public void setScale(float sx, float sy, float px, float py) {
+        nSetScale(native_instance, sx, sy, px, py);
+    }
+
+    /** Set the matrix to scale by sx and sy. */
+    public void setScale(float sx, float sy) {
+        nSetScale(native_instance, sx, sy);
+    }
+
+    /**
+     * Set the matrix to rotate by the specified number of degrees, with a pivot point at (px, py).
+     * The pivot point is the coordinate that should remain unchanged by the specified
+     * transformation.
+     */
+    public void setRotate(float degrees, float px, float py) {
+        nSetRotate(native_instance, degrees, px, py);
+    }
+
+    /**
+     * Set the matrix to rotate about (0,0) by the specified number of degrees.
+     */
+    public void setRotate(float degrees) {
+        nSetRotate(native_instance, degrees);
+    }
+
+    /**
+     * Set the matrix to rotate by the specified sine and cosine values, with a pivot point at (px,
+     * py). The pivot point is the coordinate that should remain unchanged by the specified
+     * transformation.
+     */
+    public void setSinCos(float sinValue, float cosValue, float px, float py) {
+        nSetSinCos(native_instance, sinValue, cosValue, px, py);
+    }
+
+    /** Set the matrix to rotate by the specified sine and cosine values. */
+    public void setSinCos(float sinValue, float cosValue) {
+        nSetSinCos(native_instance, sinValue, cosValue);
+    }
+
+    /**
+     * Set the matrix to skew by sx and sy, with a pivot point at (px, py). The pivot point is the
+     * coordinate that should remain unchanged by the specified transformation.
+     */
+    public void setSkew(float kx, float ky, float px, float py) {
+        nSetSkew(native_instance, kx, ky, px, py);
+    }
+
+    /** Set the matrix to skew by sx and sy. */
+    public void setSkew(float kx, float ky) {
+        nSetSkew(native_instance, kx, ky);
+    }
+
+    /**
+     * Set the matrix to the concatenation of the two specified matrices and return true.
+     * <p>
+     * Either of the two matrices may also be the target matrix, that is
+     * <code>matrixA.setConcat(matrixA, matrixB);</code> is valid.
+     * </p>
+     * <p class="note">
+     * In {@link android.os.Build.VERSION_CODES#GINGERBREAD_MR1} and below, this function returns
+     * true only if the result can be represented. In
+     * {@link android.os.Build.VERSION_CODES#HONEYCOMB} and above, it always returns true.
+     * </p>
+     */
+    public boolean setConcat(Matrix a, Matrix b) {
+        nSetConcat(native_instance, a.native_instance, b.native_instance);
+        return true;
+    }
+
+    /**
+     * Preconcats the matrix with the specified translation. M' = M * T(dx, dy)
+     */
+    public boolean preTranslate(float dx, float dy) {
+        nPreTranslate(native_instance, dx, dy);
+        return true;
+    }
+
+    /**
+     * Preconcats the matrix with the specified scale. M' = M * S(sx, sy, px, py)
+     */
+    public boolean preScale(float sx, float sy, float px, float py) {
+        nPreScale(native_instance, sx, sy, px, py);
+        return true;
+    }
+
+    /**
+     * Preconcats the matrix with the specified scale. M' = M * S(sx, sy)
+     */
+    public boolean preScale(float sx, float sy) {
+        nPreScale(native_instance, sx, sy);
+        return true;
+    }
+
+    /**
+     * Preconcats the matrix with the specified rotation. M' = M * R(degrees, px, py)
+     */
+    public boolean preRotate(float degrees, float px, float py) {
+        nPreRotate(native_instance, degrees, px, py);
+        return true;
+    }
+
+    /**
+     * Preconcats the matrix with the specified rotation. M' = M * R(degrees)
+     */
+    public boolean preRotate(float degrees) {
+        nPreRotate(native_instance, degrees);
+        return true;
+    }
+
+    /**
+     * Preconcats the matrix with the specified skew. M' = M * K(kx, ky, px, py)
+     */
+    public boolean preSkew(float kx, float ky, float px, float py) {
+        nPreSkew(native_instance, kx, ky, px, py);
+        return true;
+    }
+
+    /**
+     * Preconcats the matrix with the specified skew. M' = M * K(kx, ky)
+     */
+    public boolean preSkew(float kx, float ky) {
+        nPreSkew(native_instance, kx, ky);
+        return true;
+    }
+
+    /**
+     * Preconcats the matrix with the specified matrix. M' = M * other
+     */
+    public boolean preConcat(Matrix other) {
+        nPreConcat(native_instance, other.native_instance);
+        return true;
+    }
+
+    /**
+     * Postconcats the matrix with the specified translation. M' = T(dx, dy) * M
+     */
+    public boolean postTranslate(float dx, float dy) {
+        nPostTranslate(native_instance, dx, dy);
+        return true;
+    }
+
+    /**
+     * Postconcats the matrix with the specified scale. M' = S(sx, sy, px, py) * M
+     */
+    public boolean postScale(float sx, float sy, float px, float py) {
+        nPostScale(native_instance, sx, sy, px, py);
+        return true;
+    }
+
+    /**
+     * Postconcats the matrix with the specified scale. M' = S(sx, sy) * M
+     */
+    public boolean postScale(float sx, float sy) {
+        nPostScale(native_instance, sx, sy);
+        return true;
+    }
+
+    /**
+     * Postconcats the matrix with the specified rotation. M' = R(degrees, px, py) * M
+     */
+    public boolean postRotate(float degrees, float px, float py) {
+        nPostRotate(native_instance, degrees, px, py);
+        return true;
+    }
+
+    /**
+     * Postconcats the matrix with the specified rotation. M' = R(degrees) * M
+     */
+    public boolean postRotate(float degrees) {
+        nPostRotate(native_instance, degrees);
+        return true;
+    }
+
+    /**
+     * Postconcats the matrix with the specified skew. M' = K(kx, ky, px, py) * M
+     */
+    public boolean postSkew(float kx, float ky, float px, float py) {
+        nPostSkew(native_instance, kx, ky, px, py);
+        return true;
+    }
+
+    /**
+     * Postconcats the matrix with the specified skew. M' = K(kx, ky) * M
+     */
+    public boolean postSkew(float kx, float ky) {
+        nPostSkew(native_instance, kx, ky);
+        return true;
+    }
+
+    /**
+     * Postconcats the matrix with the specified matrix. M' = other * M
+     */
+    public boolean postConcat(Matrix other) {
+        nPostConcat(native_instance, other.native_instance);
+        return true;
+    }
+
+    /**
+     * Controlls how the src rect should align into the dst rect for setRectToRect().
+     */
+    public enum ScaleToFit {
+        /**
+         * Scale in X and Y independently, so that src matches dst exactly. This may change the
+         * aspect ratio of the src.
+         */
+        FILL(0),
+        /**
+         * Compute a scale that will maintain the original src aspect ratio, but will also ensure
+         * that src fits entirely inside dst. At least one axis (X or Y) will fit exactly. START
+         * aligns the result to the left and top edges of dst.
+         */
+        START(1),
+        /**
+         * Compute a scale that will maintain the original src aspect ratio, but will also ensure
+         * that src fits entirely inside dst. At least one axis (X or Y) will fit exactly. The
+         * result is centered inside dst.
+         */
+        CENTER(2),
+        /**
+         * Compute a scale that will maintain the original src aspect ratio, but will also ensure
+         * that src fits entirely inside dst. At least one axis (X or Y) will fit exactly. END
+         * aligns the result to the right and bottom edges of dst.
+         */
+        END(3);
+
+        // the native values must match those in SkMatrix.h
+        ScaleToFit(int nativeInt) {
+            this.nativeInt = nativeInt;
+        }
+
+        final int nativeInt;
+    }
+
+    /**
+     * Set the matrix to the scale and translate values that map the source rectangle to the
+     * destination rectangle, returning true if the the result can be represented.
+     *
+     * @param src the source rectangle to map from.
+     * @param dst the destination rectangle to map to.
+     * @param stf the ScaleToFit option
+     * @return true if the matrix can be represented by the rectangle mapping.
+     */
+    public boolean setRectToRect(RectF src, RectF dst, ScaleToFit stf) {
+        if (dst == null || src == null) {
+            throw new NullPointerException();
+        }
+        return nSetRectToRect(native_instance, src, dst, stf.nativeInt);
+    }
+
+    // private helper to perform range checks on arrays of "points"
+    private static void checkPointArrays(float[] src, int srcIndex,
+            float[] dst, int dstIndex,
+            int pointCount) {
+        // check for too-small and too-big indices
+        int srcStop = srcIndex + (pointCount << 1);
+        int dstStop = dstIndex + (pointCount << 1);
+        if ((pointCount | srcIndex | dstIndex | srcStop | dstStop) < 0 ||
+                srcStop > src.length || dstStop > dst.length) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+    }
+
+    /**
+     * Set the matrix such that the specified src points would map to the specified dst points. The
+     * "points" are represented as an array of floats, order [x0, y0, x1, y1, ...], where each
+     * "point" is 2 float values.
+     *
+     * @param src The array of src [x,y] pairs (points)
+     * @param srcIndex Index of the first pair of src values
+     * @param dst The array of dst [x,y] pairs (points)
+     * @param dstIndex Index of the first pair of dst values
+     * @param pointCount The number of pairs/points to be used. Must be [0..4]
+     * @return true if the matrix was set to the specified transformation
+     */
+    public boolean setPolyToPoly(float[] src, int srcIndex,
+            float[] dst, int dstIndex,
+            int pointCount) {
+        if (pointCount > 4) {
+            throw new IllegalArgumentException();
+        }
+        checkPointArrays(src, srcIndex, dst, dstIndex, pointCount);
+        return nSetPolyToPoly(native_instance, src, srcIndex,
+                dst, dstIndex, pointCount);
+    }
+
+    /**
+     * If this matrix can be inverted, return true and if inverse is not null, set inverse to be the
+     * inverse of this matrix. If this matrix cannot be inverted, ignore inverse and return false.
+     */
+    public boolean invert(Matrix inverse) {
+        return nInvert(native_instance, inverse.native_instance);
+    }
+
+    /**
+     * Apply this matrix to the array of 2D points specified by src, and write the transformed
+     * points into the array of points specified by dst. The two arrays represent their "points" as
+     * pairs of floats [x, y].
+     *
+     * @param dst The array of dst points (x,y pairs)
+     * @param dstIndex The index of the first [x,y] pair of dst floats
+     * @param src The array of src points (x,y pairs)
+     * @param srcIndex The index of the first [x,y] pair of src floats
+     * @param pointCount The number of points (x,y pairs) to transform
+     */
+    public void mapPoints(float[] dst, int dstIndex, float[] src, int srcIndex,
+            int pointCount) {
+        checkPointArrays(src, srcIndex, dst, dstIndex, pointCount);
+        nMapPoints(native_instance, dst, dstIndex, src, srcIndex,
+                pointCount, true);
+    }
+
+    /**
+     * Apply this matrix to the array of 2D vectors specified by src, and write the transformed
+     * vectors into the array of vectors specified by dst. The two arrays represent their "vectors"
+     * as pairs of floats [x, y]. Note: this method does not apply the translation associated with
+     * the matrix. Use {@link Matrix#mapPoints(float[], int, float[], int, int)} if you want the
+     * translation to be applied.
+     *
+     * @param dst The array of dst vectors (x,y pairs)
+     * @param dstIndex The index of the first [x,y] pair of dst floats
+     * @param src The array of src vectors (x,y pairs)
+     * @param srcIndex The index of the first [x,y] pair of src floats
+     * @param vectorCount The number of vectors (x,y pairs) to transform
+     */
+    public void mapVectors(float[] dst, int dstIndex, float[] src, int srcIndex,
+            int vectorCount) {
+        checkPointArrays(src, srcIndex, dst, dstIndex, vectorCount);
+        nMapPoints(native_instance, dst, dstIndex, src, srcIndex,
+                vectorCount, false);
+    }
+
+    /**
+     * Apply this matrix to the array of 2D points specified by src, and write the transformed
+     * points into the array of points specified by dst. The two arrays represent their "points" as
+     * pairs of floats [x, y].
+     *
+     * @param dst The array of dst points (x,y pairs)
+     * @param src The array of src points (x,y pairs)
+     */
+    public void mapPoints(float[] dst, float[] src) {
+        if (dst.length != src.length) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        mapPoints(dst, 0, src, 0, dst.length >> 1);
+    }
+
+    /**
+     * Apply this matrix to the array of 2D vectors specified by src, and write the transformed
+     * vectors into the array of vectors specified by dst. The two arrays represent their "vectors"
+     * as pairs of floats [x, y]. Note: this method does not apply the translation associated with
+     * the matrix. Use {@link Matrix#mapPoints(float[], float[])} if you want the translation to be
+     * applied.
+     *
+     * @param dst The array of dst vectors (x,y pairs)
+     * @param src The array of src vectors (x,y pairs)
+     */
+    public void mapVectors(float[] dst, float[] src) {
+        if (dst.length != src.length) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        mapVectors(dst, 0, src, 0, dst.length >> 1);
+    }
+
+    /**
+     * Apply this matrix to the array of 2D points, and write the transformed points back into the
+     * array
+     *
+     * @param pts The array [x0, y0, x1, y1, ...] of points to transform.
+     */
+    public void mapPoints(float[] pts) {
+        mapPoints(pts, 0, pts, 0, pts.length >> 1);
+    }
+
+    /**
+     * Apply this matrix to the array of 2D vectors, and write the transformed vectors back into the
+     * array. Note: this method does not apply the translation associated with the matrix. Use
+     * {@link Matrix#mapPoints(float[])} if you want the translation to be applied.
+     *
+     * @param vecs The array [x0, y0, x1, y1, ...] of vectors to transform.
+     */
+    public void mapVectors(float[] vecs) {
+        mapVectors(vecs, 0, vecs, 0, vecs.length >> 1);
+    }
+
+    /**
+     * Apply this matrix to the src rectangle, and write the transformed rectangle into dst. This is
+     * accomplished by transforming the 4 corners of src, and then setting dst to the bounds of
+     * those points.
+     *
+     * @param dst Where the transformed rectangle is written.
+     * @param src The original rectangle to be transformed.
+     * @return the result of calling rectStaysRect()
+     */
+    public boolean mapRect(RectF dst, RectF src) {
+        if (dst == null || src == null) {
+            throw new NullPointerException();
+        }
+        return nMapRect(native_instance, dst, src);
+    }
+
+    /**
+     * Apply this matrix to the rectangle, and write the transformed rectangle back into it. This is
+     * accomplished by transforming the 4 corners of rect, and then setting it to the bounds of
+     * those points
+     *
+     * @param rect The rectangle to transform.
+     * @return the result of calling rectStaysRect()
+     */
+    public boolean mapRect(RectF rect) {
+        return mapRect(rect, rect);
+    }
+
+    /**
+     * Return the mean radius of a circle after it has been mapped by this matrix. NOTE: in
+     * perspective this value assumes the circle has its center at the origin.
+     */
+    public float mapRadius(float radius) {
+        return nMapRadius(native_instance, radius);
+    }
+
+    /**
+     * Copy 9 values from the matrix into the array.
+     */
+    public void getValues(float[] values) {
+        if (values.length < 9) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        nGetValues(native_instance, values);
+    }
+
+    /**
+     * Copy 9 values from the array into the matrix. Depending on the implementation of Matrix,
+     * these may be transformed into 16.16 integers in the Matrix, such that a subsequent call to
+     * getValues() will not yield exactly the same values.
+     */
+    public void setValues(float[] values) {
+        if (values.length < 9) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        nSetValues(native_instance, values);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(64);
+        sb.append("Matrix{");
+        toShortString(sb);
+        sb.append('}');
+        return sb.toString();
+
+    }
+
+    public String toShortString() {
+        StringBuilder sb = new StringBuilder(64);
+        toShortString(sb);
+        return sb.toString();
+    }
+
+    /**
+     * @hide
+     */
+    public void toShortString(StringBuilder sb) {
+        float[] values = new float[9];
+        getValues(values);
+        sb.append('[');
+        sb.append(values[0]);
+        sb.append(", ");
+        sb.append(values[1]);
+        sb.append(", ");
+        sb.append(values[2]);
+        sb.append("][");
+        sb.append(values[3]);
+        sb.append(", ");
+        sb.append(values[4]);
+        sb.append(", ");
+        sb.append(values[5]);
+        sb.append("][");
+        sb.append(values[6]);
+        sb.append(", ");
+        sb.append(values[7]);
+        sb.append(", ");
+        sb.append(values[8]);
+        sb.append(']');
+    }
+
+    /**
+     * Print short string, to optimize dumping.
+     *
+     * @hide
+     */
+    public void printShortString(PrintWriter pw) {
+        float[] values = new float[9];
+        getValues(values);
+        pw.print('[');
+        pw.print(values[0]);
+        pw.print(", ");
+        pw.print(values[1]);
+        pw.print(", ");
+        pw.print(values[2]);
+        pw.print("][");
+        pw.print(values[3]);
+        pw.print(", ");
+        pw.print(values[4]);
+        pw.print(", ");
+        pw.print(values[5]);
+        pw.print("][");
+        pw.print(values[6]);
+        pw.print(", ");
+        pw.print(values[7]);
+        pw.print(", ");
+        pw.print(values[8]);
+        pw.print(']');
+
+    }
+
+    /** @hide */
+    public final long ni() {
+        return native_instance;
+    }
+
+    // ------------------ Regular JNI ------------------------
+
+    private static native long nCreate(long nSrc_or_zero);
+    private static native long nGetNativeFinalizer();
+
+
+    // ------------------ Fast JNI ------------------------
+
+    @FastNative
+    private static native boolean nSetRectToRect(long nObject,
+            RectF src, RectF dst, int stf);
+    @FastNative
+    private static native boolean nSetPolyToPoly(long nObject,
+            float[] src, int srcIndex, float[] dst, int dstIndex, int pointCount);
+    @FastNative
+    private static native void nMapPoints(long nObject,
+            float[] dst, int dstIndex, float[] src, int srcIndex,
+            int ptCount, boolean isPts);
+    @FastNative
+    private static native boolean nMapRect(long nObject, RectF dst, RectF src);
+    @FastNative
+    private static native void nGetValues(long nObject, float[] values);
+    @FastNative
+    private static native void nSetValues(long nObject, float[] values);
+
+
+    // ------------------ Critical JNI ------------------------
+
+    @CriticalNative
+    private static native boolean nIsIdentity(long nObject);
+    @CriticalNative
+    private static native boolean nIsAffine(long nObject);
+    @CriticalNative
+    private static native boolean nRectStaysRect(long nObject);
+    @CriticalNative
+    private static native void nReset(long nObject);
+    @CriticalNative
+    private static native void nSet(long nObject, long nOther);
+    @CriticalNative
+    private static native void nSetTranslate(long nObject, float dx, float dy);
+    @CriticalNative
+    private static native void nSetScale(long nObject, float sx, float sy, float px, float py);
+    @CriticalNative
+    private static native void nSetScale(long nObject, float sx, float sy);
+    @CriticalNative
+    private static native void nSetRotate(long nObject, float degrees, float px, float py);
+    @CriticalNative
+    private static native void nSetRotate(long nObject, float degrees);
+    @CriticalNative
+    private static native void nSetSinCos(long nObject, float sinValue, float cosValue,
+            float px, float py);
+    @CriticalNative
+    private static native void nSetSinCos(long nObject, float sinValue, float cosValue);
+    @CriticalNative
+    private static native void nSetSkew(long nObject, float kx, float ky, float px, float py);
+    @CriticalNative
+    private static native void nSetSkew(long nObject, float kx, float ky);
+    @CriticalNative
+    private static native void nSetConcat(long nObject, long nA, long nB);
+    @CriticalNative
+    private static native void nPreTranslate(long nObject, float dx, float dy);
+    @CriticalNative
+    private static native void nPreScale(long nObject, float sx, float sy, float px, float py);
+    @CriticalNative
+    private static native void nPreScale(long nObject, float sx, float sy);
+    @CriticalNative
+    private static native void nPreRotate(long nObject, float degrees, float px, float py);
+    @CriticalNative
+    private static native void nPreRotate(long nObject, float degrees);
+    @CriticalNative
+    private static native void nPreSkew(long nObject, float kx, float ky, float px, float py);
+    @CriticalNative
+    private static native void nPreSkew(long nObject, float kx, float ky);
+    @CriticalNative
+    private static native void nPreConcat(long nObject, long nOther_matrix);
+    @CriticalNative
+    private static native void nPostTranslate(long nObject, float dx, float dy);
+    @CriticalNative
+    private static native void nPostScale(long nObject, float sx, float sy, float px, float py);
+    @CriticalNative
+    private static native void nPostScale(long nObject, float sx, float sy);
+    @CriticalNative
+    private static native void nPostRotate(long nObject, float degrees, float px, float py);
+    @CriticalNative
+    private static native void nPostRotate(long nObject, float degrees);
+    @CriticalNative
+    private static native void nPostSkew(long nObject, float kx, float ky, float px, float py);
+    @CriticalNative
+    private static native void nPostSkew(long nObject, float kx, float ky);
+    @CriticalNative
+    private static native void nPostConcat(long nObject, long nOther_matrix);
+    @CriticalNative
+    private static native boolean nInvert(long nObject, long nInverse);
+    @CriticalNative
+    private static native float nMapRadius(long nObject, float radius);
+    @CriticalNative
+    private static native boolean nEquals(long nA, long nB);
+}
diff --git a/android/graphics/Matrix_Delegate.java b/android/graphics/Matrix_Delegate.java
new file mode 100644
index 0000000..354f919
--- /dev/null
+++ b/android/graphics/Matrix_Delegate.java
@@ -0,0 +1,1077 @@
+/*
+ * 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;
+
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.graphics.Matrix.ScaleToFit;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.NoninvertibleTransformException;
+
+import libcore.util.NativeAllocationRegistry_Delegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.Matrix
+ *
+ * Through the layoutlib_create tool, the original native methods of Matrix have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original Matrix class.
+ *
+ * @see DelegateManager
+ *
+ */
+public final class Matrix_Delegate {
+
+    private final static int MATRIX_SIZE = 9;
+
+    // ---- delegate manager ----
+    private static final DelegateManager<Matrix_Delegate> sManager =
+            new DelegateManager<Matrix_Delegate>(Matrix_Delegate.class);
+    private static long sFinalizer = -1;
+
+    // ---- delegate data ----
+    private float mValues[] = new float[MATRIX_SIZE];
+
+    // ---- Public Helper methods ----
+
+    public static Matrix_Delegate getDelegate(long native_instance) {
+        return sManager.getDelegate(native_instance);
+    }
+
+    /**
+     * Returns an {@link AffineTransform} matching the given Matrix.
+     */
+    public static AffineTransform getAffineTransform(Matrix m) {
+        Matrix_Delegate delegate = sManager.getDelegate(m.native_instance);
+        if (delegate == null) {
+            return null;
+        }
+
+        return delegate.getAffineTransform();
+    }
+
+    public static boolean hasPerspective(Matrix m) {
+        Matrix_Delegate delegate = sManager.getDelegate(m.native_instance);
+        if (delegate == null) {
+            return false;
+        }
+
+        return delegate.hasPerspective();
+    }
+
+    /**
+     * Sets the content of the matrix with the content of another matrix.
+     */
+    public void set(Matrix_Delegate matrix) {
+        System.arraycopy(matrix.mValues, 0, mValues, 0, MATRIX_SIZE);
+    }
+
+    /**
+     * Sets the content of the matrix with the content of another matrix represented as an array
+     * of values.
+     */
+    public void set(float[] values) {
+        System.arraycopy(values, 0, mValues, 0, MATRIX_SIZE);
+    }
+
+    /**
+     * Resets the matrix to be the identity matrix.
+     */
+    public void reset() {
+        reset(mValues);
+    }
+
+    /**
+     * Returns whether or not the matrix is identity.
+     */
+    public boolean isIdentity() {
+        for (int i = 0, k = 0; i < 3; i++) {
+            for (int j = 0; j < 3; j++, k++) {
+                if (mValues[k] != ((i==j) ? 1 : 0)) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    public static float[] makeValues(AffineTransform matrix) {
+        float[] values = new float[MATRIX_SIZE];
+        values[0] = (float) matrix.getScaleX();
+        values[1] = (float) matrix.getShearX();
+        values[2] = (float) matrix.getTranslateX();
+        values[3] = (float) matrix.getShearY();
+        values[4] = (float) matrix.getScaleY();
+        values[5] = (float) matrix.getTranslateY();
+        values[6] = 0.f;
+        values[7] = 0.f;
+        values[8] = 1.f;
+
+        return values;
+    }
+
+    public static Matrix_Delegate make(AffineTransform matrix) {
+        return new Matrix_Delegate(makeValues(matrix));
+    }
+
+    public boolean mapRect(RectF dst, RectF src) {
+        // array with 4 corners
+        float[] corners = new float[] {
+                src.left, src.top,
+                src.right, src.top,
+                src.right, src.bottom,
+                src.left, src.bottom,
+        };
+
+        // apply the transform to them.
+        mapPoints(corners);
+
+        // now put the result in the rect. We take the min/max of Xs and min/max of Ys
+        dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6]));
+        dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6]));
+
+        dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7]));
+        dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7]));
+
+
+        return (computeTypeMask() & kRectStaysRect_Mask) != 0;
+    }
+
+
+    /**
+     * Returns an {@link AffineTransform} matching the matrix.
+     */
+    public AffineTransform getAffineTransform() {
+        return getAffineTransform(mValues);
+    }
+
+    public boolean hasPerspective() {
+        return (mValues[6] != 0 || mValues[7] != 0 || mValues[8] != 1);
+    }
+
+
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static long nCreate(long native_src_or_zero) {
+        // create the delegate
+        Matrix_Delegate newDelegate = new Matrix_Delegate();
+
+        // copy from values if needed.
+        if (native_src_or_zero > 0) {
+            Matrix_Delegate oldDelegate = sManager.getDelegate(native_src_or_zero);
+            if (oldDelegate != null) {
+                System.arraycopy(
+                        oldDelegate.mValues, 0,
+                        newDelegate.mValues, 0,
+                        MATRIX_SIZE);
+            }
+        }
+
+        return sManager.addNewDelegate(newDelegate);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nIsIdentity(long native_object) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d == null) {
+            return false;
+        }
+
+        return d.isIdentity();
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nIsAffine(long native_object) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d == null) {
+            return true;
+        }
+
+        return (d.computeTypeMask() & kPerspective_Mask) == 0;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nRectStaysRect(long native_object) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d == null) {
+            return true;
+        }
+
+        return (d.computeTypeMask() & kRectStaysRect_Mask) != 0;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nReset(long native_object) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d == null) {
+            return;
+        }
+
+        reset(d.mValues);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSet(long native_object, long other) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d == null) {
+            return;
+        }
+
+        Matrix_Delegate src = sManager.getDelegate(other);
+        if (src == null) {
+            return;
+        }
+
+        System.arraycopy(src.mValues, 0, d.mValues, 0, MATRIX_SIZE);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetTranslate(long native_object, float dx, float dy) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d == null) {
+            return;
+        }
+
+        setTranslate(d.mValues, dx, dy);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetScale(long native_object, float sx, float sy,
+            float px, float py) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d == null) {
+            return;
+        }
+
+        d.mValues = getScale(sx, sy, px, py);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetScale(long native_object, float sx, float sy) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d == null) {
+            return;
+        }
+
+        d.mValues[0] = sx;
+        d.mValues[1] = 0;
+        d.mValues[2] = 0;
+        d.mValues[3] = 0;
+        d.mValues[4] = sy;
+        d.mValues[5] = 0;
+        d.mValues[6] = 0;
+        d.mValues[7] = 0;
+        d.mValues[8] = 1;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetRotate(long native_object, float degrees, float px, float py) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d == null) {
+            return;
+        }
+
+        d.mValues = getRotate(degrees, px, py);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetRotate(long native_object, float degrees) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d == null) {
+            return;
+        }
+
+        setRotate(d.mValues, degrees);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetSinCos(long native_object, float sinValue, float cosValue,
+            float px, float py) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d == null) {
+            return;
+        }
+
+        // TODO: do it in one pass
+
+        // translate so that the pivot is in 0,0
+        setTranslate(d.mValues, -px, -py);
+
+        // scale
+        d.postTransform(getRotate(sinValue, cosValue));
+        // translate back the pivot
+        d.postTransform(getTranslate(px, py));
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetSinCos(long native_object, float sinValue, float cosValue) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d == null) {
+            return;
+        }
+
+        setRotate(d.mValues, sinValue, cosValue);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetSkew(long native_object, float kx, float ky,
+            float px, float py) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d == null) {
+            return;
+        }
+
+        d.mValues = getSkew(kx, ky, px, py);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetSkew(long native_object, float kx, float ky) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d == null) {
+            return;
+        }
+
+        d.mValues[0] = 1;
+        d.mValues[1] = kx;
+        d.mValues[2] = -0;
+        d.mValues[3] = ky;
+        d.mValues[4] = 1;
+        d.mValues[5] = 0;
+        d.mValues[6] = 0;
+        d.mValues[7] = 0;
+        d.mValues[8] = 1;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetConcat(long native_object, long a, long b) {
+        if (a == native_object) {
+            nPreConcat(native_object, b);
+            return;
+        } else if (b == native_object) {
+            nPostConcat(native_object, a);
+            return;
+        }
+
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        Matrix_Delegate a_mtx = sManager.getDelegate(a);
+        Matrix_Delegate b_mtx = sManager.getDelegate(b);
+        if (d != null && a_mtx != null && b_mtx != null) {
+            multiply(d.mValues, a_mtx.mValues, b_mtx.mValues);
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nPreTranslate(long native_object, float dx, float dy) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d != null) {
+            d.preTransform(getTranslate(dx, dy));
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nPreScale(long native_object, float sx, float sy,
+            float px, float py) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d != null) {
+            d.preTransform(getScale(sx, sy, px, py));
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nPreScale(long native_object, float sx, float sy) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d != null) {
+            d.preTransform(getScale(sx, sy));
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nPreRotate(long native_object, float degrees,
+            float px, float py) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d != null) {
+            d.preTransform(getRotate(degrees, px, py));
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nPreRotate(long native_object, float degrees) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d != null) {
+
+            double rad = Math.toRadians(degrees);
+            float sin = (float) Math.sin(rad);
+            float cos = (float) Math.cos(rad);
+
+            d.preTransform(getRotate(sin, cos));
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nPreSkew(long native_object, float kx, float ky,
+            float px, float py) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d != null) {
+            d.preTransform(getSkew(kx, ky, px, py));
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nPreSkew(long native_object, float kx, float ky) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d != null) {
+            d.preTransform(getSkew(kx, ky));
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nPreConcat(long native_object, long other_matrix) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        Matrix_Delegate other = sManager.getDelegate(other_matrix);
+        if (d != null && other != null) {
+            d.preTransform(other.mValues);
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nPostTranslate(long native_object, float dx, float dy) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d != null) {
+            d.postTransform(getTranslate(dx, dy));
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nPostScale(long native_object, float sx, float sy,
+            float px, float py) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d != null) {
+            d.postTransform(getScale(sx, sy, px, py));
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nPostScale(long native_object, float sx, float sy) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d != null) {
+            d.postTransform(getScale(sx, sy));
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nPostRotate(long native_object, float degrees,
+            float px, float py) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d != null) {
+            d.postTransform(getRotate(degrees, px, py));
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nPostRotate(long native_object, float degrees) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d != null) {
+            d.postTransform(getRotate(degrees));
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nPostSkew(long native_object, float kx, float ky,
+            float px, float py) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d != null) {
+            d.postTransform(getSkew(kx, ky, px, py));
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nPostSkew(long native_object, float kx, float ky) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d != null) {
+            d.postTransform(getSkew(kx, ky));
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nPostConcat(long native_object, long other_matrix) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        Matrix_Delegate other = sManager.getDelegate(other_matrix);
+        if (d != null && other != null) {
+            d.postTransform(other.mValues);
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nSetRectToRect(long native_object, RectF src,
+            RectF dst, int stf) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d == null) {
+            return false;
+        }
+
+        if (src.isEmpty()) {
+            reset(d.mValues);
+            return false;
+        }
+
+        if (dst.isEmpty()) {
+            d.mValues[0] = d.mValues[1] = d.mValues[2] = d.mValues[3] = d.mValues[4] = d.mValues[5]
+               = d.mValues[6] = d.mValues[7] = 0;
+            d.mValues[8] = 1;
+        } else {
+            float    tx, sx = dst.width() / src.width();
+            float    ty, sy = dst.height() / src.height();
+            boolean  xLarger = false;
+
+            if (stf != ScaleToFit.FILL.nativeInt) {
+                if (sx > sy) {
+                    xLarger = true;
+                    sx = sy;
+                } else {
+                    sy = sx;
+                }
+            }
+
+            tx = dst.left - src.left * sx;
+            ty = dst.top - src.top * sy;
+            if (stf == ScaleToFit.CENTER.nativeInt || stf == ScaleToFit.END.nativeInt) {
+                float diff;
+
+                if (xLarger) {
+                    diff = dst.width() - src.width() * sy;
+                } else {
+                    diff = dst.height() - src.height() * sy;
+                }
+
+                if (stf == ScaleToFit.CENTER.nativeInt) {
+                    diff = diff / 2;
+                }
+
+                if (xLarger) {
+                    tx += diff;
+                } else {
+                    ty += diff;
+                }
+            }
+
+            d.mValues[0] = sx;
+            d.mValues[4] = sy;
+            d.mValues[2] = tx;
+            d.mValues[5] = ty;
+            d.mValues[1]  = d.mValues[3] = d.mValues[6] = d.mValues[7] = 0;
+
+        }
+        // shared cleanup
+        d.mValues[8] = 1;
+        return true;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nSetPolyToPoly(long native_object, float[] src, int srcIndex,
+            float[] dst, int dstIndex, int pointCount) {
+        // FIXME
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "Matrix.setPolyToPoly is not supported.",
+                null, null /*data*/);
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nInvert(long native_object, long inverse) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d == null) {
+            return false;
+        }
+
+        Matrix_Delegate inv_mtx = sManager.getDelegate(inverse);
+        if (inv_mtx == null) {
+            return false;
+        }
+
+        try {
+            AffineTransform affineTransform = d.getAffineTransform();
+            AffineTransform inverseTransform = affineTransform.createInverse();
+            inv_mtx.mValues[0] = (float)inverseTransform.getScaleX();
+            inv_mtx.mValues[1] = (float)inverseTransform.getShearX();
+            inv_mtx.mValues[2] = (float)inverseTransform.getTranslateX();
+            inv_mtx.mValues[3] = (float)inverseTransform.getScaleX();
+            inv_mtx.mValues[4] = (float)inverseTransform.getShearY();
+            inv_mtx.mValues[5] = (float)inverseTransform.getTranslateY();
+
+            return true;
+        } catch (NoninvertibleTransformException e) {
+            return false;
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nMapPoints(long native_object, float[] dst, int dstIndex,
+            float[] src, int srcIndex, int ptCount, boolean isPts) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d == null) {
+            return;
+        }
+
+        if (isPts) {
+            d.mapPoints(dst, dstIndex, src, srcIndex, ptCount);
+        } else {
+            d.mapVectors(dst, dstIndex, src, srcIndex, ptCount);
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nMapRect(long native_object, RectF dst, RectF src) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d == null) {
+            return false;
+        }
+
+        return d.mapRect(dst, src);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static float nMapRadius(long native_object, float radius) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d == null) {
+            return 0.f;
+        }
+
+        float[] src = new float[] { radius, 0.f, 0.f, radius };
+        d.mapVectors(src, 0, src, 0, 2);
+
+        float l1 = (float) Math.hypot(src[0], src[1]);
+        float l2 = (float) Math.hypot(src[2], src[3]);
+        return (float) Math.sqrt(l1 * l2);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nGetValues(long native_object, float[] values) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d == null) {
+            return;
+        }
+
+        System.arraycopy(d.mValues, 0, values, 0, MATRIX_SIZE);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetValues(long native_object, float[] values) {
+        Matrix_Delegate d = sManager.getDelegate(native_object);
+        if (d == null) {
+            return;
+        }
+
+        System.arraycopy(values, 0, d.mValues, 0, MATRIX_SIZE);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nEquals(long native_a, long native_b) {
+        Matrix_Delegate a = sManager.getDelegate(native_a);
+        if (a == null) {
+            return false;
+        }
+
+        Matrix_Delegate b = sManager.getDelegate(native_b);
+        if (b == null) {
+            return false;
+        }
+
+        for (int i = 0 ; i < MATRIX_SIZE ; i++) {
+            if (a.mValues[i] != b.mValues[i]) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nGetNativeFinalizer() {
+        synchronized (Matrix_Delegate.class) {
+            if (sFinalizer == -1) {
+                sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(sManager::removeJavaReferenceFor);
+            }
+        }
+        return sFinalizer;
+    }
+
+    // ---- Private helper methods ----
+
+    /*package*/ static AffineTransform getAffineTransform(float[] matrix) {
+        // the AffineTransform constructor takes the value in a different order
+        // for a matrix [ 0 1 2 ]
+        //              [ 3 4 5 ]
+        // the order is 0, 3, 1, 4, 2, 5...
+        return new AffineTransform(
+                matrix[0], matrix[3], matrix[1],
+                matrix[4], matrix[2], matrix[5]);
+    }
+
+    /**
+     * Reset a matrix to the identity
+     */
+    private static void reset(float[] mtx) {
+        for (int i = 0, k = 0; i < 3; i++) {
+            for (int j = 0; j < 3; j++, k++) {
+                mtx[k] = ((i==j) ? 1 : 0);
+            }
+        }
+    }
+
+    @SuppressWarnings("unused")
+    private final static int kIdentity_Mask      = 0;
+    private final static int kTranslate_Mask     = 0x01;  //!< set if the matrix has translation
+    private final static int kScale_Mask         = 0x02;  //!< set if the matrix has X or Y scale
+    private final static int kAffine_Mask        = 0x04;  //!< set if the matrix skews or rotates
+    private final static int kPerspective_Mask   = 0x08;  //!< set if the matrix is in perspective
+    private final static int kRectStaysRect_Mask = 0x10;
+    @SuppressWarnings("unused")
+    private final static int kUnknown_Mask       = 0x80;
+
+    @SuppressWarnings("unused")
+    private final static int kAllMasks           = kTranslate_Mask |
+                                                   kScale_Mask |
+                                                   kAffine_Mask |
+                                                   kPerspective_Mask |
+                                                   kRectStaysRect_Mask;
+
+    // these guys align with the masks, so we can compute a mask from a variable 0/1
+    @SuppressWarnings("unused")
+    private final static int kTranslate_Shift = 0;
+    @SuppressWarnings("unused")
+    private final static int kScale_Shift = 1;
+    @SuppressWarnings("unused")
+    private final static int kAffine_Shift = 2;
+    @SuppressWarnings("unused")
+    private final static int kPerspective_Shift = 3;
+    private final static int kRectStaysRect_Shift = 4;
+
+    private int computeTypeMask() {
+        int mask = 0;
+
+        if (mValues[6] != 0. || mValues[7] != 0. || mValues[8] != 1.) {
+            mask |= kPerspective_Mask;
+        }
+
+        if (mValues[2] != 0. || mValues[5] != 0.) {
+            mask |= kTranslate_Mask;
+        }
+
+        float m00 = mValues[0];
+        float m01 = mValues[1];
+        float m10 = mValues[3];
+        float m11 = mValues[4];
+
+        if (m01 != 0. || m10 != 0.) {
+            mask |= kAffine_Mask;
+        }
+
+        if (m00 != 1. || m11 != 1.) {
+            mask |= kScale_Mask;
+        }
+
+        if ((mask & kPerspective_Mask) == 0) {
+            // map non-zero to 1
+            int im00 = m00 != 0 ? 1 : 0;
+            int im01 = m01 != 0 ? 1 : 0;
+            int im10 = m10 != 0 ? 1 : 0;
+            int im11 = m11 != 0 ? 1 : 0;
+
+            // record if the (p)rimary and (s)econdary diagonals are all 0 or
+            // all non-zero (answer is 0 or 1)
+            int dp0 = (im00 | im11) ^ 1;  // true if both are 0
+            int dp1 = im00 & im11;        // true if both are 1
+            int ds0 = (im01 | im10) ^ 1;  // true if both are 0
+            int ds1 = im01 & im10;        // true if both are 1
+
+            // return 1 if primary is 1 and secondary is 0 or
+            // primary is 0 and secondary is 1
+            mask |= ((dp0 & ds1) | (dp1 & ds0)) << kRectStaysRect_Shift;
+        }
+
+        return mask;
+    }
+
+    private Matrix_Delegate() {
+        reset();
+    }
+
+    private Matrix_Delegate(float[] values) {
+        System.arraycopy(values, 0, mValues, 0, MATRIX_SIZE);
+    }
+
+    /**
+     * Adds the given transformation to the current Matrix
+     * <p/>This in effect does this = this*matrix
+     * @param matrix
+     */
+    private void postTransform(float[] matrix) {
+        float[] tmp = new float[9];
+        multiply(tmp, mValues, matrix);
+        mValues = tmp;
+    }
+
+    /**
+     * Adds the given transformation to the current Matrix
+     * <p/>This in effect does this = matrix*this
+     * @param matrix
+     */
+    private void preTransform(float[] matrix) {
+        float[] tmp = new float[9];
+        multiply(tmp, matrix, mValues);
+        mValues = tmp;
+    }
+
+    /**
+     * Apply this matrix to the array of 2D points specified by src, and write
+      * the transformed points into the array of points specified by dst. The
+      * two arrays represent their "points" as pairs of floats [x, y].
+      *
+      * @param dst   The array of dst points (x,y pairs)
+      * @param dstIndex The index of the first [x,y] pair of dst floats
+      * @param src   The array of src points (x,y pairs)
+      * @param srcIndex The index of the first [x,y] pair of src floats
+      * @param pointCount The number of points (x,y pairs) to transform
+      */
+
+     private void mapPoints(float[] dst, int dstIndex, float[] src, int srcIndex,
+                           int pointCount) {
+         final int count = pointCount * 2;
+
+         float[] tmpDest = dst;
+         boolean inPlace = dst == src;
+         if (inPlace) {
+             tmpDest = new float[dstIndex + count];
+         }
+
+         for (int i = 0 ; i < count ; i += 2) {
+             // just in case we are doing in place, we better put this in temp vars
+             float x = mValues[0] * src[i + srcIndex] +
+                       mValues[1] * src[i + srcIndex + 1] +
+                       mValues[2];
+             float y = mValues[3] * src[i + srcIndex] +
+                       mValues[4] * src[i + srcIndex + 1] +
+                       mValues[5];
+
+             tmpDest[i + dstIndex]     = x;
+             tmpDest[i + dstIndex + 1] = y;
+         }
+
+         if (inPlace) {
+             System.arraycopy(tmpDest, dstIndex, dst, dstIndex, count);
+         }
+     }
+
+     /**
+      * Apply this matrix to the array of 2D points, and write the transformed
+      * points back into the array
+      *
+      * @param pts The array [x0, y0, x1, y1, ...] of points to transform.
+      */
+
+     private void mapPoints(float[] pts) {
+         mapPoints(pts, 0, pts, 0, pts.length >> 1);
+     }
+
+     private void mapVectors(float[] dst, int dstIndex, float[] src, int srcIndex, int ptCount) {
+         if (hasPerspective()) {
+             // transform the (0,0) point
+             float[] origin = new float[] { 0.f, 0.f};
+             mapPoints(origin);
+
+             // translate the vector data as points
+             mapPoints(dst, dstIndex, src, srcIndex, ptCount);
+
+             // then substract the transformed origin.
+             final int count = ptCount * 2;
+             for (int i = 0 ; i < count ; i += 2) {
+                 dst[dstIndex + i] = dst[dstIndex + i] - origin[0];
+                 dst[dstIndex + i + 1] = dst[dstIndex + i + 1] - origin[1];
+             }
+         } else {
+             // make a copy of the matrix
+             Matrix_Delegate copy = new Matrix_Delegate(mValues);
+
+             // remove the translation
+             setTranslate(copy.mValues, 0, 0);
+
+             // map the content as points.
+             copy.mapPoints(dst, dstIndex, src, srcIndex, ptCount);
+         }
+     }
+
+    /**
+     * multiply two matrices and store them in a 3rd.
+     * <p/>This in effect does dest = a*b
+     * dest cannot be the same as a or b.
+     */
+     /*package*/ static void multiply(float dest[], float[] a, float[] b) {
+        // first row
+        dest[0] = b[0] * a[0] + b[1] * a[3] + b[2] * a[6];
+        dest[1] = b[0] * a[1] + b[1] * a[4] + b[2] * a[7];
+        dest[2] = b[0] * a[2] + b[1] * a[5] + b[2] * a[8];
+
+        // 2nd row
+        dest[3] = b[3] * a[0] + b[4] * a[3] + b[5] * a[6];
+        dest[4] = b[3] * a[1] + b[4] * a[4] + b[5] * a[7];
+        dest[5] = b[3] * a[2] + b[4] * a[5] + b[5] * a[8];
+
+        // 3rd row
+        dest[6] = b[6] * a[0] + b[7] * a[3] + b[8] * a[6];
+        dest[7] = b[6] * a[1] + b[7] * a[4] + b[8] * a[7];
+        dest[8] = b[6] * a[2] + b[7] * a[5] + b[8] * a[8];
+    }
+
+    /**
+     * Returns a matrix that represents a given translate
+     * @param dx
+     * @param dy
+     * @return
+     */
+    /*package*/ static float[] getTranslate(float dx, float dy) {
+        return setTranslate(new float[9], dx, dy);
+    }
+
+    /*package*/ static float[] setTranslate(float[] dest, float dx, float dy) {
+        dest[0] = 1;
+        dest[1] = 0;
+        dest[2] = dx;
+        dest[3] = 0;
+        dest[4] = 1;
+        dest[5] = dy;
+        dest[6] = 0;
+        dest[7] = 0;
+        dest[8] = 1;
+        return dest;
+    }
+
+    /*package*/ static float[] getScale(float sx, float sy) {
+        return new float[] { sx, 0, 0, 0, sy, 0, 0, 0, 1 };
+    }
+
+    /**
+     * Returns a matrix that represents the given scale info.
+     * @param sx
+     * @param sy
+     * @param px
+     * @param py
+     */
+    /*package*/ static float[] getScale(float sx, float sy, float px, float py) {
+        float[] tmp = new float[9];
+        float[] tmp2 = new float[9];
+
+        // TODO: do it in one pass
+
+        // translate tmp so that the pivot is in 0,0
+        setTranslate(tmp, -px, -py);
+
+        // scale into tmp2
+        multiply(tmp2, tmp, getScale(sx, sy));
+
+        // translate back the pivot back into tmp
+        multiply(tmp, tmp2, getTranslate(px, py));
+
+        return tmp;
+    }
+
+
+    /*package*/ static float[] getRotate(float degrees) {
+        double rad = Math.toRadians(degrees);
+        float sin = (float)Math.sin(rad);
+        float cos = (float)Math.cos(rad);
+
+        return getRotate(sin, cos);
+    }
+
+    /*package*/ static float[] getRotate(float sin, float cos) {
+        return setRotate(new float[9], sin, cos);
+    }
+
+    /*package*/ static float[] setRotate(float[] dest, float degrees) {
+        double rad = Math.toRadians(degrees);
+        float sin = (float)Math.sin(rad);
+        float cos = (float)Math.cos(rad);
+
+        return setRotate(dest, sin, cos);
+    }
+
+    /*package*/ static float[] setRotate(float[] dest, float sin, float cos) {
+        dest[0] = cos;
+        dest[1] = -sin;
+        dest[2] = 0;
+        dest[3] = sin;
+        dest[4] = cos;
+        dest[5] = 0;
+        dest[6] = 0;
+        dest[7] = 0;
+        dest[8] = 1;
+        return dest;
+    }
+
+    /*package*/ static float[] getRotate(float degrees, float px, float py) {
+        float[] tmp = new float[9];
+        float[] tmp2 = new float[9];
+
+        // TODO: do it in one pass
+
+        // translate so that the pivot is in 0,0
+        setTranslate(tmp, -px, -py);
+
+        // rotate into tmp2
+        double rad = Math.toRadians(degrees);
+        float cos = (float)Math.cos(rad);
+        float sin = (float)Math.sin(rad);
+        multiply(tmp2, tmp, getRotate(sin, cos));
+
+        // translate back the pivot back into tmp
+        multiply(tmp, tmp2, getTranslate(px, py));
+
+        return tmp;
+    }
+
+    /*package*/ static float[] getSkew(float kx, float ky) {
+        return new float[] { 1, kx, 0, ky, 1, 0, 0, 0, 1 };
+    }
+
+    /*package*/ static float[] getSkew(float kx, float ky, float px, float py) {
+        float[] tmp = new float[9];
+        float[] tmp2 = new float[9];
+
+        // TODO: do it in one pass
+
+        // translate so that the pivot is in 0,0
+        setTranslate(tmp, -px, -py);
+
+        // skew into tmp2
+        multiply(tmp2, tmp, new float[] { 1, kx, 0, ky, 1, 0, 0, 0, 1 });
+        // translate back the pivot back into tmp
+        multiply(tmp, tmp2, getTranslate(px, py));
+
+        return tmp;
+    }
+}
diff --git a/android/graphics/Movie.java b/android/graphics/Movie.java
new file mode 100644
index 0000000..c8f86c6
--- /dev/null
+++ b/android/graphics/Movie.java
@@ -0,0 +1,105 @@
+/*
+ * 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;
+
+import android.content.res.AssetManager;
+import java.io.InputStream;
+import java.io.FileInputStream;
+
+public class Movie {
+    private long mNativeMovie;
+
+    private Movie(long nativeMovie) {
+        if (nativeMovie == 0) {
+            throw new RuntimeException("native movie creation failed");
+        }
+        mNativeMovie = nativeMovie;
+    }
+
+    public native int width();
+    public native int height();
+    public native boolean isOpaque();
+    public native int duration();
+
+    public native boolean setTime(int relativeMilliseconds);
+
+    private native void nDraw(long nativeCanvas, float x, float y, long paintHandle);
+
+    public void draw(Canvas canvas, float x, float y, Paint paint) {
+        nDraw(canvas.getNativeCanvasWrapper(), x, y,
+                paint != null ? paint.getNativeInstance() : 0);
+    }
+
+    public void draw(Canvas canvas, float x, float y) {
+        nDraw(canvas.getNativeCanvasWrapper(), x, y, 0);
+    }
+
+    public static Movie decodeStream(InputStream is) {
+        if (is == null) {
+            return null;
+        }
+        if (is instanceof AssetManager.AssetInputStream) {
+            final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
+            return nativeDecodeAsset(asset);
+        }
+
+        return nativeDecodeStream(is);
+    }
+
+    private static native Movie nativeDecodeAsset(long asset);
+    private static native Movie nativeDecodeStream(InputStream is);
+    public static native Movie decodeByteArray(byte[] data, int offset,
+                                               int length);
+
+    private static native void nativeDestructor(long nativeMovie);
+
+    public static Movie decodeFile(String pathName) {
+        InputStream is;
+        try {
+            is = new FileInputStream(pathName);
+        }
+        catch (java.io.FileNotFoundException e) {
+            return null;
+        }
+        return decodeTempStream(is);
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            nativeDestructor(mNativeMovie);
+            mNativeMovie = 0;
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private static Movie decodeTempStream(InputStream is) {
+        Movie moov = null;
+        try {
+            moov = decodeStream(is);
+            is.close();
+        }
+        catch (java.io.IOException e) {
+            /*  do nothing.
+                If the exception happened on open, moov will be null.
+                If it happened on close, moov is still valid.
+            */
+        }
+        return moov;
+    }
+}
diff --git a/android/graphics/NinePatch.java b/android/graphics/NinePatch.java
new file mode 100644
index 0000000..b6a209f
--- /dev/null
+++ b/android/graphics/NinePatch.java
@@ -0,0 +1,281 @@
+/*
+ * 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;
+
+/**
+ * The NinePatch class permits drawing a bitmap in nine or more sections.
+ * Essentially, it allows the creation of custom graphics that will scale the
+ * way that you define, when content added within the image exceeds the normal
+ * bounds of the graphic. For a thorough explanation of a NinePatch image, 
+ * read the discussion in the 
+ * <a href="{@docRoot}guide/topics/graphics/2d-graphics.html#nine-patch">2D
+ * Graphics</a> document.
+ * <p>
+ * The <a href="{@docRoot}guide/developing/tools/draw9patch.html">Draw 9-Patch</a> 
+ * tool offers an extremely handy way to create your NinePatch images,
+ * using a WYSIWYG graphics editor.
+ * </p>
+ */
+public class NinePatch {
+    /**
+     * Struct of inset information attached to a 9 patch bitmap.
+     *
+     * Present on a 9 patch bitmap if it optical insets were manually included,
+     * or if outline insets were automatically included by aapt.
+     *
+     * @hide
+     */
+    public static class InsetStruct {
+        @SuppressWarnings({"UnusedDeclaration"}) // called from JNI
+        InsetStruct(int opticalLeft, int opticalTop, int opticalRight, int opticalBottom,
+                int outlineLeft, int outlineTop, int outlineRight, int outlineBottom,
+                float outlineRadius, int outlineAlpha, float decodeScale) {
+            opticalRect = new Rect(opticalLeft, opticalTop, opticalRight, opticalBottom);
+            opticalRect.scale(decodeScale);
+
+            outlineRect = scaleInsets(outlineLeft, outlineTop,
+                    outlineRight, outlineBottom, decodeScale);
+
+            this.outlineRadius = outlineRadius * decodeScale;
+            this.outlineAlpha = outlineAlpha / 255.0f;
+        }
+
+        public final Rect opticalRect;
+        public final Rect outlineRect;
+        public final float outlineRadius;
+        public final float outlineAlpha;
+
+        /**
+         * Scales up the rect by the given scale, ceiling values, so actual outline Rect
+         * grows toward the inside.
+         */
+        public static Rect scaleInsets(int left, int top, int right, int bottom, float scale) {
+            if (scale == 1.0f) {
+                return new Rect(left, top, right, bottom);
+            }
+
+            Rect result = new Rect();
+            result.left = (int) Math.ceil(left * scale);
+            result.top = (int) Math.ceil(top * scale);
+            result.right = (int) Math.ceil(right * scale);
+            result.bottom = (int) Math.ceil(bottom * scale);
+            return  result;
+        }
+    }
+
+    private final Bitmap mBitmap;
+
+    /**
+     * Used by native code. This pointer is an instance of Res_png_9patch*.
+     *
+     * @hide
+     */
+    public long mNativeChunk;
+
+    private Paint mPaint;
+    private String mSrcName;
+
+    /**
+     * Create a drawable projection from a bitmap to nine patches.
+     *
+     * @param bitmap The bitmap describing the patches.
+     * @param chunk The 9-patch data chunk describing how the underlying bitmap
+     *              is split apart and drawn.
+     */
+    public NinePatch(Bitmap bitmap, byte[] chunk) {
+        this(bitmap, chunk, null);
+    }
+
+    /** 
+     * Create a drawable projection from a bitmap to nine patches.
+     *
+     * @param bitmap The bitmap describing the patches.
+     * @param chunk The 9-patch data chunk describing how the underlying
+     *              bitmap is split apart and drawn.
+     * @param srcName The name of the source for the bitmap. Might be null.
+     */
+    public NinePatch(Bitmap bitmap, byte[] chunk, String srcName) {
+        mBitmap = bitmap;
+        mSrcName = srcName;
+        mNativeChunk = validateNinePatchChunk(chunk);
+    }
+
+    /**
+     * @hide
+     */
+    public NinePatch(NinePatch patch) {
+        mBitmap = patch.mBitmap;
+        mSrcName = patch.mSrcName;
+        if (patch.mPaint != null) {
+            mPaint = new Paint(patch.mPaint);
+        }
+        // No need to validate the 9patch chunk again, it was done by
+        // the instance we're copying from
+        mNativeChunk = patch.mNativeChunk;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mNativeChunk != 0) {
+                // only attempt to destroy correctly initilized chunks
+                nativeFinalize(mNativeChunk);
+                mNativeChunk = 0;
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Returns the name of this NinePatch object if one was specified
+     * when calling the constructor.
+     */
+    public String getName() {
+        return mSrcName;
+    }
+
+    /**
+     * Returns the paint used to draw this NinePatch. The paint can be null.
+     *
+     * @see #setPaint(Paint)
+     * @see #draw(Canvas, Rect)
+     * @see #draw(Canvas, RectF)
+     */
+    public Paint getPaint() {
+        return mPaint;
+    }
+
+    /**
+     * Sets the paint to use when drawing the NinePatch.
+     *
+     * @param p The paint that will be used to draw this NinePatch.
+     *
+     * @see #getPaint()
+     * @see #draw(Canvas, Rect)
+     * @see #draw(Canvas, RectF)
+     */
+    public void setPaint(Paint p) {
+        mPaint = p;
+    }
+
+    /**
+     * Returns the bitmap used to draw this NinePatch.
+     */
+    public Bitmap getBitmap() {
+        return mBitmap;
+    }
+    
+    /** 
+     * Draws the NinePatch. This method will use the paint returned by {@link #getPaint()}.
+     *
+     * @param canvas A container for the current matrix and clip used to draw the NinePatch.
+     * @param location Where to draw the NinePatch.
+     */
+    public void draw(Canvas canvas, RectF location) {
+        canvas.drawPatch(this, location, mPaint);
+    }
+
+    /** 
+     * Draws the NinePatch. This method will use the paint returned by {@link #getPaint()}.
+     *
+     * @param canvas A container for the current matrix and clip used to draw the NinePatch.
+     * @param location Where to draw the NinePatch.
+     */
+    public void draw(Canvas canvas, Rect location) {
+        canvas.drawPatch(this, location, mPaint);
+    }
+
+    /** 
+     * Draws the NinePatch. This method will ignore the paint returned
+     * by {@link #getPaint()} and use the specified paint instead.
+     *
+     * @param canvas A container for the current matrix and clip used to draw the NinePatch.
+     * @param location Where to draw the NinePatch.
+     * @param paint The Paint to draw through.
+     */
+    public void draw(Canvas canvas, Rect location, Paint paint) {
+        canvas.drawPatch(this, location, paint);
+    }
+
+    /**
+     * Return the underlying bitmap's density, as per
+     * {@link Bitmap#getDensity() Bitmap.getDensity()}.
+     */
+    public int getDensity() {
+        return mBitmap.mDensity;
+    }
+
+    /**
+     * Returns the intrinsic width, in pixels, of this NinePatch. This is equivalent
+     * to querying the width of the underlying bitmap returned by {@link #getBitmap()}.
+     */
+    public int getWidth() {
+        return mBitmap.getWidth();
+    }
+
+    /**
+     * Returns the intrinsic height, in pixels, of this NinePatch. This is equivalent
+     * to querying the height of the underlying bitmap returned by {@link #getBitmap()}.
+     */
+    public int getHeight() {
+        return mBitmap.getHeight();
+    }
+
+    /**
+     * Indicates whether this NinePatch contains transparent or translucent pixels.
+     * This is equivalent to calling <code>getBitmap().hasAlpha()</code> on this
+     * NinePatch.
+     */
+    public final boolean hasAlpha() {
+        return mBitmap.hasAlpha();
+    }
+
+    /**
+     * Returns a {@link Region} representing the parts of the NinePatch that are
+     * completely transparent.
+     *
+     * @param bounds The location and size of the NinePatch.
+     *
+     * @return null if the NinePatch has no transparent region to
+     * report, else a {@link Region} holding the parts of the specified bounds
+     * that are transparent.
+     */
+    public final Region getTransparentRegion(Rect bounds) {
+        long r = nativeGetTransparentRegion(mBitmap, mNativeChunk, bounds);
+        return r != 0 ? new Region(r) : null;
+    }
+
+    /**
+     * Verifies that the specified byte array is a valid 9-patch data chunk.
+     *
+     * @param chunk A byte array representing a 9-patch data chunk.
+     *
+     * @return True if the specified byte array represents a 9-patch data chunk,
+     *         false otherwise.
+     */
+    public native static boolean isNinePatchChunk(byte[] chunk);
+
+    /**
+     * Validates the 9-patch chunk and throws an exception if the chunk is invalid.
+     * If validation is successful, this method returns a native Res_png_9patch*
+     * object used by the renderers.
+     */
+    private static native long validateNinePatchChunk(byte[] chunk);
+    private static native void nativeFinalize(long chunk);
+    private static native long nativeGetTransparentRegion(Bitmap bitmap, long chunk, Rect location);
+}
diff --git a/android/graphics/NinePatch_Delegate.java b/android/graphics/NinePatch_Delegate.java
new file mode 100644
index 0000000..43e5b0f
--- /dev/null
+++ b/android/graphics/NinePatch_Delegate.java
@@ -0,0 +1,181 @@
+/*
+ * 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;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.ninepatch.NinePatchChunk;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.graphics.drawable.NinePatchDrawable;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.lang.ref.SoftReference;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Delegate implementing the native methods of android.graphics.NinePatch
+ *
+ * Through the layoutlib_create tool, the original native methods of NinePatch have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager}
+ * around to map int to instance of the delegate.
+ *
+ */
+public final class NinePatch_Delegate {
+
+    // ---- delegate manager ----
+    private static final DelegateManager<NinePatch_Delegate> sManager =
+            new DelegateManager<>(NinePatch_Delegate.class);
+
+    // ---- delegate helper data ----
+    /**
+     * Cache map for {@link NinePatchChunk}.
+     * When the chunks are created they are serialized into a byte[], and both are put
+     * in the cache, using a {@link SoftReference} for the chunk. The default Java classes
+     * for {@link NinePatch} and {@link NinePatchDrawable} only reference to the byte[] data, and
+     * provide this for drawing.
+     * Using the cache map allows us to not have to deserialize the byte[] back into a
+     * {@link NinePatchChunk} every time a rendering is done.
+     */
+    private final static Map<byte[], SoftReference<NinePatchChunk>> sChunkCache = new HashMap<>();
+
+    // ---- delegate data ----
+    private byte[] chunk;
+
+
+    // ---- Public Helper methods ----
+
+    /**
+     * Serializes the given chunk.
+     *
+     * @return the serialized data for the chunk.
+     */
+    public static byte[] serialize(NinePatchChunk chunk) {
+        // serialize the chunk to get a byte[]
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        ObjectOutputStream oos = null;
+        try {
+            oos = new ObjectOutputStream(baos);
+            oos.writeObject(chunk);
+        } catch (IOException e) {
+            Bridge.getLog().error(null, "Failed to serialize NinePatchChunk.", e, null /*data*/);
+            return null;
+        } finally {
+            if (oos != null) {
+                try {
+                    oos.close();
+                } catch (IOException ignored) {
+                }
+            }
+        }
+
+        // get the array and add it to the cache
+        byte[] array = baos.toByteArray();
+        sChunkCache.put(array, new SoftReference<>(chunk));
+        return array;
+    }
+
+    /**
+     * Returns a {@link NinePatchChunk} object for the given serialized representation.
+     *
+     * If the chunk is present in the cache then the object from the cache is returned, otherwise
+     * the array is deserialized into a {@link NinePatchChunk} object.
+     *
+     * @param array the serialized representation of the chunk.
+     * @return the NinePatchChunk or null if deserialization failed.
+     */
+    public static NinePatchChunk getChunk(byte[] array) {
+        SoftReference<NinePatchChunk> chunkRef = sChunkCache.get(array);
+        NinePatchChunk chunk = chunkRef.get();
+        if (chunk == null) {
+            ByteArrayInputStream bais = new ByteArrayInputStream(array);
+            ObjectInputStream ois = null;
+            try {
+                ois = new ObjectInputStream(bais);
+                chunk = (NinePatchChunk) ois.readObject();
+
+                // put back the chunk in the cache
+                if (chunk != null) {
+                    sChunkCache.put(array, new SoftReference<>(chunk));
+                }
+            } catch (IOException e) {
+                Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+                        "Failed to deserialize NinePatchChunk content.", e, null /*data*/);
+                return null;
+            } catch (ClassNotFoundException e) {
+                Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+                        "Failed to deserialize NinePatchChunk class.", e, null /*data*/);
+                return null;
+            } finally {
+                if (ois != null) {
+                    try {
+                        ois.close();
+                    } catch (IOException ignored) {
+                    }
+                }
+            }
+        }
+
+        return chunk;
+    }
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static boolean isNinePatchChunk(byte[] chunk) {
+        NinePatchChunk chunkObject = getChunk(chunk);
+        return chunkObject != null;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long validateNinePatchChunk(byte[] chunk) {
+        // the default JNI implementation only checks that the byte[] has the same
+        // size as the C struct it represent. Since we cannot do the same check (serialization
+        // will return different size depending on content), we do nothing.
+        NinePatch_Delegate newDelegate = new NinePatch_Delegate();
+        newDelegate.chunk = chunk;
+        return sManager.addNewDelegate(newDelegate);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nativeFinalize(long chunk) {
+        sManager.removeJavaReferenceFor(chunk);
+    }
+
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeGetTransparentRegion(Bitmap bitmap, long chunk, Rect location) {
+        return 0;
+    }
+
+    static byte[] getChunk(long nativeNinePatch) {
+        NinePatch_Delegate delegate = sManager.getDelegate(nativeNinePatch);
+        if (delegate != null) {
+            return delegate.chunk;
+        }
+        return null;
+    }
+
+}
diff --git a/android/graphics/Outline.java b/android/graphics/Outline.java
new file mode 100644
index 0000000..1c85df0
--- /dev/null
+++ b/android/graphics/Outline.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2014 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;
+
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.graphics.drawable.Drawable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Defines a simple shape, used for bounding graphical regions.
+ * <p>
+ * Can be computed for a View, or computed by a Drawable, to drive the shape of
+ * shadows cast by a View, or to clip the contents of the View.
+ *
+ * @see android.view.ViewOutlineProvider
+ * @see android.view.View#setOutlineProvider(android.view.ViewOutlineProvider)
+ * @see Drawable#getOutline(Outline)
+ */
+public final class Outline {
+    private static final float RADIUS_UNDEFINED = Float.NEGATIVE_INFINITY;
+
+    /** @hide */
+    public static final int MODE_EMPTY = 0;
+    /** @hide */
+    public static final int MODE_ROUND_RECT = 1;
+    /** @hide */
+    public static final int MODE_CONVEX_PATH = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = false,
+            value = {
+                    MODE_EMPTY,
+                    MODE_ROUND_RECT,
+                    MODE_CONVEX_PATH,
+            })
+    public @interface Mode {}
+
+    /** @hide */
+    @Mode
+    public int mMode = MODE_EMPTY;
+
+    /**
+     * Only guaranteed to be non-null when mode == MODE_CONVEX_PATH
+     *
+     * @hide
+     */
+    public Path mPath;
+
+    /** @hide */
+    public final Rect mRect = new Rect();
+    /** @hide */
+    public float mRadius = RADIUS_UNDEFINED;
+    /** @hide */
+    public float mAlpha;
+
+    /**
+     * Constructs an empty Outline. Call one of the setter methods to make
+     * the outline valid for use with a View.
+     */
+    public Outline() {}
+
+    /**
+     * Constructs an Outline with a copy of the data in src.
+     */
+    public Outline(@NonNull Outline src) {
+        set(src);
+    }
+
+    /**
+     * Sets the outline to be empty.
+     *
+     * @see #isEmpty()
+     */
+    public void setEmpty() {
+        if (mPath != null) {
+            // rewind here to avoid thrashing the allocations, but could alternately clear ref
+            mPath.rewind();
+        }
+        mMode = MODE_EMPTY;
+        mRect.setEmpty();
+        mRadius = RADIUS_UNDEFINED;
+    }
+
+    /**
+     * Returns whether the Outline is empty.
+     * <p>
+     * Outlines are empty when constructed, or if {@link #setEmpty()} is called,
+     * until a setter method is called
+     *
+     * @see #setEmpty()
+     */
+    public boolean isEmpty() {
+        return mMode == MODE_EMPTY;
+    }
+
+
+    /**
+     * Returns whether the outline can be used to clip a View.
+     * <p>
+     * Currently, only Outlines that can be represented as a rectangle, circle,
+     * or round rect support clipping.
+     *
+     * @see android.view.View#setClipToOutline(boolean)
+     */
+    public boolean canClip() {
+        return mMode != MODE_CONVEX_PATH;
+    }
+
+    /**
+     * Sets the alpha represented by the Outline - the degree to which the
+     * producer is guaranteed to be opaque over the Outline's shape.
+     * <p>
+     * An alpha value of <code>0.0f</code> either represents completely
+     * transparent content, or content that isn't guaranteed to fill the shape
+     * it publishes.
+     * <p>
+     * Content producing a fully opaque (alpha = <code>1.0f</code>) outline is
+     * assumed by the drawing system to fully cover content beneath it,
+     * meaning content beneath may be optimized away.
+     */
+    public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) {
+        mAlpha = alpha;
+    }
+
+    /**
+     * Returns the alpha represented by the Outline.
+     */
+    public float getAlpha() {
+        return mAlpha;
+    }
+
+    /**
+     * Replace the contents of this Outline with the contents of src.
+     *
+     * @param src Source outline to copy from.
+     */
+    public void set(@NonNull Outline src) {
+        mMode = src.mMode;
+        if (src.mMode == MODE_CONVEX_PATH) {
+            if (mPath == null) {
+                mPath = new Path();
+            }
+            mPath.set(src.mPath);
+        }
+        mRect.set(src.mRect);
+        mRadius = src.mRadius;
+        mAlpha = src.mAlpha;
+    }
+
+    /**
+     * Sets the Outline to the rounded rect defined by the input rect, and
+     * corner radius.
+     */
+    public void setRect(int left, int top, int right, int bottom) {
+        setRoundRect(left, top, right, bottom, 0.0f);
+    }
+
+    /**
+     * Convenience for {@link #setRect(int, int, int, int)}
+     */
+    public void setRect(@NonNull Rect rect) {
+        setRect(rect.left, rect.top, rect.right, rect.bottom);
+    }
+
+    /**
+     * Sets the Outline to the rounded rect defined by the input rect, and corner radius.
+     * <p>
+     * Passing a zero radius is equivalent to calling {@link #setRect(int, int, int, int)}
+     */
+    public void setRoundRect(int left, int top, int right, int bottom, float radius) {
+        if (left >= right || top >= bottom) {
+            setEmpty();
+            return;
+        }
+
+        if (mMode == MODE_CONVEX_PATH) {
+            // rewind here to avoid thrashing the allocations, but could alternately clear ref
+            mPath.rewind();
+        }
+        mMode = MODE_ROUND_RECT;
+        mRect.set(left, top, right, bottom);
+        mRadius = radius;
+    }
+
+    /**
+     * Convenience for {@link #setRoundRect(int, int, int, int, float)}
+     */
+    public void setRoundRect(@NonNull Rect rect, float radius) {
+        setRoundRect(rect.left, rect.top, rect.right, rect.bottom, radius);
+    }
+
+    /**
+     * Populates {@code outBounds} with the outline bounds, if set, and returns
+     * {@code true}. If no outline bounds are set, or if a path has been set
+     * via {@link #setConvexPath(Path)}, returns {@code false}.
+     *
+     * @param outRect the rect to populate with the outline bounds, if set
+     * @return {@code true} if {@code outBounds} was populated with outline
+     *         bounds, or {@code false} if no outline bounds are set
+     */
+    public boolean getRect(@NonNull Rect outRect) {
+        if (mMode != MODE_ROUND_RECT) {
+            return false;
+        }
+        outRect.set(mRect);
+        return true;
+    }
+
+    /**
+     * Returns the rounded rect radius, if set, or a value less than 0 if a path has
+     * been set via {@link #setConvexPath(Path)}. A return value of {@code 0}
+     * indicates a non-rounded rect.
+     *
+     * @return the rounded rect radius, or value < 0
+     */
+    public float getRadius() {
+        return mRadius;
+    }
+
+    /**
+     * Sets the outline to the oval defined by input rect.
+     */
+    public void setOval(int left, int top, int right, int bottom) {
+        if (left >= right || top >= bottom) {
+            setEmpty();
+            return;
+        }
+
+        if ((bottom - top) == (right - left)) {
+            // represent circle as round rect, for efficiency, and to enable clipping
+            setRoundRect(left, top, right, bottom, (bottom - top) / 2.0f);
+            return;
+        }
+
+        if (mPath == null) {
+            mPath = new Path();
+        } else {
+            mPath.rewind();
+        }
+
+        mMode = MODE_CONVEX_PATH;
+        mPath.addOval(left, top, right, bottom, Path.Direction.CW);
+        mRect.setEmpty();
+        mRadius = RADIUS_UNDEFINED;
+    }
+
+    /**
+     * Convenience for {@link #setOval(int, int, int, int)}
+     */
+    public void setOval(@NonNull Rect rect) {
+        setOval(rect.left, rect.top, rect.right, rect.bottom);
+    }
+
+    /**
+     * Sets the Constructs an Outline from a
+     * {@link android.graphics.Path#isConvex() convex path}.
+     */
+    public void setConvexPath(@NonNull Path convexPath) {
+        if (convexPath.isEmpty()) {
+            setEmpty();
+            return;
+        }
+
+        if (!convexPath.isConvex()) {
+            throw new IllegalArgumentException("path must be convex");
+        }
+
+        if (mPath == null) {
+            mPath = new Path();
+        }
+
+        mMode = MODE_CONVEX_PATH;
+        mPath.set(convexPath);
+        mRect.setEmpty();
+        mRadius = RADIUS_UNDEFINED;
+    }
+
+    /**
+     * Offsets the Outline by (dx,dy)
+     */
+    public void offset(int dx, int dy) {
+        if (mMode == MODE_ROUND_RECT) {
+            mRect.offset(dx, dy);
+        } else if (mMode == MODE_CONVEX_PATH) {
+            mPath.offset(dx, dy);
+        }
+    }
+}
diff --git a/android/graphics/Paint.java b/android/graphics/Paint.java
new file mode 100644
index 0000000..1a06a56
--- /dev/null
+++ b/android/graphics/Paint.java
@@ -0,0 +1,3008 @@
+/*
+ * 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;
+
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.annotation.Size;
+import android.graphics.FontListParser;
+import android.graphics.fonts.FontVariationAxis;
+import android.os.LocaleList;
+import android.text.FontConfig;
+import android.text.GraphicsOperations;
+import android.text.SpannableString;
+import android.text.SpannedString;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.GuardedBy;
+
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Locale;
+
+import libcore.util.NativeAllocationRegistry;
+
+/**
+ * The Paint class holds the style and color information about how to draw
+ * geometries, text and bitmaps.
+ */
+public class Paint {
+
+    private long mNativePaint;
+    private long mNativeShader;
+    private long mNativeColorFilter;
+
+    // The approximate size of a native paint object.
+    private static final long NATIVE_PAINT_SIZE = 98;
+
+    // Use a Holder to allow static initialization of Paint in the boot image.
+    private static class NoImagePreloadHolder {
+        public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+                Paint.class.getClassLoader(), nGetNativeFinalizer(), NATIVE_PAINT_SIZE);
+    }
+
+    private ColorFilter mColorFilter;
+    private MaskFilter  mMaskFilter;
+    private PathEffect  mPathEffect;
+    private Shader      mShader;
+    private Typeface    mTypeface;
+    private Xfermode    mXfermode;
+
+    private boolean     mHasCompatScaling;
+    private float       mCompatScaling;
+    private float       mInvCompatScaling;
+
+    private LocaleList  mLocales;
+    private String      mFontFeatureSettings;
+    private String      mFontVariationSettings;
+
+    private float mShadowLayerRadius;
+    private float mShadowLayerDx;
+    private float mShadowLayerDy;
+    private int mShadowLayerColor;
+
+    private static final Object sCacheLock = new Object();
+
+    /**
+     * Cache for the Minikin language list ID.
+     *
+     * A map from a string representation of the LocaleList to Minikin's language list ID.
+     */
+    @GuardedBy("sCacheLock")
+    private static final HashMap<String, Integer> sMinikinLangListIdCache = new HashMap<>();
+
+    /**
+     * @hide
+     */
+    public  int         mBidiFlags = BIDI_DEFAULT_LTR;
+
+    static final Style[] sStyleArray = {
+        Style.FILL, Style.STROKE, Style.FILL_AND_STROKE
+    };
+    static final Cap[] sCapArray = {
+        Cap.BUTT, Cap.ROUND, Cap.SQUARE
+    };
+    static final Join[] sJoinArray = {
+        Join.MITER, Join.ROUND, Join.BEVEL
+    };
+    static final Align[] sAlignArray = {
+        Align.LEFT, Align.CENTER, Align.RIGHT
+    };
+
+    /**
+     * Paint flag that enables antialiasing when drawing.
+     *
+     * <p>Enabling this flag will cause all draw operations that support
+     * antialiasing to use it.</p>
+     *
+     * @see #Paint(int)
+     * @see #setFlags(int)
+     */
+    public static final int ANTI_ALIAS_FLAG     = 0x01;
+    /**
+     * Paint flag that enables bilinear sampling on scaled bitmaps.
+     *
+     * <p>If cleared, scaled bitmaps will be drawn with nearest neighbor
+     * sampling, likely resulting in artifacts. This should generally be on
+     * when drawing bitmaps, unless performance-bound (rendering to software
+     * canvas) or preferring pixelation artifacts to blurriness when scaling
+     * significantly.</p>
+     *
+     * <p>If bitmaps are scaled for device density at creation time (as
+     * resource bitmaps often are) the filtering will already have been
+     * done.</p>
+     *
+     * @see #Paint(int)
+     * @see #setFlags(int)
+     */
+    public static final int FILTER_BITMAP_FLAG  = 0x02;
+    /**
+     * Paint flag that enables dithering when blitting.
+     *
+     * <p>Enabling this flag applies a dither to any blit operation where the
+     * target's colour space is more constrained than the source.
+     *
+     * @see #Paint(int)
+     * @see #setFlags(int)
+     */
+    public static final int DITHER_FLAG         = 0x04;
+    /**
+     * Paint flag that applies an underline decoration to drawn text.
+     *
+     * @see #Paint(int)
+     * @see #setFlags(int)
+     */
+    public static final int UNDERLINE_TEXT_FLAG = 0x08;
+    /**
+     * Paint flag that applies a strike-through decoration to drawn text.
+     *
+     * @see #Paint(int)
+     * @see #setFlags(int)
+     */
+    public static final int STRIKE_THRU_TEXT_FLAG = 0x10;
+    /**
+     * Paint flag that applies a synthetic bolding effect to drawn text.
+     *
+     * <p>Enabling this flag will cause text draw operations to apply a
+     * simulated bold effect when drawing a {@link Typeface} that is not
+     * already bold.</p>
+     *
+     * @see #Paint(int)
+     * @see #setFlags(int)
+     */
+    public static final int FAKE_BOLD_TEXT_FLAG = 0x20;
+    /**
+     * Paint flag that enables smooth linear scaling of text.
+     *
+     * <p>Enabling this flag does not actually scale text, but rather adjusts
+     * text draw operations to deal gracefully with smooth adjustment of scale.
+     * When this flag is enabled, font hinting is disabled to prevent shape
+     * deformation between scale factors, and glyph caching is disabled due to
+     * the large number of glyph images that will be generated.</p>
+     *
+     * <p>{@link #SUBPIXEL_TEXT_FLAG} should be used in conjunction with this
+     * flag to prevent glyph positions from snapping to whole pixel values as
+     * scale factor is adjusted.</p>
+     *
+     * @see #Paint(int)
+     * @see #setFlags(int)
+     */
+    public static final int LINEAR_TEXT_FLAG    = 0x40;
+    /**
+     * Paint flag that enables subpixel positioning of text.
+     *
+     * <p>Enabling this flag causes glyph advances to be computed with subpixel
+     * accuracy.</p>
+     *
+     * <p>This can be used with {@link #LINEAR_TEXT_FLAG} to prevent text from
+     * jittering during smooth scale transitions.</p>
+     *
+     * @see #Paint(int)
+     * @see #setFlags(int)
+     */
+    public static final int SUBPIXEL_TEXT_FLAG  = 0x80;
+    /** Legacy Paint flag, no longer used. */
+    public static final int DEV_KERN_TEXT_FLAG  = 0x100;
+    /** @hide bit mask for the flag enabling subpixel glyph rendering for text */
+    public static final int LCD_RENDER_TEXT_FLAG = 0x200;
+    /**
+     * Paint flag that enables the use of bitmap fonts when drawing text.
+     *
+     * <p>Disabling this flag will prevent text draw operations from using
+     * embedded bitmap strikes in fonts, causing fonts with both scalable
+     * outlines and bitmap strikes to draw only the scalable outlines, and
+     * fonts with only bitmap strikes to not draw at all.</p>
+     *
+     * @see #Paint(int)
+     * @see #setFlags(int)
+     */
+    public static final int EMBEDDED_BITMAP_TEXT_FLAG = 0x400;
+    /** @hide bit mask for the flag forcing freetype's autohinter on for text */
+    public static final int AUTO_HINTING_TEXT_FLAG = 0x800;
+    /** @hide bit mask for the flag enabling vertical rendering for text */
+    public static final int VERTICAL_TEXT_FLAG = 0x1000;
+
+    // These flags are always set on a new/reset paint, even if flags 0 is passed.
+    static final int HIDDEN_DEFAULT_PAINT_FLAGS = DEV_KERN_TEXT_FLAG | EMBEDDED_BITMAP_TEXT_FLAG;
+
+    /**
+     * Font hinter option that disables font hinting.
+     *
+     * @see #setHinting(int)
+     */
+    public static final int HINTING_OFF = 0x0;
+
+    /**
+     * Font hinter option that enables font hinting.
+     *
+     * @see #setHinting(int)
+     */
+    public static final int HINTING_ON = 0x1;
+
+    /**
+     * Bidi flag to set LTR paragraph direction.
+     *
+     * @hide
+     */
+    public static final int BIDI_LTR = 0x0;
+
+    /**
+     * Bidi flag to set RTL paragraph direction.
+     *
+     * @hide
+     */
+    public static final int BIDI_RTL = 0x1;
+
+    /**
+     * Bidi flag to detect paragraph direction via heuristics, defaulting to
+     * LTR.
+     *
+     * @hide
+     */
+    public static final int BIDI_DEFAULT_LTR = 0x2;
+
+    /**
+     * Bidi flag to detect paragraph direction via heuristics, defaulting to
+     * RTL.
+     *
+     * @hide
+     */
+    public static final int BIDI_DEFAULT_RTL = 0x3;
+
+    /**
+     * Bidi flag to override direction to all LTR (ignore bidi).
+     *
+     * @hide
+     */
+    public static final int BIDI_FORCE_LTR = 0x4;
+
+    /**
+     * Bidi flag to override direction to all RTL (ignore bidi).
+     *
+     * @hide
+     */
+    public static final int BIDI_FORCE_RTL = 0x5;
+
+    /**
+     * Maximum Bidi flag value.
+     * @hide
+     */
+    private static final int BIDI_MAX_FLAG_VALUE = BIDI_FORCE_RTL;
+
+    /**
+     * Mask for bidi flags.
+     * @hide
+     */
+    private static final int BIDI_FLAG_MASK = 0x7;
+
+    /**
+     * Flag for getTextRunAdvances indicating left-to-right run direction.
+     * @hide
+     */
+    public static final int DIRECTION_LTR = 0;
+
+    /**
+     * Flag for getTextRunAdvances indicating right-to-left run direction.
+     * @hide
+     */
+    public static final int DIRECTION_RTL = 1;
+
+    /**
+     * Option for getTextRunCursor to compute the valid cursor after
+     * offset or the limit of the context, whichever is less.
+     * @hide
+     */
+    public static final int CURSOR_AFTER = 0;
+
+    /**
+     * Option for getTextRunCursor to compute the valid cursor at or after
+     * the offset or the limit of the context, whichever is less.
+     * @hide
+     */
+    public static final int CURSOR_AT_OR_AFTER = 1;
+
+     /**
+     * Option for getTextRunCursor to compute the valid cursor before
+     * offset or the start of the context, whichever is greater.
+     * @hide
+     */
+    public static final int CURSOR_BEFORE = 2;
+
+   /**
+     * Option for getTextRunCursor to compute the valid cursor at or before
+     * offset or the start of the context, whichever is greater.
+     * @hide
+     */
+    public static final int CURSOR_AT_OR_BEFORE = 3;
+
+    /**
+     * Option for getTextRunCursor to return offset if the cursor at offset
+     * is valid, or -1 if it isn't.
+     * @hide
+     */
+    public static final int CURSOR_AT = 4;
+
+    /**
+     * Maximum cursor option value.
+     */
+    private static final int CURSOR_OPT_MAX_VALUE = CURSOR_AT;
+
+    /**
+     * Mask for hyphen edits that happen at the end of a line. Keep in sync with the definition in
+     * Minikin's Hyphenator.h.
+     * @hide
+     */
+    public static final int HYPHENEDIT_MASK_END_OF_LINE = 0x07;
+
+    /**
+     * Mask for hyphen edits that happen at the start of a line. Keep in sync with the definition in
+     * Minikin's Hyphenator.h.
+     * @hide
+     */
+    public static final int HYPHENEDIT_MASK_START_OF_LINE = 0x03 << 3;
+
+    /**
+     * The Style specifies if the primitive being drawn is filled, stroked, or
+     * both (in the same color). The default is FILL.
+     */
+    public enum Style {
+        /**
+         * Geometry and text drawn with this style will be filled, ignoring all
+         * stroke-related settings in the paint.
+         */
+        FILL            (0),
+        /**
+         * Geometry and text drawn with this style will be stroked, respecting
+         * the stroke-related fields on the paint.
+         */
+        STROKE          (1),
+        /**
+         * Geometry and text drawn with this style will be both filled and
+         * stroked at the same time, respecting the stroke-related fields on
+         * the paint. This mode can give unexpected results if the geometry
+         * is oriented counter-clockwise. This restriction does not apply to
+         * either FILL or STROKE.
+         */
+        FILL_AND_STROKE (2);
+
+        Style(int nativeInt) {
+            this.nativeInt = nativeInt;
+        }
+        final int nativeInt;
+    }
+
+    /**
+     * The Cap specifies the treatment for the beginning and ending of
+     * stroked lines and paths. The default is BUTT.
+     */
+    public enum Cap {
+        /**
+         * The stroke ends with the path, and does not project beyond it.
+         */
+        BUTT    (0),
+        /**
+         * The stroke projects out as a semicircle, with the center at the
+         * end of the path.
+         */
+        ROUND   (1),
+        /**
+         * The stroke projects out as a square, with the center at the end
+         * of the path.
+         */
+        SQUARE  (2);
+
+        private Cap(int nativeInt) {
+            this.nativeInt = nativeInt;
+        }
+        final int nativeInt;
+    }
+
+    /**
+     * The Join specifies the treatment where lines and curve segments
+     * join on a stroked path. The default is MITER.
+     */
+    public enum Join {
+        /**
+         * The outer edges of a join meet at a sharp angle
+         */
+        MITER   (0),
+        /**
+         * The outer edges of a join meet in a circular arc.
+         */
+        ROUND   (1),
+        /**
+         * The outer edges of a join meet with a straight line
+         */
+        BEVEL   (2);
+
+        private Join(int nativeInt) {
+            this.nativeInt = nativeInt;
+        }
+        final int nativeInt;
+    }
+
+    /**
+     * Align specifies how drawText aligns its text relative to the
+     * [x,y] coordinates. The default is LEFT.
+     */
+    public enum Align {
+        /**
+         * The text is drawn to the right of the x,y origin
+         */
+        LEFT    (0),
+        /**
+         * The text is drawn centered horizontally on the x,y origin
+         */
+        CENTER  (1),
+        /**
+         * The text is drawn to the left of the x,y origin
+         */
+        RIGHT   (2);
+
+        private Align(int nativeInt) {
+            this.nativeInt = nativeInt;
+        }
+        final int nativeInt;
+    }
+
+    /**
+     * Create a new paint with default settings.
+     */
+    public Paint() {
+        this(0);
+    }
+
+    /**
+     * Create a new paint with the specified flags. Use setFlags() to change
+     * these after the paint is created.
+     *
+     * @param flags initial flag bits, as if they were passed via setFlags().
+     */
+    public Paint(int flags) {
+        mNativePaint = nInit();
+        NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativePaint);
+        setFlags(flags | HIDDEN_DEFAULT_PAINT_FLAGS);
+        // TODO: Turning off hinting has undesirable side effects, we need to
+        //       revisit hinting once we add support for subpixel positioning
+        // setHinting(DisplayMetrics.DENSITY_DEVICE >= DisplayMetrics.DENSITY_TV
+        //        ? HINTING_OFF : HINTING_ON);
+        mCompatScaling = mInvCompatScaling = 1;
+        setTextLocales(LocaleList.getAdjustedDefault());
+    }
+
+    /**
+     * Create a new paint, initialized with the attributes in the specified
+     * paint parameter.
+     *
+     * @param paint Existing paint used to initialized the attributes of the
+     *              new paint.
+     */
+    public Paint(Paint paint) {
+        mNativePaint = nInitWithPaint(paint.getNativeInstance());
+        NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativePaint);
+        setClassVariablesFrom(paint);
+    }
+
+    /** Restores the paint to its default settings. */
+    public void reset() {
+        nReset(mNativePaint);
+        setFlags(HIDDEN_DEFAULT_PAINT_FLAGS);
+
+        // TODO: Turning off hinting has undesirable side effects, we need to
+        //       revisit hinting once we add support for subpixel positioning
+        // setHinting(DisplayMetrics.DENSITY_DEVICE >= DisplayMetrics.DENSITY_TV
+        //        ? HINTING_OFF : HINTING_ON);
+
+        mColorFilter = null;
+        mMaskFilter = null;
+        mPathEffect = null;
+        mShader = null;
+        mNativeShader = 0;
+        mTypeface = null;
+        mXfermode = null;
+
+        mHasCompatScaling = false;
+        mCompatScaling = 1;
+        mInvCompatScaling = 1;
+
+        mBidiFlags = BIDI_DEFAULT_LTR;
+        setTextLocales(LocaleList.getAdjustedDefault());
+        setElegantTextHeight(false);
+        mFontFeatureSettings = null;
+        mFontVariationSettings = null;
+
+        mShadowLayerRadius = 0.0f;
+        mShadowLayerDx = 0.0f;
+        mShadowLayerDy = 0.0f;
+        mShadowLayerColor = 0;
+    }
+
+    /**
+     * Copy the fields from src into this paint. This is equivalent to calling
+     * get() on all of the src fields, and calling the corresponding set()
+     * methods on this.
+     */
+    public void set(Paint src) {
+        if (this != src) {
+            // copy over the native settings
+            nSet(mNativePaint, src.mNativePaint);
+            setClassVariablesFrom(src);
+        }
+    }
+
+    /**
+     * Set all class variables using current values from the given
+     * {@link Paint}.
+     */
+    private void setClassVariablesFrom(Paint paint) {
+        mColorFilter = paint.mColorFilter;
+        mMaskFilter = paint.mMaskFilter;
+        mPathEffect = paint.mPathEffect;
+        mShader = paint.mShader;
+        mNativeShader = paint.mNativeShader;
+        mTypeface = paint.mTypeface;
+        mXfermode = paint.mXfermode;
+
+        mHasCompatScaling = paint.mHasCompatScaling;
+        mCompatScaling = paint.mCompatScaling;
+        mInvCompatScaling = paint.mInvCompatScaling;
+
+        mBidiFlags = paint.mBidiFlags;
+        mLocales = paint.mLocales;
+        mFontFeatureSettings = paint.mFontFeatureSettings;
+        mFontVariationSettings = paint.mFontVariationSettings;
+
+        mShadowLayerRadius = paint.mShadowLayerRadius;
+        mShadowLayerDx = paint.mShadowLayerDx;
+        mShadowLayerDy = paint.mShadowLayerDy;
+        mShadowLayerColor = paint.mShadowLayerColor;
+    }
+
+    /**
+     * Returns true if all attributes are equal.
+     *
+     * The caller is expected to have checked the trivial cases, like the pointers being equal,
+     * the objects having different classes, or the parameter being null.
+     * @hide
+     */
+    public boolean hasEqualAttributes(@NonNull Paint other) {
+        return mColorFilter == other.mColorFilter
+                && mMaskFilter == other.mMaskFilter
+                && mPathEffect == other.mPathEffect
+                && mShader == other.mShader
+                && mTypeface == other.mTypeface
+                && mXfermode == other.mXfermode
+                && mHasCompatScaling == other.mHasCompatScaling
+                && mCompatScaling == other.mCompatScaling
+                && mInvCompatScaling == other.mInvCompatScaling
+                && mBidiFlags == other.mBidiFlags
+                && mLocales.equals(other.mLocales)
+                && TextUtils.equals(mFontFeatureSettings, other.mFontFeatureSettings)
+                && TextUtils.equals(mFontVariationSettings, other.mFontVariationSettings)
+                && mShadowLayerRadius == other.mShadowLayerRadius
+                && mShadowLayerDx == other.mShadowLayerDx
+                && mShadowLayerDy == other.mShadowLayerDy
+                && mShadowLayerColor == other.mShadowLayerColor
+                && getFlags() == other.getFlags()
+                && getHinting() == other.getHinting()
+                && getStyle() == other.getStyle()
+                && getColor() == other.getColor()
+                && getStrokeWidth() == other.getStrokeWidth()
+                && getStrokeMiter() == other.getStrokeMiter()
+                && getStrokeCap() == other.getStrokeCap()
+                && getStrokeJoin() == other.getStrokeJoin()
+                && getTextAlign() == other.getTextAlign()
+                && isElegantTextHeight() == other.isElegantTextHeight()
+                && getTextSize() == other.getTextSize()
+                && getTextScaleX() == other.getTextScaleX()
+                && getTextSkewX() == other.getTextSkewX()
+                && getLetterSpacing() == other.getLetterSpacing()
+                && getWordSpacing() == other.getWordSpacing()
+                && getHyphenEdit() == other.getHyphenEdit();
+    }
+
+    /** @hide */
+    public void setCompatibilityScaling(float factor) {
+        if (factor == 1.0) {
+            mHasCompatScaling = false;
+            mCompatScaling = mInvCompatScaling = 1.0f;
+        } else {
+            mHasCompatScaling = true;
+            mCompatScaling = factor;
+            mInvCompatScaling = 1.0f/factor;
+        }
+    }
+
+    /**
+     * Return the pointer to the native object while ensuring that any
+     * mutable objects that are attached to the paint are also up-to-date.
+     *
+     * @hide
+     */
+    public long getNativeInstance() {
+        long newNativeShader = mShader == null ? 0 : mShader.getNativeInstance();
+        if (newNativeShader != mNativeShader) {
+            mNativeShader = newNativeShader;
+            nSetShader(mNativePaint, mNativeShader);
+        }
+        long newNativeColorFilter = mColorFilter == null ? 0 : mColorFilter.getNativeInstance();
+        if (newNativeColorFilter != mNativeColorFilter) {
+            mNativeColorFilter = newNativeColorFilter;
+            nSetColorFilter(mNativePaint, mNativeColorFilter);
+        }
+        return mNativePaint;
+    }
+
+    /**
+     * Return the bidi flags on the paint.
+     *
+     * @return the bidi flags on the paint
+     * @hide
+     */
+    public int getBidiFlags() {
+        return mBidiFlags;
+    }
+
+    /**
+     * Set the bidi flags on the paint.
+     * @hide
+     */
+    public void setBidiFlags(int flags) {
+        // only flag value is the 3-bit BIDI control setting
+        flags &= BIDI_FLAG_MASK;
+        if (flags > BIDI_MAX_FLAG_VALUE) {
+            throw new IllegalArgumentException("unknown bidi flag: " + flags);
+        }
+        mBidiFlags = flags;
+    }
+
+    /**
+     * Return the paint's flags. Use the Flag enum to test flag values.
+     *
+     * @return the paint's flags (see enums ending in _Flag for bit masks)
+     */
+    public int getFlags() {
+        return nGetFlags(mNativePaint);
+    }
+
+    /**
+     * Set the paint's flags. Use the Flag enum to specific flag values.
+     *
+     * @param flags The new flag bits for the paint
+     */
+    public void setFlags(int flags) {
+        nSetFlags(mNativePaint, flags);
+    }
+
+    /**
+     * Return the paint's hinting mode.  Returns either
+     * {@link #HINTING_OFF} or {@link #HINTING_ON}.
+     */
+    public int getHinting() {
+        return nGetHinting(mNativePaint);
+    }
+
+    /**
+     * Set the paint's hinting mode.  May be either
+     * {@link #HINTING_OFF} or {@link #HINTING_ON}.
+     */
+    public void setHinting(int mode) {
+        nSetHinting(mNativePaint, mode);
+    }
+
+    /**
+     * Helper for getFlags(), returning true if ANTI_ALIAS_FLAG bit is set
+     * AntiAliasing smooths out the edges of what is being drawn, but is has
+     * no impact on the interior of the shape. See setDither() and
+     * setFilterBitmap() to affect how colors are treated.
+     *
+     * @return true if the antialias bit is set in the paint's flags.
+     */
+    public final boolean isAntiAlias() {
+        return (getFlags() & ANTI_ALIAS_FLAG) != 0;
+    }
+
+    /**
+     * Helper for setFlags(), setting or clearing the ANTI_ALIAS_FLAG bit
+     * AntiAliasing smooths out the edges of what is being drawn, but is has
+     * no impact on the interior of the shape. See setDither() and
+     * setFilterBitmap() to affect how colors are treated.
+     *
+     * @param aa true to set the antialias bit in the flags, false to clear it
+     */
+    public void setAntiAlias(boolean aa) {
+        nSetAntiAlias(mNativePaint, aa);
+    }
+
+    /**
+     * Helper for getFlags(), returning true if DITHER_FLAG bit is set
+     * Dithering affects how colors that are higher precision than the device
+     * are down-sampled. No dithering is generally faster, but higher precision
+     * colors are just truncated down (e.g. 8888 -> 565). Dithering tries to
+     * distribute the error inherent in this process, to reduce the visual
+     * artifacts.
+     *
+     * @return true if the dithering bit is set in the paint's flags.
+     */
+    public final boolean isDither() {
+        return (getFlags() & DITHER_FLAG) != 0;
+    }
+
+    /**
+     * Helper for setFlags(), setting or clearing the DITHER_FLAG bit
+     * Dithering affects how colors that are higher precision than the device
+     * are down-sampled. No dithering is generally faster, but higher precision
+     * colors are just truncated down (e.g. 8888 -> 565). Dithering tries to
+     * distribute the error inherent in this process, to reduce the visual
+     * artifacts.
+     *
+     * @param dither true to set the dithering bit in flags, false to clear it
+     */
+    public void setDither(boolean dither) {
+        nSetDither(mNativePaint, dither);
+    }
+
+    /**
+     * Helper for getFlags(), returning true if LINEAR_TEXT_FLAG bit is set
+     *
+     * @return true if the lineartext bit is set in the paint's flags
+     */
+    public final boolean isLinearText() {
+        return (getFlags() & LINEAR_TEXT_FLAG) != 0;
+    }
+
+    /**
+     * Helper for setFlags(), setting or clearing the LINEAR_TEXT_FLAG bit
+     *
+     * @param linearText true to set the linearText bit in the paint's flags,
+     *                   false to clear it.
+     */
+    public void setLinearText(boolean linearText) {
+        nSetLinearText(mNativePaint, linearText);
+    }
+
+    /**
+     * Helper for getFlags(), returning true if SUBPIXEL_TEXT_FLAG bit is set
+     *
+     * @return true if the subpixel bit is set in the paint's flags
+     */
+    public final boolean isSubpixelText() {
+        return (getFlags() & SUBPIXEL_TEXT_FLAG) != 0;
+    }
+
+    /**
+     * Helper for setFlags(), setting or clearing the SUBPIXEL_TEXT_FLAG bit
+     *
+     * @param subpixelText true to set the subpixelText bit in the paint's
+     *                     flags, false to clear it.
+     */
+    public void setSubpixelText(boolean subpixelText) {
+        nSetSubpixelText(mNativePaint, subpixelText);
+    }
+
+    /**
+     * Helper for getFlags(), returning true if UNDERLINE_TEXT_FLAG bit is set
+     *
+     * @return true if the underlineText bit is set in the paint's flags.
+     */
+    public final boolean isUnderlineText() {
+        return (getFlags() & UNDERLINE_TEXT_FLAG) != 0;
+    }
+
+    /**
+     * Distance from top of the underline to the baseline. Positive values mean below the baseline.
+     * This method returns where the underline should be drawn independent of if the underlineText
+     * bit is set at the moment.
+     * @hide
+     */
+    public float getUnderlinePosition() {
+        return nGetUnderlinePosition(mNativePaint);
+    }
+
+    /**
+     * @hide
+     */
+    public float getUnderlineThickness() {
+        return nGetUnderlineThickness(mNativePaint);
+    }
+
+    /**
+     * Helper for setFlags(), setting or clearing the UNDERLINE_TEXT_FLAG bit
+     *
+     * @param underlineText true to set the underlineText bit in the paint's
+     *                      flags, false to clear it.
+     */
+    public void setUnderlineText(boolean underlineText) {
+        nSetUnderlineText(mNativePaint, underlineText);
+    }
+
+    /**
+     * Helper for getFlags(), returning true if STRIKE_THRU_TEXT_FLAG bit is set
+     *
+     * @return true if the strikeThruText bit is set in the paint's flags.
+     */
+    public final boolean isStrikeThruText() {
+        return (getFlags() & STRIKE_THRU_TEXT_FLAG) != 0;
+    }
+
+    /**
+     * Distance from top of the strike-through line to the baseline. Negative values mean above the
+     * baseline. This method returns where the strike-through line should be drawn independent of if
+     * the strikeThruText bit is set at the moment.
+     * @hide
+     */
+    public float getStrikeThruPosition() {
+        return nGetStrikeThruPosition(mNativePaint);
+    }
+
+    /**
+     * @hide
+     */
+    public float getStrikeThruThickness() {
+        return nGetStrikeThruThickness(mNativePaint);
+    }
+
+    /**
+     * Helper for setFlags(), setting or clearing the STRIKE_THRU_TEXT_FLAG bit
+     *
+     * @param strikeThruText true to set the strikeThruText bit in the paint's
+     *                       flags, false to clear it.
+     */
+    public void setStrikeThruText(boolean strikeThruText) {
+        nSetStrikeThruText(mNativePaint, strikeThruText);
+    }
+
+    /**
+     * Helper for getFlags(), returning true if FAKE_BOLD_TEXT_FLAG bit is set
+     *
+     * @return true if the fakeBoldText bit is set in the paint's flags.
+     */
+    public final boolean isFakeBoldText() {
+        return (getFlags() & FAKE_BOLD_TEXT_FLAG) != 0;
+    }
+
+    /**
+     * Helper for setFlags(), setting or clearing the FAKE_BOLD_TEXT_FLAG bit
+     *
+     * @param fakeBoldText true to set the fakeBoldText bit in the paint's
+     *                     flags, false to clear it.
+     */
+    public void setFakeBoldText(boolean fakeBoldText) {
+        nSetFakeBoldText(mNativePaint, fakeBoldText);
+    }
+
+    /**
+     * Whether or not the bitmap filter is activated.
+     * Filtering affects the sampling of bitmaps when they are transformed.
+     * Filtering does not affect how the colors in the bitmap are converted into
+     * device pixels. That is dependent on dithering and xfermodes.
+     *
+     * @see #setFilterBitmap(boolean) setFilterBitmap()
+     */
+    public final boolean isFilterBitmap() {
+        return (getFlags() & FILTER_BITMAP_FLAG) != 0;
+    }
+
+    /**
+     * Helper for setFlags(), setting or clearing the FILTER_BITMAP_FLAG bit.
+     * Filtering affects the sampling of bitmaps when they are transformed.
+     * Filtering does not affect how the colors in the bitmap are converted into
+     * device pixels. That is dependent on dithering and xfermodes.
+     *
+     * @param filter true to set the FILTER_BITMAP_FLAG bit in the paint's
+     *               flags, false to clear it.
+     */
+    public void setFilterBitmap(boolean filter) {
+        nSetFilterBitmap(mNativePaint, filter);
+    }
+
+    /**
+     * Return the paint's style, used for controlling how primitives'
+     * geometries are interpreted (except for drawBitmap, which always assumes
+     * FILL_STYLE).
+     *
+     * @return the paint's style setting (Fill, Stroke, StrokeAndFill)
+     */
+    public Style getStyle() {
+        return sStyleArray[nGetStyle(mNativePaint)];
+    }
+
+    /**
+     * Set the paint's style, used for controlling how primitives'
+     * geometries are interpreted (except for drawBitmap, which always assumes
+     * Fill).
+     *
+     * @param style The new style to set in the paint
+     */
+    public void setStyle(Style style) {
+        nSetStyle(mNativePaint, style.nativeInt);
+    }
+
+    /**
+     * Return the paint's color. Note that the color is a 32bit value
+     * containing alpha as well as r,g,b. This 32bit value is not premultiplied,
+     * meaning that its alpha can be any value, regardless of the values of
+     * r,g,b. See the Color class for more details.
+     *
+     * @return the paint's color (and alpha).
+     */
+    @ColorInt
+    public int getColor() {
+        return nGetColor(mNativePaint);
+    }
+
+    /**
+     * Set the paint's color. Note that the color is an int containing alpha
+     * as well as r,g,b. This 32bit value is not premultiplied, meaning that
+     * its alpha can be any value, regardless of the values of r,g,b.
+     * See the Color class for more details.
+     *
+     * @param color The new color (including alpha) to set in the paint.
+     */
+    public void setColor(@ColorInt int color) {
+        nSetColor(mNativePaint, color);
+    }
+
+    /**
+     * Helper to getColor() that just returns the color's alpha value. This is
+     * the same as calling getColor() >>> 24. It always returns a value between
+     * 0 (completely transparent) and 255 (completely opaque).
+     *
+     * @return the alpha component of the paint's color.
+     */
+    public int getAlpha() {
+        return nGetAlpha(mNativePaint);
+    }
+
+    /**
+     * Helper to setColor(), that only assigns the color's alpha value,
+     * leaving its r,g,b values unchanged. Results are undefined if the alpha
+     * value is outside of the range [0..255]
+     *
+     * @param a set the alpha component [0..255] of the paint's color.
+     */
+    public void setAlpha(int a) {
+        nSetAlpha(mNativePaint, a);
+    }
+
+    /**
+     * Helper to setColor(), that takes a,r,g,b and constructs the color int
+     *
+     * @param a The new alpha component (0..255) of the paint's color.
+     * @param r The new red component (0..255) of the paint's color.
+     * @param g The new green component (0..255) of the paint's color.
+     * @param b The new blue component (0..255) of the paint's color.
+     */
+    public void setARGB(int a, int r, int g, int b) {
+        setColor((a << 24) | (r << 16) | (g << 8) | b);
+    }
+
+    /**
+     * Return the width for stroking.
+     * <p />
+     * A value of 0 strokes in hairline mode.
+     * Hairlines always draws a single pixel independent of the canva's matrix.
+     *
+     * @return the paint's stroke width, used whenever the paint's style is
+     *         Stroke or StrokeAndFill.
+     */
+    public float getStrokeWidth() {
+        return nGetStrokeWidth(mNativePaint);
+    }
+
+    /**
+     * Set the width for stroking.
+     * Pass 0 to stroke in hairline mode.
+     * Hairlines always draws a single pixel independent of the canva's matrix.
+     *
+     * @param width set the paint's stroke width, used whenever the paint's
+     *              style is Stroke or StrokeAndFill.
+     */
+    public void setStrokeWidth(float width) {
+        nSetStrokeWidth(mNativePaint, width);
+    }
+
+    /**
+     * Return the paint's stroke miter value. Used to control the behavior
+     * of miter joins when the joins angle is sharp.
+     *
+     * @return the paint's miter limit, used whenever the paint's style is
+     *         Stroke or StrokeAndFill.
+     */
+    public float getStrokeMiter() {
+        return nGetStrokeMiter(mNativePaint);
+    }
+
+    /**
+     * Set the paint's stroke miter value. This is used to control the behavior
+     * of miter joins when the joins angle is sharp. This value must be >= 0.
+     *
+     * @param miter set the miter limit on the paint, used whenever the paint's
+     *              style is Stroke or StrokeAndFill.
+     */
+    public void setStrokeMiter(float miter) {
+        nSetStrokeMiter(mNativePaint, miter);
+    }
+
+    /**
+     * Return the paint's Cap, controlling how the start and end of stroked
+     * lines and paths are treated.
+     *
+     * @return the line cap style for the paint, used whenever the paint's
+     *         style is Stroke or StrokeAndFill.
+     */
+    public Cap getStrokeCap() {
+        return sCapArray[nGetStrokeCap(mNativePaint)];
+    }
+
+    /**
+     * Set the paint's Cap.
+     *
+     * @param cap set the paint's line cap style, used whenever the paint's
+     *            style is Stroke or StrokeAndFill.
+     */
+    public void setStrokeCap(Cap cap) {
+        nSetStrokeCap(mNativePaint, cap.nativeInt);
+    }
+
+    /**
+     * Return the paint's stroke join type.
+     *
+     * @return the paint's Join.
+     */
+    public Join getStrokeJoin() {
+        return sJoinArray[nGetStrokeJoin(mNativePaint)];
+    }
+
+    /**
+     * Set the paint's Join.
+     *
+     * @param join set the paint's Join, used whenever the paint's style is
+     *             Stroke or StrokeAndFill.
+     */
+    public void setStrokeJoin(Join join) {
+        nSetStrokeJoin(mNativePaint, join.nativeInt);
+    }
+
+    /**
+     * Applies any/all effects (patheffect, stroking) to src, returning the
+     * result in dst. The result is that drawing src with this paint will be
+     * the same as drawing dst with a default paint (at least from the
+     * geometric perspective).
+     *
+     * @param src input path
+     * @param dst output path (may be the same as src)
+     * @return    true if the path should be filled, or false if it should be
+     *                 drawn with a hairline (width == 0)
+     */
+    public boolean getFillPath(Path src, Path dst) {
+        return nGetFillPath(mNativePaint, src.readOnlyNI(), dst.mutateNI());
+    }
+
+    /**
+     * Get the paint's shader object.
+     *
+     * @return the paint's shader (or null)
+     */
+    public Shader getShader() {
+        return mShader;
+    }
+
+    /**
+     * Set or clear the shader object.
+     * <p />
+     * Pass null to clear any previous shader.
+     * As a convenience, the parameter passed is also returned.
+     *
+     * @param shader May be null. the new shader to be installed in the paint
+     * @return       shader
+     */
+    public Shader setShader(Shader shader) {
+        // If mShader changes, cached value of native shader aren't valid, since
+        // old shader's pointer may be reused by another shader allocation later
+        if (mShader != shader) {
+            mNativeShader = -1;
+            // Release any native references to the old shader content
+            nSetShader(mNativePaint, 0);
+        }
+        // Defer setting the shader natively until getNativeInstance() is called
+        mShader = shader;
+        return shader;
+    }
+
+    /**
+     * Get the paint's colorfilter (maybe be null).
+     *
+     * @return the paint's colorfilter (maybe be null)
+     */
+    public ColorFilter getColorFilter() {
+        return mColorFilter;
+    }
+
+    /**
+     * Set or clear the paint's colorfilter, returning the parameter.
+     *
+     * @param filter May be null. The new filter to be installed in the paint
+     * @return       filter
+     */
+    public ColorFilter setColorFilter(ColorFilter filter) {
+        // If mColorFilter changes, cached value of native shader aren't valid, since
+        // old shader's pointer may be reused by another shader allocation later
+        if (mColorFilter != filter) {
+            mNativeColorFilter = -1;
+        }
+
+        // Defer setting the filter natively until getNativeInstance() is called
+        mColorFilter = filter;
+        return filter;
+    }
+
+    /**
+     * Get the paint's transfer mode object.
+     *
+     * @return the paint's transfer mode (or null)
+     */
+    public Xfermode getXfermode() {
+        return mXfermode;
+    }
+
+    /**
+     * Set or clear the transfer mode object. A transfer mode defines how
+     * source pixels (generate by a drawing command) are composited with
+     * the destination pixels (content of the render target).
+     * <p />
+     * Pass null to clear any previous transfer mode.
+     * As a convenience, the parameter passed is also returned.
+     * <p />
+     * {@link PorterDuffXfermode} is the most common transfer mode.
+     *
+     * @param xfermode May be null. The xfermode to be installed in the paint
+     * @return         xfermode
+     */
+    public Xfermode setXfermode(Xfermode xfermode) {
+        int newMode = xfermode != null ? xfermode.porterDuffMode : Xfermode.DEFAULT;
+        int curMode = mXfermode != null ? mXfermode.porterDuffMode : Xfermode.DEFAULT;
+        if (newMode != curMode) {
+            nSetXfermode(mNativePaint, newMode);
+        }
+        mXfermode = xfermode;
+        return xfermode;
+    }
+
+    /**
+     * Get the paint's patheffect object.
+     *
+     * @return the paint's patheffect (or null)
+     */
+    public PathEffect getPathEffect() {
+        return mPathEffect;
+    }
+
+    /**
+     * Set or clear the patheffect object.
+     * <p />
+     * Pass null to clear any previous patheffect.
+     * As a convenience, the parameter passed is also returned.
+     *
+     * @param effect May be null. The patheffect to be installed in the paint
+     * @return       effect
+     */
+    public PathEffect setPathEffect(PathEffect effect) {
+        long effectNative = 0;
+        if (effect != null) {
+            effectNative = effect.native_instance;
+        }
+        nSetPathEffect(mNativePaint, effectNative);
+        mPathEffect = effect;
+        return effect;
+    }
+
+    /**
+     * Get the paint's maskfilter object.
+     *
+     * @return the paint's maskfilter (or null)
+     */
+    public MaskFilter getMaskFilter() {
+        return mMaskFilter;
+    }
+
+    /**
+     * Set or clear the maskfilter object.
+     * <p />
+     * Pass null to clear any previous maskfilter.
+     * As a convenience, the parameter passed is also returned.
+     *
+     * @param maskfilter May be null. The maskfilter to be installed in the
+     *                   paint
+     * @return           maskfilter
+     */
+    public MaskFilter setMaskFilter(MaskFilter maskfilter) {
+        long maskfilterNative = 0;
+        if (maskfilter != null) {
+            maskfilterNative = maskfilter.native_instance;
+        }
+        nSetMaskFilter(mNativePaint, maskfilterNative);
+        mMaskFilter = maskfilter;
+        return maskfilter;
+    }
+
+    /**
+     * Get the paint's typeface object.
+     * <p />
+     * The typeface object identifies which font to use when drawing or
+     * measuring text.
+     *
+     * @return the paint's typeface (or null)
+     */
+    public Typeface getTypeface() {
+        return mTypeface;
+    }
+
+    /**
+     * Set or clear the typeface object.
+     * <p />
+     * Pass null to clear any previous typeface.
+     * As a convenience, the parameter passed is also returned.
+     *
+     * @param typeface May be null. The typeface to be installed in the paint
+     * @return         typeface
+     */
+    public Typeface setTypeface(Typeface typeface) {
+        final long typefaceNative = typeface == null ? 0 : typeface.native_instance;
+        nSetTypeface(mNativePaint, typefaceNative);
+        mTypeface = typeface;
+        return typeface;
+    }
+
+    /**
+     * Get the paint's rasterizer (or null).
+     * <p />
+     * The raster controls/modifies how paths/text are turned into alpha masks.
+     *
+     * @return         the paint's rasterizer (or null)
+     *
+     * @deprecated Rasterizer is not supported by either the HW or PDF backends.
+     * @removed
+     */
+    @Deprecated
+    public Rasterizer getRasterizer() {
+        return null;
+    }
+
+    /**
+     * Set or clear the rasterizer object.
+     * <p />
+     * Pass null to clear any previous rasterizer.
+     * As a convenience, the parameter passed is also returned.
+     *
+     * @param rasterizer May be null. The new rasterizer to be installed in
+     *                   the paint.
+     * @return           rasterizer
+     *
+     * @deprecated Rasterizer is not supported by either the HW or PDF backends.
+     * @removed
+     */
+    @Deprecated
+    public Rasterizer setRasterizer(Rasterizer rasterizer) {
+        return rasterizer;
+    }
+
+    /**
+     * This draws a shadow layer below the main layer, with the specified
+     * offset and color, and blur radius. If radius is 0, then the shadow
+     * layer is removed.
+     * <p>
+     * Can be used to create a blurred shadow underneath text. Support for use
+     * with other drawing operations is constrained to the software rendering
+     * pipeline.
+     * <p>
+     * The alpha of the shadow will be the paint's alpha if the shadow color is
+     * opaque, or the alpha from the shadow color if not.
+     */
+    public void setShadowLayer(float radius, float dx, float dy, int shadowColor) {
+      mShadowLayerRadius = radius;
+      mShadowLayerDx = dx;
+      mShadowLayerDy = dy;
+      mShadowLayerColor = shadowColor;
+      nSetShadowLayer(mNativePaint, radius, dx, dy, shadowColor);
+    }
+
+    /**
+     * Clear the shadow layer.
+     */
+    public void clearShadowLayer() {
+        setShadowLayer(0, 0, 0, 0);
+    }
+
+    /**
+     * Checks if the paint has a shadow layer attached
+     *
+     * @return true if the paint has a shadow layer attached and false otherwise
+     * @hide
+     */
+    public boolean hasShadowLayer() {
+        return nHasShadowLayer(mNativePaint);
+    }
+
+    /**
+     * Return the paint's Align value for drawing text. This controls how the
+     * text is positioned relative to its origin. LEFT align means that all of
+     * the text will be drawn to the right of its origin (i.e. the origin
+     * specifieds the LEFT edge of the text) and so on.
+     *
+     * @return the paint's Align value for drawing text.
+     */
+    public Align getTextAlign() {
+        return sAlignArray[nGetTextAlign(mNativePaint)];
+    }
+
+    /**
+     * Set the paint's text alignment. This controls how the
+     * text is positioned relative to its origin. LEFT align means that all of
+     * the text will be drawn to the right of its origin (i.e. the origin
+     * specifieds the LEFT edge of the text) and so on.
+     *
+     * @param align set the paint's Align value for drawing text.
+     */
+    public void setTextAlign(Align align) {
+        nSetTextAlign(mNativePaint, align.nativeInt);
+    }
+
+    /**
+     * Get the text's primary Locale. Note that this is not all of the locale-related information
+     * Paint has. Use {@link #getTextLocales()} to get the complete list.
+     *
+     * @return the paint's primary Locale used for drawing text, never null.
+     */
+    @NonNull
+    public Locale getTextLocale() {
+        return mLocales.get(0);
+    }
+
+    /**
+     * Get the text locale list.
+     *
+     * @return the paint's LocaleList used for drawing text, never null or empty.
+     */
+    @NonNull @Size(min=1)
+    public LocaleList getTextLocales() {
+        return mLocales;
+    }
+
+    /**
+     * Set the text locale list to a one-member list consisting of just the locale.
+     *
+     * See {@link #setTextLocales(LocaleList)} for how the locale list affects
+     * the way the text is drawn for some languages.
+     *
+     * @param locale the paint's locale value for drawing text, must not be null.
+     */
+    public void setTextLocale(@NonNull Locale locale) {
+        if (locale == null) {
+            throw new IllegalArgumentException("locale cannot be null");
+        }
+        if (mLocales != null && mLocales.size() == 1 && locale.equals(mLocales.get(0))) {
+            return;
+        }
+        mLocales = new LocaleList(locale);
+        syncTextLocalesWithMinikin();
+    }
+
+    /**
+     * Set the text locale list.
+     *
+     * The text locale list affects how the text is drawn for some languages.
+     *
+     * For example, if the locale list contains {@link Locale#CHINESE} or {@link Locale#CHINA},
+     * then the text renderer will prefer to draw text using a Chinese font. Likewise,
+     * if the locale list contains {@link Locale#JAPANESE} or {@link Locale#JAPAN}, then the text
+     * renderer will prefer to draw text using a Japanese font. If the locale list contains both,
+     * the order those locales appear in the list is considered for deciding the font.
+     *
+     * This distinction is important because Chinese and Japanese text both use many
+     * of the same Unicode code points but their appearance is subtly different for
+     * each language.
+     *
+     * By default, the text locale list is initialized to a one-member list just containing the
+     * system locales. This assumes that the text to be rendered will most likely be in the user's
+     * preferred language.
+     *
+     * If the actual language or languages of the text is/are known, then they can be provided to
+     * the text renderer using this method. The text renderer may attempt to guess the
+     * language script based on the contents of the text to be drawn independent of
+     * the text locale here. Specifying the text locales just helps it do a better
+     * job in certain ambiguous cases.
+     *
+     * @param locales the paint's locale list for drawing text, must not be null or empty.
+     */
+    public void setTextLocales(@NonNull @Size(min=1) LocaleList locales) {
+        if (locales == null || locales.isEmpty()) {
+            throw new IllegalArgumentException("locales cannot be null or empty");
+        }
+        if (locales.equals(mLocales)) return;
+        mLocales = locales;
+        syncTextLocalesWithMinikin();
+    }
+
+    private void syncTextLocalesWithMinikin() {
+        final String languageTags = mLocales.toLanguageTags();
+        final Integer minikinLangListId;
+        synchronized (sCacheLock) {
+            minikinLangListId = sMinikinLangListIdCache.get(languageTags);
+            if (minikinLangListId == null) {
+                final int newID = nSetTextLocales(mNativePaint, languageTags);
+                sMinikinLangListIdCache.put(languageTags, newID);
+                return;
+            }
+        }
+        nSetTextLocalesByMinikinLangListId(mNativePaint, minikinLangListId.intValue());
+    }
+
+    /**
+     * Get the elegant metrics flag.
+     *
+     * @return true if elegant metrics are enabled for text drawing.
+     */
+    public boolean isElegantTextHeight() {
+        return nIsElegantTextHeight(mNativePaint);
+    }
+
+    /**
+     * Set the paint's elegant height metrics flag. This setting selects font
+     * variants that have not been compacted to fit Latin-based vertical
+     * metrics, and also increases top and bottom bounds to provide more space.
+     *
+     * @param elegant set the paint's elegant metrics flag for drawing text.
+     */
+    public void setElegantTextHeight(boolean elegant) {
+        nSetElegantTextHeight(mNativePaint, elegant);
+    }
+
+    /**
+     * Return the paint's text size.
+     *
+     * @return the paint's text size in pixel units.
+     */
+    public float getTextSize() {
+        return nGetTextSize(mNativePaint);
+    }
+
+    /**
+     * Set the paint's text size. This value must be > 0
+     *
+     * @param textSize set the paint's text size in pixel units.
+     */
+    public void setTextSize(float textSize) {
+        nSetTextSize(mNativePaint, textSize);
+    }
+
+    /**
+     * Return the paint's horizontal scale factor for text. The default value
+     * is 1.0.
+     *
+     * @return the paint's scale factor in X for drawing/measuring text
+     */
+    public float getTextScaleX() {
+        return nGetTextScaleX(mNativePaint);
+    }
+
+    /**
+     * Set the paint's horizontal scale factor for text. The default value
+     * is 1.0. Values > 1.0 will stretch the text wider. Values < 1.0 will
+     * stretch the text narrower.
+     *
+     * @param scaleX set the paint's scale in X for drawing/measuring text.
+     */
+    public void setTextScaleX(float scaleX) {
+        nSetTextScaleX(mNativePaint, scaleX);
+    }
+
+    /**
+     * Return the paint's horizontal skew factor for text. The default value
+     * is 0.
+     *
+     * @return         the paint's skew factor in X for drawing text.
+     */
+    public float getTextSkewX() {
+        return nGetTextSkewX(mNativePaint);
+    }
+
+    /**
+     * Set the paint's horizontal skew factor for text. The default value
+     * is 0. For approximating oblique text, use values around -0.25.
+     *
+     * @param skewX set the paint's skew factor in X for drawing text.
+     */
+    public void setTextSkewX(float skewX) {
+        nSetTextSkewX(mNativePaint, skewX);
+    }
+
+    /**
+     * Return the paint's letter-spacing for text. The default value
+     * is 0.
+     *
+     * @return         the paint's letter-spacing for drawing text.
+     */
+    public float getLetterSpacing() {
+        return nGetLetterSpacing(mNativePaint);
+    }
+
+    /**
+     * Set the paint's letter-spacing for text. The default value
+     * is 0.  The value is in 'EM' units.  Typical values for slight
+     * expansion will be around 0.05.  Negative values tighten text.
+     *
+     * @param letterSpacing set the paint's letter-spacing for drawing text.
+     */
+    public void setLetterSpacing(float letterSpacing) {
+        nSetLetterSpacing(mNativePaint, letterSpacing);
+    }
+
+    /**
+     * Return the paint's word-spacing for text. The default value is 0.
+     *
+     * @return the paint's word-spacing for drawing text.
+     * @hide
+     */
+    public float getWordSpacing() {
+        return nGetWordSpacing(mNativePaint);
+    }
+
+    /**
+     * Set the paint's word-spacing for text. The default value is 0.
+     * The value is in pixels (note the units are not the same as for
+     * letter-spacing).
+     *
+     * @param wordSpacing set the paint's word-spacing for drawing text.
+     * @hide
+     */
+    public void setWordSpacing(float wordSpacing) {
+        nSetWordSpacing(mNativePaint, wordSpacing);
+    }
+
+    /**
+     * Returns the font feature settings. The format is the same as the CSS
+     * font-feature-settings attribute:
+     * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
+     *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
+     *
+     * @return the paint's currently set font feature settings. Default is null.
+     *
+     * @see #setFontFeatureSettings(String)
+     */
+    public String getFontFeatureSettings() {
+        return mFontFeatureSettings;
+    }
+
+    /**
+     * Set font feature settings.
+     *
+     * The format is the same as the CSS font-feature-settings attribute:
+     * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
+     *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
+     *
+     * @see #getFontFeatureSettings()
+     *
+     * @param settings the font feature settings string to use, may be null.
+     */
+    public void setFontFeatureSettings(String settings) {
+        if (settings != null && settings.equals("")) {
+            settings = null;
+        }
+        if ((settings == null && mFontFeatureSettings == null)
+                || (settings != null && settings.equals(mFontFeatureSettings))) {
+            return;
+        }
+        mFontFeatureSettings = settings;
+        nSetFontFeatureSettings(mNativePaint, settings);
+    }
+
+    /**
+     * Returns the font variation settings.
+     *
+     * @return the paint's currently set font variation settings. Default is null.
+     *
+     * @see #setFontVariationSettings(String)
+     */
+    public String getFontVariationSettings() {
+        return mFontVariationSettings;
+    }
+
+    /**
+     * Sets TrueType or OpenType font variation settings. The settings string is constructed from
+     * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters
+     * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that
+     * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E
+     * are invalid. If a specified axis name is not defined in the font, the settings will be
+     * ignored.
+     *
+     * Examples,
+     * <ul>
+     * <li>Set font width to 150.
+     * <pre>
+     * <code>
+     *   Paint paint = new Paint();
+     *   paint.setFontVariationSettings("'wdth' 150");
+     * </code>
+     * </pre>
+     * </li>
+     *
+     * <li>Set the font slant to 20 degrees and ask for italic style.
+     * <pre>
+     * <code>
+     *   Paint paint = new Paint();
+     *   paint.setFontVariationSettings("'slnt' 20, 'ital' 1");
+     * </code>
+     * </pre>
+     * </li>
+     * </ul>
+     *
+     * @param fontVariationSettings font variation settings. You can pass null or empty string as
+     *                              no variation settings.
+     *
+     * @return true if the given settings is effective to at least one font file underlying this
+     *         typeface. This function also returns true for empty settings string. Otherwise
+     *         returns false
+     *
+     * @throws IllegalArgumentException If given string is not a valid font variation settings
+     *                                  format
+     *
+     * @see #getFontVariationSettings()
+     * @see FontVariationAxis
+     */
+    public boolean setFontVariationSettings(String fontVariationSettings) {
+        final String settings = TextUtils.nullIfEmpty(fontVariationSettings);
+        if (settings == mFontVariationSettings
+                || (settings != null && settings.equals(mFontVariationSettings))) {
+            return true;
+        }
+
+        if (settings == null || settings.length() == 0) {
+            mFontVariationSettings = null;
+            setTypeface(Typeface.createFromTypefaceWithVariation(mTypeface,
+                      Collections.emptyList()));
+            return true;
+        }
+
+        // The null typeface is valid and it is equivalent to Typeface.DEFAULT.
+        // To call isSupportedAxes method, use Typeface.DEFAULT instance.
+        Typeface targetTypeface = mTypeface == null ? Typeface.DEFAULT : mTypeface;
+        FontVariationAxis[] axes = FontVariationAxis.fromFontVariationSettings(settings);
+        final ArrayList<FontVariationAxis> filteredAxes = new ArrayList<FontVariationAxis>();
+        for (final FontVariationAxis axis : axes) {
+            if (targetTypeface.isSupportedAxes(axis.getOpenTypeTagValue())) {
+                filteredAxes.add(axis);
+            }
+        }
+        if (filteredAxes.isEmpty()) {
+            return false;
+        }
+        mFontVariationSettings = settings;
+        setTypeface(Typeface.createFromTypefaceWithVariation(targetTypeface, filteredAxes));
+        return true;
+    }
+
+    /**
+     * Get the current value of hyphen edit.
+     *
+     * @return the current hyphen edit value
+     *
+     * @hide
+     */
+    public int getHyphenEdit() {
+        return nGetHyphenEdit(mNativePaint);
+    }
+
+    /**
+     * Set a hyphen edit on the paint (causes a hyphen to be added to text when
+     * measured or drawn).
+     *
+     * @param hyphen 0 for no edit, 1 for adding a hyphen at the end, etc.
+     *        Definition of various values are in the HyphenEdit class in Minikin's Hyphenator.h.
+     *
+     * @hide
+     */
+    public void setHyphenEdit(int hyphen) {
+        nSetHyphenEdit(mNativePaint, hyphen);
+    }
+
+    /**
+     * Return the distance above (negative) the baseline (ascent) based on the
+     * current typeface and text size.
+     *
+     * <p>Note that this is the ascent of the main typeface, and actual text rendered may need a
+     * larger ascent because fallback fonts may get used in rendering the text.
+     *
+     * @return the distance above (negative) the baseline (ascent) based on the
+     *         current typeface and text size.
+     */
+    public float ascent() {
+        return nAscent(mNativePaint);
+    }
+
+    /**
+     * Return the distance below (positive) the baseline (descent) based on the
+     * current typeface and text size.
+     *
+     * <p>Note that this is the descent of the main typeface, and actual text rendered may need a
+     * larger descent because fallback fonts may get used in rendering the text.
+     *
+     * @return the distance below (positive) the baseline (descent) based on
+     *         the current typeface and text size.
+     */
+    public float descent() {
+        return nDescent(mNativePaint);
+    }
+
+    /**
+     * Class that describes the various metrics for a font at a given text size.
+     * Remember, Y values increase going down, so those values will be positive,
+     * and values that measure distances going up will be negative. This class
+     * is returned by getFontMetrics().
+     */
+    public static class FontMetrics {
+        /**
+         * The maximum distance above the baseline for the tallest glyph in
+         * the font at a given text size.
+         */
+        public float   top;
+        /**
+         * The recommended distance above the baseline for singled spaced text.
+         */
+        public float   ascent;
+        /**
+         * The recommended distance below the baseline for singled spaced text.
+         */
+        public float   descent;
+        /**
+         * The maximum distance below the baseline for the lowest glyph in
+         * the font at a given text size.
+         */
+        public float   bottom;
+        /**
+         * The recommended additional space to add between lines of text.
+         */
+        public float   leading;
+    }
+
+    /**
+     * Return the font's recommended interline spacing, given the Paint's
+     * settings for typeface, textSize, etc. If metrics is not null, return the
+     * fontmetric values in it.
+     *
+     * <p>Note that these are the values for the main typeface, and actual text rendered may need a
+     * larger set of values because fallback fonts may get used in rendering the text.
+     *
+     * @param metrics If this object is not null, its fields are filled with
+     *                the appropriate values given the paint's text attributes.
+     * @return the font's recommended interline spacing.
+     */
+    public float getFontMetrics(FontMetrics metrics) {
+        return nGetFontMetrics(mNativePaint, metrics);
+    }
+
+    /**
+     * Allocates a new FontMetrics object, and then calls getFontMetrics(fm)
+     * with it, returning the object.
+     */
+    public FontMetrics getFontMetrics() {
+        FontMetrics fm = new FontMetrics();
+        getFontMetrics(fm);
+        return fm;
+    }
+
+    /**
+     * Convenience method for callers that want to have FontMetrics values as
+     * integers.
+     */
+    public static class FontMetricsInt {
+        /**
+         * The maximum distance above the baseline for the tallest glyph in
+         * the font at a given text size.
+         */
+        public int   top;
+        /**
+         * The recommended distance above the baseline for singled spaced text.
+         */
+        public int   ascent;
+        /**
+         * The recommended distance below the baseline for singled spaced text.
+         */
+        public int   descent;
+        /**
+         * The maximum distance below the baseline for the lowest glyph in
+         * the font at a given text size.
+         */
+        public int   bottom;
+        /**
+         * The recommended additional space to add between lines of text.
+         */
+        public int   leading;
+
+        @Override public String toString() {
+            return "FontMetricsInt: top=" + top + " ascent=" + ascent +
+                    " descent=" + descent + " bottom=" + bottom +
+                    " leading=" + leading;
+        }
+    }
+
+    /**
+     * Return the font's interline spacing, given the Paint's settings for
+     * typeface, textSize, etc. If metrics is not null, return the fontmetric
+     * values in it. Note: all values have been converted to integers from
+     * floats, in such a way has to make the answers useful for both spacing
+     * and clipping. If you want more control over the rounding, call
+     * getFontMetrics().
+     *
+     * <p>Note that these are the values for the main typeface, and actual text rendered may need a
+     * larger set of values because fallback fonts may get used in rendering the text.
+     *
+     * @return the font's interline spacing.
+     */
+    public int getFontMetricsInt(FontMetricsInt fmi) {
+        return nGetFontMetricsInt(mNativePaint, fmi);
+    }
+
+    public FontMetricsInt getFontMetricsInt() {
+        FontMetricsInt fm = new FontMetricsInt();
+        getFontMetricsInt(fm);
+        return fm;
+    }
+
+    /**
+     * Return the recommend line spacing based on the current typeface and
+     * text size.
+     *
+     * <p>Note that this is the value for the main typeface, and actual text rendered may need a
+     * larger value because fallback fonts may get used in rendering the text.
+     *
+     * @return  recommend line spacing based on the current typeface and
+     *          text size.
+     */
+    public float getFontSpacing() {
+        return getFontMetrics(null);
+    }
+
+    /**
+     * Return the width of the text.
+     *
+     * @param text  The text to measure. Cannot be null.
+     * @param index The index of the first character to start measuring
+     * @param count THe number of characters to measure, beginning with start
+     * @return      The width of the text
+     */
+    public float measureText(char[] text, int index, int count) {
+        if (text == null) {
+            throw new IllegalArgumentException("text cannot be null");
+        }
+        if ((index | count) < 0 || index + count > text.length) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+
+        if (text.length == 0 || count == 0) {
+            return 0f;
+        }
+        if (!mHasCompatScaling) {
+            return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
+                    index, count, index, count, mBidiFlags, null, 0));
+        }
+
+        final float oldSize = getTextSize();
+        setTextSize(oldSize * mCompatScaling);
+        final float w = nGetTextAdvances(mNativePaint, text, index, count, index, count,
+                mBidiFlags, null, 0);
+        setTextSize(oldSize);
+        return (float) Math.ceil(w*mInvCompatScaling);
+    }
+
+    /**
+     * Return the width of the text.
+     *
+     * @param text  The text to measure. Cannot be null.
+     * @param start The index of the first character to start measuring
+     * @param end   1 beyond the index of the last character to measure
+     * @return      The width of the text
+     */
+    public float measureText(String text, int start, int end) {
+        if (text == null) {
+            throw new IllegalArgumentException("text cannot be null");
+        }
+        if ((start | end | (end - start) | (text.length() - end)) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        if (text.length() == 0 || start == end) {
+            return 0f;
+        }
+        if (!mHasCompatScaling) {
+            return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
+                    start, end, start, end, mBidiFlags, null, 0));
+        }
+        final float oldSize = getTextSize();
+        setTextSize(oldSize * mCompatScaling);
+        final float w = nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags,
+                null, 0);
+        setTextSize(oldSize);
+        return (float) Math.ceil(w * mInvCompatScaling);
+    }
+
+    /**
+     * Return the width of the text.
+     *
+     * @param text  The text to measure. Cannot be null.
+     * @return      The width of the text
+     */
+    public float measureText(String text) {
+        if (text == null) {
+            throw new IllegalArgumentException("text cannot be null");
+        }
+        return measureText(text, 0, text.length());
+    }
+
+    /**
+     * Return the width of the text.
+     *
+     * @param text  The text to measure
+     * @param start The index of the first character to start measuring
+     * @param end   1 beyond the index of the last character to measure
+     * @return      The width of the text
+     */
+    public float measureText(CharSequence text, int start, int end) {
+        if (text == null) {
+            throw new IllegalArgumentException("text cannot be null");
+        }
+        if ((start | end | (end - start) | (text.length() - end)) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        if (text.length() == 0 || start == end) {
+            return 0f;
+        }
+        if (text instanceof String) {
+            return measureText((String)text, start, end);
+        }
+        if (text instanceof SpannedString ||
+            text instanceof SpannableString) {
+            return measureText(text.toString(), start, end);
+        }
+        if (text instanceof GraphicsOperations) {
+            return ((GraphicsOperations)text).measureText(start, end, this);
+        }
+
+        char[] buf = TemporaryBuffer.obtain(end - start);
+        TextUtils.getChars(text, start, end, buf, 0);
+        float result = measureText(buf, 0, end - start);
+        TemporaryBuffer.recycle(buf);
+        return result;
+    }
+
+    /**
+     * Measure the text, stopping early if the measured width exceeds maxWidth.
+     * Return the number of chars that were measured, and if measuredWidth is
+     * not null, return in it the actual width measured.
+     *
+     * @param text  The text to measure. Cannot be null.
+     * @param index The offset into text to begin measuring at
+     * @param count The number of maximum number of entries to measure. If count
+     *              is negative, then the characters are measured in reverse order.
+     * @param maxWidth The maximum width to accumulate.
+     * @param measuredWidth Optional. If not null, returns the actual width
+     *                     measured.
+     * @return The number of chars that were measured. Will always be <=
+     *         abs(count).
+     */
+    public int breakText(char[] text, int index, int count,
+                                float maxWidth, float[] measuredWidth) {
+        if (text == null) {
+            throw new IllegalArgumentException("text cannot be null");
+        }
+        if (index < 0 || text.length - index < Math.abs(count)) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+
+        if (text.length == 0 || count == 0) {
+            return 0;
+        }
+        if (!mHasCompatScaling) {
+            return nBreakText(mNativePaint, text, index, count, maxWidth, mBidiFlags,
+                    measuredWidth);
+        }
+
+        final float oldSize = getTextSize();
+        setTextSize(oldSize * mCompatScaling);
+        final int res = nBreakText(mNativePaint, text, index, count, maxWidth * mCompatScaling,
+                mBidiFlags, measuredWidth);
+        setTextSize(oldSize);
+        if (measuredWidth != null) measuredWidth[0] *= mInvCompatScaling;
+        return res;
+    }
+
+    /**
+     * Measure the text, stopping early if the measured width exceeds maxWidth.
+     * Return the number of chars that were measured, and if measuredWidth is
+     * not null, return in it the actual width measured.
+     *
+     * @param text  The text to measure. Cannot be null.
+     * @param start The offset into text to begin measuring at
+     * @param end   The end of the text slice to measure.
+     * @param measureForwards If true, measure forwards, starting at start.
+     *                        Otherwise, measure backwards, starting with end.
+     * @param maxWidth The maximum width to accumulate.
+     * @param measuredWidth Optional. If not null, returns the actual width
+     *                     measured.
+     * @return The number of chars that were measured. Will always be <=
+     *         abs(end - start).
+     */
+    public int breakText(CharSequence text, int start, int end,
+                         boolean measureForwards,
+                         float maxWidth, float[] measuredWidth) {
+        if (text == null) {
+            throw new IllegalArgumentException("text cannot be null");
+        }
+        if ((start | end | (end - start) | (text.length() - end)) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        if (text.length() == 0 || start == end) {
+            return 0;
+        }
+        if (start == 0 && text instanceof String && end == text.length()) {
+            return breakText((String) text, measureForwards, maxWidth,
+                             measuredWidth);
+        }
+
+        char[] buf = TemporaryBuffer.obtain(end - start);
+        int result;
+
+        TextUtils.getChars(text, start, end, buf, 0);
+
+        if (measureForwards) {
+            result = breakText(buf, 0, end - start, maxWidth, measuredWidth);
+        } else {
+            result = breakText(buf, 0, -(end - start), maxWidth, measuredWidth);
+        }
+
+        TemporaryBuffer.recycle(buf);
+        return result;
+    }
+
+    /**
+     * Measure the text, stopping early if the measured width exceeds maxWidth.
+     * Return the number of chars that were measured, and if measuredWidth is
+     * not null, return in it the actual width measured.
+     *
+     * @param text  The text to measure. Cannot be null.
+     * @param measureForwards If true, measure forwards, starting with the
+     *                        first character in the string. Otherwise,
+     *                        measure backwards, starting with the
+     *                        last character in the string.
+     * @param maxWidth The maximum width to accumulate.
+     * @param measuredWidth Optional. If not null, returns the actual width
+     *                     measured.
+     * @return The number of chars that were measured. Will always be <=
+     *         abs(count).
+     */
+    public int breakText(String text, boolean measureForwards,
+                                float maxWidth, float[] measuredWidth) {
+        if (text == null) {
+            throw new IllegalArgumentException("text cannot be null");
+        }
+
+        if (text.length() == 0) {
+            return 0;
+        }
+        if (!mHasCompatScaling) {
+            return nBreakText(mNativePaint, text, measureForwards,
+                    maxWidth, mBidiFlags, measuredWidth);
+        }
+
+        final float oldSize = getTextSize();
+        setTextSize(oldSize*mCompatScaling);
+        final int res = nBreakText(mNativePaint, text, measureForwards, maxWidth*mCompatScaling,
+                mBidiFlags, measuredWidth);
+        setTextSize(oldSize);
+        if (measuredWidth != null) measuredWidth[0] *= mInvCompatScaling;
+        return res;
+    }
+
+    /**
+     * Return the advance widths for the characters in the string.
+     *
+     * @param text     The text to measure. Cannot be null.
+     * @param index    The index of the first char to to measure
+     * @param count    The number of chars starting with index to measure
+     * @param widths   array to receive the advance widths of the characters.
+     *                 Must be at least a large as count.
+     * @return         the actual number of widths returned.
+     */
+    public int getTextWidths(char[] text, int index, int count,
+                             float[] widths) {
+        if (text == null) {
+            throw new IllegalArgumentException("text cannot be null");
+        }
+        if ((index | count) < 0 || index + count > text.length
+                || count > widths.length) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+
+        if (text.length == 0 || count == 0) {
+            return 0;
+        }
+        if (!mHasCompatScaling) {
+            nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0);
+            return count;
+        }
+
+        final float oldSize = getTextSize();
+        setTextSize(oldSize * mCompatScaling);
+        nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0);
+        setTextSize(oldSize);
+        for (int i = 0; i < count; i++) {
+            widths[i] *= mInvCompatScaling;
+        }
+        return count;
+    }
+
+    /**
+     * Return the advance widths for the characters in the string.
+     *
+     * @param text     The text to measure. Cannot be null.
+     * @param start    The index of the first char to to measure
+     * @param end      The end of the text slice to measure
+     * @param widths   array to receive the advance widths of the characters.
+     *                 Must be at least a large as (end - start).
+     * @return         the actual number of widths returned.
+     */
+    public int getTextWidths(CharSequence text, int start, int end,
+                             float[] widths) {
+        if (text == null) {
+            throw new IllegalArgumentException("text cannot be null");
+        }
+        if ((start | end | (end - start) | (text.length() - end)) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        if (end - start > widths.length) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+
+        if (text.length() == 0 || start == end) {
+            return 0;
+        }
+        if (text instanceof String) {
+            return getTextWidths((String) text, start, end, widths);
+        }
+        if (text instanceof SpannedString ||
+            text instanceof SpannableString) {
+            return getTextWidths(text.toString(), start, end, widths);
+        }
+        if (text instanceof GraphicsOperations) {
+            return ((GraphicsOperations) text).getTextWidths(start, end,
+                                                                 widths, this);
+        }
+
+        char[] buf = TemporaryBuffer.obtain(end - start);
+        TextUtils.getChars(text, start, end, buf, 0);
+        int result = getTextWidths(buf, 0, end - start, widths);
+        TemporaryBuffer.recycle(buf);
+        return result;
+    }
+
+    /**
+     * Return the advance widths for the characters in the string.
+     *
+     * @param text   The text to measure. Cannot be null.
+     * @param start  The index of the first char to to measure
+     * @param end    The end of the text slice to measure
+     * @param widths array to receive the advance widths of the characters.
+     *               Must be at least a large as the text.
+     * @return       the number of code units in the specified text.
+     */
+    public int getTextWidths(String text, int start, int end, float[] widths) {
+        if (text == null) {
+            throw new IllegalArgumentException("text cannot be null");
+        }
+        if ((start | end | (end - start) | (text.length() - end)) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        if (end - start > widths.length) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+
+        if (text.length() == 0 || start == end) {
+            return 0;
+        }
+        if (!mHasCompatScaling) {
+            nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
+            return end - start;
+        }
+
+        final float oldSize = getTextSize();
+        setTextSize(oldSize * mCompatScaling);
+        nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
+        setTextSize(oldSize);
+        for (int i = 0; i < end - start; i++) {
+            widths[i] *= mInvCompatScaling;
+        }
+        return end - start;
+    }
+
+    /**
+     * Return the advance widths for the characters in the string.
+     *
+     * @param text   The text to measure
+     * @param widths array to receive the advance widths of the characters.
+     *               Must be at least a large as the text.
+     * @return       the number of code units in the specified text.
+     */
+    public int getTextWidths(String text, float[] widths) {
+        return getTextWidths(text, 0, text.length(), widths);
+    }
+
+    /**
+     * Convenience overload that takes a char array instead of a
+     * String.
+     *
+     * @see #getTextRunAdvances(String, int, int, int, int, boolean, float[], int)
+     * @hide
+     */
+    public float getTextRunAdvances(char[] chars, int index, int count,
+            int contextIndex, int contextCount, boolean isRtl, float[] advances,
+            int advancesIndex) {
+
+        if (chars == null) {
+            throw new IllegalArgumentException("text cannot be null");
+        }
+        if ((index | count | contextIndex | contextCount | advancesIndex
+                | (index - contextIndex) | (contextCount - count)
+                | ((contextIndex + contextCount) - (index + count))
+                | (chars.length - (contextIndex + contextCount))
+                | (advances == null ? 0 :
+                    (advances.length - (advancesIndex + count)))) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        if (chars.length == 0 || count == 0){
+            return 0f;
+        }
+        if (!mHasCompatScaling) {
+            return nGetTextAdvances(mNativePaint, chars, index, count, contextIndex, contextCount,
+                    isRtl ? BIDI_FORCE_RTL : BIDI_FORCE_LTR, advances,
+                    advancesIndex);
+        }
+
+        final float oldSize = getTextSize();
+        setTextSize(oldSize * mCompatScaling);
+        final float res = nGetTextAdvances(mNativePaint, chars, index, count, contextIndex,
+                contextCount, isRtl ? BIDI_FORCE_RTL : BIDI_FORCE_LTR, advances, advancesIndex);
+        setTextSize(oldSize);
+
+        if (advances != null) {
+            for (int i = advancesIndex, e = i + count; i < e; i++) {
+                advances[i] *= mInvCompatScaling;
+            }
+        }
+        return res * mInvCompatScaling; // assume errors are not significant
+    }
+
+    /**
+     * Convenience overload that takes a CharSequence instead of a
+     * String.
+     *
+     * @see #getTextRunAdvances(String, int, int, int, int, boolean, float[], int)
+     * @hide
+     */
+    public float getTextRunAdvances(CharSequence text, int start, int end,
+            int contextStart, int contextEnd, boolean isRtl, float[] advances,
+            int advancesIndex) {
+        if (text == null) {
+            throw new IllegalArgumentException("text cannot be null");
+        }
+        if ((start | end | contextStart | contextEnd | advancesIndex | (end - start)
+                | (start - contextStart) | (contextEnd - end)
+                | (text.length() - contextEnd)
+                | (advances == null ? 0 :
+                    (advances.length - advancesIndex - (end - start)))) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        if (text instanceof String) {
+            return getTextRunAdvances((String) text, start, end,
+                    contextStart, contextEnd, isRtl, advances, advancesIndex);
+        }
+        if (text instanceof SpannedString ||
+            text instanceof SpannableString) {
+            return getTextRunAdvances(text.toString(), start, end,
+                    contextStart, contextEnd, isRtl, advances, advancesIndex);
+        }
+        if (text instanceof GraphicsOperations) {
+            return ((GraphicsOperations) text).getTextRunAdvances(start, end,
+                    contextStart, contextEnd, isRtl, advances, advancesIndex, this);
+        }
+        if (text.length() == 0 || end == start) {
+            return 0f;
+        }
+
+        int contextLen = contextEnd - contextStart;
+        int len = end - start;
+        char[] buf = TemporaryBuffer.obtain(contextLen);
+        TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
+        float result = getTextRunAdvances(buf, start - contextStart, len,
+                0, contextLen, isRtl, advances, advancesIndex);
+        TemporaryBuffer.recycle(buf);
+        return result;
+    }
+
+    /**
+     * Returns the total advance width for the characters in the run
+     * between start and end, and if advances is not null, the advance
+     * assigned to each of these characters (java chars).
+     *
+     * <p>The trailing surrogate in a valid surrogate pair is assigned
+     * an advance of 0.  Thus the number of returned advances is
+     * always equal to count, not to the number of unicode codepoints
+     * represented by the run.
+     *
+     * <p>In the case of conjuncts or combining marks, the total
+     * advance is assigned to the first logical character, and the
+     * following characters are assigned an advance of 0.
+     *
+     * <p>This generates the sum of the advances of glyphs for
+     * characters in a reordered cluster as the width of the first
+     * logical character in the cluster, and 0 for the widths of all
+     * other characters in the cluster.  In effect, such clusters are
+     * treated like conjuncts.
+     *
+     * <p>The shaping bounds limit the amount of context available
+     * outside start and end that can be used for shaping analysis.
+     * These bounds typically reflect changes in bidi level or font
+     * metrics across which shaping does not occur.
+     *
+     * @param text the text to measure. Cannot be null.
+     * @param start the index of the first character to measure
+     * @param end the index past the last character to measure
+     * @param contextStart the index of the first character to use for shaping context,
+     * must be <= start
+     * @param contextEnd the index past the last character to use for shaping context,
+     * must be >= end
+     * @param isRtl whether the run is in RTL direction
+     * @param advances array to receive the advances, must have room for all advances,
+     * can be null if only total advance is needed
+     * @param advancesIndex the position in advances at which to put the
+     * advance corresponding to the character at start
+     * @return the total advance
+     *
+     * @hide
+     */
+    public float getTextRunAdvances(String text, int start, int end, int contextStart,
+            int contextEnd, boolean isRtl, float[] advances, int advancesIndex) {
+        if (text == null) {
+            throw new IllegalArgumentException("text cannot be null");
+        }
+        if ((start | end | contextStart | contextEnd | advancesIndex | (end - start)
+                | (start - contextStart) | (contextEnd - end)
+                | (text.length() - contextEnd)
+                | (advances == null ? 0 :
+                    (advances.length - advancesIndex - (end - start)))) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        if (text.length() == 0 || start == end) {
+            return 0f;
+        }
+
+        if (!mHasCompatScaling) {
+            return nGetTextAdvances(mNativePaint, text, start, end, contextStart, contextEnd,
+                    isRtl ? BIDI_FORCE_RTL : BIDI_FORCE_LTR, advances, advancesIndex);
+        }
+
+        final float oldSize = getTextSize();
+        setTextSize(oldSize * mCompatScaling);
+        final float totalAdvance = nGetTextAdvances(mNativePaint, text, start, end, contextStart,
+                contextEnd, isRtl ? BIDI_FORCE_RTL : BIDI_FORCE_LTR, advances, advancesIndex);
+        setTextSize(oldSize);
+
+        if (advances != null) {
+            for (int i = advancesIndex, e = i + (end - start); i < e; i++) {
+                advances[i] *= mInvCompatScaling;
+            }
+        }
+        return totalAdvance * mInvCompatScaling; // assume errors are insignificant
+    }
+
+    /**
+     * Returns the next cursor position in the run.  This avoids placing the
+     * cursor between surrogates, between characters that form conjuncts,
+     * between base characters and combining marks, or within a reordering
+     * cluster.
+     *
+     * <p>ContextStart and offset are relative to the start of text.
+     * The context is the shaping context for cursor movement, generally
+     * the bounds of the metric span enclosing the cursor in the direction of
+     * movement.
+     *
+     * <p>If cursorOpt is {@link #CURSOR_AT} and the offset is not a valid
+     * cursor position, this returns -1.  Otherwise this will never return a
+     * value before contextStart or after contextStart + contextLength.
+     *
+     * @param text the text
+     * @param contextStart the start of the context
+     * @param contextLength the length of the context
+     * @param dir either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR}
+     * @param offset the cursor position to move from
+     * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER},
+     * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE},
+     * {@link #CURSOR_AT_OR_BEFORE}, or {@link #CURSOR_AT}
+     * @return the offset of the next position, or -1
+     * @hide
+     */
+    public int getTextRunCursor(char[] text, int contextStart, int contextLength,
+            int dir, int offset, int cursorOpt) {
+        int contextEnd = contextStart + contextLength;
+        if (((contextStart | contextEnd | offset | (contextEnd - contextStart)
+                | (offset - contextStart) | (contextEnd - offset)
+                | (text.length - contextEnd) | cursorOpt) < 0)
+                || cursorOpt > CURSOR_OPT_MAX_VALUE) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        return nGetTextRunCursor(mNativePaint, text, contextStart, contextLength, dir, offset,
+                cursorOpt);
+    }
+
+    /**
+     * Returns the next cursor position in the run.  This avoids placing the
+     * cursor between surrogates, between characters that form conjuncts,
+     * between base characters and combining marks, or within a reordering
+     * cluster.
+     *
+     * <p>ContextStart, contextEnd, and offset are relative to the start of
+     * text.  The context is the shaping context for cursor movement, generally
+     * the bounds of the metric span enclosing the cursor in the direction of
+     * movement.
+     *
+     * <p>If cursorOpt is {@link #CURSOR_AT} and the offset is not a valid
+     * cursor position, this returns -1.  Otherwise this will never return a
+     * value before contextStart or after contextEnd.
+     *
+     * @param text the text
+     * @param contextStart the start of the context
+     * @param contextEnd the end of the context
+     * @param dir either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR}
+     * @param offset the cursor position to move from
+     * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER},
+     * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE},
+     * {@link #CURSOR_AT_OR_BEFORE}, or {@link #CURSOR_AT}
+     * @return the offset of the next position, or -1
+     * @hide
+     */
+    public int getTextRunCursor(CharSequence text, int contextStart,
+           int contextEnd, int dir, int offset, int cursorOpt) {
+
+        if (text instanceof String || text instanceof SpannedString ||
+                text instanceof SpannableString) {
+            return getTextRunCursor(text.toString(), contextStart, contextEnd,
+                    dir, offset, cursorOpt);
+        }
+        if (text instanceof GraphicsOperations) {
+            return ((GraphicsOperations) text).getTextRunCursor(
+                    contextStart, contextEnd, dir, offset, cursorOpt, this);
+        }
+
+        int contextLen = contextEnd - contextStart;
+        char[] buf = TemporaryBuffer.obtain(contextLen);
+        TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
+        int relPos = getTextRunCursor(buf, 0, contextLen, dir, offset - contextStart, cursorOpt);
+        TemporaryBuffer.recycle(buf);
+        return (relPos == -1) ? -1 : relPos + contextStart;
+    }
+
+    /**
+     * Returns the next cursor position in the run.  This avoids placing the
+     * cursor between surrogates, between characters that form conjuncts,
+     * between base characters and combining marks, or within a reordering
+     * cluster.
+     *
+     * <p>ContextStart, contextEnd, and offset are relative to the start of
+     * text.  The context is the shaping context for cursor movement, generally
+     * the bounds of the metric span enclosing the cursor in the direction of
+     * movement.
+     *
+     * <p>If cursorOpt is {@link #CURSOR_AT} and the offset is not a valid
+     * cursor position, this returns -1.  Otherwise this will never return a
+     * value before contextStart or after contextEnd.
+     *
+     * @param text the text
+     * @param contextStart the start of the context
+     * @param contextEnd the end of the context
+     * @param dir either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR}
+     * @param offset the cursor position to move from
+     * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER},
+     * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE},
+     * {@link #CURSOR_AT_OR_BEFORE}, or {@link #CURSOR_AT}
+     * @return the offset of the next position, or -1
+     * @hide
+     */
+    public int getTextRunCursor(String text, int contextStart, int contextEnd,
+            int dir, int offset, int cursorOpt) {
+        if (((contextStart | contextEnd | offset | (contextEnd - contextStart)
+                | (offset - contextStart) | (contextEnd - offset)
+                | (text.length() - contextEnd) | cursorOpt) < 0)
+                || cursorOpt > CURSOR_OPT_MAX_VALUE) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        return nGetTextRunCursor(mNativePaint, text, contextStart, contextEnd, dir, offset,
+                cursorOpt);
+    }
+
+    /**
+     * Return the path (outline) for the specified text.
+     * Note: just like Canvas.drawText, this will respect the Align setting in
+     * the paint.
+     *
+     * @param text the text to retrieve the path from
+     * @param index the index of the first character in text
+     * @param count the number of characters starting with index
+     * @param x the x coordinate of the text's origin
+     * @param y the y coordinate of the text's origin
+     * @param path the path to receive the data describing the text. Must be allocated by the caller
+     */
+    public void getTextPath(char[] text, int index, int count,
+                            float x, float y, Path path) {
+        if ((index | count) < 0 || index + count > text.length) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        nGetTextPath(mNativePaint, mBidiFlags, text, index, count, x, y, path.mutateNI());
+    }
+
+    /**
+     * Return the path (outline) for the specified text.
+     * Note: just like Canvas.drawText, this will respect the Align setting
+     * in the paint.
+     *
+     * @param text the text to retrieve the path from
+     * @param start the first character in the text
+     * @param end 1 past the last character in the text
+     * @param x the x coordinate of the text's origin
+     * @param y the y coordinate of the text's origin
+     * @param path the path to receive the data describing the text. Must be allocated by the caller
+     */
+    public void getTextPath(String text, int start, int end,
+                            float x, float y, Path path) {
+        if ((start | end | (end - start) | (text.length() - end)) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        nGetTextPath(mNativePaint, mBidiFlags, text, start, end, x, y, path.mutateNI());
+    }
+
+    /**
+     * Return in bounds (allocated by the caller) the smallest rectangle that
+     * encloses all of the characters, with an implied origin at (0,0).
+     *
+     * @param text string to measure and return its bounds
+     * @param start index of the first char in the string to measure
+     * @param end 1 past the last char in the string to measure
+     * @param bounds returns the unioned bounds of all the text. Must be allocated by the caller
+     */
+    public void getTextBounds(String text, int start, int end, Rect bounds) {
+        if ((start | end | (end - start) | (text.length() - end)) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        if (bounds == null) {
+            throw new NullPointerException("need bounds Rect");
+        }
+        nGetStringBounds(mNativePaint, text, start, end, mBidiFlags, bounds);
+    }
+
+    /**
+     * Return in bounds (allocated by the caller) the smallest rectangle that
+     * encloses all of the characters, with an implied origin at (0,0).
+     *
+     * @param text text to measure and return its bounds
+     * @param start index of the first char in the text to measure
+     * @param end 1 past the last char in the text to measure
+     * @param bounds returns the unioned bounds of all the text. Must be allocated by the caller
+     * @hide
+     */
+    public void getTextBounds(CharSequence text, int start, int end, Rect bounds) {
+        if ((start | end | (end - start) | (text.length() - end)) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        if (bounds == null) {
+            throw new NullPointerException("need bounds Rect");
+        }
+        char[] buf = TemporaryBuffer.obtain(end - start);
+        TextUtils.getChars(text, start, end, buf, 0);
+        getTextBounds(buf, 0, end - start, bounds);
+        TemporaryBuffer.recycle(buf);
+    }
+
+    /**
+     * Return in bounds (allocated by the caller) the smallest rectangle that
+     * encloses all of the characters, with an implied origin at (0,0).
+     *
+     * @param text  array of chars to measure and return their unioned bounds
+     * @param index index of the first char in the array to measure
+     * @param count the number of chars, beginning at index, to measure
+     * @param bounds returns the unioned bounds of all the text. Must be allocated by the caller
+     */
+    public void getTextBounds(char[] text, int index, int count, Rect bounds) {
+        if ((index | count) < 0 || index + count > text.length) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        if (bounds == null) {
+            throw new NullPointerException("need bounds Rect");
+        }
+        nGetCharArrayBounds(mNativePaint, text, index, count, mBidiFlags,
+            bounds);
+    }
+
+    /**
+     * Determine whether the typeface set on the paint has a glyph supporting the string. The
+     * simplest case is when the string contains a single character, in which this method
+     * determines whether the font has the character. In the case of multiple characters, the
+     * method returns true if there is a single glyph representing the ligature. For example, if
+     * the input is a pair of regional indicator symbols, determine whether there is an emoji flag
+     * for the pair.
+     *
+     * <p>Finally, if the string contains a variation selector, the method only returns true if
+     * the fonts contains a glyph specific to that variation.
+     *
+     * <p>Checking is done on the entire fallback chain, not just the immediate font referenced.
+     *
+     * @param string the string to test whether there is glyph support
+     * @return true if the typeface has a glyph for the string
+     */
+    public boolean hasGlyph(String string) {
+        return nHasGlyph(mNativePaint, mBidiFlags, string);
+    }
+
+    /**
+     * Measure cursor position within a run of text.
+     *
+     * <p>The run of text includes the characters from {@code start} to {@code end} in the text. In
+     * addition, the range {@code contextStart} to {@code contextEnd} is used as context for the
+     * purpose of complex text shaping, such as Arabic text potentially shaped differently based on
+     * the text next to it.
+     *
+     * <p>All text outside the range {@code contextStart..contextEnd} is ignored. The text between
+     * {@code start} and {@code end} will be laid out to be measured.
+     *
+     * <p>The returned width measurement is the advance from {@code start} to {@code offset}. It is
+     * generally a positive value, no matter the direction of the run. If {@code offset == end},
+     * the return value is simply the width of the whole run from {@code start} to {@code end}.
+     *
+     * <p>Ligatures are formed for characters in the range {@code start..end} (but not for
+     * {@code start..contextStart} or {@code end..contextEnd}). If {@code offset} points to a
+     * character in the middle of such a formed ligature, but at a grapheme cluster boundary, the
+     * return value will also reflect an advance in the middle of the ligature. See
+     * {@link #getOffsetForAdvance} for more discussion of grapheme cluster boundaries.
+     *
+     * <p>The direction of the run is explicitly specified by {@code isRtl}. Thus, this method is
+     * suitable only for runs of a single direction.
+     *
+     * <p>All indices are relative to the start of {@code text}. Further, {@code 0 <= contextStart
+     * <= start <= offset <= end <= contextEnd <= text.length} must hold on entry.
+     *
+     * @param text the text to measure. Cannot be null.
+     * @param start the index of the start of the range to measure
+     * @param end the index + 1 of the end of the range to measure
+     * @param contextStart the index of the start of the shaping context
+     * @param contextEnd the index + 1 of the end of the shaping context
+     * @param isRtl whether the run is in RTL direction
+     * @param offset index of caret position
+     * @return width measurement between start and offset
+     */
+    public float getRunAdvance(char[] text, int start, int end, int contextStart, int contextEnd,
+            boolean isRtl, int offset) {
+        if (text == null) {
+            throw new IllegalArgumentException("text cannot be null");
+        }
+        if ((contextStart | start | offset | end | contextEnd
+                | start - contextStart | offset - start | end - offset
+                | contextEnd - end | text.length - contextEnd) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        if (end == start) {
+            return 0.0f;
+        }
+        // TODO: take mCompatScaling into account (or eliminate compat scaling)?
+        return nGetRunAdvance(mNativePaint, text, start, end, contextStart, contextEnd, isRtl,
+                offset);
+    }
+
+    /**
+     * @see #getRunAdvance(char[], int, int, int, int, boolean, int)
+     *
+     * @param text the text to measure. Cannot be null.
+     * @param start the index of the start of the range to measure
+     * @param end the index + 1 of the end of the range to measure
+     * @param contextStart the index of the start of the shaping context
+     * @param contextEnd the index + 1 of the end of the shaping context
+     * @param isRtl whether the run is in RTL direction
+     * @param offset index of caret position
+     * @return width measurement between start and offset
+     */
+    public float getRunAdvance(@NonNull CharSequence text, int start, int end, int contextStart,
+            int contextEnd, boolean isRtl, int offset) {
+        if (text == null) {
+            throw new IllegalArgumentException("text cannot be null");
+        }
+        if ((contextStart | start | offset | end | contextEnd
+                | start - contextStart | offset - start | end - offset
+                | contextEnd - end | text.length() - contextEnd) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        if (end == start) {
+            return 0.0f;
+        }
+        // TODO performance: specialized alternatives to avoid buffer copy, if win is significant
+        char[] buf = TemporaryBuffer.obtain(contextEnd - contextStart);
+        TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
+        float result = getRunAdvance(buf, start - contextStart, end - contextStart, 0,
+                contextEnd - contextStart, isRtl, offset - contextStart);
+        TemporaryBuffer.recycle(buf);
+        return result;
+    }
+
+    /**
+     * Get the character offset within the string whose position is closest to the specified
+     * horizontal position.
+     *
+     * <p>The returned value is generally the value of {@code offset} for which
+     * {@link #getRunAdvance} yields a result most closely approximating {@code advance},
+     * and which is also on a grapheme cluster boundary. As such, it is the preferred method
+     * for positioning a cursor in response to a touch or pointer event. The grapheme cluster
+     * boundaries are based on
+     * <a href="http://unicode.org/reports/tr29/">Unicode Standard Annex #29</a> but with some
+     * tailoring for better user experience.
+     *
+     * <p>Note that {@code advance} is a (generally positive) width measurement relative to the start
+     * of the run. Thus, for RTL runs it the distance from the point to the right edge.
+     *
+     * <p>All indices are relative to the start of {@code text}. Further, {@code 0 <= contextStart
+     * <= start <= end <= contextEnd <= text.length} must hold on entry, and {@code start <= result
+     * <= end} will hold on return.
+     *
+     * @param text the text to measure. Cannot be null.
+     * @param start the index of the start of the range to measure
+     * @param end the index + 1 of the end of the range to measure
+     * @param contextStart the index of the start of the shaping context
+     * @param contextEnd the index + 1 of the end of the range to measure
+     * @param isRtl whether the run is in RTL direction
+     * @param advance width relative to start of run
+     * @return index of offset
+     */
+    public int getOffsetForAdvance(char[] text, int start, int end, int contextStart,
+            int contextEnd, boolean isRtl, float advance) {
+        if (text == null) {
+            throw new IllegalArgumentException("text cannot be null");
+        }
+        if ((contextStart | start | end | contextEnd
+                | start - contextStart | end - start | contextEnd - end
+                | text.length - contextEnd) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        // TODO: take mCompatScaling into account (or eliminate compat scaling)?
+        return nGetOffsetForAdvance(mNativePaint, text, start, end, contextStart, contextEnd,
+                isRtl, advance);
+    }
+
+    /**
+     * @see #getOffsetForAdvance(char[], int, int, int, int, boolean, float)
+     *
+     * @param text the text to measure. Cannot be null.
+     * @param start the index of the start of the range to measure
+     * @param end the index + 1 of the end of the range to measure
+     * @param contextStart the index of the start of the shaping context
+     * @param contextEnd the index + 1 of the end of the range to measure
+     * @param isRtl whether the run is in RTL direction
+     * @param advance width relative to start of run
+     * @return index of offset
+     */
+    public int getOffsetForAdvance(CharSequence text, int start, int end, int contextStart,
+            int contextEnd, boolean isRtl, float advance) {
+        if (text == null) {
+            throw new IllegalArgumentException("text cannot be null");
+        }
+        if ((contextStart | start | end | contextEnd
+                | start - contextStart | end - start | contextEnd - end
+                | text.length() - contextEnd) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        // TODO performance: specialized alternatives to avoid buffer copy, if win is significant
+        char[] buf = TemporaryBuffer.obtain(contextEnd - contextStart);
+        TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
+        int result = getOffsetForAdvance(buf, start - contextStart, end - contextStart, 0,
+                contextEnd - contextStart, isRtl, advance) + contextStart;
+        TemporaryBuffer.recycle(buf);
+        return result;
+    }
+
+    // regular JNI
+    private static native long nGetNativeFinalizer();
+    private static native long nInit();
+    private static native long nInitWithPaint(long paint);
+    private static native int nBreakText(long nObject, char[] text, int index, int count,
+            float maxWidth, int bidiFlags, float[] measuredWidth);
+    private static native int nBreakText(long nObject, String text, boolean measureForwards,
+            float maxWidth, int bidiFlags, float[] measuredWidth);
+    private static native float nGetTextAdvances(long paintPtr, char[] text, int index, int count,
+            int contextIndex, int contextCount, int bidiFlags, float[] advances, int advancesIndex);
+    private static native float nGetTextAdvances(long paintPtr, String text, int start, int end,
+            int contextStart, int contextEnd, int bidiFlags, float[] advances, int advancesIndex);
+    private native int nGetTextRunCursor(long paintPtr, char[] text, int contextStart,
+            int contextLength, int dir, int offset, int cursorOpt);
+    private native int nGetTextRunCursor(long paintPtr, String text, int contextStart,
+            int contextEnd, int dir, int offset, int cursorOpt);
+    private static native void nGetTextPath(long paintPtr, int bidiFlags, char[] text, int index,
+            int count, float x, float y, long path);
+    private static native void nGetTextPath(long paintPtr, int bidiFlags, String text, int start,
+            int end, float x, float y, long path);
+    private static native void nGetStringBounds(long nativePaint, String text, int start, int end,
+            int bidiFlags, Rect bounds);
+    private static native void nGetCharArrayBounds(long nativePaint, char[] text, int index,
+            int count, int bidiFlags, Rect bounds);
+    private static native boolean nHasGlyph(long paintPtr, int bidiFlags, String string);
+    private static native float nGetRunAdvance(long paintPtr, char[] text, int start, int end,
+            int contextStart, int contextEnd, boolean isRtl, int offset);
+    private static native int nGetOffsetForAdvance(long paintPtr, char[] text, int start, int end,
+            int contextStart, int contextEnd, boolean isRtl, float advance);
+
+
+    // ---------------- @FastNative ------------------------
+
+    @FastNative
+    private static native int nSetTextLocales(long paintPtr, String locales);
+    @FastNative
+    private static native void nSetFontFeatureSettings(long paintPtr, String settings);
+    @FastNative
+    private static native float nGetFontMetrics(long paintPtr, FontMetrics metrics);
+    @FastNative
+    private static native int nGetFontMetricsInt(long paintPtr, FontMetricsInt fmi);
+
+
+    // ---------------- @CriticalNative ------------------------
+
+    @CriticalNative
+    private static native void nReset(long paintPtr);
+    @CriticalNative
+    private static native void nSet(long paintPtrDest, long paintPtrSrc);
+    @CriticalNative
+    private static native int nGetStyle(long paintPtr);
+    @CriticalNative
+    private static native void nSetStyle(long paintPtr, int style);
+    @CriticalNative
+    private static native int nGetStrokeCap(long paintPtr);
+    @CriticalNative
+    private static native void nSetStrokeCap(long paintPtr, int cap);
+    @CriticalNative
+    private static native int nGetStrokeJoin(long paintPtr);
+    @CriticalNative
+    private static native void nSetStrokeJoin(long paintPtr, int join);
+    @CriticalNative
+    private static native boolean nGetFillPath(long paintPtr, long src, long dst);
+    @CriticalNative
+    private static native long nSetShader(long paintPtr, long shader);
+    @CriticalNative
+    private static native long nSetColorFilter(long paintPtr, long filter);
+    @CriticalNative
+    private static native void nSetXfermode(long paintPtr, int xfermode);
+    @CriticalNative
+    private static native long nSetPathEffect(long paintPtr, long effect);
+    @CriticalNative
+    private static native long nSetMaskFilter(long paintPtr, long maskfilter);
+    @CriticalNative
+    private static native void nSetTypeface(long paintPtr, long typeface);
+    @CriticalNative
+    private static native int nGetTextAlign(long paintPtr);
+    @CriticalNative
+    private static native void nSetTextAlign(long paintPtr, int align);
+    @CriticalNative
+    private static native void nSetTextLocalesByMinikinLangListId(long paintPtr,
+            int mMinikinLangListId);
+    @CriticalNative
+    private static native void nSetShadowLayer(long paintPtr,
+            float radius, float dx, float dy, int color);
+    @CriticalNative
+    private static native boolean nHasShadowLayer(long paintPtr);
+    @CriticalNative
+    private static native float nGetLetterSpacing(long paintPtr);
+    @CriticalNative
+    private static native void nSetLetterSpacing(long paintPtr, float letterSpacing);
+    @CriticalNative
+    private static native float nGetWordSpacing(long paintPtr);
+    @CriticalNative
+    private static native void nSetWordSpacing(long paintPtr, float wordSpacing);
+    @CriticalNative
+    private static native int nGetHyphenEdit(long paintPtr);
+    @CriticalNative
+    private static native void nSetHyphenEdit(long paintPtr, int hyphen);
+    @CriticalNative
+    private static native void nSetStrokeMiter(long paintPtr, float miter);
+    @CriticalNative
+    private static native float nGetStrokeMiter(long paintPtr);
+    @CriticalNative
+    private static native void nSetStrokeWidth(long paintPtr, float width);
+    @CriticalNative
+    private static native float nGetStrokeWidth(long paintPtr);
+    @CriticalNative
+    private static native void nSetAlpha(long paintPtr, int a);
+    @CriticalNative
+    private static native void nSetDither(long paintPtr, boolean dither);
+    @CriticalNative
+    private static native int nGetFlags(long paintPtr);
+    @CriticalNative
+    private static native void nSetFlags(long paintPtr, int flags);
+    @CriticalNative
+    private static native int nGetHinting(long paintPtr);
+    @CriticalNative
+    private static native void nSetHinting(long paintPtr, int mode);
+    @CriticalNative
+    private static native void nSetAntiAlias(long paintPtr, boolean aa);
+    @CriticalNative
+    private static native void nSetLinearText(long paintPtr, boolean linearText);
+    @CriticalNative
+    private static native void nSetSubpixelText(long paintPtr, boolean subpixelText);
+    @CriticalNative
+    private static native void nSetUnderlineText(long paintPtr, boolean underlineText);
+    @CriticalNative
+    private static native void nSetFakeBoldText(long paintPtr, boolean fakeBoldText);
+    @CriticalNative
+    private static native void nSetFilterBitmap(long paintPtr, boolean filter);
+    @CriticalNative
+    private static native int nGetColor(long paintPtr);
+    @CriticalNative
+    private static native void nSetColor(long paintPtr, @ColorInt int color);
+    @CriticalNative
+    private static native int nGetAlpha(long paintPtr);
+    @CriticalNative
+    private static native void nSetStrikeThruText(long paintPtr, boolean strikeThruText);
+    @CriticalNative
+    private static native boolean nIsElegantTextHeight(long paintPtr);
+    @CriticalNative
+    private static native void nSetElegantTextHeight(long paintPtr, boolean elegant);
+    @CriticalNative
+    private static native float nGetTextSize(long paintPtr);
+    @CriticalNative
+    private static native float nGetTextScaleX(long paintPtr);
+    @CriticalNative
+    private static native void nSetTextScaleX(long paintPtr, float scaleX);
+    @CriticalNative
+    private static native float nGetTextSkewX(long paintPtr);
+    @CriticalNative
+    private static native void nSetTextSkewX(long paintPtr, float skewX);
+    @CriticalNative
+    private static native float nAscent(long paintPtr);
+    @CriticalNative
+    private static native float nDescent(long paintPtr);
+    @CriticalNative
+    private static native float nGetUnderlinePosition(long paintPtr);
+    @CriticalNative
+    private static native float nGetUnderlineThickness(long paintPtr);
+    @CriticalNative
+    private static native float nGetStrikeThruPosition(long paintPtr);
+    @CriticalNative
+    private static native float nGetStrikeThruThickness(long paintPtr);
+    @CriticalNative
+    private static native void nSetTextSize(long paintPtr, float textSize);
+}
diff --git a/android/graphics/PaintFlagsDrawFilter.java b/android/graphics/PaintFlagsDrawFilter.java
new file mode 100644
index 0000000..2326611
--- /dev/null
+++ b/android/graphics/PaintFlagsDrawFilter.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2008 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;
+
+public class PaintFlagsDrawFilter extends DrawFilter {
+    /**
+     * Subclass of DrawFilter that affects every paint by first clearing
+     * the specified clearBits in the paint's flags, and then setting the
+     * specified setBits in the paint's flags.
+     *
+     * @param clearBits These bits will be cleared in the paint's flags
+     * @param setBits These bits will be set in the paint's flags
+     */
+    public PaintFlagsDrawFilter(int clearBits, int setBits) {
+        // our native constructor can return 0, if the specified bits
+        // are effectively a no-op
+        mNativeInt = nativeConstructor(clearBits, setBits);
+    }
+    
+    private static native long nativeConstructor(int clearBits, int setBits);
+}
+
diff --git a/android/graphics/PaintFlagsDrawFilter_Delegate.java b/android/graphics/PaintFlagsDrawFilter_Delegate.java
new file mode 100644
index 0000000..fa20746
--- /dev/null
+++ b/android/graphics/PaintFlagsDrawFilter_Delegate.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.PaintFlagsDrawFilter
+ *
+ * Through the layoutlib_create tool, the original native methods of PaintFlagsDrawFilter have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original PaintFlagsDrawFilter class.
+ *
+ * Because this extends {@link DrawFilter_Delegate}, there's no need to use a
+ * {@link DelegateManager}, as all the DrawFilter classes will be added to the manager owned by
+ * {@link DrawFilter_Delegate}.
+ *
+ * @see DrawFilter_Delegate
+ *
+ */
+public class PaintFlagsDrawFilter_Delegate extends DrawFilter_Delegate {
+
+    // ---- delegate data ----
+
+    // ---- Public Helper methods ----
+
+    @Override
+    public boolean isSupported() {
+        return false;
+    }
+
+    @Override
+    public String getSupportMessage() {
+        return "Paint Flags Draw Filters are not supported.";
+    }
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeConstructor(int clearBits, int setBits) {
+        PaintFlagsDrawFilter_Delegate newDelegate = new PaintFlagsDrawFilter_Delegate();
+        return sManager.addNewDelegate(newDelegate);
+    }
+
+    // ---- Private delegate/helper methods ----
+}
diff --git a/android/graphics/Paint_Delegate.java b/android/graphics/Paint_Delegate.java
new file mode 100644
index 0000000..ef45203
--- /dev/null
+++ b/android/graphics/Paint_Delegate.java
@@ -0,0 +1,1355 @@
+/*
+ * 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;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.FontFamily_Delegate.FontVariant;
+import android.graphics.Paint.FontMetrics;
+import android.graphics.Paint.FontMetricsInt;
+import android.text.TextUtils;
+
+import java.awt.BasicStroke;
+import java.awt.Font;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.Toolkit;
+import java.awt.geom.AffineTransform;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import libcore.util.NativeAllocationRegistry_Delegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.Paint
+ *
+ * Through the layoutlib_create tool, the original native methods of Paint have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original Paint class.
+ *
+ * @see DelegateManager
+ *
+ */
+public class Paint_Delegate {
+    private static final float DEFAULT_TEXT_SIZE = 20.f;
+    private static final float DEFAULT_TEXT_SCALE_X = 1.f;
+    private static final float DEFAULT_TEXT_SKEW_X = 0.f;
+
+    /**
+     * Class associating a {@link Font} and its {@link java.awt.FontMetrics}.
+     */
+    /*package*/ static final class FontInfo {
+        final Font mFont;
+        final java.awt.FontMetrics mMetrics;
+
+        FontInfo(@NonNull Font font, @NonNull java.awt.FontMetrics fontMetrics) {
+            this.mFont = font;
+            this.mMetrics = fontMetrics;
+        }
+    }
+
+    // ---- delegate manager ----
+    private static final DelegateManager<Paint_Delegate> sManager =
+            new DelegateManager<>(Paint_Delegate.class);
+    private static long sFinalizer = -1;
+
+    // ---- delegate helper data ----
+
+    // This list can contain null elements.
+    @Nullable
+    private List<FontInfo> mFonts;
+
+    // ---- delegate data ----
+    private int mFlags;
+    private int mColor;
+    private int mStyle;
+    private int mCap;
+    private int mJoin;
+    private int mTextAlign;
+    private Typeface_Delegate mTypeface;
+    private float mStrokeWidth;
+    private float mStrokeMiter;
+    private float mTextSize;
+    private float mTextScaleX;
+    private float mTextSkewX;
+    private int mHintingMode = Paint.HINTING_ON;
+    private int mHyphenEdit;
+    private float mLetterSpacing;  // not used in actual text rendering.
+    private float mWordSpacing;  // not used in actual text rendering.
+    // Variant of the font. A paint's variant can only be compact or elegant.
+    private FontVariant mFontVariant = FontVariant.COMPACT;
+
+    private int mPorterDuffMode = Xfermode.DEFAULT;
+    private ColorFilter_Delegate mColorFilter;
+    private Shader_Delegate mShader;
+    private PathEffect_Delegate mPathEffect;
+    private MaskFilter_Delegate mMaskFilter;
+
+    @SuppressWarnings("FieldCanBeLocal") // Used to store the locale for future use
+    private Locale mLocale = Locale.getDefault();
+
+    // ---- Public Helper methods ----
+
+    @Nullable
+    public static Paint_Delegate getDelegate(long native_paint) {
+        return sManager.getDelegate(native_paint);
+    }
+
+    /**
+     * Returns the list of {@link Font} objects.
+     */
+    @NonNull
+    public List<FontInfo> getFonts() {
+        if (mTypeface == null) {
+            return Collections.emptyList();
+        }
+
+        if (mFonts != null) {
+            return mFonts;
+        }
+
+        // Apply an optional transformation for skew and scale
+        AffineTransform affineTransform = mTextScaleX != 1.0 || mTextSkewX != 0 ?
+                new AffineTransform(mTextScaleX, mTextSkewX, 0, 1, 0, 0) :
+                null;
+
+        List<FontInfo> infoList = StreamSupport.stream(mTypeface.getFonts(mFontVariant).spliterator
+                (), false)
+                .map(font -> getFontInfo(font, mTextSize, affineTransform))
+                .collect(Collectors.toList());
+        mFonts = Collections.unmodifiableList(infoList);
+
+        return mFonts;
+    }
+
+    public boolean isAntiAliased() {
+        return (mFlags & Paint.ANTI_ALIAS_FLAG) != 0;
+    }
+
+    public boolean isFilterBitmap() {
+        return (mFlags & Paint.FILTER_BITMAP_FLAG) != 0;
+    }
+
+    public int getStyle() {
+        return mStyle;
+    }
+
+    public int getColor() {
+        return mColor;
+    }
+
+    public int getAlpha() {
+        return mColor >>> 24;
+    }
+
+    public void setAlpha(int alpha) {
+        mColor = (alpha << 24) | (mColor & 0x00FFFFFF);
+    }
+
+    public int getTextAlign() {
+        return mTextAlign;
+    }
+
+    public float getStrokeWidth() {
+        return mStrokeWidth;
+    }
+
+    /**
+     * returns the value of stroke miter needed by the java api.
+     */
+    public float getJavaStrokeMiter() {
+        return mStrokeMiter;
+    }
+
+    public int getJavaCap() {
+        switch (Paint.sCapArray[mCap]) {
+            case BUTT:
+                return BasicStroke.CAP_BUTT;
+            case ROUND:
+                return BasicStroke.CAP_ROUND;
+            default:
+            case SQUARE:
+                return BasicStroke.CAP_SQUARE;
+        }
+    }
+
+    public int getJavaJoin() {
+        switch (Paint.sJoinArray[mJoin]) {
+            default:
+            case MITER:
+                return BasicStroke.JOIN_MITER;
+            case ROUND:
+                return BasicStroke.JOIN_ROUND;
+            case BEVEL:
+                return BasicStroke.JOIN_BEVEL;
+        }
+    }
+
+    public Stroke getJavaStroke() {
+        if (mPathEffect != null) {
+            if (mPathEffect.isSupported()) {
+                Stroke stroke = mPathEffect.getStroke(this);
+                assert stroke != null;
+                //noinspection ConstantConditions
+                if (stroke != null) {
+                    return stroke;
+                }
+            } else {
+                Bridge.getLog().fidelityWarning(LayoutLog.TAG_PATHEFFECT,
+                        mPathEffect.getSupportMessage(),
+                        null, null /*data*/);
+            }
+        }
+
+        // if no custom stroke as been set, set the default one.
+        return new BasicStroke(
+                    getStrokeWidth(),
+                    getJavaCap(),
+                    getJavaJoin(),
+                    getJavaStrokeMiter());
+    }
+
+    /**
+     * Returns the {@link PorterDuff.Mode} as an int
+     */
+    public int getPorterDuffMode() {
+        return mPorterDuffMode;
+    }
+
+    /**
+     * Returns the {@link ColorFilter} delegate or null if none have been set
+     *
+     * @return the delegate or null.
+     */
+    public ColorFilter_Delegate getColorFilter() {
+        return mColorFilter;
+    }
+
+    public void setColorFilter(long colorFilterPtr) {
+        mColorFilter = ColorFilter_Delegate.getDelegate(colorFilterPtr);
+    }
+
+    public void setShader(long shaderPtr) {
+        mShader = Shader_Delegate.getDelegate(shaderPtr);
+    }
+
+    /**
+     * Returns the {@link Shader} delegate or null if none have been set
+     *
+     * @return the delegate or null.
+     */
+    public Shader_Delegate getShader() {
+        return mShader;
+    }
+
+    /**
+     * Returns the {@link MaskFilter} delegate or null if none have been set
+     *
+     * @return the delegate or null.
+     */
+    public MaskFilter_Delegate getMaskFilter() {
+        return mMaskFilter;
+    }
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static int nGetFlags(long nativePaint) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return 0;
+        }
+
+        return delegate.mFlags;
+    }
+
+
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetFlags(long nativePaint, int flags) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return;
+        }
+
+        delegate.mFlags = flags;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetFilterBitmap(long nativePaint, boolean filter) {
+        setFlag(nativePaint, Paint.FILTER_BITMAP_FLAG, filter);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static int nGetHinting(long nativePaint) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return Paint.HINTING_ON;
+        }
+
+        return delegate.mHintingMode;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetHinting(long nativePaint, int mode) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return;
+        }
+
+        delegate.mHintingMode = mode;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetAntiAlias(long nativePaint, boolean aa) {
+        setFlag(nativePaint, Paint.ANTI_ALIAS_FLAG, aa);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetSubpixelText(long nativePaint,
+            boolean subpixelText) {
+        setFlag(nativePaint, Paint.SUBPIXEL_TEXT_FLAG, subpixelText);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetUnderlineText(long nativePaint,
+            boolean underlineText) {
+        setFlag(nativePaint, Paint.UNDERLINE_TEXT_FLAG, underlineText);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetStrikeThruText(long nativePaint,
+            boolean strikeThruText) {
+        setFlag(nativePaint, Paint.STRIKE_THRU_TEXT_FLAG, strikeThruText);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetFakeBoldText(long nativePaint,
+            boolean fakeBoldText) {
+        setFlag(nativePaint, Paint.FAKE_BOLD_TEXT_FLAG, fakeBoldText);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetDither(long nativePaint, boolean dither) {
+        setFlag(nativePaint, Paint.DITHER_FLAG, dither);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetLinearText(long nativePaint, boolean linearText) {
+        setFlag(nativePaint, Paint.LINEAR_TEXT_FLAG, linearText);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static int nGetColor(long nativePaint) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return 0;
+        }
+
+        return delegate.mColor;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetColor(long nativePaint, int color) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return;
+        }
+
+        delegate.mColor = color;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static int nGetAlpha(long nativePaint) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return 0;
+        }
+
+        return delegate.getAlpha();
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetAlpha(long nativePaint, int a) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return;
+        }
+
+        delegate.setAlpha(a);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static float nGetStrokeWidth(long nativePaint) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return 1.f;
+        }
+
+        return delegate.mStrokeWidth;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetStrokeWidth(long nativePaint, float width) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return;
+        }
+
+        delegate.mStrokeWidth = width;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static float nGetStrokeMiter(long nativePaint) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return 1.f;
+        }
+
+        return delegate.mStrokeMiter;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetStrokeMiter(long nativePaint, float miter) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return;
+        }
+
+        delegate.mStrokeMiter = miter;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetShadowLayer(long paint, float radius, float dx, float dy,
+            int color) {
+        // FIXME
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "Paint.setShadowLayer is not supported.", null, null /*data*/);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nHasShadowLayer(long paint) {
+        // FIXME
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "Paint.hasShadowLayer is not supported.", null, null /*data*/);
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nIsElegantTextHeight(long nativePaint) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        return delegate != null && delegate.mFontVariant == FontVariant.ELEGANT;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetElegantTextHeight(long nativePaint,
+            boolean elegant) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return;
+        }
+
+        delegate.mFontVariant = elegant ? FontVariant.ELEGANT : FontVariant.COMPACT;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static float nGetTextSize(long nativePaint) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return 1.f;
+        }
+
+        return delegate.mTextSize;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetTextSize(long nativePaint, float textSize) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return;
+        }
+
+        if (delegate.mTextSize != textSize) {
+            delegate.mTextSize = textSize;
+            delegate.invalidateFonts();
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static float nGetTextScaleX(long nativePaint) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return 1.f;
+        }
+
+        return delegate.mTextScaleX;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetTextScaleX(long nativePaint, float scaleX) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return;
+        }
+
+        if (delegate.mTextScaleX != scaleX) {
+            delegate.mTextScaleX = scaleX;
+            delegate.invalidateFonts();
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static float nGetTextSkewX(long nativePaint) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return 1.f;
+        }
+
+        return delegate.mTextSkewX;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetTextSkewX(long nativePaint, float skewX) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return;
+        }
+
+        if (delegate.mTextSkewX != skewX) {
+            delegate.mTextSkewX = skewX;
+            delegate.invalidateFonts();
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static float nAscent(long nativePaint) {
+        // get the delegate
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return 0;
+        }
+
+        List<FontInfo> fonts = delegate.getFonts();
+        if (fonts.size() > 0) {
+            java.awt.FontMetrics javaMetrics = fonts.get(0).mMetrics;
+            // Android expects negative ascent so we invert the value from Java.
+            return - javaMetrics.getAscent();
+        }
+
+        return 0;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static float nDescent(long nativePaint) {
+        // get the delegate
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return 0;
+        }
+
+        List<FontInfo> fonts = delegate.getFonts();
+        if (fonts.size() > 0) {
+            java.awt.FontMetrics javaMetrics = fonts.get(0).mMetrics;
+            return javaMetrics.getDescent();
+        }
+
+        return 0;
+
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static float nGetFontMetrics(long nativePaint,
+            FontMetrics metrics) {
+        // get the delegate
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return 0;
+        }
+
+        return delegate.getFontMetrics(metrics);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static int nGetFontMetricsInt(long nativePaint, FontMetricsInt fmi) {
+        // get the delegate
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return 0;
+        }
+
+        List<FontInfo> fonts = delegate.getFonts();
+        if (fonts.size() > 0) {
+            java.awt.FontMetrics javaMetrics = fonts.get(0).mMetrics;
+            if (fmi != null) {
+                // Android expects negative ascent so we invert the value from Java.
+                fmi.top = (int)(- javaMetrics.getMaxAscent() * 1.15);
+                fmi.ascent = - javaMetrics.getAscent();
+                fmi.descent = javaMetrics.getDescent();
+                fmi.bottom = (int)(javaMetrics.getMaxDescent() * 1.15);
+                fmi.leading = javaMetrics.getLeading();
+            }
+
+            return javaMetrics.getHeight();
+        }
+
+        return 0;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static int nBreakText(long nativePaint, char[] text,
+            int index, int count, float maxWidth, int bidiFlags, float[] measuredWidth) {
+
+        // get the delegate
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return 0;
+        }
+
+        int inc = count > 0 ? 1 : -1;
+
+        int measureIndex = 0;
+        for (int i = index; i != index + count; i += inc, measureIndex++) {
+            int start, end;
+            if (i < index) {
+                start = i;
+                end = index;
+            } else {
+                start = index;
+                end = i;
+            }
+
+            // measure from start to end
+            RectF bounds = delegate.measureText(text, start, end - start + 1, null, 0, bidiFlags);
+            float res = bounds.right - bounds.left;
+
+            if (measuredWidth != null) {
+                measuredWidth[measureIndex] = res;
+            }
+
+            if (res > maxWidth) {
+                // we should not return this char index, but since it's 0-based
+                // and we need to return a count, we simply return measureIndex;
+                return measureIndex;
+            }
+
+        }
+
+        return measureIndex;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static int nBreakText(long nativePaint, String text, boolean measureForwards,
+            float maxWidth, int bidiFlags, float[] measuredWidth) {
+        return nBreakText(nativePaint, text.toCharArray(), 0, text.length(),
+                maxWidth, bidiFlags, measuredWidth);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nInit() {
+        Paint_Delegate newDelegate = new Paint_Delegate();
+        return sManager.addNewDelegate(newDelegate);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nInitWithPaint(long paint) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(paint);
+        if (delegate == null) {
+            return 0;
+        }
+
+        Paint_Delegate newDelegate = new Paint_Delegate(delegate);
+        return sManager.addNewDelegate(newDelegate);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nReset(long native_object) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(native_object);
+        if (delegate == null) {
+            return;
+        }
+
+        delegate.reset();
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSet(long native_dst, long native_src) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate_dst = sManager.getDelegate(native_dst);
+        if (delegate_dst == null) {
+            return;
+        }
+
+        // get the delegate from the native int.
+        Paint_Delegate delegate_src = sManager.getDelegate(native_src);
+        if (delegate_src == null) {
+            return;
+        }
+
+        delegate_dst.set(delegate_src);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static int nGetStyle(long native_object) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(native_object);
+        if (delegate == null) {
+            return 0;
+        }
+
+        return delegate.mStyle;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetStyle(long native_object, int style) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(native_object);
+        if (delegate == null) {
+            return;
+        }
+
+        delegate.mStyle = style;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static int nGetStrokeCap(long native_object) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(native_object);
+        if (delegate == null) {
+            return 0;
+        }
+
+        return delegate.mCap;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetStrokeCap(long native_object, int cap) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(native_object);
+        if (delegate == null) {
+            return;
+        }
+
+        delegate.mCap = cap;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static int nGetStrokeJoin(long native_object) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(native_object);
+        if (delegate == null) {
+            return 0;
+        }
+
+        return delegate.mJoin;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetStrokeJoin(long native_object, int join) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(native_object);
+        if (delegate == null) {
+            return;
+        }
+
+        delegate.mJoin = join;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nGetFillPath(long native_object, long src, long dst) {
+        Paint_Delegate paint = sManager.getDelegate(native_object);
+        if (paint == null) {
+            return false;
+        }
+
+        Path_Delegate srcPath = Path_Delegate.getDelegate(src);
+        if (srcPath == null) {
+            return true;
+        }
+
+        Path_Delegate dstPath = Path_Delegate.getDelegate(dst);
+        if (dstPath == null) {
+            return true;
+        }
+
+        Stroke stroke = paint.getJavaStroke();
+        Shape strokeShape = stroke.createStrokedShape(srcPath.getJavaShape());
+
+        dstPath.setJavaShape(strokeShape);
+
+        // FIXME figure out the return value?
+        return true;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nSetShader(long native_object, long shader) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(native_object);
+        if (delegate == null) {
+            return shader;
+        }
+
+        delegate.mShader = Shader_Delegate.getDelegate(shader);
+
+        return shader;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nSetColorFilter(long native_object, long filter) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(native_object);
+        if (delegate == null) {
+            return filter;
+        }
+
+        delegate.mColorFilter = ColorFilter_Delegate.getDelegate(filter);
+
+        // Log warning if it's not supported.
+        if (delegate.mColorFilter != null && !delegate.mColorFilter.isSupported()) {
+            Bridge.getLog().fidelityWarning(LayoutLog.TAG_COLORFILTER,
+                    delegate.mColorFilter.getSupportMessage(), null, null /*data*/);
+        }
+
+        return filter;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetXfermode(long native_object, int xfermode) {
+        Paint_Delegate delegate = sManager.getDelegate(native_object);
+        if (delegate == null) {
+            return;
+        }
+        delegate.mPorterDuffMode = xfermode;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nSetPathEffect(long native_object, long effect) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(native_object);
+        if (delegate == null) {
+            return effect;
+        }
+
+        delegate.mPathEffect = PathEffect_Delegate.getDelegate(effect);
+
+        return effect;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nSetMaskFilter(long native_object, long maskfilter) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(native_object);
+        if (delegate == null) {
+            return maskfilter;
+        }
+
+        delegate.mMaskFilter = MaskFilter_Delegate.getDelegate(maskfilter);
+
+        // since none of those are supported, display a fidelity warning right away
+        if (delegate.mMaskFilter != null && !delegate.mMaskFilter.isSupported()) {
+            Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER,
+                    delegate.mMaskFilter.getSupportMessage(), null, null /*data*/);
+        }
+
+        return maskfilter;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetTypeface(long native_object, long typeface) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(native_object);
+        if (delegate == null) {
+            return;
+        }
+
+        Typeface_Delegate typefaceDelegate = Typeface_Delegate.getDelegate(typeface);
+        if (delegate.mTypeface != typefaceDelegate) {
+            delegate.mTypeface = typefaceDelegate;
+            delegate.invalidateFonts();
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static int nGetTextAlign(long native_object) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(native_object);
+        if (delegate == null) {
+            return 0;
+        }
+
+        return delegate.mTextAlign;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetTextAlign(long native_object, int align) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(native_object);
+        if (delegate == null) {
+            return;
+        }
+
+        delegate.mTextAlign = align;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static int nSetTextLocales(long native_object, String locale) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(native_object);
+        if (delegate == null) {
+            return 0;
+        }
+
+        delegate.setTextLocale(locale);
+        return 0;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetTextLocalesByMinikinLangListId(long paintPtr,
+            int mMinikinLangListId) {
+        // FIXME
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static float nGetTextAdvances(long native_object, char[] text, int index,
+            int count, int contextIndex, int contextCount,
+            int bidiFlags, float[] advances, int advancesIndex) {
+
+        if (advances != null)
+            for (int i = advancesIndex; i< advancesIndex+count; i++)
+                advances[i]=0;
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(native_object);
+        if (delegate == null) {
+            return 0.f;
+        }
+
+        RectF bounds = delegate.measureText(text, index, count, advances, advancesIndex, bidiFlags);
+        return bounds.right - bounds.left;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static float nGetTextAdvances(long native_object, String text, int start, int end,
+            int contextStart, int contextEnd, int bidiFlags, float[] advances, int advancesIndex) {
+        // FIXME: support contextStart and contextEnd
+        int count = end - start;
+        char[] buffer = TemporaryBuffer.obtain(count);
+        TextUtils.getChars(text, start, end, buffer, 0);
+
+        return nGetTextAdvances(native_object, buffer, 0, count,
+                contextStart, contextEnd - contextStart, bidiFlags, advances, advancesIndex);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static int nGetTextRunCursor(Paint paint, long native_object, char[] text,
+            int contextStart, int contextLength, int flags, int offset, int cursorOpt) {
+        // FIXME
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "Paint.getTextRunCursor is not supported.", null, null /*data*/);
+        return 0;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static int nGetTextRunCursor(Paint paint, long native_object, String text,
+            int contextStart, int contextEnd, int flags, int offset, int cursorOpt) {
+        // FIXME
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "Paint.getTextRunCursor is not supported.", null, null /*data*/);
+        return 0;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nGetTextPath(long native_object, int bidiFlags, char[] text,
+            int index, int count, float x, float y, long path) {
+        // FIXME
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "Paint.getTextPath is not supported.", null, null /*data*/);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nGetTextPath(long native_object, int bidiFlags, String text, int start,
+            int end, float x, float y, long path) {
+        // FIXME
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "Paint.getTextPath is not supported.", null, null /*data*/);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nGetStringBounds(long nativePaint, String text, int start, int end,
+            int bidiFlags, Rect bounds) {
+        nGetCharArrayBounds(nativePaint, text.toCharArray(), start,
+                end - start, bidiFlags, bounds);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nGetCharArrayBounds(long nativePaint, char[] text, int index,
+            int count, int bidiFlags, Rect bounds) {
+
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return;
+        }
+
+        delegate.measureText(text, index, count, null, 0, bidiFlags).roundOut(bounds);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nGetNativeFinalizer() {
+        synchronized (Paint_Delegate.class) {
+            if (sFinalizer == -1) {
+                sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
+                        sManager::removeJavaReferenceFor);
+            }
+        }
+        return sFinalizer;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static float nGetLetterSpacing(long nativePaint) {
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return 0;
+        }
+        return delegate.mLetterSpacing;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetLetterSpacing(long nativePaint, float letterSpacing) {
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_TEXT_RENDERING,
+                "Paint.setLetterSpacing() not supported.", null, null);
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return;
+        }
+        delegate.mLetterSpacing = letterSpacing;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static float nGetWordSpacing(long nativePaint) {
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return 0;
+        }
+        return delegate.mWordSpacing;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetWordSpacing(long nativePaint, float wordSpacing) {
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return;
+        }
+        delegate.mWordSpacing = wordSpacing;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetFontFeatureSettings(long nativePaint, String settings) {
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_TEXT_RENDERING,
+                "Paint.setFontFeatureSettings() not supported.", null, null);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static int nGetHyphenEdit(long nativePaint) {
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return 0;
+        }
+        return delegate.mHyphenEdit;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetHyphenEdit(long nativePaint, int hyphen) {
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return;
+        }
+        delegate.mHyphenEdit = hyphen;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nHasGlyph(long nativePaint, int bidiFlags, String string) {
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return false;
+        }
+        if (string.length() == 0) {
+            return false;
+        }
+        if (string.length() > 1) {
+            Bridge.getLog().fidelityWarning(LayoutLog.TAG_TEXT_RENDERING,
+                    "Paint.hasGlyph() is not supported for ligatures.", null, null);
+            return false;
+        }
+
+        char c = string.charAt(0);
+        for (Font font : delegate.mTypeface.getFonts(delegate.mFontVariant)) {
+            if (font.canDisplay(c)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+
+    @LayoutlibDelegate
+    /*package*/ static float nGetRunAdvance(long nativePaint, @NonNull char[] text, int start,
+            int end, int contextStart, int contextEnd,
+            boolean isRtl, int offset) {
+        int count = end - start;
+        float[] advances = new float[count];
+        int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
+        nGetTextAdvances(nativePaint, text, start, count, contextStart,
+                contextEnd - contextStart, bidiFlags, advances, 0);
+        int startOffset = offset - start;  // offset from start.
+        float sum = 0;
+        for (int i = 0; i < startOffset; i++) {
+            sum += advances[i];
+        }
+        return sum;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static int nGetOffsetForAdvance(long nativePaint, char[] text, int start,
+            int end, int contextStart, int contextEnd, boolean isRtl, float advance) {
+        int count = end - start;
+        float[] advances = new float[count];
+        int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
+        nGetTextAdvances(nativePaint, text, start, count, contextStart,
+                contextEnd - contextStart, bidiFlags, advances, 0);
+        float sum = 0;
+        int i;
+        for (i = 0; i < count && sum < advance; i++) {
+            sum += advances[i];
+        }
+        float distanceToI = sum - advance;
+        float distanceToIMinus1 = advance - (sum - advances[i]);
+        return distanceToI > distanceToIMinus1 ? i : i - 1;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static float nGetUnderlinePosition(long paintPtr) {
+        return (1.0f / 9.0f) * nGetTextSize(paintPtr);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static float nGetUnderlineThickness(long paintPtr) {
+        return (1.0f / 18.0f) * nGetTextSize(paintPtr);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static float nGetStrikeThruPosition(long paintPtr) {
+        return (-79.0f / 252.0f) * nGetTextSize(paintPtr);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static float nGetStrikeThruThickness(long paintPtr) {
+        return (1.0f / 18.0f) * nGetTextSize(paintPtr);
+    }
+
+    // ---- Private delegate/helper methods ----
+
+    /*package*/ Paint_Delegate() {
+        reset();
+    }
+
+    private Paint_Delegate(Paint_Delegate paint) {
+        set(paint);
+    }
+
+    private void set(Paint_Delegate paint) {
+        mFlags = paint.mFlags;
+        mColor = paint.mColor;
+        mStyle = paint.mStyle;
+        mCap = paint.mCap;
+        mJoin = paint.mJoin;
+        mTextAlign = paint.mTextAlign;
+
+        if (mTypeface != paint.mTypeface) {
+            mTypeface = paint.mTypeface;
+            invalidateFonts();
+        }
+
+        if (mTextSize != paint.mTextSize) {
+            mTextSize = paint.mTextSize;
+            invalidateFonts();
+        }
+
+        if (mTextScaleX != paint.mTextScaleX) {
+            mTextScaleX = paint.mTextScaleX;
+            invalidateFonts();
+        }
+
+        if (mTextSkewX != paint.mTextSkewX) {
+            mTextSkewX = paint.mTextSkewX;
+            invalidateFonts();
+        }
+
+        mStrokeWidth = paint.mStrokeWidth;
+        mStrokeMiter = paint.mStrokeMiter;
+        mPorterDuffMode = paint.mPorterDuffMode;
+        mColorFilter = paint.mColorFilter;
+        mShader = paint.mShader;
+        mPathEffect = paint.mPathEffect;
+        mMaskFilter = paint.mMaskFilter;
+        mHintingMode = paint.mHintingMode;
+    }
+
+    private void reset() {
+        Typeface_Delegate defaultTypeface =
+                Typeface_Delegate.getDelegate(Typeface.sDefaults[0].native_instance);
+
+        mFlags = Paint.HIDDEN_DEFAULT_PAINT_FLAGS;
+        mColor = 0xFF000000;
+        mStyle = Paint.Style.FILL.nativeInt;
+        mCap = Paint.Cap.BUTT.nativeInt;
+        mJoin = Paint.Join.MITER.nativeInt;
+        mTextAlign = 0;
+
+        if (mTypeface != defaultTypeface) {
+            mTypeface = defaultTypeface;
+            invalidateFonts();
+        }
+
+        mStrokeWidth = 1.f;
+        mStrokeMiter = 4.f;
+
+        if (mTextSize != DEFAULT_TEXT_SIZE) {
+            mTextSize = DEFAULT_TEXT_SIZE;
+            invalidateFonts();
+        }
+
+        if (mTextScaleX != DEFAULT_TEXT_SCALE_X) {
+            mTextScaleX = DEFAULT_TEXT_SCALE_X;
+            invalidateFonts();
+        }
+
+        if (mTextSkewX != DEFAULT_TEXT_SKEW_X) {
+            mTextSkewX = DEFAULT_TEXT_SKEW_X;
+            invalidateFonts();
+        }
+
+        mPorterDuffMode = Xfermode.DEFAULT;
+        mColorFilter = null;
+        mShader = null;
+        mPathEffect = null;
+        mMaskFilter = null;
+        mHintingMode = Paint.HINTING_ON;
+    }
+
+    private void invalidateFonts() {
+        mFonts = null;
+    }
+
+    @Nullable
+    private static FontInfo getFontInfo(@Nullable Font font, float textSize,
+            @Nullable AffineTransform transform) {
+        if (font == null) {
+            return null;
+        }
+
+        Font transformedFont = font.deriveFont(textSize);
+        if (transform != null) {
+            // TODO: support skew
+            transformedFont = transformedFont.deriveFont(transform);
+        }
+
+        // The metrics here don't have anti-aliasing set.
+        return new FontInfo(transformedFont,
+                Toolkit.getDefaultToolkit().getFontMetrics(transformedFont));
+    }
+
+    /*package*/ RectF measureText(char[] text, int index, int count, float[] advances,
+            int advancesIndex, int bidiFlags) {
+        return new BidiRenderer(null, this, text)
+                .renderText(index, index + count, bidiFlags, advances, advancesIndex, false);
+    }
+
+    /*package*/ RectF measureText(char[] text, int index, int count, float[] advances,
+            int advancesIndex, boolean isRtl) {
+        return new BidiRenderer(null, this, text)
+                .renderText(index, index + count, isRtl, advances, advancesIndex, false);
+    }
+
+    private float getFontMetrics(FontMetrics metrics) {
+        List<FontInfo> fonts = getFonts();
+        if (fonts.size() > 0) {
+            java.awt.FontMetrics javaMetrics = fonts.get(0).mMetrics;
+            if (metrics != null) {
+                // Android expects negative ascent so we invert the value from Java.
+                metrics.top = - javaMetrics.getMaxAscent();
+                metrics.ascent = - javaMetrics.getAscent();
+                metrics.descent = javaMetrics.getDescent();
+                metrics.bottom = javaMetrics.getMaxDescent();
+                metrics.leading = javaMetrics.getLeading();
+            }
+
+            return javaMetrics.getHeight();
+        }
+
+        return 0;
+    }
+
+    private void setTextLocale(String locale) {
+        mLocale = new Locale(locale);
+    }
+
+    private static void setFlag(long nativePaint, int flagMask, boolean flagValue) {
+        // get the delegate from the native int.
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return;
+        }
+
+        if (flagValue) {
+            delegate.mFlags |= flagMask;
+        } else {
+            delegate.mFlags &= ~flagMask;
+        }
+    }
+}
diff --git a/android/graphics/Path.java b/android/graphics/Path.java
new file mode 100644
index 0000000..098cdc6
--- /dev/null
+++ b/android/graphics/Path.java
@@ -0,0 +1,880 @@
+/*
+ * 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;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.Size;
+
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
+/**
+ * The Path class encapsulates compound (multiple contour) geometric paths
+ * consisting of straight line segments, quadratic curves, and cubic curves.
+ * It can be drawn with canvas.drawPath(path, paint), either filled or stroked
+ * (based on the paint's Style), or it can be used for clipping or to draw
+ * text on a path.
+ */
+public class Path {
+    /**
+     * @hide
+     */
+    public long mNativePath;
+
+    /**
+     * @hide
+     */
+    public boolean isSimplePath = true;
+    /**
+     * @hide
+     */
+    public Region rects;
+    private Direction mLastDirection = null;
+
+    /**
+     * Create an empty path
+     */
+    public Path() {
+        mNativePath = nInit();
+    }
+
+    /**
+     * Create a new path, copying the contents from the src path.
+     *
+     * @param src The path to copy from when initializing the new path
+     */
+    public Path(Path src) {
+        long valNative = 0;
+        if (src != null) {
+            valNative = src.mNativePath;
+            isSimplePath = src.isSimplePath;
+            if (src.rects != null) {
+                rects = new Region(src.rects);
+            }
+        }
+        mNativePath = nInit(valNative);
+    }
+
+    /**
+     * Clear any lines and curves from the path, making it empty.
+     * This does NOT change the fill-type setting.
+     */
+    public void reset() {
+        isSimplePath = true;
+        mLastDirection = null;
+        if (rects != null) rects.setEmpty();
+        // We promised not to change this, so preserve it around the native
+        // call, which does now reset fill type.
+        final FillType fillType = getFillType();
+        nReset(mNativePath);
+        setFillType(fillType);
+    }
+
+    /**
+     * Rewinds the path: clears any lines and curves from the path but
+     * keeps the internal data structure for faster reuse.
+     */
+    public void rewind() {
+        isSimplePath = true;
+        mLastDirection = null;
+        if (rects != null) rects.setEmpty();
+        nRewind(mNativePath);
+    }
+
+    /** Replace the contents of this with the contents of src.
+    */
+    public void set(@NonNull Path src) {
+        if (this == src) {
+            return;
+        }
+        isSimplePath = src.isSimplePath;
+        nSet(mNativePath, src.mNativePath);
+        if (!isSimplePath) {
+            return;
+        }
+
+        if (rects != null && src.rects != null) {
+            rects.set(src.rects);
+        } else if (rects != null && src.rects == null) {
+            rects.setEmpty();
+        } else if (src.rects != null) {
+            rects = new Region(src.rects);
+        }
+    }
+
+    /**
+     * The logical operations that can be performed when combining two paths.
+     *
+     * @see #op(Path, android.graphics.Path.Op)
+     * @see #op(Path, Path, android.graphics.Path.Op)
+     */
+    public enum Op {
+        /**
+         * Subtract the second path from the first path.
+         */
+        DIFFERENCE,
+        /**
+         * Intersect the two paths.
+         */
+        INTERSECT,
+        /**
+         * Union (inclusive-or) the two paths.
+         */
+        UNION,
+        /**
+         * Exclusive-or the two paths.
+         */
+        XOR,
+        /**
+         * Subtract the first path from the second path.
+         */
+        REVERSE_DIFFERENCE
+    }
+
+    /**
+     * Set this path to the result of applying the Op to this path and the specified path.
+     * The resulting path will be constructed from non-overlapping contours.
+     * The curve order is reduced where possible so that cubics may be turned
+     * into quadratics, and quadratics maybe turned into lines.
+     *
+     * @param path The second operand (for difference, the subtrahend)
+     *
+     * @return True if operation succeeded, false otherwise and this path remains unmodified.
+     *
+     * @see Op
+     * @see #op(Path, Path, android.graphics.Path.Op)
+     */
+    public boolean op(Path path, Op op) {
+        return op(this, path, op);
+    }
+
+    /**
+     * Set this path to the result of applying the Op to the two specified paths.
+     * The resulting path will be constructed from non-overlapping contours.
+     * The curve order is reduced where possible so that cubics may be turned
+     * into quadratics, and quadratics maybe turned into lines.
+     *
+     * @param path1 The first operand (for difference, the minuend)
+     * @param path2 The second operand (for difference, the subtrahend)
+     *
+     * @return True if operation succeeded, false otherwise and this path remains unmodified.
+     *
+     * @see Op
+     * @see #op(Path, android.graphics.Path.Op)
+     */
+    public boolean op(Path path1, Path path2, Op op) {
+        if (nOp(path1.mNativePath, path2.mNativePath, op.ordinal(), this.mNativePath)) {
+            isSimplePath = false;
+            rects = null;
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns the path's convexity, as defined by the content of the path.
+     * <p>
+     * A path is convex if it has a single contour, and only ever curves in a
+     * single direction.
+     * <p>
+     * This function will calculate the convexity of the path from its control
+     * points, and cache the result.
+     *
+     * @return True if the path is convex.
+     */
+    public boolean isConvex() {
+        return nIsConvex(mNativePath);
+    }
+
+    /**
+     * Enum for the ways a path may be filled.
+     */
+    public enum FillType {
+        // these must match the values in SkPath.h
+        /**
+         * Specifies that "inside" is computed by a non-zero sum of signed
+         * edge crossings.
+         */
+        WINDING         (0),
+        /**
+         * Specifies that "inside" is computed by an odd number of edge
+         * crossings.
+         */
+        EVEN_ODD        (1),
+        /**
+         * Same as {@link #WINDING}, but draws outside of the path, rather than inside.
+         */
+        INVERSE_WINDING (2),
+        /**
+         * Same as {@link #EVEN_ODD}, but draws outside of the path, rather than inside.
+         */
+        INVERSE_EVEN_ODD(3);
+
+        FillType(int ni) {
+            nativeInt = ni;
+        }
+
+        final int nativeInt;
+    }
+
+    // these must be in the same order as their native values
+    static final FillType[] sFillTypeArray = {
+        FillType.WINDING,
+        FillType.EVEN_ODD,
+        FillType.INVERSE_WINDING,
+        FillType.INVERSE_EVEN_ODD
+    };
+
+    /**
+     * Return the path's fill type. This defines how "inside" is
+     * computed. The default value is WINDING.
+     *
+     * @return the path's fill type
+     */
+    public FillType getFillType() {
+        return sFillTypeArray[nGetFillType(mNativePath)];
+    }
+
+    /**
+     * Set the path's fill type. This defines how "inside" is computed.
+     *
+     * @param ft The new fill type for this path
+     */
+    public void setFillType(FillType ft) {
+        nSetFillType(mNativePath, ft.nativeInt);
+    }
+
+    /**
+     * Returns true if the filltype is one of the INVERSE variants
+     *
+     * @return true if the filltype is one of the INVERSE variants
+     */
+    public boolean isInverseFillType() {
+        final int ft = nGetFillType(mNativePath);
+        return (ft & FillType.INVERSE_WINDING.nativeInt) != 0;
+    }
+
+    /**
+     * Toggles the INVERSE state of the filltype
+     */
+    public void toggleInverseFillType() {
+        int ft = nGetFillType(mNativePath);
+        ft ^= FillType.INVERSE_WINDING.nativeInt;
+        nSetFillType(mNativePath, ft);
+    }
+
+    /**
+     * Returns true if the path is empty (contains no lines or curves)
+     *
+     * @return true if the path is empty (contains no lines or curves)
+     */
+    public boolean isEmpty() {
+        return nIsEmpty(mNativePath);
+    }
+
+    /**
+     * Returns true if the path specifies a rectangle. If so, and if rect is
+     * not null, set rect to the bounds of the path. If the path does not
+     * specify a rectangle, return false and ignore rect.
+     *
+     * @param rect If not null, returns the bounds of the path if it specifies
+     *             a rectangle
+     * @return     true if the path specifies a rectangle
+     */
+    public boolean isRect(RectF rect) {
+        return nIsRect(mNativePath, rect);
+    }
+
+    /**
+     * Compute the bounds of the control points of the path, and write the
+     * answer into bounds. If the path contains 0 or 1 points, the bounds is
+     * set to (0,0,0,0)
+     *
+     * @param bounds Returns the computed bounds of the path's control points.
+     * @param exact This parameter is no longer used.
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    public void computeBounds(RectF bounds, boolean exact) {
+        nComputeBounds(mNativePath, bounds);
+    }
+
+    /**
+     * Hint to the path to prepare for adding more points. This can allow the
+     * path to more efficiently allocate its storage.
+     *
+     * @param extraPtCount The number of extra points that may be added to this
+     *                     path
+     */
+    public void incReserve(int extraPtCount) {
+        nIncReserve(mNativePath, extraPtCount);
+    }
+
+    /**
+     * Set the beginning of the next contour to the point (x,y).
+     *
+     * @param x The x-coordinate of the start of a new contour
+     * @param y The y-coordinate of the start of a new contour
+     */
+    public void moveTo(float x, float y) {
+        nMoveTo(mNativePath, x, y);
+    }
+
+    /**
+     * Set the beginning of the next contour relative to the last point on the
+     * previous contour. If there is no previous contour, this is treated the
+     * same as moveTo().
+     *
+     * @param dx The amount to add to the x-coordinate of the end of the
+     *           previous contour, to specify the start of a new contour
+     * @param dy The amount to add to the y-coordinate of the end of the
+     *           previous contour, to specify the start of a new contour
+     */
+    public void rMoveTo(float dx, float dy) {
+        nRMoveTo(mNativePath, dx, dy);
+    }
+
+    /**
+     * Add a line from the last point to the specified point (x,y).
+     * If no moveTo() call has been made for this contour, the first point is
+     * automatically set to (0,0).
+     *
+     * @param x The x-coordinate of the end of a line
+     * @param y The y-coordinate of the end of a line
+     */
+    public void lineTo(float x, float y) {
+        isSimplePath = false;
+        nLineTo(mNativePath, x, y);
+    }
+
+    /**
+     * Same as lineTo, but the coordinates are considered relative to the last
+     * point on this contour. If there is no previous point, then a moveTo(0,0)
+     * is inserted automatically.
+     *
+     * @param dx The amount to add to the x-coordinate of the previous point on
+     *           this contour, to specify a line
+     * @param dy The amount to add to the y-coordinate of the previous point on
+     *           this contour, to specify a line
+     */
+    public void rLineTo(float dx, float dy) {
+        isSimplePath = false;
+        nRLineTo(mNativePath, dx, dy);
+    }
+
+    /**
+     * Add a quadratic bezier from the last point, approaching control point
+     * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for
+     * this contour, the first point is automatically set to (0,0).
+     *
+     * @param x1 The x-coordinate of the control point on a quadratic curve
+     * @param y1 The y-coordinate of the control point on a quadratic curve
+     * @param x2 The x-coordinate of the end point on a quadratic curve
+     * @param y2 The y-coordinate of the end point on a quadratic curve
+     */
+    public void quadTo(float x1, float y1, float x2, float y2) {
+        isSimplePath = false;
+        nQuadTo(mNativePath, x1, y1, x2, y2);
+    }
+
+    /**
+     * Same as quadTo, but the coordinates are considered relative to the last
+     * point on this contour. If there is no previous point, then a moveTo(0,0)
+     * is inserted automatically.
+     *
+     * @param dx1 The amount to add to the x-coordinate of the last point on
+     *            this contour, for the control point of a quadratic curve
+     * @param dy1 The amount to add to the y-coordinate of the last point on
+     *            this contour, for the control point of a quadratic curve
+     * @param dx2 The amount to add to the x-coordinate of the last point on
+     *            this contour, for the end point of a quadratic curve
+     * @param dy2 The amount to add to the y-coordinate of the last point on
+     *            this contour, for the end point of a quadratic curve
+     */
+    public void rQuadTo(float dx1, float dy1, float dx2, float dy2) {
+        isSimplePath = false;
+        nRQuadTo(mNativePath, dx1, dy1, dx2, dy2);
+    }
+
+    /**
+     * Add a cubic bezier from the last point, approaching control points
+     * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been
+     * made for this contour, the first point is automatically set to (0,0).
+     *
+     * @param x1 The x-coordinate of the 1st control point on a cubic curve
+     * @param y1 The y-coordinate of the 1st control point on a cubic curve
+     * @param x2 The x-coordinate of the 2nd control point on a cubic curve
+     * @param y2 The y-coordinate of the 2nd control point on a cubic curve
+     * @param x3 The x-coordinate of the end point on a cubic curve
+     * @param y3 The y-coordinate of the end point on a cubic curve
+     */
+    public void cubicTo(float x1, float y1, float x2, float y2,
+                        float x3, float y3) {
+        isSimplePath = false;
+        nCubicTo(mNativePath, x1, y1, x2, y2, x3, y3);
+    }
+
+    /**
+     * Same as cubicTo, but the coordinates are considered relative to the
+     * current point on this contour. If there is no previous point, then a
+     * moveTo(0,0) is inserted automatically.
+     */
+    public void rCubicTo(float x1, float y1, float x2, float y2,
+                         float x3, float y3) {
+        isSimplePath = false;
+        nRCubicTo(mNativePath, x1, y1, x2, y2, x3, y3);
+    }
+
+    /**
+     * Append the specified arc to the path as a new contour. If the start of
+     * the path is different from the path's current last point, then an
+     * automatic lineTo() is added to connect the current contour to the
+     * start of the arc. However, if the path is empty, then we call moveTo()
+     * with the first point of the arc.
+     *
+     * @param oval        The bounds of oval defining shape and size of the arc
+     * @param startAngle  Starting angle (in degrees) where the arc begins
+     * @param sweepAngle  Sweep angle (in degrees) measured clockwise, treated
+     *                    mod 360.
+     * @param forceMoveTo If true, always begin a new contour with the arc
+     */
+    public void arcTo(RectF oval, float startAngle, float sweepAngle,
+                      boolean forceMoveTo) {
+        arcTo(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, forceMoveTo);
+    }
+
+    /**
+     * Append the specified arc to the path as a new contour. If the start of
+     * the path is different from the path's current last point, then an
+     * automatic lineTo() is added to connect the current contour to the
+     * start of the arc. However, if the path is empty, then we call moveTo()
+     * with the first point of the arc.
+     *
+     * @param oval        The bounds of oval defining shape and size of the arc
+     * @param startAngle  Starting angle (in degrees) where the arc begins
+     * @param sweepAngle  Sweep angle (in degrees) measured clockwise
+     */
+    public void arcTo(RectF oval, float startAngle, float sweepAngle) {
+        arcTo(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, false);
+    }
+
+    /**
+     * Append the specified arc to the path as a new contour. If the start of
+     * the path is different from the path's current last point, then an
+     * automatic lineTo() is added to connect the current contour to the
+     * start of the arc. However, if the path is empty, then we call moveTo()
+     * with the first point of the arc.
+     *
+     * @param startAngle  Starting angle (in degrees) where the arc begins
+     * @param sweepAngle  Sweep angle (in degrees) measured clockwise, treated
+     *                    mod 360.
+     * @param forceMoveTo If true, always begin a new contour with the arc
+     */
+    public void arcTo(float left, float top, float right, float bottom, float startAngle,
+            float sweepAngle, boolean forceMoveTo) {
+        isSimplePath = false;
+        nArcTo(mNativePath, left, top, right, bottom, startAngle, sweepAngle, forceMoveTo);
+    }
+
+    /**
+     * Close the current contour. If the current point is not equal to the
+     * first point of the contour, a line segment is automatically added.
+     */
+    public void close() {
+        isSimplePath = false;
+        nClose(mNativePath);
+    }
+
+    /**
+     * Specifies how closed shapes (e.g. rects, ovals) are oriented when they
+     * are added to a path.
+     */
+    public enum Direction {
+        /** clockwise */
+        CW  (0),    // must match enum in SkPath.h
+        /** counter-clockwise */
+        CCW (1);    // must match enum in SkPath.h
+
+        Direction(int ni) {
+            nativeInt = ni;
+        }
+        final int nativeInt;
+    }
+
+    private void detectSimplePath(float left, float top, float right, float bottom, Direction dir) {
+        if (mLastDirection == null) {
+            mLastDirection = dir;
+        }
+        if (mLastDirection != dir) {
+            isSimplePath = false;
+        } else {
+            if (rects == null) rects = new Region();
+            rects.op((int) left, (int) top, (int) right, (int) bottom, Region.Op.UNION);
+        }
+    }
+
+    /**
+     * Add a closed rectangle contour to the path
+     *
+     * @param rect The rectangle to add as a closed contour to the path
+     * @param dir  The direction to wind the rectangle's contour
+     */
+    public void addRect(RectF rect, Direction dir) {
+        addRect(rect.left, rect.top, rect.right, rect.bottom, dir);
+    }
+
+    /**
+     * Add a closed rectangle contour to the path
+     *
+     * @param left   The left side of a rectangle to add to the path
+     * @param top    The top of a rectangle to add to the path
+     * @param right  The right side of a rectangle to add to the path
+     * @param bottom The bottom of a rectangle to add to the path
+     * @param dir    The direction to wind the rectangle's contour
+     */
+    public void addRect(float left, float top, float right, float bottom, Direction dir) {
+        detectSimplePath(left, top, right, bottom, dir);
+        nAddRect(mNativePath, left, top, right, bottom, dir.nativeInt);
+    }
+
+    /**
+     * Add a closed oval contour to the path
+     *
+     * @param oval The bounds of the oval to add as a closed contour to the path
+     * @param dir  The direction to wind the oval's contour
+     */
+    public void addOval(RectF oval, Direction dir) {
+        addOval(oval.left, oval.top, oval.right, oval.bottom, dir);
+    }
+
+    /**
+     * Add a closed oval contour to the path
+     *
+     * @param dir The direction to wind the oval's contour
+     */
+    public void addOval(float left, float top, float right, float bottom, Direction dir) {
+        isSimplePath = false;
+        nAddOval(mNativePath, left, top, right, bottom, dir.nativeInt);
+    }
+
+    /**
+     * Add a closed circle contour to the path
+     *
+     * @param x   The x-coordinate of the center of a circle to add to the path
+     * @param y   The y-coordinate of the center of a circle to add to the path
+     * @param radius The radius of a circle to add to the path
+     * @param dir    The direction to wind the circle's contour
+     */
+    public void addCircle(float x, float y, float radius, Direction dir) {
+        isSimplePath = false;
+        nAddCircle(mNativePath, x, y, radius, dir.nativeInt);
+    }
+
+    /**
+     * Add the specified arc to the path as a new contour.
+     *
+     * @param oval The bounds of oval defining the shape and size of the arc
+     * @param startAngle Starting angle (in degrees) where the arc begins
+     * @param sweepAngle Sweep angle (in degrees) measured clockwise
+     */
+    public void addArc(RectF oval, float startAngle, float sweepAngle) {
+        addArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle);
+    }
+
+    /**
+     * Add the specified arc to the path as a new contour.
+     *
+     * @param startAngle Starting angle (in degrees) where the arc begins
+     * @param sweepAngle Sweep angle (in degrees) measured clockwise
+     */
+    public void addArc(float left, float top, float right, float bottom, float startAngle,
+            float sweepAngle) {
+        isSimplePath = false;
+        nAddArc(mNativePath, left, top, right, bottom, startAngle, sweepAngle);
+    }
+
+    /**
+        * Add a closed round-rectangle contour to the path
+     *
+     * @param rect The bounds of a round-rectangle to add to the path
+     * @param rx   The x-radius of the rounded corners on the round-rectangle
+     * @param ry   The y-radius of the rounded corners on the round-rectangle
+     * @param dir  The direction to wind the round-rectangle's contour
+     */
+    public void addRoundRect(RectF rect, float rx, float ry, Direction dir) {
+        addRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, dir);
+    }
+
+    /**
+     * Add a closed round-rectangle contour to the path
+     *
+     * @param rx   The x-radius of the rounded corners on the round-rectangle
+     * @param ry   The y-radius of the rounded corners on the round-rectangle
+     * @param dir  The direction to wind the round-rectangle's contour
+     */
+    public void addRoundRect(float left, float top, float right, float bottom, float rx, float ry,
+            Direction dir) {
+        isSimplePath = false;
+        nAddRoundRect(mNativePath, left, top, right, bottom, rx, ry, dir.nativeInt);
+    }
+
+    /**
+     * Add a closed round-rectangle contour to the path. Each corner receives
+     * two radius values [X, Y]. The corners are ordered top-left, top-right,
+     * bottom-right, bottom-left
+     *
+     * @param rect The bounds of a round-rectangle to add to the path
+     * @param radii Array of 8 values, 4 pairs of [X,Y] radii
+     * @param dir  The direction to wind the round-rectangle's contour
+     */
+    public void addRoundRect(RectF rect, float[] radii, Direction dir) {
+        if (rect == null) {
+            throw new NullPointerException("need rect parameter");
+        }
+        addRoundRect(rect.left, rect.top, rect.right, rect.bottom, radii, dir);
+    }
+
+    /**
+     * Add a closed round-rectangle contour to the path. Each corner receives
+     * two radius values [X, Y]. The corners are ordered top-left, top-right,
+     * bottom-right, bottom-left
+     *
+     * @param radii Array of 8 values, 4 pairs of [X,Y] radii
+     * @param dir  The direction to wind the round-rectangle's contour
+     */
+    public void addRoundRect(float left, float top, float right, float bottom, float[] radii,
+            Direction dir) {
+        if (radii.length < 8) {
+            throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values");
+        }
+        isSimplePath = false;
+        nAddRoundRect(mNativePath, left, top, right, bottom, radii, dir.nativeInt);
+    }
+
+    /**
+     * Add a copy of src to the path, offset by (dx,dy)
+     *
+     * @param src The path to add as a new contour
+     * @param dx  The amount to translate the path in X as it is added
+     */
+    public void addPath(Path src, float dx, float dy) {
+        isSimplePath = false;
+        nAddPath(mNativePath, src.mNativePath, dx, dy);
+    }
+
+    /**
+     * Add a copy of src to the path
+     *
+     * @param src The path that is appended to the current path
+     */
+    public void addPath(Path src) {
+        isSimplePath = false;
+        nAddPath(mNativePath, src.mNativePath);
+    }
+
+    /**
+     * Add a copy of src to the path, transformed by matrix
+     *
+     * @param src The path to add as a new contour
+     */
+    public void addPath(Path src, Matrix matrix) {
+        if (!src.isSimplePath) isSimplePath = false;
+        nAddPath(mNativePath, src.mNativePath, matrix.native_instance);
+    }
+
+    /**
+     * Offset the path by (dx,dy)
+     *
+     * @param dx  The amount in the X direction to offset the entire path
+     * @param dy  The amount in the Y direction to offset the entire path
+     * @param dst The translated path is written here. If this is null, then
+     *            the original path is modified.
+     */
+    public void offset(float dx, float dy, @Nullable Path dst) {
+        if (dst != null) {
+            dst.set(this);
+        } else {
+            dst = this;
+        }
+        dst.offset(dx, dy);
+    }
+
+    /**
+     * Offset the path by (dx,dy)
+     *
+     * @param dx The amount in the X direction to offset the entire path
+     * @param dy The amount in the Y direction to offset the entire path
+     */
+    public void offset(float dx, float dy) {
+        if (isSimplePath && rects == null) {
+            // nothing to offset
+            return;
+        }
+        if (isSimplePath && dx == Math.rint(dx) && dy == Math.rint(dy)) {
+            rects.translate((int) dx, (int) dy);
+        } else {
+            isSimplePath = false;
+        }
+        nOffset(mNativePath, dx, dy);
+    }
+
+    /**
+     * Sets the last point of the path.
+     *
+     * @param dx The new X coordinate for the last point
+     * @param dy The new Y coordinate for the last point
+     */
+    public void setLastPoint(float dx, float dy) {
+        isSimplePath = false;
+        nSetLastPoint(mNativePath, dx, dy);
+    }
+
+    /**
+     * Transform the points in this path by matrix, and write the answer
+     * into dst. If dst is null, then the the original path is modified.
+     *
+     * @param matrix The matrix to apply to the path
+     * @param dst    The transformed path is written here. If dst is null,
+     *               then the the original path is modified
+     */
+    public void transform(Matrix matrix, Path dst) {
+        long dstNative = 0;
+        if (dst != null) {
+            dst.isSimplePath = false;
+            dstNative = dst.mNativePath;
+        }
+        nTransform(mNativePath, matrix.native_instance, dstNative);
+    }
+
+    /**
+     * Transform the points in this path by matrix.
+     *
+     * @param matrix The matrix to apply to the path
+     */
+    public void transform(Matrix matrix) {
+        isSimplePath = false;
+        nTransform(mNativePath, matrix.native_instance);
+    }
+
+    protected void finalize() throws Throwable {
+        try {
+            nFinalize(mNativePath);
+            mNativePath = 0;  //  Other finalizers can still call us.
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /** @hide */
+    public final long readOnlyNI() {
+        return mNativePath;
+    }
+
+    final long mutateNI() {
+        isSimplePath = false;
+        return mNativePath;
+    }
+
+    /**
+     * Approximate the <code>Path</code> with a series of line segments.
+     * This returns float[] with the array containing point components.
+     * There are three components for each point, in order:
+     * <ul>
+     *     <li>Fraction along the length of the path that the point resides</li>
+     *     <li>The x coordinate of the point</li>
+     *     <li>The y coordinate of the point</li>
+     * </ul>
+     * <p>Two points may share the same fraction along its length when there is
+     * a move action within the Path.</p>
+     *
+     * @param acceptableError The acceptable error for a line on the
+     *                        Path. Typically this would be 0.5 so that
+     *                        the error is less than half a pixel.
+     * @return An array of components for points approximating the Path.
+     */
+    @NonNull
+    @Size(min = 6, multiple = 3)
+    public float[] approximate(@FloatRange(from = 0) float acceptableError) {
+        if (acceptableError < 0) {
+            throw new IllegalArgumentException("AcceptableError must be greater than or equal to 0");
+        }
+        return nApproximate(mNativePath, acceptableError);
+    }
+
+    // ------------------ Regular JNI ------------------------
+
+    private static native long nInit();
+    private static native long nInit(long nPath);
+    private static native void nFinalize(long nPath);
+    private static native void nSet(long native_dst, long nSrc);
+    private static native void nComputeBounds(long nPath, RectF bounds);
+    private static native void nIncReserve(long nPath, int extraPtCount);
+    private static native void nMoveTo(long nPath, float x, float y);
+    private static native void nRMoveTo(long nPath, float dx, float dy);
+    private static native void nLineTo(long nPath, float x, float y);
+    private static native void nRLineTo(long nPath, float dx, float dy);
+    private static native void nQuadTo(long nPath, float x1, float y1, float x2, float y2);
+    private static native void nRQuadTo(long nPath, float dx1, float dy1, float dx2, float dy2);
+    private static native void nCubicTo(long nPath, float x1, float y1, float x2, float y2,
+            float x3, float y3);
+    private static native void nRCubicTo(long nPath, float x1, float y1, float x2, float y2,
+            float x3, float y3);
+    private static native void nArcTo(long nPath, float left, float top, float right, float bottom,
+            float startAngle, float sweepAngle, boolean forceMoveTo);
+    private static native void nClose(long nPath);
+    private static native void nAddRect(long nPath, float left, float top,
+            float right, float bottom, int dir);
+    private static native void nAddOval(long nPath, float left, float top,
+            float right, float bottom, int dir);
+    private static native void nAddCircle(long nPath, float x, float y, float radius, int dir);
+    private static native void nAddArc(long nPath, float left, float top, float right, float bottom,
+            float startAngle, float sweepAngle);
+    private static native void nAddRoundRect(long nPath, float left, float top,
+            float right, float bottom, float rx, float ry, int dir);
+    private static native void nAddRoundRect(long nPath, float left, float top,
+            float right, float bottom, float[] radii, int dir);
+    private static native void nAddPath(long nPath, long src, float dx, float dy);
+    private static native void nAddPath(long nPath, long src);
+    private static native void nAddPath(long nPath, long src, long matrix);
+    private static native void nOffset(long nPath, float dx, float dy);
+    private static native void nSetLastPoint(long nPath, float dx, float dy);
+    private static native void nTransform(long nPath, long matrix, long dst_path);
+    private static native void nTransform(long nPath, long matrix);
+    private static native boolean nOp(long path1, long path2, int op, long result);
+    private static native float[] nApproximate(long nPath, float error);
+
+    // ------------------ Fast JNI ------------------------
+
+    @FastNative
+    private static native boolean nIsRect(long nPath, RectF rect);
+
+    // ------------------ Critical JNI ------------------------
+
+    @CriticalNative
+    private static native void nReset(long nPath);
+    @CriticalNative
+    private static native void nRewind(long nPath);
+    @CriticalNative
+    private static native boolean nIsEmpty(long nPath);
+    @CriticalNative
+    private static native boolean nIsConvex(long nPath);
+    @CriticalNative
+    private static native int nGetFillType(long nPath);
+    @CriticalNative
+    private static native void nSetFillType(long nPath, int ft);
+}
diff --git a/android/graphics/PathDashPathEffect.java b/android/graphics/PathDashPathEffect.java
new file mode 100644
index 0000000..2b6a6ed
--- /dev/null
+++ b/android/graphics/PathDashPathEffect.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2007 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;
+
+public class PathDashPathEffect extends PathEffect {
+
+    public enum Style {
+        TRANSLATE(0),   //!< translate the shape to each position
+        ROTATE(1),      //!< rotate the shape about its center
+        MORPH(2);       //!< transform each point, and turn lines into curves
+        
+        Style(int value) {
+            native_style = value;
+        }
+        int native_style;
+    }
+
+    /**
+     * Dash the drawn path by stamping it with the specified shape. This only
+     * applies to drawings when the paint's style is STROKE or STROKE_AND_FILL.
+     * If the paint's style is FILL, then this effect is ignored. The paint's
+     * strokeWidth does not affect the results.
+     * @param shape The path to stamp along
+     * @param advance spacing between each stamp of shape
+     * @param phase amount to offset before the first shape is stamped
+     * @param style how to transform the shape at each position as it is stamped
+     */
+    public PathDashPathEffect(Path shape, float advance, float phase,
+                              Style style) {
+        native_instance = nativeCreate(shape.readOnlyNI(), advance, phase,
+                                       style.native_style);
+    }
+    
+    private static native long nativeCreate(long native_path, float advance,
+                                           float phase, int native_style);
+}
+
diff --git a/android/graphics/PathDashPathEffect_Delegate.java b/android/graphics/PathDashPathEffect_Delegate.java
new file mode 100644
index 0000000..fd9ba62
--- /dev/null
+++ b/android/graphics/PathDashPathEffect_Delegate.java
@@ -0,0 +1,72 @@
+/*
+ * 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;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.awt.Stroke;
+
+/**
+ * Delegate implementing the native methods of android.graphics.PathDashPathEffect
+ *
+ * Through the layoutlib_create tool, the original native methods of PathDashPathEffect have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original PathDashPathEffect class.
+ *
+ * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager},
+ * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}.
+ *
+ * @see PathEffect_Delegate
+ *
+ */
+public class PathDashPathEffect_Delegate extends PathEffect_Delegate {
+
+    // ---- delegate data ----
+
+    // ---- Public Helper methods ----
+
+    @Override
+    public Stroke getStroke(Paint_Delegate paint) {
+        // FIXME
+        return null;
+    }
+
+    @Override
+    public boolean isSupported() {
+        return false;
+    }
+
+    @Override
+    public String getSupportMessage() {
+        return "Path Dash Path Effects are not supported in Layout Preview mode.";
+    }
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeCreate(long native_path, float advance, float phase,
+            int native_style) {
+        PathDashPathEffect_Delegate newDelegate = new PathDashPathEffect_Delegate();
+        return sManager.addNewDelegate(newDelegate);
+    }
+
+    // ---- Private delegate/helper methods ----
+}
diff --git a/android/graphics/PathEffect.java b/android/graphics/PathEffect.java
new file mode 100644
index 0000000..3292501
--- /dev/null
+++ b/android/graphics/PathEffect.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+/**
+ * PathEffect is the base class for objects in the Paint that affect
+ * the geometry of a drawing primitive before it is transformed by the
+ * canvas' matrix and drawn.
+ */
+public class PathEffect {
+
+    protected void finalize() throws Throwable {
+        nativeDestructor(native_instance);
+        native_instance = 0;  // Other finalizers can still call us.
+    }
+
+    private static native void nativeDestructor(long native_patheffect);
+    long native_instance;
+}
diff --git a/android/graphics/PathEffect_Delegate.java b/android/graphics/PathEffect_Delegate.java
new file mode 100644
index 0000000..000481e
--- /dev/null
+++ b/android/graphics/PathEffect_Delegate.java
@@ -0,0 +1,69 @@
+/*
+ * 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;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.awt.Stroke;
+
+/**
+ * Delegate implementing the native methods of android.graphics.PathEffect
+ *
+ * Through the layoutlib_create tool, the original native methods of PathEffect have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original PathEffect class.
+ *
+ * This also serve as a base class for all PathEffect delegate classes.
+ *
+ * @see DelegateManager
+ *
+ */
+public abstract class PathEffect_Delegate {
+
+    // ---- delegate manager ----
+    protected static final DelegateManager<PathEffect_Delegate> sManager =
+            new DelegateManager<PathEffect_Delegate>(PathEffect_Delegate.class);
+
+    // ---- delegate helper data ----
+
+    // ---- delegate data ----
+
+    // ---- Public Helper methods ----
+
+    public static PathEffect_Delegate getDelegate(long nativeShader) {
+        return sManager.getDelegate(nativeShader);
+    }
+
+    public abstract Stroke getStroke(Paint_Delegate paint);
+    public abstract boolean isSupported();
+    public abstract String getSupportMessage();
+
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static void nativeDestructor(long native_patheffect) {
+        sManager.removeJavaReferenceFor(native_patheffect);
+    }
+
+    // ---- Private delegate/helper methods ----
+
+}
diff --git a/android/graphics/PathMeasure.java b/android/graphics/PathMeasure.java
new file mode 100644
index 0000000..bf79656
--- /dev/null
+++ b/android/graphics/PathMeasure.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2007 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;
+
+public class PathMeasure {
+    private Path mPath;
+
+    /**
+     * Create an empty PathMeasure object. To uses this to measure the length
+     * of a path, and/or to find the position and tangent along it, call
+     * setPath.
+     *
+     * Note that once a path is associated with the measure object, it is
+     * undefined if the path is subsequently modified and the the measure object
+     * is used. If the path is modified, you must call setPath with the path.
+     */
+    public PathMeasure() {
+        mPath = null;
+        native_instance = native_create(0, false);
+    }
+    
+    /**
+     * Create a PathMeasure object associated with the specified path object
+     * (already created and specified). The measure object can now return the
+     * path's length, and the position and tangent of any position along the
+     * path.
+     *
+     * Note that once a path is associated with the measure object, it is
+     * undefined if the path is subsequently modified and the the measure object
+     * is used. If the path is modified, you must call setPath with the path.
+     *
+     * @param path The path that will be measured by this object
+     * @param forceClosed If true, then the path will be considered as "closed"
+     *        even if its contour was not explicitly closed.
+     */
+    public PathMeasure(Path path, boolean forceClosed) {
+        // The native implementation does not copy the path, prevent it from being GC'd
+        mPath = path;
+        native_instance = native_create(path != null ? path.readOnlyNI() : 0,
+                                        forceClosed);
+    }
+
+    /**
+     * Assign a new path, or null to have none.
+     */
+    public void setPath(Path path, boolean forceClosed) {
+        mPath = path;
+        native_setPath(native_instance,
+                       path != null ? path.readOnlyNI() : 0,
+                       forceClosed);
+    }
+
+    /**
+     * Return the total length of the current contour, or 0 if no path is
+     * associated with this measure object.
+     */
+    public float getLength() {
+        return native_getLength(native_instance);
+    }
+
+    /**
+     * Pins distance to 0 <= distance <= getLength(), and then computes the
+     * corresponding position and tangent. Returns false if there is no path,
+     * or a zero-length path was specified, in which case position and tangent
+     * are unchanged.
+     *
+     * @param distance The distance along the current contour to sample
+     * @param pos If not null, returns the sampled position (x==[0], y==[1])
+     * @param tan If not null, returns the sampled tangent (x==[0], y==[1])
+     * @return false if there was no path associated with this measure object
+    */
+    public boolean getPosTan(float distance, float pos[], float tan[]) {
+        if (pos != null && pos.length < 2 ||
+            tan != null && tan.length < 2) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        return native_getPosTan(native_instance, distance, pos, tan);
+    }
+
+    public static final int POSITION_MATRIX_FLAG = 0x01;    // must match flags in SkPathMeasure.h
+    public static final int TANGENT_MATRIX_FLAG  = 0x02;    // must match flags in SkPathMeasure.h
+
+    /**
+     * Pins distance to 0 <= distance <= getLength(), and then computes the
+     * corresponding matrix. Returns false if there is no path, or a zero-length
+     * path was specified, in which case matrix is unchanged.
+     *
+     * @param distance The distance along the associated path
+     * @param matrix Allocated by the caller, this is set to the transformation
+     *        associated with the position and tangent at the specified distance
+     * @param flags Specified what aspects should be returned in the matrix.
+     */
+    public boolean getMatrix(float distance, Matrix matrix, int flags) {
+        return native_getMatrix(native_instance, distance, matrix.native_instance, flags);
+    }
+
+    /**
+     * Given a start and stop distance, return in dst the intervening
+     * segment(s). If the segment is zero-length, return false, else return
+     * true. startD and stopD are pinned to legal values (0..getLength()).
+     * If startD >= stopD then return false (and leave dst untouched).
+     * Begin the segment with a moveTo if startWithMoveTo is true.
+     *
+     * <p>On {@link android.os.Build.VERSION_CODES#KITKAT} and earlier
+     * releases, the resulting path may not display on a hardware-accelerated
+     * Canvas. A simple workaround is to add a single operation to this path,
+     * such as <code>dst.rLineTo(0, 0)</code>.</p>
+     */
+    public boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) {
+        // Skia used to enforce this as part of it's API, but has since relaxed that restriction
+        // so to maintain consistency in our API we enforce the preconditions here.
+        float length = getLength();
+        if (startD < 0) {
+            startD = 0;
+        }
+        if (stopD > length) {
+            stopD = length;
+        }
+        if (startD >= stopD) {
+            return false;
+        }
+
+        return native_getSegment(native_instance, startD, stopD, dst.mutateNI(), startWithMoveTo);
+    }
+
+    /**
+     * Return true if the current contour is closed()
+     */
+    public boolean isClosed() {
+        return native_isClosed(native_instance);
+    }
+
+    /**
+     * Move to the next contour in the path. Return true if one exists, or
+     * false if we're done with the path.
+     */
+    public boolean nextContour() {
+        return native_nextContour(native_instance);
+    }
+
+    protected void finalize() throws Throwable {
+        native_destroy(native_instance);
+        native_instance = 0;  // Other finalizers can still call us.
+    }
+
+    private static native long native_create(long native_path, boolean forceClosed);
+    private static native void native_setPath(long native_instance, long native_path, boolean forceClosed);
+    private static native float native_getLength(long native_instance);
+    private static native boolean native_getPosTan(long native_instance, float distance, float pos[], float tan[]);
+    private static native boolean native_getMatrix(long native_instance, float distance, long native_matrix, int flags);
+    private static native boolean native_getSegment(long native_instance, float startD, float stopD, long native_path, boolean startWithMoveTo);
+    private static native boolean native_isClosed(long native_instance);
+    private static native boolean native_nextContour(long native_instance);
+    private static native void native_destroy(long native_instance);
+
+    /* package */private long native_instance;
+}
+
diff --git a/android/graphics/PathMeasure_Delegate.java b/android/graphics/PathMeasure_Delegate.java
new file mode 100644
index 0000000..7f707c9
--- /dev/null
+++ b/android/graphics/PathMeasure_Delegate.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2015 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;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.util.CachedPathIteratorFactory;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import com.android.layoutlib.bridge.util.CachedPathIteratorFactory.CachedPathIterator;
+
+import java.awt.geom.PathIterator;
+
+/**
+ * Delegate implementing the native methods of {@link android.graphics.PathMeasure}
+ * <p/>
+ * Through the layoutlib_create tool, the original native methods of PathMeasure have been
+ * replaced by
+ * calls to methods of the same name in this delegate class.
+ * <p/>
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between it
+ * and the original PathMeasure class.
+ *
+ * @see DelegateManager
+ */
+public final class PathMeasure_Delegate {
+
+    // ---- delegate manager ----
+    private static final DelegateManager<PathMeasure_Delegate> sManager =
+            new DelegateManager<PathMeasure_Delegate>(PathMeasure_Delegate.class);
+
+    // ---- delegate data ----
+    private CachedPathIteratorFactory mOriginalPathIterator;
+
+    private long mNativePath;
+
+
+    private PathMeasure_Delegate(long native_path, boolean forceClosed) {
+        mNativePath = native_path;
+        if (native_path != 0) {
+            if (forceClosed) {
+                // Copy the path and call close
+                native_path = Path_Delegate.nInit(native_path);
+                Path_Delegate.nClose(native_path);
+            }
+
+            Path_Delegate pathDelegate = Path_Delegate.getDelegate(native_path);
+            mOriginalPathIterator = new CachedPathIteratorFactory(pathDelegate.getJavaShape()
+                    .getPathIterator(null));
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long native_create(long native_path, boolean forceClosed) {
+        return sManager.addNewDelegate(new PathMeasure_Delegate(native_path, forceClosed));
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void native_destroy(long native_instance) {
+        sManager.removeJavaReferenceFor(native_instance);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean native_getPosTan(long native_instance, float distance, float pos[],
+            float tan[]) {
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "PathMeasure.getPostTan is not supported.", null, null);
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean native_getMatrix(long native_instance, float distance, long
+            native_matrix, int flags) {
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "PathMeasure.getMatrix is not supported.", null, null);
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean native_nextContour(long native_instance) {
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "PathMeasure.nextContour is not supported.", null, null);
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void native_setPath(long native_instance, long native_path, boolean
+            forceClosed) {
+        PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
+        assert pathMeasure != null;
+
+        if (native_path != 0) {
+            if (forceClosed) {
+                // Copy the path and call close
+                native_path = Path_Delegate.nInit(native_path);
+                Path_Delegate.nClose(native_path);
+            }
+
+            Path_Delegate pathDelegate = Path_Delegate.getDelegate(native_path);
+            pathMeasure.mOriginalPathIterator = new CachedPathIteratorFactory(pathDelegate.getJavaShape()
+                    .getPathIterator(null));
+        }
+
+        pathMeasure.mNativePath = native_path;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static float native_getLength(long native_instance) {
+        PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
+        assert pathMeasure != null;
+
+        if (pathMeasure.mOriginalPathIterator == null) {
+            return 0;
+        }
+
+        return pathMeasure.mOriginalPathIterator.iterator().getTotalLength();
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean native_isClosed(long native_instance) {
+        PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
+        assert pathMeasure != null;
+
+        Path_Delegate path = Path_Delegate.getDelegate(pathMeasure.mNativePath);
+        if (path == null) {
+            return false;
+        }
+
+        int type = 0;
+        float segment[] = new float[6];
+        for (PathIterator pi = path.getJavaShape().getPathIterator(null); !pi.isDone(); pi.next()) {
+            type = pi.currentSegment(segment);
+        }
+
+        // A path is a closed path if the last element is SEG_CLOSE
+        return type == PathIterator.SEG_CLOSE;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean native_getSegment(long native_instance, float startD, float stopD,
+            long native_dst_path, boolean startWithMoveTo) {
+        if (startD < 0) {
+            startD = 0;
+        }
+
+        if (startD >= stopD) {
+            return false;
+        }
+
+        PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
+        assert pathMeasure != null;
+
+        CachedPathIterator iterator = pathMeasure.mOriginalPathIterator.iterator();
+        float accLength = startD;
+        boolean isZeroLength = true; // Whether the output has zero length or not
+        float[] points = new float[6];
+
+        iterator.jumpToSegment(accLength);
+        while (!iterator.isDone() && (stopD - accLength > 0.1f)) {
+            int type = iterator.currentSegment(points, stopD - accLength);
+
+            if (accLength - iterator.getCurrentSegmentLength() <= stopD) {
+                if (startWithMoveTo) {
+                    startWithMoveTo = false;
+
+                    // If this segment is a MOVETO, then we just use that one. If not, then we issue
+                    // a first moveto
+                    if (type != PathIterator.SEG_MOVETO) {
+                        float[] lastPoint = new float[2];
+                        iterator.getCurrentSegmentEnd(lastPoint);
+                        Path_Delegate.nMoveTo(native_dst_path, lastPoint[0], lastPoint[1]);
+                    }
+                }
+
+                isZeroLength = isZeroLength && iterator.getCurrentSegmentLength() > 0;
+                switch (type) {
+                    case PathIterator.SEG_MOVETO:
+                        Path_Delegate.nMoveTo(native_dst_path, points[0], points[1]);
+                        break;
+                    case PathIterator.SEG_LINETO:
+                        Path_Delegate.nLineTo(native_dst_path, points[0], points[1]);
+                        break;
+                    case PathIterator.SEG_CLOSE:
+                        Path_Delegate.nClose(native_dst_path);
+                        break;
+                    case PathIterator.SEG_CUBICTO:
+                        Path_Delegate.nCubicTo(native_dst_path, points[0], points[1],
+                                points[2], points[3],
+                                points[4], points[5]);
+                        break;
+                    case PathIterator.SEG_QUADTO:
+                        Path_Delegate.nQuadTo(native_dst_path, points[0], points[1],
+                                points[2],
+                                points[3]);
+                        break;
+                    default:
+                        assert false;
+                }
+            }
+
+            accLength += iterator.getCurrentSegmentLength();
+            iterator.next();
+        }
+
+        return !isZeroLength;
+    }
+}
diff --git a/android/graphics/Path_Delegate.java b/android/graphics/Path_Delegate.java
new file mode 100644
index 0000000..0979017
--- /dev/null
+++ b/android/graphics/Path_Delegate.java
@@ -0,0 +1,899 @@
+/*
+ * 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;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.annotation.NonNull;
+import android.graphics.Path.Direction;
+import android.graphics.Path.FillType;
+
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Arc2D;
+import java.awt.geom.Area;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Path2D;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+import java.util.ArrayList;
+
+/**
+ * Delegate implementing the native methods of android.graphics.Path
+ *
+ * Through the layoutlib_create tool, the original native methods of Path have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original Path class.
+ *
+ * @see DelegateManager
+ *
+ */
+public final class Path_Delegate {
+
+    // ---- delegate manager ----
+    private static final DelegateManager<Path_Delegate> sManager =
+            new DelegateManager<Path_Delegate>(Path_Delegate.class);
+
+    private static final float EPSILON = 1e-4f;
+
+    // ---- delegate data ----
+    private FillType mFillType = FillType.WINDING;
+    private Path2D mPath = new Path2D.Double();
+
+    private float mLastX = 0;
+    private float mLastY = 0;
+
+    // true if the path contains does not contain a curve or line.
+    private boolean mCachedIsEmpty = true;
+
+    // ---- Public Helper methods ----
+
+    public static Path_Delegate getDelegate(long nPath) {
+        return sManager.getDelegate(nPath);
+    }
+
+    public Path2D getJavaShape() {
+        return mPath;
+    }
+
+    public void setJavaShape(Shape shape) {
+        reset();
+        mPath.append(shape, false /*connect*/);
+    }
+
+    public void reset() {
+        mPath.reset();
+        mLastX = 0;
+        mLastY = 0;
+    }
+
+    public void setPathIterator(PathIterator iterator) {
+        reset();
+        mPath.append(iterator, false /*connect*/);
+    }
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static long nInit() {
+        // create the delegate
+        Path_Delegate newDelegate = new Path_Delegate();
+
+        return sManager.addNewDelegate(newDelegate);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nInit(long nPath) {
+        // create the delegate
+        Path_Delegate newDelegate = new Path_Delegate();
+
+        // get the delegate to copy, which could be null if nPath is 0
+        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+        if (pathDelegate != null) {
+            newDelegate.set(pathDelegate);
+        }
+
+        return sManager.addNewDelegate(newDelegate);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nReset(long nPath) {
+        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+        if (pathDelegate == null) {
+            return;
+        }
+
+        pathDelegate.reset();
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nRewind(long nPath) {
+        // call out to reset since there's nothing to optimize in
+        // terms of data structs.
+        nReset(nPath);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSet(long native_dst, long nSrc) {
+        Path_Delegate pathDstDelegate = sManager.getDelegate(native_dst);
+        if (pathDstDelegate == null) {
+            return;
+        }
+
+        Path_Delegate pathSrcDelegate = sManager.getDelegate(nSrc);
+        if (pathSrcDelegate == null) {
+            return;
+        }
+
+        pathDstDelegate.set(pathSrcDelegate);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nIsConvex(long nPath) {
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "Path.isConvex is not supported.", null, null);
+        return true;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static int nGetFillType(long nPath) {
+        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+        if (pathDelegate == null) {
+            return 0;
+        }
+
+        return pathDelegate.mFillType.nativeInt;
+    }
+
+    @LayoutlibDelegate
+    public static void nSetFillType(long nPath, int ft) {
+        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+        if (pathDelegate == null) {
+            return;
+        }
+
+        pathDelegate.setFillType(Path.sFillTypeArray[ft]);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nIsEmpty(long nPath) {
+        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+        return pathDelegate == null || pathDelegate.isEmpty();
+
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nIsRect(long nPath, RectF rect) {
+        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+        if (pathDelegate == null) {
+            return false;
+        }
+
+        // create an Area that can test if the path is a rect
+        Area area = new Area(pathDelegate.mPath);
+        if (area.isRectangular()) {
+            if (rect != null) {
+                pathDelegate.fillBounds(rect);
+            }
+
+            return true;
+        }
+
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nComputeBounds(long nPath, RectF bounds) {
+        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+        if (pathDelegate == null) {
+            return;
+        }
+
+        pathDelegate.fillBounds(bounds);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nIncReserve(long nPath, int extraPtCount) {
+        // since we use a java2D path, there's no way to pre-allocate new points,
+        // so we do nothing.
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nMoveTo(long nPath, float x, float y) {
+        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+        if (pathDelegate == null) {
+            return;
+        }
+
+        pathDelegate.moveTo(x, y);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nRMoveTo(long nPath, float dx, float dy) {
+        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+        if (pathDelegate == null) {
+            return;
+        }
+
+        pathDelegate.rMoveTo(dx, dy);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nLineTo(long nPath, float x, float y) {
+        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+        if (pathDelegate == null) {
+            return;
+        }
+
+        pathDelegate.lineTo(x, y);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nRLineTo(long nPath, float dx, float dy) {
+        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+        if (pathDelegate == null) {
+            return;
+        }
+
+        pathDelegate.rLineTo(dx, dy);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nQuadTo(long nPath, float x1, float y1, float x2, float y2) {
+        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+        if (pathDelegate == null) {
+            return;
+        }
+
+        pathDelegate.quadTo(x1, y1, x2, y2);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nRQuadTo(long nPath, float dx1, float dy1, float dx2, float dy2) {
+        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+        if (pathDelegate == null) {
+            return;
+        }
+
+        pathDelegate.rQuadTo(dx1, dy1, dx2, dy2);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nCubicTo(long nPath, float x1, float y1,
+            float x2, float y2, float x3, float y3) {
+        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+        if (pathDelegate == null) {
+            return;
+        }
+
+        pathDelegate.cubicTo(x1, y1, x2, y2, x3, y3);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nRCubicTo(long nPath, float x1, float y1,
+            float x2, float y2, float x3, float y3) {
+        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+        if (pathDelegate == null) {
+            return;
+        }
+
+        pathDelegate.rCubicTo(x1, y1, x2, y2, x3, y3);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nArcTo(long nPath, float left, float top, float right,
+            float bottom,
+                    float startAngle, float sweepAngle, boolean forceMoveTo) {
+        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+        if (pathDelegate == null) {
+            return;
+        }
+
+        pathDelegate.arcTo(left, top, right, bottom, startAngle, sweepAngle, forceMoveTo);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nClose(long nPath) {
+        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+        if (pathDelegate == null) {
+            return;
+        }
+
+        pathDelegate.close();
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nAddRect(long nPath,
+            float left, float top, float right, float bottom, int dir) {
+        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+        if (pathDelegate == null) {
+            return;
+        }
+
+        pathDelegate.addRect(left, top, right, bottom, dir);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nAddOval(long nPath, float left, float top, float right,
+            float bottom, int dir) {
+        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+        if (pathDelegate == null) {
+            return;
+        }
+
+        pathDelegate.mPath.append(new Ellipse2D.Float(
+                left, top, right - left, bottom - top), false);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nAddCircle(long nPath, float x, float y, float radius, int dir) {
+        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+        if (pathDelegate == null) {
+            return;
+        }
+
+        // because x/y is the center of the circle, need to offset this by the radius
+        pathDelegate.mPath.append(new Ellipse2D.Float(
+                x - radius, y - radius, radius * 2, radius * 2), false);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nAddArc(long nPath, float left, float top, float right,
+            float bottom, float startAngle, float sweepAngle) {
+        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+        if (pathDelegate == null) {
+            return;
+        }
+
+        // because x/y is the center of the circle, need to offset this by the radius
+        pathDelegate.mPath.append(new Arc2D.Float(
+                left, top, right - left, bottom - top,
+                -startAngle, -sweepAngle, Arc2D.OPEN), false);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nAddRoundRect(long nPath, float left, float top, float right,
+            float bottom, float rx, float ry, int dir) {
+
+        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+        if (pathDelegate == null) {
+            return;
+        }
+
+        pathDelegate.mPath.append(new RoundRectangle2D.Float(
+                left, top, right - left, bottom - top, rx * 2, ry * 2), false);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nAddRoundRect(long nPath, float left, float top, float right,
+            float bottom, float[] radii, int dir) {
+
+        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+        if (pathDelegate == null) {
+            return;
+        }
+
+        float[] cornerDimensions = new float[radii.length];
+        for (int i = 0; i < radii.length; i++) {
+            cornerDimensions[i] = 2 * radii[i];
+        }
+        pathDelegate.mPath.append(new RoundRectangle(left, top, right - left, bottom - top,
+                cornerDimensions), false);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nAddPath(long nPath, long src, float dx, float dy) {
+        addPath(nPath, src, AffineTransform.getTranslateInstance(dx, dy));
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nAddPath(long nPath, long src) {
+        addPath(nPath, src, null /*transform*/);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nAddPath(long nPath, long src, long matrix) {
+        Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix);
+        if (matrixDelegate == null) {
+            return;
+        }
+
+        addPath(nPath, src, matrixDelegate.getAffineTransform());
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nOffset(long nPath, float dx, float dy) {
+        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+        if (pathDelegate == null) {
+            return;
+        }
+
+        pathDelegate.offset(dx, dy);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetLastPoint(long nPath, float dx, float dy) {
+        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+        if (pathDelegate == null) {
+            return;
+        }
+
+        pathDelegate.mLastX = dx;
+        pathDelegate.mLastY = dy;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nTransform(long nPath, long matrix,
+                                                long dst_path) {
+        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+        if (pathDelegate == null) {
+            return;
+        }
+
+        Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix);
+        if (matrixDelegate == null) {
+            return;
+        }
+
+        // this can be null if dst_path is 0
+        Path_Delegate dstDelegate = sManager.getDelegate(dst_path);
+
+        pathDelegate.transform(matrixDelegate, dstDelegate);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nTransform(long nPath, long matrix) {
+        nTransform(nPath, matrix, 0);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nOp(long nPath1, long nPath2, int op, long result) {
+        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "Path.op() not supported", null);
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nFinalize(long nPath) {
+        sManager.removeJavaReferenceFor(nPath);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static float[] nApproximate(long nPath, float error) {
+        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+        if (pathDelegate == null) {
+            return null;
+        }
+        // Get a FlatteningIterator
+        PathIterator iterator = pathDelegate.getJavaShape().getPathIterator(null, error);
+
+        float segment[] = new float[6];
+        float totalLength = 0;
+        ArrayList<Point2D.Float> points = new ArrayList<Point2D.Float>();
+        Point2D.Float previousPoint = null;
+        while (!iterator.isDone()) {
+            int type = iterator.currentSegment(segment);
+            Point2D.Float currentPoint = new Point2D.Float(segment[0], segment[1]);
+            // MoveTo shouldn't affect the length
+            if (previousPoint != null && type != PathIterator.SEG_MOVETO) {
+                totalLength += currentPoint.distance(previousPoint);
+            }
+            previousPoint = currentPoint;
+            points.add(currentPoint);
+            iterator.next();
+        }
+
+        int nPoints = points.size();
+        float[] result = new float[nPoints * 3];
+        previousPoint = null;
+        // Distance that we've covered so far. Used to calculate the fraction of the path that
+        // we've covered up to this point.
+        float walkedDistance = .0f;
+        for (int i = 0; i < nPoints; i++) {
+            Point2D.Float point = points.get(i);
+            float distance = previousPoint != null ? (float) previousPoint.distance(point) : .0f;
+            walkedDistance += distance;
+            result[i * 3] = walkedDistance / totalLength;
+            result[i * 3 + 1] = point.x;
+            result[i * 3 + 2] = point.y;
+
+            previousPoint = point;
+        }
+
+        return result;
+    }
+
+    // ---- Private helper methods ----
+
+    private void set(Path_Delegate delegate) {
+        mPath.reset();
+        setFillType(delegate.mFillType);
+        mPath.append(delegate.mPath, false /*connect*/);
+    }
+
+    private void setFillType(FillType fillType) {
+        mFillType = fillType;
+        mPath.setWindingRule(getWindingRule(fillType));
+    }
+
+    /**
+     * Returns the Java2D winding rules matching a given Android {@link FillType}.
+     * @param type the android fill type
+     * @return the matching java2d winding rule.
+     */
+    private static int getWindingRule(FillType type) {
+        switch (type) {
+            case WINDING:
+            case INVERSE_WINDING:
+                return GeneralPath.WIND_NON_ZERO;
+            case EVEN_ODD:
+            case INVERSE_EVEN_ODD:
+                return GeneralPath.WIND_EVEN_ODD;
+
+            default:
+                assert false;
+                return GeneralPath.WIND_NON_ZERO;
+        }
+    }
+
+    @NonNull
+    private static Direction getDirection(int direction) {
+        for (Direction d : Direction.values()) {
+            if (direction == d.nativeInt) {
+                return d;
+            }
+        }
+
+        assert false;
+        return null;
+    }
+
+    public static void addPath(long destPath, long srcPath, AffineTransform transform) {
+        Path_Delegate destPathDelegate = sManager.getDelegate(destPath);
+        if (destPathDelegate == null) {
+            return;
+        }
+
+        Path_Delegate srcPathDelegate = sManager.getDelegate(srcPath);
+        if (srcPathDelegate == null) {
+            return;
+        }
+
+        if (transform != null) {
+            destPathDelegate.mPath.append(
+                    srcPathDelegate.mPath.getPathIterator(transform), false);
+        } else {
+            destPathDelegate.mPath.append(srcPathDelegate.mPath, false);
+        }
+    }
+
+
+    /**
+     * Returns whether the path already contains any points.
+     * Note that this is different to
+     * {@link #isEmpty} because if all elements are {@link PathIterator#SEG_MOVETO},
+     * {@link #isEmpty} will return true while hasPoints will return false.
+     */
+    public boolean hasPoints() {
+        return !mPath.getPathIterator(null).isDone();
+    }
+
+    /**
+     * Returns whether the path is empty (contains no lines or curves).
+     * @see Path#isEmpty
+     */
+    public boolean isEmpty() {
+        if (!mCachedIsEmpty) {
+            return false;
+        }
+
+        float[] coords = new float[6];
+        mCachedIsEmpty = Boolean.TRUE;
+        for (PathIterator it = mPath.getPathIterator(null); !it.isDone(); it.next()) {
+            int type = it.currentSegment(coords);
+            if (type != PathIterator.SEG_MOVETO) {
+                // Once we know that the path is not empty, we do not need to check again unless
+                // Path#reset is called.
+                mCachedIsEmpty = false;
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Fills the given {@link RectF} with the path bounds.
+     * @param bounds the RectF to be filled.
+     */
+    public void fillBounds(RectF bounds) {
+        Rectangle2D rect = mPath.getBounds2D();
+        bounds.left = (float)rect.getMinX();
+        bounds.right = (float)rect.getMaxX();
+        bounds.top = (float)rect.getMinY();
+        bounds.bottom = (float)rect.getMaxY();
+    }
+
+    /**
+     * Set the beginning of the next contour to the point (x,y).
+     *
+     * @param x The x-coordinate of the start of a new contour
+     * @param y The y-coordinate of the start of a new contour
+     */
+    public void moveTo(float x, float y) {
+        mPath.moveTo(mLastX = x, mLastY = y);
+    }
+
+    /**
+     * Set the beginning of the next contour relative to the last point on the
+     * previous contour. If there is no previous contour, this is treated the
+     * same as moveTo().
+     *
+     * @param dx The amount to add to the x-coordinate of the end of the
+     *           previous contour, to specify the start of a new contour
+     * @param dy The amount to add to the y-coordinate of the end of the
+     *           previous contour, to specify the start of a new contour
+     */
+    public void rMoveTo(float dx, float dy) {
+        dx += mLastX;
+        dy += mLastY;
+        mPath.moveTo(mLastX = dx, mLastY = dy);
+    }
+
+    /**
+     * Add a line from the last point to the specified point (x,y).
+     * If no moveTo() call has been made for this contour, the first point is
+     * automatically set to (0,0).
+     *
+     * @param x The x-coordinate of the end of a line
+     * @param y The y-coordinate of the end of a line
+     */
+    public void lineTo(float x, float y) {
+        if (!hasPoints()) {
+            mPath.moveTo(mLastX = 0, mLastY = 0);
+        }
+        mPath.lineTo(mLastX = x, mLastY = y);
+    }
+
+    /**
+     * Same as lineTo, but the coordinates are considered relative to the last
+     * point on this contour. If there is no previous point, then a moveTo(0,0)
+     * is inserted automatically.
+     *
+     * @param dx The amount to add to the x-coordinate of the previous point on
+     *           this contour, to specify a line
+     * @param dy The amount to add to the y-coordinate of the previous point on
+     *           this contour, to specify a line
+     */
+    public void rLineTo(float dx, float dy) {
+        if (!hasPoints()) {
+            mPath.moveTo(mLastX = 0, mLastY = 0);
+        }
+
+        if (Math.abs(dx) < EPSILON && Math.abs(dy) < EPSILON) {
+            // The delta is so small that this shouldn't generate a line
+            return;
+        }
+
+        dx += mLastX;
+        dy += mLastY;
+        mPath.lineTo(mLastX = dx, mLastY = dy);
+    }
+
+    /**
+     * Add a quadratic bezier from the last point, approaching control point
+     * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for
+     * this contour, the first point is automatically set to (0,0).
+     *
+     * @param x1 The x-coordinate of the control point on a quadratic curve
+     * @param y1 The y-coordinate of the control point on a quadratic curve
+     * @param x2 The x-coordinate of the end point on a quadratic curve
+     * @param y2 The y-coordinate of the end point on a quadratic curve
+     */
+    public void quadTo(float x1, float y1, float x2, float y2) {
+        mPath.quadTo(x1, y1, mLastX = x2, mLastY = y2);
+    }
+
+    /**
+     * Same as quadTo, but the coordinates are considered relative to the last
+     * point on this contour. If there is no previous point, then a moveTo(0,0)
+     * is inserted automatically.
+     *
+     * @param dx1 The amount to add to the x-coordinate of the last point on
+     *            this contour, for the control point of a quadratic curve
+     * @param dy1 The amount to add to the y-coordinate of the last point on
+     *            this contour, for the control point of a quadratic curve
+     * @param dx2 The amount to add to the x-coordinate of the last point on
+     *            this contour, for the end point of a quadratic curve
+     * @param dy2 The amount to add to the y-coordinate of the last point on
+     *            this contour, for the end point of a quadratic curve
+     */
+    public void rQuadTo(float dx1, float dy1, float dx2, float dy2) {
+        if (!hasPoints()) {
+            mPath.moveTo(mLastX = 0, mLastY = 0);
+        }
+        dx1 += mLastX;
+        dy1 += mLastY;
+        dx2 += mLastX;
+        dy2 += mLastY;
+        mPath.quadTo(dx1, dy1, mLastX = dx2, mLastY = dy2);
+    }
+
+    /**
+     * Add a cubic bezier from the last point, approaching control points
+     * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been
+     * made for this contour, the first point is automatically set to (0,0).
+     *
+     * @param x1 The x-coordinate of the 1st control point on a cubic curve
+     * @param y1 The y-coordinate of the 1st control point on a cubic curve
+     * @param x2 The x-coordinate of the 2nd control point on a cubic curve
+     * @param y2 The y-coordinate of the 2nd control point on a cubic curve
+     * @param x3 The x-coordinate of the end point on a cubic curve
+     * @param y3 The y-coordinate of the end point on a cubic curve
+     */
+    public void cubicTo(float x1, float y1, float x2, float y2,
+                        float x3, float y3) {
+        if (!hasPoints()) {
+            mPath.moveTo(0, 0);
+        }
+        mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3);
+    }
+
+    /**
+     * Same as cubicTo, but the coordinates are considered relative to the
+     * current point on this contour. If there is no previous point, then a
+     * moveTo(0,0) is inserted automatically.
+     */
+    public void rCubicTo(float dx1, float dy1, float dx2, float dy2,
+                         float dx3, float dy3) {
+        if (!hasPoints()) {
+            mPath.moveTo(mLastX = 0, mLastY = 0);
+        }
+        dx1 += mLastX;
+        dy1 += mLastY;
+        dx2 += mLastX;
+        dy2 += mLastY;
+        dx3 += mLastX;
+        dy3 += mLastY;
+        mPath.curveTo(dx1, dy1, dx2, dy2, mLastX = dx3, mLastY = dy3);
+    }
+
+    /**
+     * Append the specified arc to the path as a new contour. If the start of
+     * the path is different from the path's current last point, then an
+     * automatic lineTo() is added to connect the current contour to the
+     * start of the arc. However, if the path is empty, then we call moveTo()
+     * with the first point of the arc. The sweep angle is tread mod 360.
+     *
+     * @param left        The left of oval defining shape and size of the arc
+     * @param top         The top of oval defining shape and size of the arc
+     * @param right       The right of oval defining shape and size of the arc
+     * @param bottom      The bottom of oval defining shape and size of the arc
+     * @param startAngle  Starting angle (in degrees) where the arc begins
+     * @param sweepAngle  Sweep angle (in degrees) measured clockwise, treated
+     *                    mod 360.
+     * @param forceMoveTo If true, always begin a new contour with the arc
+     */
+    public void arcTo(float left, float top, float right, float bottom, float startAngle,
+            float sweepAngle,
+            boolean forceMoveTo) {
+        Arc2D arc = new Arc2D.Float(left, top, right - left, bottom - top, -startAngle,
+                -sweepAngle, Arc2D.OPEN);
+        mPath.append(arc, true /*connect*/);
+
+        resetLastPointFromPath();
+    }
+
+    /**
+     * Close the current contour. If the current point is not equal to the
+     * first point of the contour, a line segment is automatically added.
+     */
+    public void close() {
+        mPath.closePath();
+    }
+
+    private void resetLastPointFromPath() {
+        Point2D last = mPath.getCurrentPoint();
+        mLastX = (float) last.getX();
+        mLastY = (float) last.getY();
+    }
+
+    /**
+     * Add a closed rectangle contour to the path
+     *
+     * @param left   The left side of a rectangle to add to the path
+     * @param top    The top of a rectangle to add to the path
+     * @param right  The right side of a rectangle to add to the path
+     * @param bottom The bottom of a rectangle to add to the path
+     * @param dir    The direction to wind the rectangle's contour
+     */
+    public void addRect(float left, float top, float right, float bottom,
+                        int dir) {
+        moveTo(left, top);
+
+        Direction direction = getDirection(dir);
+
+        switch (direction) {
+            case CW:
+                lineTo(right, top);
+                lineTo(right, bottom);
+                lineTo(left, bottom);
+                break;
+            case CCW:
+                lineTo(left, bottom);
+                lineTo(right, bottom);
+                lineTo(right, top);
+                break;
+        }
+
+        close();
+
+        resetLastPointFromPath();
+    }
+
+    /**
+     * Offset the path by (dx,dy), returning true on success
+     *
+     * @param dx  The amount in the X direction to offset the entire path
+     * @param dy  The amount in the Y direction to offset the entire path
+     */
+    public void offset(float dx, float dy) {
+        GeneralPath newPath = new GeneralPath();
+
+        PathIterator iterator = mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy));
+
+        newPath.append(iterator, false /*connect*/);
+        mPath = newPath;
+    }
+
+    /**
+     * Transform the points in this path by matrix, and write the answer
+     * into dst. If dst is null, then the the original path is modified.
+     *
+     * @param matrix The matrix to apply to the path
+     * @param dst    The transformed path is written here. If dst is null,
+     *               then the the original path is modified
+     */
+    public void transform(Matrix_Delegate matrix, Path_Delegate dst) {
+        if (matrix.hasPerspective()) {
+            assert false;
+            Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_AFFINE,
+                    "android.graphics.Path#transform() only " +
+                    "supports affine transformations.", null, null /*data*/);
+        }
+
+        GeneralPath newPath = new GeneralPath();
+
+        PathIterator iterator = mPath.getPathIterator(matrix.getAffineTransform());
+
+        newPath.append(iterator, false /*connect*/);
+
+        if (dst != null) {
+            dst.mPath = newPath;
+        } else {
+            mPath = newPath;
+        }
+    }
+}
diff --git a/android/graphics/Picture.java b/android/graphics/Picture.java
new file mode 100644
index 0000000..08eeaff
--- /dev/null
+++ b/android/graphics/Picture.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2007 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;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * A Picture records drawing calls (via the canvas returned by beginRecording)
+ * and can then play them back into Canvas (via {@link Picture#draw(Canvas)} or
+ * {@link Canvas#drawPicture(Picture)}).For most content (e.g. text, lines, rectangles),
+ * drawing a sequence from a picture can be faster than the equivalent API
+ * calls, since the picture performs its playback without incurring any
+ * method-call overhead.
+ *
+ * <p class="note"><strong>Note:</strong> Prior to API level 23 a picture cannot
+ * be replayed on a hardware accelerated canvas.</p>
+ */
+public class Picture {
+    private Canvas mRecordingCanvas;
+    private long mNativePicture;
+
+    private static final int WORKING_STREAM_STORAGE = 16 * 1024;
+
+    /**
+     * Creates an empty picture that is ready to record.
+     */
+    public Picture() {
+        this(nativeConstructor(0));
+    }
+
+    /**
+     * Create a picture by making a copy of what has already been recorded in
+     * src. The contents of src are unchanged, and if src changes later, those
+     * changes will not be reflected in this picture.
+     */
+    public Picture(Picture src) {
+        this(nativeConstructor(src != null ? src.mNativePicture : 0));
+    }
+
+    private Picture(long nativePicture) {
+        if (nativePicture == 0) {
+            throw new RuntimeException();
+        }
+        mNativePicture = nativePicture;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            nativeDestructor(mNativePicture);
+            mNativePicture = 0;
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * To record a picture, call beginRecording() and then draw into the Canvas
+     * that is returned. Nothing we appear on screen, but all of the draw
+     * commands (e.g. {@link Canvas#drawRect(Rect, Paint)}) will be recorded.
+     * To stop recording, call endRecording(). After endRecording() the Canvas
+     * that was returned must no longer be used, and nothing should be drawn
+     * into it.
+     */
+    public Canvas beginRecording(int width, int height) {
+        long ni = nativeBeginRecording(mNativePicture, width, height);
+        mRecordingCanvas = new RecordingCanvas(this, ni);
+        return mRecordingCanvas;
+    }
+
+    /**
+     * Call endRecording when the picture is built. After this call, the picture
+     * may be drawn, but the canvas that was returned by beginRecording must not
+     * be used anymore. This is automatically called if {@link Picture#draw}
+     * or {@link Canvas#drawPicture(Picture)} is called.
+     */
+    public void endRecording() {
+        if (mRecordingCanvas != null) {
+            mRecordingCanvas = null;
+            nativeEndRecording(mNativePicture);
+        }
+    }
+
+    /**
+     * Get the width of the picture as passed to beginRecording. This
+     * does not reflect (per se) the content of the picture.
+     */
+    public int getWidth() {
+      return nativeGetWidth(mNativePicture);
+    }
+
+    /**
+     * Get the height of the picture as passed to beginRecording. This
+     * does not reflect (per se) the content of the picture.
+     */
+    public int getHeight() {
+      return nativeGetHeight(mNativePicture);
+    }
+
+    /**
+     * Draw this picture on the canvas.
+     * <p>
+     * Prior to {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this call could
+     * have the side effect of changing the matrix and clip of the canvas
+     * if this picture had imbalanced saves/restores.
+     *
+     * <p>
+     * <strong>Note:</strong> This forces the picture to internally call
+     * {@link Picture#endRecording()} in order to prepare for playback.
+     *
+     * @param canvas  The picture is drawn to this canvas
+     */
+    public void draw(Canvas canvas) {
+        if (mRecordingCanvas != null) {
+            endRecording();
+        }
+        nativeDraw(canvas.getNativeCanvasWrapper(), mNativePicture);
+    }
+
+    /**
+     * Create a new picture (already recorded) from the data in the stream. This
+     * data was generated by a previous call to writeToStream(). Pictures that
+     * have been persisted across device restarts are not guaranteed to decode
+     * properly and are highly discouraged.
+     *
+     * @see #writeToStream(java.io.OutputStream)
+     * @deprecated The recommended alternative is to not use writeToStream and
+     * instead draw the picture into a Bitmap from which you can persist it as
+     * raw or compressed pixels.
+     */
+    @Deprecated
+    public static Picture createFromStream(InputStream stream) {
+        return new Picture(nativeCreateFromStream(stream, new byte[WORKING_STREAM_STORAGE]));
+    }
+
+    /**
+     * Write the picture contents to a stream. The data can be used to recreate
+     * the picture in this or another process by calling createFromStream(...)
+     * The resulting stream is NOT to be persisted across device restarts as
+     * there is no guarantee that the Picture can be successfully reconstructed.
+     *
+     * @see #createFromStream(java.io.InputStream)
+     * @deprecated The recommended alternative is to draw the picture into a
+     * Bitmap from which you can persist it as raw or compressed pixels.
+     */
+    @Deprecated
+    public void writeToStream(OutputStream stream) {
+        // do explicit check before calling the native method
+        if (stream == null) {
+            throw new NullPointerException();
+        }
+        if (!nativeWriteToStream(mNativePicture, stream,
+                             new byte[WORKING_STREAM_STORAGE])) {
+            throw new RuntimeException();
+        }
+    }
+
+    // return empty picture if src is 0, or a copy of the native src
+    private static native long nativeConstructor(long nativeSrcOr0);
+    private static native long nativeCreateFromStream(InputStream stream, byte[] storage);
+    private static native int nativeGetWidth(long nativePicture);
+    private static native int nativeGetHeight(long nativePicture);
+    private static native long nativeBeginRecording(long nativeCanvas, int w, int h);
+    private static native void nativeEndRecording(long nativeCanvas);
+    private static native void nativeDraw(long nativeCanvas, long nativePicture);
+    private static native boolean nativeWriteToStream(long nativePicture,
+                                           OutputStream stream, byte[] storage);
+    private static native void nativeDestructor(long nativePicture);
+
+    private static class RecordingCanvas extends Canvas {
+        private final Picture mPicture;
+
+        public RecordingCanvas(Picture pict, long nativeCanvas) {
+            super(nativeCanvas);
+            mPicture = pict;
+        }
+
+        @Override
+        public void setBitmap(Bitmap bitmap) {
+            throw new RuntimeException("Cannot call setBitmap on a picture canvas");
+        }
+
+        @Override
+        public void drawPicture(Picture picture) {
+            if (mPicture == picture) {
+                throw new RuntimeException("Cannot draw a picture into its recording canvas");
+            }
+            super.drawPicture(picture);
+        }
+    }
+}
diff --git a/android/graphics/PixelFormat.java b/android/graphics/PixelFormat.java
new file mode 100644
index 0000000..f93886d
--- /dev/null
+++ b/android/graphics/PixelFormat.java
@@ -0,0 +1,188 @@
+/*
+ * 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;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+public class PixelFormat {
+    /** @hide */
+    @IntDef({UNKNOWN, TRANSLUCENT, TRANSPARENT, OPAQUE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Opacity {}
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({RGBA_8888, RGBX_8888, RGBA_F16, RGBA_1010102, RGB_888, RGB_565})
+    public @interface Format { }
+
+    // NOTE: these constants must match the values from graphics/common/x.x/types.hal
+
+    public static final int UNKNOWN      = 0;
+
+    /** System chooses a format that supports translucency (many alpha bits) */
+    public static final int TRANSLUCENT  = -3;
+
+    /**
+     * System chooses a format that supports transparency
+     * (at least 1 alpha bit)
+     */
+    public static final int TRANSPARENT  = -2;
+
+    /** System chooses an opaque format (no alpha bits required) */
+    public static final int OPAQUE       = -1;
+
+    public static final int RGBA_8888    = 1;
+    public static final int RGBX_8888    = 2;
+    public static final int RGB_888      = 3;
+    public static final int RGB_565      = 4;
+
+    @Deprecated
+    public static final int RGBA_5551    = 6;
+    @Deprecated
+    public static final int RGBA_4444    = 7;
+    @Deprecated
+    public static final int A_8          = 8;
+    @Deprecated
+    public static final int L_8          = 9;
+    @Deprecated
+    public static final int LA_88        = 0xA;
+    @Deprecated
+    public static final int RGB_332      = 0xB;
+
+    /**
+     * @deprecated use {@link android.graphics.ImageFormat#NV16
+     * ImageFormat.NV16} instead.
+     */
+    @Deprecated
+    public static final int YCbCr_422_SP = 0x10;
+
+    /**
+     * @deprecated use {@link android.graphics.ImageFormat#NV21
+     * ImageFormat.NV21} instead.
+     */
+    @Deprecated
+    public static final int YCbCr_420_SP = 0x11;
+
+    /**
+     * @deprecated use {@link android.graphics.ImageFormat#YUY2
+     * ImageFormat.YUY2} instead.
+     */
+    @Deprecated
+    public static final int YCbCr_422_I  = 0x14;
+
+    public static final int RGBA_F16     = 0x16;
+    public static final int RGBA_1010102 = 0x2B;
+
+    /**
+     * @deprecated use {@link android.graphics.ImageFormat#JPEG
+     * ImageFormat.JPEG} instead.
+     */
+    @Deprecated
+    public static final int JPEG        = 0x100;
+
+    public int bytesPerPixel;
+    public int bitsPerPixel;
+
+    public static void getPixelFormatInfo(@Format int format, PixelFormat info) {
+        switch (format) {
+            case RGBA_8888:
+            case RGBX_8888:
+            case RGBA_1010102:
+                info.bitsPerPixel = 32;
+                info.bytesPerPixel = 4;
+                break;
+            case RGB_888:
+                info.bitsPerPixel = 24;
+                info.bytesPerPixel = 3;
+                break;
+            case RGB_565:
+            case RGBA_5551:
+            case RGBA_4444:
+            case LA_88:
+                info.bitsPerPixel = 16;
+                info.bytesPerPixel = 2;
+                break;
+            case A_8:
+            case L_8:
+            case RGB_332:
+                info.bitsPerPixel = 8;
+                info.bytesPerPixel = 1;
+                break;
+            case YCbCr_422_SP:
+            case YCbCr_422_I:
+                info.bitsPerPixel = 16;
+                info.bytesPerPixel = 1;
+                break;
+            case YCbCr_420_SP:
+                info.bitsPerPixel = 12;
+                info.bytesPerPixel = 1;
+                break;
+            case RGBA_F16:
+                info.bitsPerPixel = 64;
+                info.bytesPerPixel = 8;
+                break;
+            default:
+                throw new IllegalArgumentException("unknown pixel format " + format);
+        }
+    }
+
+    public static boolean formatHasAlpha(@Format int format) {
+        switch (format) {
+            case PixelFormat.A_8:
+            case PixelFormat.LA_88:
+            case PixelFormat.RGBA_4444:
+            case PixelFormat.RGBA_5551:
+            case PixelFormat.RGBA_8888:
+            case PixelFormat.RGBA_F16:
+            case PixelFormat.RGBA_1010102:
+            case PixelFormat.TRANSLUCENT:
+            case PixelFormat.TRANSPARENT:
+                return true;
+        }
+        return false;
+    }
+
+    /**
+     * Determine whether or not this is a public-visible and non-deprecated {@code format}.
+     *
+     * <p>In particular, {@code @hide} formats will return {@code false}.</p>
+     *
+     * <p>Any other indirect formats (such as {@code TRANSPARENT} or {@code TRANSLUCENT})
+     * will return {@code false}.</p>
+     *
+     * @param format an integer format
+     * @return a boolean
+     *
+     * @hide
+     */
+    public static boolean isPublicFormat(@Format int format) {
+        switch (format) {
+            case RGBA_8888:
+            case RGBX_8888:
+            case RGB_888:
+            case RGB_565:
+            case RGBA_F16:
+            case RGBA_1010102:
+                return true;
+        }
+
+        return false;
+    }
+}
diff --git a/android/graphics/PixelXorXfermode.java b/android/graphics/PixelXorXfermode.java
new file mode 100644
index 0000000..27884e0
--- /dev/null
+++ b/android/graphics/PixelXorXfermode.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2007 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;
+
+/**
+ * @removed
+ */
+@Deprecated
+public class PixelXorXfermode extends Xfermode {
+
+    public PixelXorXfermode(int opColor) {
+    }
+}
diff --git a/android/graphics/Point.java b/android/graphics/Point.java
new file mode 100644
index 0000000..abcccbd
--- /dev/null
+++ b/android/graphics/Point.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2007 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;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.io.PrintWriter;
+
+
+/**
+ * Point holds two integer coordinates
+ */
+public class Point implements Parcelable {
+    public int x;
+    public int y;
+
+    public Point() {}
+
+    public Point(int x, int y) {
+        this.x = x;
+        this.y = y;
+    }
+
+    public Point(Point src) {
+        this.x = src.x;
+        this.y = src.y;
+    }
+
+    /**
+     * Set the point's x and y coordinates
+     */
+    public void set(int x, int y) {
+        this.x = x;
+        this.y = y;
+    }
+
+    /**
+     * Negate the point's coordinates
+     */
+    public final void negate() {
+        x = -x;
+        y = -y;
+    }
+
+    /**
+     * Offset the point's coordinates by dx, dy
+     */
+    public final void offset(int dx, int dy) {
+        x += dx;
+        y += dy;
+    }
+
+    /**
+     * Returns true if the point's coordinates equal (x,y)
+     */
+    public final boolean equals(int x, int y) {
+        return this.x == x && this.y == y;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        Point point = (Point) o;
+
+        if (x != point.x) return false;
+        if (y != point.y) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = x;
+        result = 31 * result + y;
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "Point(" + x + ", " + y + ")";
+    }
+
+    /** @hide */
+    public void printShortString(PrintWriter pw) {
+        pw.print("["); pw.print(x); pw.print(","); pw.print(y); pw.print("]");
+    }
+
+    /**
+     * Parcelable interface methods
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Write this point to the specified parcel. To restore a point from
+     * a parcel, use readFromParcel()
+     * @param out The parcel to write the point's coordinates into
+     */
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(x);
+        out.writeInt(y);
+    }
+
+    public static final Parcelable.Creator<Point> CREATOR = new Parcelable.Creator<Point>() {
+        /**
+         * Return a new point from the data in the specified parcel.
+         */
+        public Point createFromParcel(Parcel in) {
+            Point r = new Point();
+            r.readFromParcel(in);
+            return r;
+        }
+
+        /**
+         * Return an array of rectangles of the specified size.
+         */
+        public Point[] newArray(int size) {
+            return new Point[size];
+        }
+    };
+
+    /**
+     * Set the point's coordinates from the data stored in the specified
+     * parcel. To write a point to a parcel, call writeToParcel().
+     *
+     * @param in The parcel to read the point's coordinates from
+     */
+    public void readFromParcel(Parcel in) {
+        x = in.readInt();
+        y = in.readInt();
+    }
+}
diff --git a/android/graphics/PointF.java b/android/graphics/PointF.java
new file mode 100644
index 0000000..8e4288e
--- /dev/null
+++ b/android/graphics/PointF.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2007 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;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+
+/**
+ * PointF holds two float coordinates
+ */
+public class PointF implements Parcelable {
+    public float x;
+    public float y;
+    
+    public PointF() {}
+
+    public PointF(float x, float y) {
+        this.x = x;
+        this.y = y; 
+    }
+    
+    public PointF(Point p) { 
+        this.x = p.x;
+        this.y = p.y;
+    }
+    
+    /**
+     * Set the point's x and y coordinates
+     */
+    public final void set(float x, float y) {
+        this.x = x;
+        this.y = y;
+    }
+    
+    /**
+     * Set the point's x and y coordinates to the coordinates of p
+     */
+    public final void set(PointF p) { 
+        this.x = p.x;
+        this.y = p.y;
+    }
+    
+    public final void negate() { 
+        x = -x;
+        y = -y; 
+    }
+    
+    public final void offset(float dx, float dy) {
+        x += dx;
+        y += dy;
+    }
+    
+    /**
+     * Returns true if the point's coordinates equal (x,y)
+     */
+    public final boolean equals(float x, float y) { 
+        return this.x == x && this.y == y; 
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        PointF pointF = (PointF) o;
+
+        if (Float.compare(pointF.x, x) != 0) return false;
+        if (Float.compare(pointF.y, y) != 0) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = (x != +0.0f ? Float.floatToIntBits(x) : 0);
+        result = 31 * result + (y != +0.0f ? Float.floatToIntBits(y) : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "PointF(" + x + ", " + y + ")";
+    }
+
+    /**
+     * Return the euclidian distance from (0,0) to the point
+     */
+    public final float length() { 
+        return length(x, y); 
+    }
+    
+    /**
+     * Returns the euclidian distance from (0,0) to (x,y)
+     */
+    public static float length(float x, float y) {
+        return (float) Math.hypot(x, y);
+    }
+
+    /**
+     * Parcelable interface methods
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Write this point to the specified parcel. To restore a point from
+     * a parcel, use readFromParcel()
+     * @param out The parcel to write the point's coordinates into
+     */
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeFloat(x);
+        out.writeFloat(y);
+    }
+
+    public static final Parcelable.Creator<PointF> CREATOR = new Parcelable.Creator<PointF>() {
+        /**
+         * Return a new point from the data in the specified parcel.
+         */
+        public PointF createFromParcel(Parcel in) {
+            PointF r = new PointF();
+            r.readFromParcel(in);
+            return r;
+        }
+
+        /**
+         * Return an array of rectangles of the specified size.
+         */
+        public PointF[] newArray(int size) {
+            return new PointF[size];
+        }
+    };
+
+    /**
+     * Set the point's coordinates from the data stored in the specified
+     * parcel. To write a point to a parcel, call writeToParcel().
+     *
+     * @param in The parcel to read the point's coordinates from
+     */
+    public void readFromParcel(Parcel in) {
+        x = in.readFloat();
+        y = in.readFloat();
+    }
+}
diff --git a/android/graphics/PorterDuff.java b/android/graphics/PorterDuff.java
new file mode 100644
index 0000000..d7d3049
--- /dev/null
+++ b/android/graphics/PorterDuff.java
@@ -0,0 +1,403 @@
+/*
+ * 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;
+
+/**
+ * <p>This class contains the list of alpha compositing and blending modes
+ * that can be passed to {@link PorterDuffXfermode}, a specialized implementation
+ * of {@link Paint}'s {@link Paint#setXfermode(Xfermode) transfer mode}.
+ * All the available modes can be found in the {@link Mode} enum.</p>
+ */
+public class PorterDuff {
+    /**
+     * {@usesMathJax}
+     *
+     * <h3>Porter-Duff</h3>
+     *
+     * <p>The name of the parent class is an homage to the work of Thomas Porter and
+     * Tom Duff, presented in their seminal 1984 paper titled "Compositing Digital Images".
+     * In this paper, the authors describe 12 compositing operators that govern how to
+     * compute the color resulting of the composition of a source (the graphics object
+     * to render) with a destination (the content of the render target).</p>
+     *
+     * <p>"Compositing Digital Images" was published in <em>Computer Graphics</em>
+     * Volume 18, Number 3 dated July 1984.</p>
+     *
+     * <p>Because the work of Porter and Duff focuses solely on the effects of the alpha
+     * channel of the source and destination, the 12 operators described in the original
+     * paper are called alpha compositing modes here.</p>
+     *
+     * <p>For convenience, this class also provides several blending modes, which similarly
+     * define the result of compositing a source and a destination but without being
+     * constrained to the alpha channel. These blending modes are not defined by Porter
+     * and Duff but have been included in this class for convenience purposes.</p>
+     *
+     * <h3>Diagrams</h3>
+     *
+     * <p>All the example diagrams presented below use the same source and destination
+     * images:</p>
+     *
+     * <table summary="Source and Destination" style="background-color: transparent;">
+     *     <tr>
+     *         <td style="border: none; text-align: center;">
+     *             <img src="{@docRoot}reference/android/images/graphics/composite_SRC.png" />
+     *             <figcaption>Source image</figcaption>
+     *         </td>
+     *         <td style="border: none; text-align: center;">
+     *             <img src="{@docRoot}reference/android/images/graphics/composite_DST.png" />
+     *             <figcaption>Destination image</figcaption>
+     *         </td>
+     *     </tr>
+     * </table>
+     *
+     * <p>The order of drawing operations used to generate each diagram is shown in the
+     * following code snippet:</p>
+     *
+     * <pre class="prettyprint">
+     * Paint paint = new Paint();
+     * canvas.drawBitmap(destinationImage, 0, 0, paint);
+     *
+     * PorterDuff.Mode mode = // choose a mode
+     * paint.setXfermode(new PorterDuffXfermode(mode));
+     *
+     * canvas.drawBitmap(sourceImage, 0, 0, paint);
+     * </pre>
+
+     *
+     * <h3>Alpha compositing modes</h3>
+     *
+     * <table summary="Alpha compositing modes" style="background-color: transparent;">
+     *     <tr>
+     *         <td style="border: none; text-align: center;">
+     *             <img src="{@docRoot}reference/android/images/graphics/composite_SRC.png" />
+     *             <figcaption>{@link #SRC Source}</figcaption>
+     *         </td>
+     *         <td style="border: none; text-align: center;">
+     *             <img src="{@docRoot}reference/android/images/graphics/composite_SRC_OVER.png" />
+     *             <figcaption>{@link #SRC_OVER Source Over}</figcaption>
+     *         </td>
+     *         <td style="border: none; text-align: center;">
+     *             <img src="{@docRoot}reference/android/images/graphics/composite_SRC_IN.png" />
+     *             <figcaption>{@link #SRC_IN Source In}</figcaption>
+     *         </td>
+     *         <td style="border: none; text-align: center;">
+     *             <img src="{@docRoot}reference/android/images/graphics/composite_SRC_ATOP.png" />
+     *             <figcaption>{@link #SRC_ATOP Source Atop}</figcaption>
+     *         </td>
+     *     </tr>
+     *     <tr>
+     *         <td style="border: none; text-align: center;">
+     *             <img src="{@docRoot}reference/android/images/graphics/composite_DST.png" />
+     *             <figcaption>{@link #DST Destination}</figcaption>
+     *         </td>
+     *         <td style="border: none; text-align: center;">
+     *             <img src="{@docRoot}reference/android/images/graphics/composite_DST_OVER.png" />
+     *             <figcaption>{@link #DST_OVER Destination Over}</figcaption>
+     *         </td>
+     *         <td style="border: none; text-align: center;">
+     *             <img src="{@docRoot}reference/android/images/graphics/composite_DST_IN.png" />
+     *             <figcaption>{@link #DST_IN Destination In}</figcaption>
+     *         </td>
+     *         <td style="border: none; text-align: center;">
+     *             <img src="{@docRoot}reference/android/images/graphics/composite_DST_ATOP.png" />
+     *             <figcaption>{@link #DST_ATOP Destination Atop}</figcaption>
+     *         </td>
+     *     </tr>
+     *     <tr>
+     *         <td style="border: none; text-align: center;">
+     *             <img src="{@docRoot}reference/android/images/graphics/composite_CLEAR.png" />
+     *             <figcaption>{@link #CLEAR Clear}</figcaption>
+     *         </td>
+     *         <td style="border: none; text-align: center;">
+     *             <img src="{@docRoot}reference/android/images/graphics/composite_SRC_OUT.png" />
+     *             <figcaption>{@link #SRC_OUT Source Out}</figcaption>
+     *         </td>
+     *         <td style="border: none; text-align: center;">
+     *             <img src="{@docRoot}reference/android/images/graphics/composite_DST_OUT.png" />
+     *             <figcaption>{@link #DST_OUT Destination Out}</figcaption>
+     *         </td>
+     *         <td style="border: none; text-align: center;">
+     *             <img src="{@docRoot}reference/android/images/graphics/composite_XOR.png" />
+     *             <figcaption>{@link #XOR Exclusive Or}</figcaption>
+     *         </td>
+     *     </tr>
+     * </table>
+     *
+     * <h3>Blending modes</h3>
+     *
+     * <table summary="Blending modes" style="background-color: transparent;">
+     *     <tr>
+     *         <td style="border: none; text-align: center;">
+     *             <img src="{@docRoot}reference/android/images/graphics/composite_DARKEN.png" />
+     *             <figcaption>{@link #DARKEN Darken}</figcaption>
+     *         </td>
+     *         <td style="border: none; text-align: center;">
+     *             <img src="{@docRoot}reference/android/images/graphics/composite_LIGHTEN.png" />
+     *             <figcaption>{@link #LIGHTEN Lighten}</figcaption>
+     *         </td>
+     *         <td style="border: none; text-align: center;">
+     *             <img src="{@docRoot}reference/android/images/graphics/composite_MULTIPLY.png" />
+     *             <figcaption>{@link #MULTIPLY Multiply}</figcaption>
+     *         </td>
+     *     </tr>
+     *     <tr>
+     *         <td style="border: none; text-align: center;">
+     *             <img src="{@docRoot}reference/android/images/graphics/composite_SCREEN.png" />
+     *             <figcaption>{@link #SCREEN Screen}</figcaption>
+     *         </td>
+     *         <td style="border: none; text-align: center;">
+     *             <img src="{@docRoot}reference/android/images/graphics/composite_OVERLAY.png" />
+     *             <figcaption>{@link #OVERLAY Overlay}</figcaption>
+     *         </td>
+     *     </tr>
+     * </table>
+     *
+     * <h3>Compositing equations</h3>
+     *
+     * <p>The documentation of each individual alpha compositing or blending mode below
+     * provides the exact equation used to compute alpha and color value of the result
+     * of the composition of a source and destination.</p>
+     *
+     * <p>The result (or output) alpha value is noted \(\alpha_{out}\). The result (or output)
+     * color value is noted \(C_{out}\).</p>
+     */
+    public enum Mode {
+        // these value must match their native equivalents. See SkXfermode.h
+        /**
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/composite_CLEAR.png" />
+         *     <figcaption>Destination pixels covered by the source are cleared to 0.</figcaption>
+         * </p>
+         * <p>\(\alpha_{out} = 0\)</p>
+         * <p>\(C_{out} = 0\)</p>
+         */
+        CLEAR       (0),
+        /**
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/composite_SRC.png" />
+         *     <figcaption>The source pixels replace the destination pixels.</figcaption>
+         * </p>
+         * <p>\(\alpha_{out} = \alpha_{src}\)</p>
+         * <p>\(C_{out} = C_{src}\)</p>
+         */
+        SRC         (1),
+        /**
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/composite_DST.png" />
+         *     <figcaption>The source pixels are discarded, leaving the destination intact.</figcaption>
+         * </p>
+         * <p>\(\alpha_{out} = \alpha_{dst}\)</p>
+         * <p>\(C_{out} = C_{dst}\)</p>
+         */
+        DST         (2),
+        /**
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/composite_SRC_OVER.png" />
+         *     <figcaption>The source pixels are drawn over the destination pixels.</figcaption>
+         * </p>
+         * <p>\(\alpha_{out} = \alpha_{src} + (1 - \alpha_{src}) * \alpha_{dst}\)</p>
+         * <p>\(C_{out} = C_{src} + (1 - \alpha_{src}) * C_{dst}\)</p>
+         */
+        SRC_OVER    (3),
+        /**
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/composite_DST_OVER.png" />
+         *     <figcaption>The source pixels are drawn behind the destination pixels.</figcaption>
+         * </p>
+         * <p>\(\alpha_{out} = \alpha_{dst} + (1 - \alpha_{dst}) * \alpha_{src}\)</p>
+         * <p>\(C_{out} = C_{dst} + (1 - \alpha_{dst}) * C_{src}\)</p>
+         */
+        DST_OVER    (4),
+        /**
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/composite_SRC_IN.png" />
+         *     <figcaption>Keeps the source pixels that cover the destination pixels,
+         *     discards the remaining source and destination pixels.</figcaption>
+         * </p>
+         * <p>\(\alpha_{out} = \alpha_{src} * \alpha_{dst}\)</p>
+         * <p>\(C_{out} = C_{src} * \alpha_{dst}\)</p>
+         */
+        SRC_IN      (5),
+        /**
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/composite_DST_IN.png" />
+         *     <figcaption>Keeps the destination pixels that cover source pixels,
+         *     discards the remaining source and destination pixels.</figcaption>
+         * </p>
+         * <p>\(\alpha_{out} = \alpha_{src} * \alpha_{dst}\)</p>
+         * <p>\(C_{out} = C_{dst} * \alpha_{src}\)</p>
+         */
+        DST_IN      (6),
+        /**
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/composite_SRC_OUT.png" />
+         *     <figcaption>Keeps the source pixels that do not cover destination pixels.
+         *     Discards source pixels that cover destination pixels. Discards all
+         *     destination pixels.</figcaption>
+         * </p>
+         * <p>\(\alpha_{out} = (1 - \alpha_{dst}) * \alpha_{src}\)</p>
+         * <p>\(C_{out} = (1 - \alpha_{dst}) * C_{src}\)</p>
+         */
+        SRC_OUT     (7),
+        /**
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/composite_DST_OUT.png" />
+         *     <figcaption>Keeps the destination pixels that are not covered by source pixels.
+         *     Discards destination pixels that are covered by source pixels. Discards all
+         *     source pixels.</figcaption>
+         * </p>
+         * <p>\(\alpha_{out} = (1 - \alpha_{src}) * \alpha_{dst}\)</p>
+         * <p>\(C_{out} = (1 - \alpha_{src}) * C_{dst}\)</p>
+         */
+        DST_OUT     (8),
+        /**
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/composite_SRC_ATOP.png" />
+         *     <figcaption>Discards the source pixels that do not cover destination pixels.
+         *     Draws remaining source pixels over destination pixels.</figcaption>
+         * </p>
+         * <p>\(\alpha_{out} = \alpha_{dst}\)</p>
+         * <p>\(C_{out} = \alpha_{dst} * C_{src} + (1 - \alpha_{src}) * C_{dst}\)</p>
+         */
+        SRC_ATOP    (9),
+        /**
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/composite_DST_ATOP.png" />
+         *     <figcaption>Discards the destination pixels that are not covered by source pixels.
+         *     Draws remaining destination pixels over source pixels.</figcaption>
+         * </p>
+         * <p>\(\alpha_{out} = \alpha_{src}\)</p>
+         * <p>\(C_{out} = \alpha_{src} * C_{dst} + (1 - \alpha_{dst}) * C_{src}\)</p>
+         */
+        DST_ATOP    (10),
+        /**
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/composite_XOR.png" />
+         *     <figcaption>Discards the source and destination pixels where source pixels
+         *     cover destination pixels. Draws remaining source pixels.</figcaption>
+         * </p>
+         * <p>\(\alpha_{out} = (1 - \alpha_{dst}) * \alpha_{src} + (1 - \alpha_{src}) * \alpha_{dst}\)</p>
+         * <p>\(C_{out} = (1 - \alpha_{dst}) * C_{src} + (1 - \alpha_{src}) * C_{dst}\)</p>
+         */
+        XOR         (11),
+        /**
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/composite_DARKEN.png" />
+         *     <figcaption>Retains the smallest component of the source and
+         *     destination pixels.</figcaption>
+         * </p>
+         * <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p>
+         * <p>\(C_{out} = (1 - \alpha_{dst}) * C_{src} + (1 - \alpha_{src}) * C_{dst} + min(C_{src}, C_{dst})\)</p>
+         */
+        DARKEN      (16),
+        /**
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/composite_LIGHTEN.png" />
+         *     <figcaption>Retains the largest component of the source and
+         *     destination pixel.</figcaption>
+         * </p>
+         * <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p>
+         * <p>\(C_{out} = (1 - \alpha_{dst}) * C_{src} + (1 - \alpha_{src}) * C_{dst} + max(C_{src}, C_{dst})\)</p>
+         */
+        LIGHTEN     (17),
+        /**
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/composite_MULTIPLY.png" />
+         *     <figcaption>Multiplies the source and destination pixels.</figcaption>
+         * </p>
+         * <p>\(\alpha_{out} = \alpha_{src} * \alpha_{dst}\)</p>
+         * <p>\(C_{out} = C_{src} * C_{dst}\)</p>
+         */
+        MULTIPLY    (13),
+        /**
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/composite_SCREEN.png" />
+         *     <figcaption>Adds the source and destination pixels, then subtracts the
+         *     source pixels multiplied by the destination.</figcaption>
+         * </p>
+         * <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p>
+         * <p>\(C_{out} = C_{src} + C_{dst} - C_{src} * C_{dst}\)</p>
+         */
+        SCREEN      (14),
+        /**
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/composite_ADD.png" />
+         *     <figcaption>Adds the source pixels to the destination pixels and saturates
+         *     the result.</figcaption>
+         * </p>
+         * <p>\(\alpha_{out} = max(0, min(\alpha_{src} + \alpha_{dst}, 1))\)</p>
+         * <p>\(C_{out} = max(0, min(C_{src} + C_{dst}, 1))\)</p>
+         */
+        ADD         (12),
+        /**
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/composite_OVERLAY.png" />
+         *     <figcaption>Multiplies or screens the source and destination depending on the
+         *     destination color.</figcaption>
+         * </p>
+         * <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p>
+         * <p>\(\begin{equation}
+         * C_{out} = \begin{cases} 2 * C_{src} * C_{dst} & 2 * C_{dst} \lt \alpha_{dst} \\
+         * \alpha_{src} * \alpha_{dst} - 2 (\alpha_{dst} - C_{src}) (\alpha_{src} - C_{dst}) & otherwise \end{cases}
+         * \end{equation}\)</p>
+         */
+        OVERLAY     (15);
+
+        Mode(int nativeInt) {
+            this.nativeInt = nativeInt;
+        }
+
+        /**
+         * @hide
+         */
+        public final int nativeInt;
+    }
+
+    /**
+     * @hide
+     */
+    public static int modeToInt(Mode mode) {
+        return mode.nativeInt;
+    }
+
+    /**
+     * @hide
+     */
+    public static Mode intToMode(int val) {
+        switch (val) {
+            default:
+            case  0: return Mode.CLEAR;
+            case  1: return Mode.SRC;
+            case  2: return Mode.DST;
+            case  3: return Mode.SRC_OVER;
+            case  4: return Mode.DST_OVER;
+            case  5: return Mode.SRC_IN;
+            case  6: return Mode.DST_IN;
+            case  7: return Mode.SRC_OUT;
+            case  8: return Mode.DST_OUT;
+            case  9: return Mode.SRC_ATOP;
+            case 10: return Mode.DST_ATOP;
+            case 11: return Mode.XOR;
+            case 16: return Mode.DARKEN;
+            case 17: return Mode.LIGHTEN;
+            case 13: return Mode.MULTIPLY;
+            case 14: return Mode.SCREEN;
+            case 12: return Mode.ADD;
+            case 15: return Mode.OVERLAY;
+        }
+    }
+}
diff --git a/android/graphics/PorterDuffColorFilter.java b/android/graphics/PorterDuffColorFilter.java
new file mode 100644
index 0000000..01d5825
--- /dev/null
+++ b/android/graphics/PorterDuffColorFilter.java
@@ -0,0 +1,133 @@
+/*
+ * 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;
+
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+
+/**
+ * A color filter that can be used to tint the source pixels using a single
+ * color and a specific {@link PorterDuff Porter-Duff composite mode}.
+ */
+public class PorterDuffColorFilter extends ColorFilter {
+    @ColorInt
+    private int mColor;
+    private PorterDuff.Mode mMode;
+
+    /**
+     * Create a color filter that uses the specified color and Porter-Duff mode.
+     *
+     * @param color The ARGB source color used with the specified Porter-Duff mode
+     * @param mode The porter-duff mode that is applied
+     *
+     * @see Color
+     * @see #setColor(int)
+     * @see #setMode(android.graphics.PorterDuff.Mode)
+     */
+    public PorterDuffColorFilter(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
+        mColor = color;
+        mMode = mode;
+    }
+
+    /**
+     * Returns the ARGB color used to tint the source pixels when this filter
+     * is applied.
+     *
+     * @see Color
+     * @see #setColor(int)
+     *
+     * @hide
+     */
+    @ColorInt
+    public int getColor() {
+        return mColor;
+    }
+
+    /**
+     * Specifies the color to tint the source pixels with when this color
+     * filter is applied.
+     *
+     * @param color An ARGB {@link Color color}
+     *
+     * @see Color
+     * @see #getColor()
+     * @see #getMode()
+     *
+     * @hide
+     */
+    public void setColor(@ColorInt int color) {
+        if (mColor != color) {
+            mColor = color;
+            discardNativeInstance();
+        }
+    }
+
+    /**
+     * Returns the Porter-Duff mode used to composite this color filter's
+     * color with the source pixel when this filter is applied.
+     *
+     * @see PorterDuff
+     * @see #setMode(android.graphics.PorterDuff.Mode)
+     *
+     * @hide
+     */
+    public PorterDuff.Mode getMode() {
+        return mMode;
+    }
+
+    /**
+     * Specifies the Porter-Duff mode to use when compositing this color
+     * filter's color with the source pixel at draw time.
+     *
+     * @see PorterDuff
+     * @see #getMode()
+     * @see #getColor()
+     *
+     * @hide
+     */
+    public void setMode(@NonNull PorterDuff.Mode mode) {
+        if (mode == null) {
+            throw new IllegalArgumentException("mode must be non-null");
+        }
+        mMode = mode;
+        discardNativeInstance();
+    }
+
+    @Override
+    long createNativeInstance() {
+        return native_CreatePorterDuffFilter(mColor, mMode.nativeInt);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (object == null || getClass() != object.getClass()) {
+            return false;
+        }
+        final PorterDuffColorFilter other = (PorterDuffColorFilter) object;
+        return (mColor == other.mColor && mMode.nativeInt == other.mMode.nativeInt);
+    }
+
+    @Override
+    public int hashCode() {
+        return 31 *  mMode.hashCode() + mColor;
+    }
+
+    private static native long native_CreatePorterDuffFilter(int srcColor, int porterDuffMode);
+}
diff --git a/android/graphics/PorterDuffColorFilter_Delegate.java b/android/graphics/PorterDuffColorFilter_Delegate.java
new file mode 100644
index 0000000..ff3f19f
--- /dev/null
+++ b/android/graphics/PorterDuffColorFilter_Delegate.java
@@ -0,0 +1,124 @@
+/*
+ * 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;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.graphics.PorterDuff.Mode;
+
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+
+import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getComposite;
+import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getPorterDuffMode;
+
+/**
+ * Delegate implementing the native methods of android.graphics.PorterDuffColorFilter
+ *
+ * Through the layoutlib_create tool, the original native methods of PorterDuffColorFilter have
+ * been replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original PorterDuffColorFilter class.
+ *
+ * Because this extends {@link ColorFilter_Delegate}, there's no need to use a
+ * {@link DelegateManager}, as all the Shader classes will be added to the manager
+ * owned by {@link ColorFilter_Delegate}.
+ *
+ * @see ColorFilter_Delegate
+ *
+ */
+public class PorterDuffColorFilter_Delegate extends ColorFilter_Delegate {
+
+    // ---- delegate data ----
+
+    private final java.awt.Color mSrcColor;
+    private final Mode mMode;
+
+
+    // ---- Public Helper methods ----
+
+    @Override
+    public boolean isSupported() {
+        return true;
+    }
+
+    @Override
+    public String getSupportMessage() {
+        return "PorterDuff Color Filter is not supported for mode: " + mMode.name() + ".";
+    }
+
+    @Override
+    public void applyFilter(Graphics2D g, int width, int height) {
+        g.setComposite(getComposite(mMode, 0xFF));
+        g.setColor(mSrcColor);
+        g.fillRect(0, 0, width, height);
+    }
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static long native_CreatePorterDuffFilter(int srcColor, int porterDuffMode) {
+        PorterDuffColorFilter_Delegate newDelegate =
+                new PorterDuffColorFilter_Delegate(srcColor, porterDuffMode);
+        return sManager.addNewDelegate(newDelegate);
+    }
+
+
+    // ---- Private delegate/helper methods ----
+
+    private PorterDuffColorFilter_Delegate(int srcColor, int mode) {
+        mSrcColor = new java.awt.Color(srcColor, true /* hasAlpha */);
+        mMode = getCompatibleMode(getPorterDuffMode(mode));
+    }
+
+    // For filtering the colors, the src image should contain the "color" only for pixel values
+    // which are not transparent in the target image. But, we are using a simple rectangular image
+    // completely filled with color. Hence some Composite rules do not apply as intended. However,
+    // in such cases, they can usually be mapped to some other mode, which produces an approximately
+    // equivalent result.
+    private Mode getCompatibleMode(Mode mode) {
+        Mode m = mode;
+        // Modes that are directly supported:
+        // CLEAR, DST, SRC_IN, DST_IN, DST_OUT, SRC_ATOP, DARKEN, LIGHTEN, MULTIPLY, SCREEN,
+        // ADD, OVERLAY
+        switch (mode) {
+        // Modes that can be mapped to one of the supported modes.
+        case SRC:
+            m = Mode.SRC_IN;
+            break;
+        case SRC_OVER:
+            m = Mode.SRC_ATOP;
+            break;
+        case DST_OVER:
+            m = Mode.DST;
+            break;
+        case SRC_OUT:
+            m = Mode.CLEAR;
+            break;
+        case DST_ATOP:
+            m = Mode.DST_IN;
+            break;
+        case XOR:
+            m = Mode.DST_OUT;
+            break;
+        }
+        return m;
+    }
+}
diff --git a/android/graphics/PorterDuffXfermode.java b/android/graphics/PorterDuffXfermode.java
new file mode 100644
index 0000000..84d953d
--- /dev/null
+++ b/android/graphics/PorterDuffXfermode.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+/**
+ * <p>Specialized implementation of {@link Paint}'s
+ * {@link Paint#setXfermode(Xfermode) transfer mode}. Refer to the
+ * documentation of the {@link PorterDuff.Mode} enum for more
+ * information on the available alpha compositing and blending modes.</p>
+ */
+public class PorterDuffXfermode extends Xfermode {
+    /**
+     * Create an xfermode that uses the specified porter-duff mode.
+     *
+     * @param mode           The porter-duff mode that is applied
+     */
+    public PorterDuffXfermode(PorterDuff.Mode mode) {
+        porterDuffMode = mode.nativeInt;
+    }
+}
diff --git a/android/graphics/RadialGradient.java b/android/graphics/RadialGradient.java
new file mode 100644
index 0000000..f4b1191
--- /dev/null
+++ b/android/graphics/RadialGradient.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2007 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.ColorInt;
+
+public class RadialGradient extends Shader {
+
+    private static final int TYPE_COLORS_AND_POSITIONS = 1;
+    private static final int TYPE_COLOR_CENTER_AND_COLOR_EDGE = 2;
+
+    /**
+     * Type of the RadialGradient: can be either TYPE_COLORS_AND_POSITIONS or
+     * TYPE_COLOR_CENTER_AND_COLOR_EDGE.
+     */
+    private int mType;
+
+    private float mX;
+    private float mY;
+    private float mRadius;
+    private int[] mColors;
+    private float[] mPositions;
+    private int mCenterColor;
+    private int mEdgeColor;
+
+    private TileMode mTileMode;
+
+    /**
+     * Create a shader that draws a radial gradient given the center and radius.
+     *
+     * @param centerX  The x-coordinate of the center of the radius
+     * @param centerY  The y-coordinate of the center of the radius
+     * @param radius   Must be positive. The radius of the circle for this gradient.
+     * @param colors   The colors to be distributed between the center and edge of the circle
+     * @param stops    May be <code>null</code>. Valid values are between <code>0.0f</code> and
+     *                 <code>1.0f</code>. The relative position of each corresponding color in
+     *                 the colors array. If <code>null</code>, colors are distributed evenly
+     *                 between the center and edge of the circle.
+     * @param tileMode The Shader tiling mode
+     */
+    public RadialGradient(float centerX, float centerY, float radius,
+            @NonNull @ColorInt int colors[], @Nullable float stops[],
+            @NonNull TileMode tileMode) {
+        if (radius <= 0) {
+            throw new IllegalArgumentException("radius must be > 0");
+        }
+        if (colors.length < 2) {
+            throw new IllegalArgumentException("needs >= 2 number of colors");
+        }
+        if (stops != null && colors.length != stops.length) {
+            throw new IllegalArgumentException("color and position arrays must be of equal length");
+        }
+        mType = TYPE_COLORS_AND_POSITIONS;
+        mX = centerX;
+        mY = centerY;
+        mRadius = radius;
+        mColors = colors.clone();
+        mPositions = stops != null ? stops.clone() : null;
+        mTileMode = tileMode;
+    }
+
+    /**
+     * Create a shader that draws a radial gradient given the center and radius.
+     *
+     * @param centerX     The x-coordinate of the center of the radius
+     * @param centerY     The y-coordinate of the center of the radius
+     * @param radius      Must be positive. The radius of the circle for this gradient
+     * @param centerColor The color at the center of the circle.
+     * @param edgeColor   The color at the edge of the circle.
+     * @param tileMode    The Shader tiling mode
+     */
+    public RadialGradient(float centerX, float centerY, float radius,
+            @ColorInt int centerColor, @ColorInt int edgeColor, @NonNull TileMode tileMode) {
+        if (radius <= 0) {
+            throw new IllegalArgumentException("radius must be > 0");
+        }
+        mType = TYPE_COLOR_CENTER_AND_COLOR_EDGE;
+        mX = centerX;
+        mY = centerY;
+        mRadius = radius;
+        mCenterColor = centerColor;
+        mEdgeColor = edgeColor;
+        mTileMode = tileMode;
+    }
+
+    @Override
+    long createNativeInstance(long nativeMatrix) {
+        if (mType == TYPE_COLORS_AND_POSITIONS) {
+            return nativeCreate1(nativeMatrix, mX, mY, mRadius,
+                    mColors, mPositions, mTileMode.nativeInt);
+        } else { // TYPE_COLOR_CENTER_AND_COLOR_EDGE
+            return nativeCreate2(nativeMatrix, mX, mY, mRadius,
+                    mCenterColor, mEdgeColor, mTileMode.nativeInt);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    protected Shader copy() {
+        final RadialGradient copy;
+        if (mType == TYPE_COLORS_AND_POSITIONS) {
+            copy = new RadialGradient(mX, mY, mRadius, mColors.clone(),
+                    mPositions != null ? mPositions.clone() : null, mTileMode);
+        } else { // TYPE_COLOR_CENTER_AND_COLOR_EDGE
+            copy = new RadialGradient(mX, mY, mRadius, mCenterColor, mEdgeColor, mTileMode);
+        }
+        copyLocalMatrix(copy);
+        return copy;
+    }
+
+    private static native long nativeCreate1(long matrix, float x, float y, float radius,
+            int colors[], float positions[], int tileMode);
+    private static native long nativeCreate2(long matrix, float x, float y, float radius,
+            int color0, int color1, int tileMode);
+}
+
diff --git a/android/graphics/RadialGradient_Delegate.java b/android/graphics/RadialGradient_Delegate.java
new file mode 100644
index 0000000..1defc90
--- /dev/null
+++ b/android/graphics/RadialGradient_Delegate.java
@@ -0,0 +1,204 @@
+/*
+ * 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;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.graphics.Shader.TileMode;
+
+import java.awt.image.ColorModel;
+import java.awt.image.DataBufferInt;
+import java.awt.image.Raster;
+import java.awt.image.SampleModel;
+
+/**
+ * Delegate implementing the native methods of android.graphics.RadialGradient
+ *
+ * Through the layoutlib_create tool, the original native methods of RadialGradient have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original RadialGradient class.
+ *
+ * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager},
+ * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}.
+ *
+ * @see Shader_Delegate
+ *
+ */
+public class RadialGradient_Delegate extends Gradient_Delegate {
+
+    // ---- delegate data ----
+    private java.awt.Paint mJavaPaint;
+
+    // ---- Public Helper methods ----
+
+    @Override
+    public java.awt.Paint getJavaPaint() {
+        return mJavaPaint;
+    }
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeCreate1(long matrix, float x, float y, float radius,
+            int colors[], float positions[], int tileMode) {
+        RadialGradient_Delegate newDelegate = new RadialGradient_Delegate(matrix, x, y, radius,
+                colors, positions, Shader_Delegate.getTileMode(tileMode));
+        return sManager.addNewDelegate(newDelegate);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeCreate2(long matrix, float x, float y, float radius,
+            int color0, int color1, int tileMode) {
+        return nativeCreate1(matrix, x, y, radius, new int[] { color0, color1 },
+                null /*positions*/, tileMode);
+    }
+
+    // ---- Private delegate/helper methods ----
+
+    /**
+     * Create a shader that draws a radial gradient given the center and radius.
+     *
+     * @param nativeMatrix reference to the shader's native transformation matrix
+     * @param x The x-coordinate of the center of the radius
+     * @param y The y-coordinate of the center of the radius
+     * @param radius Must be positive. The radius of the circle for this
+     *            gradient
+     * @param colors The colors to be distributed between the center and edge of
+     *            the circle
+     * @param positions May be NULL. The relative position of each corresponding
+     *            color in the colors array. If this is NULL, the the colors are
+     *            distributed evenly between the center and edge of the circle.
+     * @param tile The Shader tiling mode
+     */
+    private RadialGradient_Delegate(long nativeMatrix, float x, float y, float radius,
+            int colors[], float positions[], TileMode tile) {
+        super(nativeMatrix, colors, positions);
+        mJavaPaint = new RadialGradientPaint(x, y, radius, mColors, mPositions, tile);
+    }
+
+    private class RadialGradientPaint extends GradientPaint {
+
+        private final float mX;
+        private final float mY;
+        private final float mRadius;
+
+        public RadialGradientPaint(float x, float y, float radius,
+                int[] colors, float[] positions, TileMode mode) {
+            super(colors, positions, mode);
+            mX = x;
+            mY = y;
+            mRadius = radius;
+        }
+
+        @Override
+        public java.awt.PaintContext createContext(
+                java.awt.image.ColorModel     colorModel,
+                java.awt.Rectangle            deviceBounds,
+                java.awt.geom.Rectangle2D     userBounds,
+                java.awt.geom.AffineTransform xform,
+                java.awt.RenderingHints       hints) {
+            precomputeGradientColors();
+
+            java.awt.geom.AffineTransform canvasMatrix;
+            try {
+                canvasMatrix = xform.createInverse();
+            } catch (java.awt.geom.NoninvertibleTransformException e) {
+                Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE,
+                        "Unable to inverse matrix in RadialGradient", e, null /*data*/);
+                canvasMatrix = new java.awt.geom.AffineTransform();
+            }
+
+            java.awt.geom.AffineTransform localMatrix = getLocalMatrix();
+            try {
+                localMatrix = localMatrix.createInverse();
+            } catch (java.awt.geom.NoninvertibleTransformException e) {
+                Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE,
+                        "Unable to inverse matrix in RadialGradient", e, null /*data*/);
+                localMatrix = new java.awt.geom.AffineTransform();
+            }
+
+            return new RadialGradientPaintContext(canvasMatrix, localMatrix, colorModel);
+        }
+
+        private class RadialGradientPaintContext implements java.awt.PaintContext {
+
+            private final java.awt.geom.AffineTransform mCanvasMatrix;
+            private final java.awt.geom.AffineTransform mLocalMatrix;
+            private final java.awt.image.ColorModel mColorModel;
+
+            public RadialGradientPaintContext(
+                    java.awt.geom.AffineTransform canvasMatrix,
+                    java.awt.geom.AffineTransform localMatrix,
+                    java.awt.image.ColorModel colorModel) {
+                mCanvasMatrix = canvasMatrix;
+                mLocalMatrix = localMatrix;
+                mColorModel = colorModel.hasAlpha() ? colorModel : ColorModel.getRGBdefault();
+            }
+
+            @Override
+            public void dispose() {
+            }
+
+            @Override
+            public java.awt.image.ColorModel getColorModel() {
+                return mColorModel;
+            }
+
+            @Override
+            public java.awt.image.Raster getRaster(int x, int y, int w, int h) {
+                int[] data = new int[w*h];
+
+                // compute distance from each point to the center, and figure out the distance from
+                // it.
+                int index = 0;
+                float[] pt1 = new float[2];
+                float[] pt2 = new float[2];
+                for (int iy = 0 ; iy < h ; iy++) {
+                    for (int ix = 0 ; ix < w ; ix++) {
+                        // handle the canvas transform
+                        pt1[0] = x + ix;
+                        pt1[1] = y + iy;
+                        mCanvasMatrix.transform(pt1, 0, pt2, 0, 1);
+
+                        // handle the local matrix
+                        pt1[0] = pt2[0] - mX;
+                        pt1[1] = pt2[1] - mY;
+                        mLocalMatrix.transform(pt1, 0, pt2, 0, 1);
+
+                        float _x = pt2[0];
+                        float _y = pt2[1];
+                        float distance = (float) Math.hypot(_x, _y);
+
+                        data[index++] = getGradientColor(distance / mRadius);
+                    }
+                }
+
+                DataBufferInt dataBuffer = new DataBufferInt(data, data.length);
+                SampleModel colorModel = mColorModel.createCompatibleSampleModel(w, h);
+                return Raster.createWritableRaster(colorModel, dataBuffer, null);
+            }
+
+        }
+    }
+
+}
diff --git a/android/graphics/Rasterizer.java b/android/graphics/Rasterizer.java
new file mode 100644
index 0000000..29d82fa
--- /dev/null
+++ b/android/graphics/Rasterizer.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+// This file was generated from the C++ include file: SkRasterizer.h
+// Any changes made to this file will be discarded by the build.
+// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, 
+// or one of the auxilary file specifications in device/tools/gluemaker.
+
+package android.graphics;
+
+/**
+ * @removed feature is not supported by hw-accerlerated or PDF backends
+ */
+public class Rasterizer {
+
+    protected void finalize() throws Throwable { }
+}
diff --git a/android/graphics/Rect.java b/android/graphics/Rect.java
new file mode 100644
index 0000000..3dc928d
--- /dev/null
+++ b/android/graphics/Rect.java
@@ -0,0 +1,673 @@
+/*
+ * 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;
+
+import android.annotation.CheckResult;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import android.text.TextUtils;
+import android.util.proto.ProtoOutputStream;
+import java.io.PrintWriter;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Rect holds four integer coordinates for a rectangle. The rectangle is
+ * represented by the coordinates of its 4 edges (left, top, right bottom).
+ * These fields can be accessed directly. Use width() and height() to retrieve
+ * the rectangle's width and height. Note: most methods do not check to see that
+ * the coordinates are sorted correctly (i.e. left <= right and top <= bottom).
+ * <p>
+ * Note that the right and bottom coordinates are exclusive. This means a Rect
+ * being drawn untransformed onto a {@link android.graphics.Canvas} will draw
+ * into the column and row described by its left and top coordinates, but not
+ * those of its bottom and right.
+ */
+public final class Rect implements Parcelable {
+    public int left;
+    public int top;
+    public int right;
+    public int bottom;
+
+    /**
+     * A helper class for flattened rectange pattern recognition. A separate
+     * class to avoid an initialization dependency on a regular expression
+     * causing Rect to not be initializable with an ahead-of-time compilation
+     * scheme.
+     */
+    private static final class UnflattenHelper {
+        private static final Pattern FLATTENED_PATTERN = Pattern.compile(
+            "(-?\\d+) (-?\\d+) (-?\\d+) (-?\\d+)");
+
+        static Matcher getMatcher(String str) {
+            return FLATTENED_PATTERN.matcher(str);
+        }
+    }
+
+    /**
+     * Create a new empty Rect. All coordinates are initialized to 0.
+     */
+    public Rect() {}
+
+    /**
+     * Create a new rectangle with the specified coordinates. Note: no range
+     * checking is performed, so the caller must ensure that left <= right and
+     * top <= bottom.
+     *
+     * @param left   The X coordinate of the left side of the rectangle
+     * @param top    The Y coordinate of the top of the rectangle
+     * @param right  The X coordinate of the right side of the rectangle
+     * @param bottom The Y coordinate of the bottom of the rectangle
+     */
+    public Rect(int left, int top, int right, int bottom) {
+        this.left = left;
+        this.top = top;
+        this.right = right;
+        this.bottom = bottom;
+    }
+
+    /**
+     * Create a new rectangle, initialized with the values in the specified
+     * rectangle (which is left unmodified).
+     *
+     * @param r The rectangle whose coordinates are copied into the new
+     *          rectangle.
+     */
+    public Rect(Rect r) {
+        if (r == null) {
+            left = top = right = bottom = 0;
+        } else {
+            left = r.left;
+            top = r.top;
+            right = r.right;
+            bottom = r.bottom;
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        Rect r = (Rect) o;
+        return left == r.left && top == r.top && right == r.right && bottom == r.bottom;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = left;
+        result = 31 * result + top;
+        result = 31 * result + right;
+        result = 31 * result + bottom;
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(32);
+        sb.append("Rect("); sb.append(left); sb.append(", ");
+        sb.append(top); sb.append(" - "); sb.append(right);
+        sb.append(", "); sb.append(bottom); sb.append(")");
+        return sb.toString();
+    }
+
+    /**
+     * Return a string representation of the rectangle in a compact form.
+     */
+    public String toShortString() {
+        return toShortString(new StringBuilder(32));
+    }
+    
+    /**
+     * Return a string representation of the rectangle in a compact form.
+     * @hide
+     */
+    public String toShortString(StringBuilder sb) {
+        sb.setLength(0);
+        sb.append('['); sb.append(left); sb.append(',');
+        sb.append(top); sb.append("]["); sb.append(right);
+        sb.append(','); sb.append(bottom); sb.append(']');
+        return sb.toString();
+    }
+
+    /**
+     * Return a string representation of the rectangle in a well-defined format.
+     *
+     * <p>You can later recover the Rect from this string through
+     * {@link #unflattenFromString(String)}.
+     * 
+     * @return Returns a new String of the form "left top right bottom"
+     */
+    public String flattenToString() {
+        StringBuilder sb = new StringBuilder(32);
+        // WARNING: Do not change the format of this string, it must be
+        // preserved because Rects are saved in this flattened format.
+        sb.append(left);
+        sb.append(' ');
+        sb.append(top);
+        sb.append(' ');
+        sb.append(right);
+        sb.append(' ');
+        sb.append(bottom);
+        return sb.toString();
+    }
+
+    /**
+     * Returns a Rect from a string of the form returned by {@link #flattenToString},
+     * or null if the string is not of that form.
+     */
+    public static Rect unflattenFromString(String str) {
+        if (TextUtils.isEmpty(str)) {
+            return null;
+        }
+
+        Matcher matcher = UnflattenHelper.getMatcher(str);
+        if (!matcher.matches()) {
+            return null;
+        }
+        return new Rect(Integer.parseInt(matcher.group(1)),
+                Integer.parseInt(matcher.group(2)),
+                Integer.parseInt(matcher.group(3)),
+                Integer.parseInt(matcher.group(4)));
+    }
+
+    /**
+     * Print short representation to given writer.
+     * @hide
+     */
+    public void printShortString(PrintWriter pw) {
+        pw.print('['); pw.print(left); pw.print(',');
+        pw.print(top); pw.print("]["); pw.print(right);
+        pw.print(','); pw.print(bottom); pw.print(']');
+    }
+
+    /**
+     * Write to a protocol buffer output stream.
+     * Protocol buffer message definition at {@link android.graphics.RectProto}
+     *
+     * @param protoOutputStream Stream to write the Rect object to.
+     * @param fieldId           Field Id of the Rect as defined in the parent message
+     * @hide
+     */
+    public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
+        final long token = protoOutputStream.start(fieldId);
+        protoOutputStream.write(RectProto.LEFT, left);
+        protoOutputStream.write(RectProto.TOP, top);
+        protoOutputStream.write(RectProto.RIGHT, right);
+        protoOutputStream.write(RectProto.BOTTOM, bottom);
+        protoOutputStream.end(token);
+    }
+
+    /**
+     * Returns true if the rectangle is empty (left >= right or top >= bottom)
+     */
+    public final boolean isEmpty() {
+        return left >= right || top >= bottom;
+    }
+
+    /**
+     * @return the rectangle's width. This does not check for a valid rectangle
+     * (i.e. left <= right) so the result may be negative.
+     */
+    public final int width() {
+        return right - left;
+    }
+
+    /**
+     * @return the rectangle's height. This does not check for a valid rectangle
+     * (i.e. top <= bottom) so the result may be negative.
+     */
+    public final int height() {
+        return bottom - top;
+    }
+    
+    /**
+     * @return the horizontal center of the rectangle. If the computed value
+     *         is fractional, this method returns the largest integer that is
+     *         less than the computed value.
+     */
+    public final int centerX() {
+        return (left + right) >> 1;
+    }
+    
+    /**
+     * @return the vertical center of the rectangle. If the computed value
+     *         is fractional, this method returns the largest integer that is
+     *         less than the computed value.
+     */
+    public final int centerY() {
+        return (top + bottom) >> 1;
+    }
+    
+    /**
+     * @return the exact horizontal center of the rectangle as a float.
+     */
+    public final float exactCenterX() {
+        return (left + right) * 0.5f;
+    }
+    
+    /**
+     * @return the exact vertical center of the rectangle as a float.
+     */
+    public final float exactCenterY() {
+        return (top + bottom) * 0.5f;
+    }
+
+    /**
+     * Set the rectangle to (0,0,0,0)
+     */
+    public void setEmpty() {
+        left = right = top = bottom = 0;
+    }
+
+    /**
+     * Set the rectangle's coordinates to the specified values. Note: no range
+     * checking is performed, so it is up to the caller to ensure that
+     * left <= right and top <= bottom.
+     *
+     * @param left   The X coordinate of the left side of the rectangle
+     * @param top    The Y coordinate of the top of the rectangle
+     * @param right  The X coordinate of the right side of the rectangle
+     * @param bottom The Y coordinate of the bottom of the rectangle
+     */
+    public void set(int left, int top, int right, int bottom) {
+        this.left = left;
+        this.top = top;
+        this.right = right;
+        this.bottom = bottom;
+    }
+
+    /**
+     * Copy the coordinates from src into this rectangle.
+     *
+     * @param src The rectangle whose coordinates are copied into this
+     *           rectangle.
+     */
+    public void set(Rect src) {
+        this.left = src.left;
+        this.top = src.top;
+        this.right = src.right;
+        this.bottom = src.bottom;
+    }
+
+    /**
+     * Offset the rectangle by adding dx to its left and right coordinates, and
+     * adding dy to its top and bottom coordinates.
+     *
+     * @param dx The amount to add to the rectangle's left and right coordinates
+     * @param dy The amount to add to the rectangle's top and bottom coordinates
+     */
+    public void offset(int dx, int dy) {
+        left += dx;
+        top += dy;
+        right += dx;
+        bottom += dy;
+    }
+
+    /**
+     * Offset the rectangle to a specific (left, top) position,
+     * keeping its width and height the same.
+     *
+     * @param newLeft   The new "left" coordinate for the rectangle
+     * @param newTop    The new "top" coordinate for the rectangle
+     */
+    public void offsetTo(int newLeft, int newTop) {
+        right += newLeft - left;
+        bottom += newTop - top;
+        left = newLeft;
+        top = newTop;
+    }
+
+    /**
+     * Inset the rectangle by (dx,dy). If dx is positive, then the sides are
+     * moved inwards, making the rectangle narrower. If dx is negative, then the
+     * sides are moved outwards, making the rectangle wider. The same holds true
+     * for dy and the top and bottom.
+     *
+     * @param dx The amount to add(subtract) from the rectangle's left(right)
+     * @param dy The amount to add(subtract) from the rectangle's top(bottom)
+     */
+    public void inset(int dx, int dy) {
+        left += dx;
+        top += dy;
+        right -= dx;
+        bottom -= dy;
+    }
+
+    /**
+     * Insets the rectangle on all sides specified by the dimensions of the {@code insets}
+     * rectangle.
+     * @hide
+     * @param insets The rectangle specifying the insets on all side.
+     */
+    public void inset(Rect insets) {
+        left += insets.left;
+        top += insets.top;
+        right -= insets.right;
+        bottom -= insets.bottom;
+    }
+
+    /**
+     * Insets the rectangle on all sides specified by the insets.
+     * @hide
+     * @param left The amount to add from the rectangle's left
+     * @param top The amount to add from the rectangle's top
+     * @param right The amount to subtract from the rectangle's right
+     * @param bottom The amount to subtract from the rectangle's bottom
+     */
+    public void inset(int left, int top, int right, int bottom) {
+        this.left += left;
+        this.top += top;
+        this.right -= right;
+        this.bottom -= bottom;
+    }
+
+    /**
+     * Returns true if (x,y) is inside the rectangle. The left and top are
+     * considered to be inside, while the right and bottom are not. This means
+     * that for a x,y to be contained: left <= x < right and top <= y < bottom.
+     * An empty rectangle never contains any point.
+     *
+     * @param x The X coordinate of the point being tested for containment
+     * @param y The Y coordinate of the point being tested for containment
+     * @return true iff (x,y) are contained by the rectangle, where containment
+     *              means left <= x < right and top <= y < bottom
+     */
+    public boolean contains(int x, int y) {
+        return left < right && top < bottom  // check for empty first
+               && x >= left && x < right && y >= top && y < bottom;
+    }
+
+    /**
+     * Returns true iff the 4 specified sides of a rectangle are inside or equal
+     * to this rectangle. i.e. is this rectangle a superset of the specified
+     * rectangle. An empty rectangle never contains another rectangle.
+     *
+     * @param left The left side of the rectangle being tested for containment
+     * @param top The top of the rectangle being tested for containment
+     * @param right The right side of the rectangle being tested for containment
+     * @param bottom The bottom of the rectangle being tested for containment
+     * @return true iff the the 4 specified sides of a rectangle are inside or
+     *              equal to this rectangle
+     */
+    public boolean contains(int left, int top, int right, int bottom) {
+               // check for empty first
+        return this.left < this.right && this.top < this.bottom
+               // now check for containment
+                && this.left <= left && this.top <= top
+                && this.right >= right && this.bottom >= bottom;
+    }
+
+    /**
+     * Returns true iff the specified rectangle r is inside or equal to this
+     * rectangle. An empty rectangle never contains another rectangle.
+     *
+     * @param r The rectangle being tested for containment.
+     * @return true iff the specified rectangle r is inside or equal to this
+     *              rectangle
+     */
+    public boolean contains(Rect r) {
+               // check for empty first
+        return this.left < this.right && this.top < this.bottom
+               // now check for containment
+               && left <= r.left && top <= r.top && right >= r.right && bottom >= r.bottom;
+    }
+
+    /**
+     * If the rectangle specified by left,top,right,bottom intersects this
+     * rectangle, return true and set this rectangle to that intersection,
+     * otherwise return false and do not change this rectangle. No check is
+     * performed to see if either rectangle is empty. Note: To just test for
+     * intersection, use {@link #intersects(Rect, Rect)}.
+     *
+     * @param left The left side of the rectangle being intersected with this
+     *             rectangle
+     * @param top The top of the rectangle being intersected with this rectangle
+     * @param right The right side of the rectangle being intersected with this
+     *              rectangle.
+     * @param bottom The bottom of the rectangle being intersected with this
+     *             rectangle.
+     * @return true if the specified rectangle and this rectangle intersect
+     *              (and this rectangle is then set to that intersection) else
+     *              return false and do not change this rectangle.
+     */
+    @CheckResult
+    public boolean intersect(int left, int top, int right, int bottom) {
+        if (this.left < right && left < this.right && this.top < bottom && top < this.bottom) {
+            if (this.left < left) this.left = left;
+            if (this.top < top) this.top = top;
+            if (this.right > right) this.right = right;
+            if (this.bottom > bottom) this.bottom = bottom;
+            return true;
+        }
+        return false;
+    }
+    
+    /**
+     * If the specified rectangle intersects this rectangle, return true and set
+     * this rectangle to that intersection, otherwise return false and do not
+     * change this rectangle. No check is performed to see if either rectangle
+     * is empty. To just test for intersection, use intersects()
+     *
+     * @param r The rectangle being intersected with this rectangle.
+     * @return true if the specified rectangle and this rectangle intersect
+     *              (and this rectangle is then set to that intersection) else
+     *              return false and do not change this rectangle.
+     */
+    @CheckResult
+    public boolean intersect(Rect r) {
+        return intersect(r.left, r.top, r.right, r.bottom);
+    }
+
+    /**
+     * If rectangles a and b intersect, return true and set this rectangle to
+     * that intersection, otherwise return false and do not change this
+     * rectangle. No check is performed to see if either rectangle is empty.
+     * To just test for intersection, use intersects()
+     *
+     * @param a The first rectangle being intersected with
+     * @param b The second rectangle being intersected with
+     * @return true iff the two specified rectangles intersect. If they do, set
+     *              this rectangle to that intersection. If they do not, return
+     *              false and do not change this rectangle.
+     */
+    @CheckResult
+    public boolean setIntersect(Rect a, Rect b) {
+        if (a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom) {
+            left = Math.max(a.left, b.left);
+            top = Math.max(a.top, b.top);
+            right = Math.min(a.right, b.right);
+            bottom = Math.min(a.bottom, b.bottom);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if this rectangle intersects the specified rectangle.
+     * In no event is this rectangle modified. No check is performed to see
+     * if either rectangle is empty. To record the intersection, use intersect()
+     * or setIntersect().
+     *
+     * @param left The left side of the rectangle being tested for intersection
+     * @param top The top of the rectangle being tested for intersection
+     * @param right The right side of the rectangle being tested for
+     *              intersection
+     * @param bottom The bottom of the rectangle being tested for intersection
+     * @return true iff the specified rectangle intersects this rectangle. In
+     *              no event is this rectangle modified.
+     */
+    public boolean intersects(int left, int top, int right, int bottom) {
+        return this.left < right && left < this.right && this.top < bottom && top < this.bottom;
+    }
+
+    /**
+     * Returns true iff the two specified rectangles intersect. In no event are
+     * either of the rectangles modified. To record the intersection,
+     * use {@link #intersect(Rect)} or {@link #setIntersect(Rect, Rect)}.
+     *
+     * @param a The first rectangle being tested for intersection
+     * @param b The second rectangle being tested for intersection
+     * @return true iff the two specified rectangles intersect. In no event are
+     *              either of the rectangles modified.
+     */
+    public static boolean intersects(Rect a, Rect b) {
+        return a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom;
+    }
+
+    /**
+     * Update this Rect to enclose itself and the specified rectangle. If the
+     * specified rectangle is empty, nothing is done. If this rectangle is empty
+     * it is set to the specified rectangle.
+     *
+     * @param left The left edge being unioned with this rectangle
+     * @param top The top edge being unioned with this rectangle
+     * @param right The right edge being unioned with this rectangle
+     * @param bottom The bottom edge being unioned with this rectangle
+     */
+    public void union(int left, int top, int right, int bottom) {
+        if ((left < right) && (top < bottom)) {
+            if ((this.left < this.right) && (this.top < this.bottom)) {
+                if (this.left > left) this.left = left;
+                if (this.top > top) this.top = top;
+                if (this.right < right) this.right = right;
+                if (this.bottom < bottom) this.bottom = bottom;
+            } else {
+                this.left = left;
+                this.top = top;
+                this.right = right;
+                this.bottom = bottom;
+            }
+        }
+    }
+
+    /**
+     * Update this Rect to enclose itself and the specified rectangle. If the
+     * specified rectangle is empty, nothing is done. If this rectangle is empty
+     * it is set to the specified rectangle.
+     *
+     * @param r The rectangle being unioned with this rectangle
+     */
+    public void union(Rect r) {
+        union(r.left, r.top, r.right, r.bottom);
+    }
+    
+    /**
+     * Update this Rect to enclose itself and the [x,y] coordinate. There is no
+     * check to see that this rectangle is non-empty.
+     *
+     * @param x The x coordinate of the point to add to the rectangle
+     * @param y The y coordinate of the point to add to the rectangle
+     */
+    public void union(int x, int y) {
+        if (x < left) {
+            left = x;
+        } else if (x > right) {
+            right = x;
+        }
+        if (y < top) {
+            top = y;
+        } else if (y > bottom) {
+            bottom = y;
+        }
+    }
+
+    /**
+     * Swap top/bottom or left/right if there are flipped (i.e. left > right
+     * and/or top > bottom). This can be called if
+     * the edges are computed separately, and may have crossed over each other.
+     * If the edges are already correct (i.e. left <= right and top <= bottom)
+     * then nothing is done.
+     */
+    public void sort() {
+        if (left > right) {
+            int temp = left;
+            left = right;
+            right = temp;
+        }
+        if (top > bottom) {
+            int temp = top;
+            top = bottom;
+            bottom = temp;
+        }
+    }
+
+    /**
+     * Parcelable interface methods
+     */
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Write this rectangle to the specified parcel. To restore a rectangle from
+     * a parcel, use readFromParcel()
+     * @param out The parcel to write the rectangle's coordinates into
+     */
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(left);
+        out.writeInt(top);
+        out.writeInt(right);
+        out.writeInt(bottom);
+    }
+
+    public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() {
+        /**
+         * Return a new rectangle from the data in the specified parcel.
+         */
+        public Rect createFromParcel(Parcel in) {
+            Rect r = new Rect();
+            r.readFromParcel(in);
+            return r;
+        }
+
+        /**
+         * Return an array of rectangles of the specified size.
+         */
+        public Rect[] newArray(int size) {
+            return new Rect[size];
+        }
+    };
+
+    /**
+     * Set the rectangle's coordinates from the data stored in the specified
+     * parcel. To write a rectangle to a parcel, call writeToParcel().
+     *
+     * @param in The parcel to read the rectangle's coordinates from
+     */
+    public void readFromParcel(Parcel in) {
+        left = in.readInt();
+        top = in.readInt();
+        right = in.readInt();
+        bottom = in.readInt();
+    }
+
+    /**
+     * Scales up the rect by the given scale.
+     * @hide
+     */
+    public void scale(float scale) {
+        if (scale != 1.0f) {
+            left = (int) (left * scale + 0.5f);
+            top = (int) (top * scale + 0.5f);
+            right = (int) (right * scale + 0.5f);
+            bottom = (int) (bottom * scale + 0.5f);
+        }
+    }
+
+}
diff --git a/android/graphics/RectF.java b/android/graphics/RectF.java
new file mode 100644
index 0000000..b490545
--- /dev/null
+++ b/android/graphics/RectF.java
@@ -0,0 +1,599 @@
+/*
+ * 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;
+
+import java.io.PrintWriter;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import com.android.internal.util.FastMath;
+
+/**
+ * RectF holds four float coordinates for a rectangle. The rectangle is
+ * represented by the coordinates of its 4 edges (left, top, right bottom).
+ * These fields can be accessed directly. Use width() and height() to retrieve
+ * the rectangle's width and height. Note: most methods do not check to see that
+ * the coordinates are sorted correctly (i.e. left <= right and top <= bottom).
+ */
+public class RectF implements Parcelable {
+    public float left;
+    public float top;
+    public float right;
+    public float bottom;
+    
+    /**
+     * Create a new empty RectF. All coordinates are initialized to 0.
+     */
+    public RectF() {}
+
+    /**
+     * Create a new rectangle with the specified coordinates. Note: no range
+     * checking is performed, so the caller must ensure that left <= right and
+     * top <= bottom.
+     *
+     * @param left   The X coordinate of the left side of the rectangle
+     * @param top    The Y coordinate of the top of the rectangle
+     * @param right  The X coordinate of the right side of the rectangle
+     * @param bottom The Y coordinate of the bottom of the rectangle
+     */
+    public RectF(float left, float top, float right, float bottom) {
+        this.left = left;
+        this.top = top;
+        this.right = right;
+        this.bottom = bottom;
+    }
+
+    /**
+     * Create a new rectangle, initialized with the values in the specified
+     * rectangle (which is left unmodified).
+     *
+     * @param r The rectangle whose coordinates are copied into the new
+     *          rectangle.
+     */
+    public RectF(RectF r) {
+        if (r == null) {
+            left = top = right = bottom = 0.0f;
+        } else {
+            left = r.left;
+            top = r.top;
+            right = r.right;
+            bottom = r.bottom;
+        }
+    }
+    
+    public RectF(Rect r) {
+        if (r == null) {
+            left = top = right = bottom = 0.0f;
+        } else {
+            left = r.left;
+            top = r.top;
+            right = r.right;
+            bottom = r.bottom;
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        RectF r = (RectF) o;
+        return left == r.left && top == r.top && right == r.right && bottom == r.bottom;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = (left != +0.0f ? Float.floatToIntBits(left) : 0);
+        result = 31 * result + (top != +0.0f ? Float.floatToIntBits(top) : 0);
+        result = 31 * result + (right != +0.0f ? Float.floatToIntBits(right) : 0);
+        result = 31 * result + (bottom != +0.0f ? Float.floatToIntBits(bottom) : 0);
+        return result;
+    }
+
+    public String toString() {
+        return "RectF(" + left + ", " + top + ", "
+                      + right + ", " + bottom + ")";
+    }
+
+    /**
+     * Return a string representation of the rectangle in a compact form.
+     */
+    public String toShortString() {
+        return toShortString(new StringBuilder(32));
+    }
+    
+    /**
+     * Return a string representation of the rectangle in a compact form.
+     * @hide
+     */
+    public String toShortString(StringBuilder sb) {
+        sb.setLength(0);
+        sb.append('['); sb.append(left); sb.append(',');
+        sb.append(top); sb.append("]["); sb.append(right);
+        sb.append(','); sb.append(bottom); sb.append(']');
+        return sb.toString();
+    }
+    
+    /**
+     * Print short representation to given writer.
+     * @hide
+     */
+    public void printShortString(PrintWriter pw) {
+        pw.print('['); pw.print(left); pw.print(',');
+        pw.print(top); pw.print("]["); pw.print(right);
+        pw.print(','); pw.print(bottom); pw.print(']');
+    }
+
+    /**
+     * Returns true if the rectangle is empty (left >= right or top >= bottom)
+     */
+    public final boolean isEmpty() {
+        return left >= right || top >= bottom;
+    }
+
+    /**
+     * @return the rectangle's width. This does not check for a valid rectangle
+     * (i.e. left <= right) so the result may be negative.
+     */
+    public final float width() {
+        return right - left;
+    }
+
+    /**
+     * @return the rectangle's height. This does not check for a valid rectangle
+     * (i.e. top <= bottom) so the result may be negative.
+     */
+    public final float height() {
+        return bottom - top;
+    }
+
+    /**
+     * @return the horizontal center of the rectangle. This does not check for
+     * a valid rectangle (i.e. left <= right)
+     */
+    public final float centerX() {
+        return (left + right) * 0.5f;
+    }
+
+    /**
+     * @return the vertical center of the rectangle. This does not check for
+     * a valid rectangle (i.e. top <= bottom)
+     */
+    public final float centerY() {
+        return (top + bottom) * 0.5f;
+    }
+    
+    /**
+     * Set the rectangle to (0,0,0,0)
+     */
+    public void setEmpty() {
+        left = right = top = bottom = 0;
+    }
+    
+    /**
+     * Set the rectangle's coordinates to the specified values. Note: no range
+     * checking is performed, so it is up to the caller to ensure that
+     * left <= right and top <= bottom.
+     *
+     * @param left   The X coordinate of the left side of the rectangle
+     * @param top    The Y coordinate of the top of the rectangle
+     * @param right  The X coordinate of the right side of the rectangle
+     * @param bottom The Y coordinate of the bottom of the rectangle
+     */
+    public void set(float left, float top, float right, float bottom) {
+        this.left   = left;
+        this.top    = top;
+        this.right  = right;
+        this.bottom = bottom;
+    }
+
+    /**
+     * Copy the coordinates from src into this rectangle.
+     *
+     * @param src The rectangle whose coordinates are copied into this
+     *           rectangle.
+     */
+    public void set(RectF src) {
+        this.left   = src.left;
+        this.top    = src.top;
+        this.right  = src.right;
+        this.bottom = src.bottom;
+    }
+    
+    /**
+     * Copy the coordinates from src into this rectangle.
+     *
+     * @param src The rectangle whose coordinates are copied into this
+     *           rectangle.
+     */
+    public void set(Rect src) {
+        this.left   = src.left;
+        this.top    = src.top;
+        this.right  = src.right;
+        this.bottom = src.bottom;
+    }
+
+    /**
+     * Offset the rectangle by adding dx to its left and right coordinates, and
+     * adding dy to its top and bottom coordinates.
+     *
+     * @param dx The amount to add to the rectangle's left and right coordinates
+     * @param dy The amount to add to the rectangle's top and bottom coordinates
+     */
+    public void offset(float dx, float dy) {
+        left    += dx;
+        top     += dy;
+        right   += dx;
+        bottom  += dy;
+    }
+
+    /**
+     * Offset the rectangle to a specific (left, top) position,
+     * keeping its width and height the same.
+     *
+     * @param newLeft   The new "left" coordinate for the rectangle
+     * @param newTop    The new "top" coordinate for the rectangle
+     */
+    public void offsetTo(float newLeft, float newTop) {
+        right += newLeft - left;
+        bottom += newTop - top;
+        left = newLeft;
+        top = newTop;
+    }
+    
+    /**
+     * Inset the rectangle by (dx,dy). If dx is positive, then the sides are
+     * moved inwards, making the rectangle narrower. If dx is negative, then the
+     * sides are moved outwards, making the rectangle wider. The same holds true
+     * for dy and the top and bottom.
+     *
+     * @param dx The amount to add(subtract) from the rectangle's left(right)
+     * @param dy The amount to add(subtract) from the rectangle's top(bottom)
+     */
+    public void inset(float dx, float dy) {
+        left    += dx;
+        top     += dy;
+        right   -= dx;
+        bottom  -= dy;
+    }
+
+    /**
+     * Returns true if (x,y) is inside the rectangle. The left and top are
+     * considered to be inside, while the right and bottom are not. This means
+     * that for a x,y to be contained: left <= x < right and top <= y < bottom.
+     * An empty rectangle never contains any point.
+     *
+     * @param x The X coordinate of the point being tested for containment
+     * @param y The Y coordinate of the point being tested for containment
+     * @return true iff (x,y) are contained by the rectangle, where containment
+     *              means left <= x < right and top <= y < bottom
+     */
+    public boolean contains(float x, float y) {
+        return left < right && top < bottom  // check for empty first
+                && x >= left && x < right && y >= top && y < bottom;
+    }
+    
+    /**
+     * Returns true iff the 4 specified sides of a rectangle are inside or equal
+     * to this rectangle. i.e. is this rectangle a superset of the specified
+     * rectangle. An empty rectangle never contains another rectangle.
+     *
+     * @param left The left side of the rectangle being tested for containment
+     * @param top The top of the rectangle being tested for containment
+     * @param right The right side of the rectangle being tested for containment
+     * @param bottom The bottom of the rectangle being tested for containment
+     * @return true iff the the 4 specified sides of a rectangle are inside or
+     *              equal to this rectangle
+     */
+    public boolean contains(float left, float top, float right, float bottom) {
+                // check for empty first
+        return this.left < this.right && this.top < this.bottom
+                // now check for containment
+                && this.left <= left && this.top <= top
+                && this.right >= right && this.bottom >= bottom;
+    }
+    
+    /**
+     * Returns true iff the specified rectangle r is inside or equal to this
+     * rectangle. An empty rectangle never contains another rectangle.
+     *
+     * @param r The rectangle being tested for containment.
+     * @return true iff the specified rectangle r is inside or equal to this
+     *              rectangle
+     */
+    public boolean contains(RectF r) {
+                // check for empty first
+        return this.left < this.right && this.top < this.bottom
+                // now check for containment
+                && left <= r.left && top <= r.top
+                && right >= r.right && bottom >= r.bottom;
+    }
+    
+    /**
+     * If the rectangle specified by left,top,right,bottom intersects this
+     * rectangle, return true and set this rectangle to that intersection,
+     * otherwise return false and do not change this rectangle. No check is
+     * performed to see if either rectangle is empty. Note: To just test for
+     * intersection, use intersects()
+     *
+     * @param left The left side of the rectangle being intersected with this
+     *             rectangle
+     * @param top The top of the rectangle being intersected with this rectangle
+     * @param right The right side of the rectangle being intersected with this
+     *              rectangle.
+     * @param bottom The bottom of the rectangle being intersected with this
+     *             rectangle.
+     * @return true if the specified rectangle and this rectangle intersect
+     *              (and this rectangle is then set to that intersection) else
+     *              return false and do not change this rectangle.
+     */
+    public boolean intersect(float left, float top, float right, float bottom) {
+        if (this.left < right && left < this.right
+                && this.top < bottom && top < this.bottom) {
+            if (this.left < left) {
+                this.left = left;
+            }
+            if (this.top < top) {
+                this.top = top;
+            }
+            if (this.right > right) {
+                this.right = right;
+            }
+            if (this.bottom > bottom) {
+                this.bottom = bottom;
+            }
+            return true;
+        }
+        return false;
+    }
+    
+    /**
+     * If the specified rectangle intersects this rectangle, return true and set
+     * this rectangle to that intersection, otherwise return false and do not
+     * change this rectangle. No check is performed to see if either rectangle
+     * is empty. To just test for intersection, use intersects()
+     *
+     * @param r The rectangle being intersected with this rectangle.
+     * @return true if the specified rectangle and this rectangle intersect
+     *              (and this rectangle is then set to that intersection) else
+     *              return false and do not change this rectangle.
+     */
+    public boolean intersect(RectF r) {
+        return intersect(r.left, r.top, r.right, r.bottom);
+    }
+    
+    /**
+     * If rectangles a and b intersect, return true and set this rectangle to
+     * that intersection, otherwise return false and do not change this
+     * rectangle. No check is performed to see if either rectangle is empty.
+     * To just test for intersection, use intersects()
+     *
+     * @param a The first rectangle being intersected with
+     * @param b The second rectangle being intersected with
+     * @return true iff the two specified rectangles intersect. If they do, set
+     *              this rectangle to that intersection. If they do not, return
+     *              false and do not change this rectangle.
+     */
+    public boolean setIntersect(RectF a, RectF b) {
+        if (a.left < b.right && b.left < a.right
+                && a.top < b.bottom && b.top < a.bottom) {
+            left = Math.max(a.left, b.left);
+            top = Math.max(a.top, b.top);
+            right = Math.min(a.right, b.right);
+            bottom = Math.min(a.bottom, b.bottom);
+            return true;
+        }
+        return false;
+    }
+    
+    /**
+     * Returns true if this rectangle intersects the specified rectangle.
+     * In no event is this rectangle modified. No check is performed to see
+     * if either rectangle is empty. To record the intersection, use intersect()
+     * or setIntersect().
+     *
+     * @param left The left side of the rectangle being tested for intersection
+     * @param top The top of the rectangle being tested for intersection
+     * @param right The right side of the rectangle being tested for
+     *              intersection
+     * @param bottom The bottom of the rectangle being tested for intersection
+     * @return true iff the specified rectangle intersects this rectangle. In
+     *              no event is this rectangle modified.
+     */
+    public boolean intersects(float left, float top, float right,
+                              float bottom) {
+        return this.left < right && left < this.right
+                && this.top < bottom && top < this.bottom;
+    }
+    
+    /**
+     * Returns true iff the two specified rectangles intersect. In no event are
+     * either of the rectangles modified. To record the intersection,
+     * use intersect() or setIntersect().
+     *
+     * @param a The first rectangle being tested for intersection
+     * @param b The second rectangle being tested for intersection
+     * @return true iff the two specified rectangles intersect. In no event are
+     *              either of the rectangles modified.
+     */
+    public static boolean intersects(RectF a, RectF b) {
+        return a.left < b.right && b.left < a.right
+                && a.top < b.bottom && b.top < a.bottom;
+    }
+    
+    /**
+     * Set the dst integer Rect by rounding this rectangle's coordinates
+     * to their nearest integer values.
+     */
+    public void round(Rect dst) {
+        dst.set(FastMath.round(left), FastMath.round(top),
+                FastMath.round(right), FastMath.round(bottom));
+    }
+
+    /**
+     * Set the dst integer Rect by rounding "out" this rectangle, choosing the
+     * floor of top and left, and the ceiling of right and bottom.
+     */
+    public void roundOut(Rect dst) {
+        dst.set((int) Math.floor(left), (int) Math.floor(top),
+                (int) Math.ceil(right), (int) Math.ceil(bottom));
+    }
+
+    /**
+     * Update this Rect to enclose itself and the specified rectangle. If the
+     * specified rectangle is empty, nothing is done. If this rectangle is empty
+     * it is set to the specified rectangle.
+     *
+     * @param left The left edge being unioned with this rectangle
+     * @param top The top edge being unioned with this rectangle
+     * @param right The right edge being unioned with this rectangle
+     * @param bottom The bottom edge being unioned with this rectangle
+     */
+    public void union(float left, float top, float right, float bottom) {
+        if ((left < right) && (top < bottom)) {
+            if ((this.left < this.right) && (this.top < this.bottom)) {
+                if (this.left > left)
+                    this.left = left;
+                if (this.top > top)
+                    this.top = top;
+                if (this.right < right)
+                    this.right = right;
+                if (this.bottom < bottom)
+                    this.bottom = bottom;
+            } else {
+                this.left = left;
+                this.top = top;
+                this.right = right;
+                this.bottom = bottom;
+            }
+        }
+    }
+    
+    /**
+     * Update this Rect to enclose itself and the specified rectangle. If the
+     * specified rectangle is empty, nothing is done. If this rectangle is empty
+     * it is set to the specified rectangle.
+     *
+     * @param r The rectangle being unioned with this rectangle
+     */
+    public void union(RectF r) {
+        union(r.left, r.top, r.right, r.bottom);
+    }
+    
+    /**
+     * Update this Rect to enclose itself and the [x,y] coordinate. There is no
+     * check to see that this rectangle is non-empty.
+     *
+     * @param x The x coordinate of the point to add to the rectangle
+     * @param y The y coordinate of the point to add to the rectangle
+     */
+    public void union(float x, float y) {
+        if (x < left) {
+            left = x;
+        } else if (x > right) {
+            right = x;
+        }
+        if (y < top) {
+            top = y;
+        } else if (y > bottom) {
+            bottom = y;
+        }
+    }
+    
+    /**
+     * Swap top/bottom or left/right if there are flipped (i.e. left > right
+     * and/or top > bottom). This can be called if
+     * the edges are computed separately, and may have crossed over each other.
+     * If the edges are already correct (i.e. left <= right and top <= bottom)
+     * then nothing is done.
+     */
+    public void sort() {
+        if (left > right) {
+            float temp = left;
+            left = right;
+            right = temp;
+        }
+        if (top > bottom) {
+            float temp = top;
+            top = bottom;
+            bottom = temp;
+        }
+    }
+
+    /**
+     * Parcelable interface methods
+     */
+    public int describeContents() {
+        return 0;
+    }
+    
+    /**
+     * Write this rectangle to the specified parcel. To restore a rectangle from
+     * a parcel, use readFromParcel()
+     * @param out The parcel to write the rectangle's coordinates into
+     */
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeFloat(left);
+        out.writeFloat(top);
+        out.writeFloat(right);
+        out.writeFloat(bottom);
+    }
+    
+    public static final Parcelable.Creator<RectF> CREATOR = new Parcelable.Creator<RectF>() {
+        /**
+         * Return a new rectangle from the data in the specified parcel.
+         */
+        public RectF createFromParcel(Parcel in) {
+            RectF r = new RectF();
+            r.readFromParcel(in);
+            return r;
+        }
+        
+        /**
+         * Return an array of rectangles of the specified size.
+         */
+        public RectF[] newArray(int size) {
+            return new RectF[size];
+        }
+    };
+    
+    /**
+     * Set the rectangle's coordinates from the data stored in the specified
+     * parcel. To write a rectangle to a parcel, call writeToParcel().
+     *
+     * @param in The parcel to read the rectangle's coordinates from
+     */
+    public void readFromParcel(Parcel in) {
+        left = in.readFloat();
+        top = in.readFloat();
+        right = in.readFloat();
+        bottom = in.readFloat();
+    }
+
+    /**
+     * Scales up the rect by the given scale.
+     * @hide
+     */
+    public void scale(float scale) {
+        if (scale != 1.0f) {
+            left = left * scale;
+            top = top * scale ;
+            right = right * scale;
+            bottom = bottom * scale;
+        }
+    }
+}
diff --git a/android/graphics/Region.java b/android/graphics/Region.java
new file mode 100644
index 0000000..dca6d9e
--- /dev/null
+++ b/android/graphics/Region.java
@@ -0,0 +1,432 @@
+/*
+ * 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;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Pools.SynchronizedPool;
+
+public class Region implements Parcelable {
+
+    private static final int MAX_POOL_SIZE = 10;
+
+    private static final SynchronizedPool<Region> sPool =
+            new SynchronizedPool<Region>(MAX_POOL_SIZE);
+
+    /**
+     * @hide
+     */
+    public long mNativeRegion;
+
+    // the native values for these must match up with the enum in SkRegion.h
+    public enum Op {
+        DIFFERENCE(0),
+        INTERSECT(1),
+        UNION(2),
+        XOR(3),
+        REVERSE_DIFFERENCE(4),
+        REPLACE(5);
+
+        Op(int nativeInt) {
+            this.nativeInt = nativeInt;
+        }
+
+        /**
+         * @hide
+         */
+        public final int nativeInt;
+    }
+
+    /** Create an empty region
+    */
+    public Region() {
+        this(nativeConstructor());
+    }
+
+    /** Return a copy of the specified region
+    */
+    public Region(Region region) {
+        this(nativeConstructor());
+        nativeSetRegion(mNativeRegion, region.mNativeRegion);
+    }
+
+    /** Return a region set to the specified rectangle
+    */
+    public Region(Rect r) {
+        mNativeRegion = nativeConstructor();
+        nativeSetRect(mNativeRegion, r.left, r.top, r.right, r.bottom);
+    }
+
+    /** Return a region set to the specified rectangle
+    */
+    public Region(int left, int top, int right, int bottom) {
+        mNativeRegion = nativeConstructor();
+        nativeSetRect(mNativeRegion, left, top, right, bottom);
+    }
+
+    /** Set the region to the empty region
+    */
+    public void setEmpty() {
+        nativeSetRect(mNativeRegion, 0, 0, 0, 0);
+    }
+
+    /** Set the region to the specified region.
+    */
+    public boolean set(Region region) {
+        nativeSetRegion(mNativeRegion, region.mNativeRegion);
+        return true;
+    }
+
+    /** Set the region to the specified rectangle
+    */
+    public boolean set(Rect r) {
+        return nativeSetRect(mNativeRegion, r.left, r.top, r.right, r.bottom);
+    }
+    
+    /** Set the region to the specified rectangle
+    */
+    public boolean set(int left, int top, int right, int bottom) {
+        return nativeSetRect(mNativeRegion, left, top, right, bottom);
+    }
+
+    /**
+     * Set the region to the area described by the path and clip.
+     * Return true if the resulting region is non-empty. This produces a region
+     * that is identical to the pixels that would be drawn by the path
+     * (with no antialiasing).
+     */
+    public boolean setPath(Path path, Region clip) {
+        return nativeSetPath(mNativeRegion, path.readOnlyNI(), clip.mNativeRegion);
+    }
+
+    /**
+     * Return true if this region is empty
+     */
+    public native boolean isEmpty();
+    
+    /**
+     * Return true if the region contains a single rectangle
+     */
+    public native boolean isRect();
+    
+    /**
+     * Return true if the region contains more than one rectangle
+     */
+    public native boolean isComplex();
+
+    /**
+     * Return a new Rect set to the bounds of the region. If the region is
+     * empty, the Rect will be set to [0, 0, 0, 0]
+     */
+    public Rect getBounds() {
+        Rect r = new Rect();
+        nativeGetBounds(mNativeRegion, r);
+        return r;
+    }
+    
+    /**
+     * Set the Rect to the bounds of the region. If the region is empty, the
+     * Rect will be set to [0, 0, 0, 0]
+     */
+    public boolean getBounds(Rect r) {
+        if (r == null) {
+            throw new NullPointerException();
+        }
+        return nativeGetBounds(mNativeRegion, r);
+    }
+
+    /**
+     * Return the boundary of the region as a new Path. If the region is empty,
+     * the path will also be empty.
+     */
+    public Path getBoundaryPath() {
+        Path path = new Path();
+        nativeGetBoundaryPath(mNativeRegion, path.mutateNI());
+        return path;
+    }
+
+    /**
+     * Set the path to the boundary of the region. If the region is empty, the
+     * path will also be empty.
+     */
+    public boolean getBoundaryPath(Path path) {
+        return nativeGetBoundaryPath(mNativeRegion, path.mutateNI());
+    }
+        
+    /**
+     * Return true if the region contains the specified point
+     */
+    public native boolean contains(int x, int y);
+
+    /**
+     * Return true if the region is a single rectangle (not complex) and it
+     * contains the specified rectangle. Returning false is not a guarantee
+     * that the rectangle is not contained by this region, but return true is a
+     * guarantee that the rectangle is contained by this region.
+     */
+    public boolean quickContains(Rect r) {
+        return quickContains(r.left, r.top, r.right, r.bottom);
+    }
+
+    /**
+     * Return true if the region is a single rectangle (not complex) and it
+     * contains the specified rectangle. Returning false is not a guarantee
+     * that the rectangle is not contained by this region, but return true is a
+     * guarantee that the rectangle is contained by this region.
+     */
+    public native boolean quickContains(int left, int top, int right,
+                                        int bottom);
+
+    /**
+     * Return true if the region is empty, or if the specified rectangle does
+     * not intersect the region. Returning false is not a guarantee that they
+     * intersect, but returning true is a guarantee that they do not.
+     */
+    public boolean quickReject(Rect r) {
+        return quickReject(r.left, r.top, r.right, r.bottom);
+    }
+
+    /**
+     * Return true if the region is empty, or if the specified rectangle does
+     * not intersect the region. Returning false is not a guarantee that they
+     * intersect, but returning true is a guarantee that they do not.
+     */
+    public native boolean quickReject(int left, int top, int right, int bottom);
+
+    /**
+     * Return true if the region is empty, or if the specified region does not
+     * intersect the region. Returning false is not a guarantee that they
+     * intersect, but returning true is a guarantee that they do not.
+     */
+    public native boolean quickReject(Region rgn);
+
+    /**
+     * Translate the region by [dx, dy]. If the region is empty, do nothing.
+     */
+    public void translate(int dx, int dy) {
+        translate(dx, dy, null);
+    }
+
+    /**
+     * Set the dst region to the result of translating this region by [dx, dy].
+     * If this region is empty, then dst will be set to empty.
+     */
+    public native void translate(int dx, int dy, Region dst);
+
+    /**
+     * Scale the region by the given scale amount. This re-constructs new region by
+     * scaling the rects that this region consists of. New rectis are computed by scaling 
+     * coordinates by float, then rounded by roundf() function to integers. This may results
+     * in less internal rects if 0 < scale < 1. Zero and Negative scale result in
+     * an empty region. If this region is empty, do nothing.
+     *
+     * @hide
+     */
+    public void scale(float scale) {
+        scale(scale, null);
+    }
+
+    /**
+     * Set the dst region to the result of scaling this region by the given scale amount.
+     * If this region is empty, then dst will be set to empty.
+     * @hide
+     */
+    public native void scale(float scale, Region dst);
+
+    public final boolean union(Rect r) {
+        return op(r, Op.UNION);
+    }
+
+    /**
+     * Perform the specified Op on this region and the specified rect. Return
+     * true if the result of the op is not empty.
+     */
+    public boolean op(Rect r, Op op) {
+        return nativeOp(mNativeRegion, r.left, r.top, r.right, r.bottom,
+                        op.nativeInt);
+    }
+
+    /**
+     * Perform the specified Op on this region and the specified rect. Return
+     * true if the result of the op is not empty.
+     */
+    public boolean op(int left, int top, int right, int bottom, Op op) {
+        return nativeOp(mNativeRegion, left, top, right, bottom,
+                        op.nativeInt);
+    }
+
+    /**
+     * Perform the specified Op on this region and the specified region. Return
+     * true if the result of the op is not empty.
+     */
+    public boolean op(Region region, Op op) {
+        return op(this, region, op);
+    }
+
+    /**
+     * Set this region to the result of performing the Op on the specified rect
+     * and region. Return true if the result is not empty.
+     */
+    public boolean op(Rect rect, Region region, Op op) {
+        return nativeOp(mNativeRegion, rect, region.mNativeRegion,
+                        op.nativeInt);
+    }
+
+    /**
+     * Set this region to the result of performing the Op on the specified
+     * regions. Return true if the result is not empty.
+     */
+    public boolean op(Region region1, Region region2, Op op) {
+        return nativeOp(mNativeRegion, region1.mNativeRegion,
+                        region2.mNativeRegion, op.nativeInt);
+    }
+
+    public String toString() {
+        return nativeToString(mNativeRegion);
+    }
+
+    /**
+     * @return An instance from a pool if such or a new one.
+     *
+     * @hide
+     */
+    public static Region obtain() {
+        Region region = sPool.acquire();
+        return (region != null) ? region : new Region();
+    }
+
+    /**
+     * @return An instance from a pool if such or a new one.
+     *
+     * @param other Region to copy values from for initialization.
+     *
+     * @hide
+     */
+    public static Region obtain(Region other) {
+        Region region = obtain();
+        region.set(other);
+        return region;
+    }
+
+    /**
+     * Recycles an instance.
+     *
+     * @hide
+     */
+    public void recycle() {
+        setEmpty();
+        sPool.release(this);
+    }
+
+    //////////////////////////////////////////////////////////////////////////
+    
+    public static final Parcelable.Creator<Region> CREATOR
+        = new Parcelable.Creator<Region>() {
+            /**
+            * Rebuild a Region previously stored with writeToParcel().
+             * @param p    Parcel object to read the region from
+             * @return a new region created from the data in the parcel
+             */
+            public Region createFromParcel(Parcel p) {
+                long ni = nativeCreateFromParcel(p);
+                if (ni == 0) {
+                    throw new RuntimeException();
+                }
+                return new Region(ni);
+            }
+            public Region[] newArray(int size) {
+                return new Region[size];
+            }
+    };
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Write the region and its pixels to the parcel. The region can be
+     * rebuilt from the parcel by calling CREATOR.createFromParcel().
+     * @param p    Parcel object to write the region data into
+     */
+    public void writeToParcel(Parcel p, int flags) {
+        if (!nativeWriteToParcel(mNativeRegion, p)) {
+            throw new RuntimeException();
+        }
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || !(obj instanceof Region)) {
+            return false;
+        }
+        Region peer = (Region) obj;
+        return nativeEquals(mNativeRegion, peer.mNativeRegion);
+    }
+
+    protected void finalize() throws Throwable {
+        try {
+            nativeDestructor(mNativeRegion);
+            mNativeRegion = 0;
+        } finally {
+            super.finalize();
+        }
+    }
+    
+    Region(long ni) {
+        if (ni == 0) {
+            throw new RuntimeException();
+        }
+        mNativeRegion = ni;
+    }
+
+    /* add dummy parameter so constructor can be called from jni without
+       triggering 'not cloneable' exception */
+    private Region(long ni, int dummy) {
+        this(ni);
+    }
+
+    final long ni() {
+        return mNativeRegion;
+    }
+
+    private static native boolean nativeEquals(long native_r1, long native_r2);
+
+    private static native long nativeConstructor();
+    private static native void nativeDestructor(long native_region);
+
+    private static native void nativeSetRegion(long native_dst, long native_src);
+    private static native boolean nativeSetRect(long native_dst, int left,
+                                                int top, int right, int bottom);
+    private static native boolean nativeSetPath(long native_dst, long native_path,
+                                                long native_clip);
+    private static native boolean nativeGetBounds(long native_region, Rect rect);
+    private static native boolean nativeGetBoundaryPath(long native_region,
+                                                        long native_path);
+
+    private static native boolean nativeOp(long native_dst, int left, int top,
+                                           int right, int bottom, int op);
+    private static native boolean nativeOp(long native_dst, Rect rect,
+                                           long native_region, int op);
+    private static native boolean nativeOp(long native_dst, long native_region1,
+                                           long native_region2, int op);
+
+    private static native long nativeCreateFromParcel(Parcel p);
+    private static native boolean nativeWriteToParcel(long native_region,
+                                                      Parcel p);
+
+    private static native String nativeToString(long native_region);
+}
diff --git a/android/graphics/RegionIterator.java b/android/graphics/RegionIterator.java
new file mode 100644
index 0000000..443b23c
--- /dev/null
+++ b/android/graphics/RegionIterator.java
@@ -0,0 +1,55 @@
+/*
+ * 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;
+
+public class RegionIterator {
+
+    /**
+     * Construct an iterator for all of the rectangles in a region. This
+     * effectively makes a private copy of the region, so any subsequent edits
+     * to region will not affect the iterator.
+     *
+     * @param region the region that will be iterated
+     */
+    public RegionIterator(Region region) {
+        mNativeIter = nativeConstructor(region.ni());
+    }
+
+    /**
+     * Return the next rectangle in the region. If there are no more rectangles
+     * this returns false and r is unchanged. If there is at least one more,
+     * this returns true and r is set to that rectangle.
+     */
+    public final boolean next(Rect r) {
+        if (r == null) {
+            throw new NullPointerException("The Rect must be provided");
+        }
+        return nativeNext(mNativeIter, r);
+    }
+    
+    protected void finalize() throws Throwable {
+        nativeDestructor(mNativeIter);
+        mNativeIter = 0;  // Other finalizers can still call us.
+    }
+    
+    private static native long nativeConstructor(long native_region);
+    private static native void nativeDestructor(long native_iter);
+    private static native boolean nativeNext(long native_iter, Rect r);
+
+    private long mNativeIter;
+}
+
diff --git a/android/graphics/Region_Delegate.java b/android/graphics/Region_Delegate.java
new file mode 100644
index 0000000..edb7025
--- /dev/null
+++ b/android/graphics/Region_Delegate.java
@@ -0,0 +1,483 @@
+/*
+ * 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;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.os.Parcel;
+
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Area;
+import java.awt.geom.Rectangle2D;
+
+/**
+ * Delegate implementing the native methods of android.graphics.Region
+ *
+ * Through the layoutlib_create tool, the original native methods of Region have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original Region class.
+ *
+ * This also serve as a base class for all Region delegate classes.
+ *
+ * @see DelegateManager
+ *
+ */
+public class Region_Delegate {
+
+    // ---- delegate manager ----
+    protected static final DelegateManager<Region_Delegate> sManager =
+            new DelegateManager<Region_Delegate>(Region_Delegate.class);
+
+    // ---- delegate helper data ----
+
+    // ---- delegate data ----
+    private Area mArea = new Area();
+
+    // ---- Public Helper methods ----
+
+    public static Region_Delegate getDelegate(long nativeShader) {
+        return sManager.getDelegate(nativeShader);
+    }
+
+    public Area getJavaArea() {
+        return mArea;
+    }
+
+    /**
+     * Combines two {@link Shape} into another one (actually an {@link Area}), according
+     * to the given {@link Region.Op}.
+     *
+     * If the Op is not one that combines two shapes, then this return null
+     *
+     * @param shape1 the firt shape to combine which can be null if there's no original clip.
+     * @param shape2 the 2nd shape to combine
+     * @param regionOp the operande for the combine
+     * @return a new area or null.
+     */
+    public static Area combineShapes(Shape shape1, Shape shape2, int regionOp) {
+        if (regionOp == Region.Op.DIFFERENCE.nativeInt) {
+            // if shape1 is null (empty), then the result is null.
+            if (shape1 == null) {
+                return null;
+            }
+
+            // result is always a new area.
+            Area result = new Area(shape1);
+            result.subtract(shape2 instanceof Area ? (Area) shape2 : new Area(shape2));
+            return result;
+
+        } else if (regionOp == Region.Op.INTERSECT.nativeInt) {
+            // if shape1 is null, then the result is simply shape2.
+            if (shape1 == null) {
+                return new Area(shape2);
+            }
+
+            // result is always a new area.
+            Area result = new Area(shape1);
+            result.intersect(shape2 instanceof Area ? (Area) shape2 : new Area(shape2));
+            return result;
+
+        } else if (regionOp == Region.Op.UNION.nativeInt) {
+            // if shape1 is null, then the result is simply shape2.
+            if (shape1 == null) {
+                return new Area(shape2);
+            }
+
+            // result is always a new area.
+            Area result = new Area(shape1);
+            result.add(shape2 instanceof Area ? (Area) shape2 : new Area(shape2));
+            return result;
+
+        } else if (regionOp == Region.Op.XOR.nativeInt) {
+            // if shape1 is null, then the result is simply shape2
+            if (shape1 == null) {
+                return new Area(shape2);
+            }
+
+            // result is always a new area.
+            Area result = new Area(shape1);
+            result.exclusiveOr(shape2 instanceof Area ? (Area) shape2 : new Area(shape2));
+            return result;
+
+        } else if (regionOp == Region.Op.REVERSE_DIFFERENCE.nativeInt) {
+            // result is always a new area.
+            Area result = new Area(shape2);
+
+            if (shape1 != null) {
+                result.subtract(shape1 instanceof Area ? (Area) shape1 : new Area(shape1));
+            }
+
+            return result;
+        }
+
+        return null;
+    }
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static boolean isEmpty(Region thisRegion) {
+        Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
+        if (regionDelegate == null) {
+            return true;
+        }
+
+        return regionDelegate.mArea.isEmpty();
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean isRect(Region thisRegion) {
+        Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
+        if (regionDelegate == null) {
+            return true;
+        }
+
+        return regionDelegate.mArea.isRectangular();
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean isComplex(Region thisRegion) {
+        Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
+        if (regionDelegate == null) {
+            return true;
+        }
+
+        return regionDelegate.mArea.isSingular() == false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean contains(Region thisRegion, int x, int y) {
+        Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
+        if (regionDelegate == null) {
+            return false;
+        }
+
+        return regionDelegate.mArea.contains(x, y);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean quickContains(Region thisRegion,
+            int left, int top, int right, int bottom) {
+        Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
+        if (regionDelegate == null) {
+            return false;
+        }
+
+        return regionDelegate.mArea.isRectangular() &&
+                regionDelegate.mArea.contains(left, top, right - left, bottom - top);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean quickReject(Region thisRegion,
+            int left, int top, int right, int bottom) {
+        Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
+        if (regionDelegate == null) {
+            return false;
+        }
+
+        return regionDelegate.mArea.isEmpty() ||
+                regionDelegate.mArea.intersects(left, top, right - left, bottom - top) == false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean quickReject(Region thisRegion, Region rgn) {
+        Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
+        if (regionDelegate == null) {
+            return false;
+        }
+
+        Region_Delegate targetRegionDelegate = sManager.getDelegate(rgn.mNativeRegion);
+        if (targetRegionDelegate == null) {
+            return false;
+        }
+
+        return regionDelegate.mArea.isEmpty() ||
+                regionDelegate.mArea.getBounds().intersects(
+                        targetRegionDelegate.mArea.getBounds()) == false;
+
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void translate(Region thisRegion, int dx, int dy, Region dst) {
+        Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
+        if (regionDelegate == null) {
+            return;
+        }
+
+        Region_Delegate targetRegionDelegate = sManager.getDelegate(dst.mNativeRegion);
+        if (targetRegionDelegate == null) {
+            return;
+        }
+
+        if (regionDelegate.mArea.isEmpty()) {
+            targetRegionDelegate.mArea = new Area();
+        } else {
+            targetRegionDelegate.mArea = new Area(regionDelegate.mArea);
+            AffineTransform mtx = new AffineTransform();
+            mtx.translate(dx, dy);
+            targetRegionDelegate.mArea.transform(mtx);
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void scale(Region thisRegion, float scale, Region dst) {
+        Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
+        if (regionDelegate == null) {
+            return;
+        }
+
+        Region_Delegate targetRegionDelegate = sManager.getDelegate(dst.mNativeRegion);
+        if (targetRegionDelegate == null) {
+            return;
+        }
+
+        if (regionDelegate.mArea.isEmpty()) {
+            targetRegionDelegate.mArea = new Area();
+        } else {
+            targetRegionDelegate.mArea = new Area(regionDelegate.mArea);
+            AffineTransform mtx = new AffineTransform();
+            mtx.scale(scale, scale);
+            targetRegionDelegate.mArea.transform(mtx);
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeConstructor() {
+        Region_Delegate newDelegate = new Region_Delegate();
+        return sManager.addNewDelegate(newDelegate);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nativeDestructor(long native_region) {
+        sManager.removeJavaReferenceFor(native_region);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nativeSetRegion(long native_dst, long native_src) {
+        Region_Delegate dstRegion = sManager.getDelegate(native_dst);
+        if (dstRegion == null) {
+            return;
+        }
+
+        Region_Delegate srcRegion = sManager.getDelegate(native_src);
+        if (srcRegion == null) {
+            return;
+        }
+
+        dstRegion.mArea.reset();
+        dstRegion.mArea.add(srcRegion.mArea);
+
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nativeSetRect(long native_dst,
+            int left, int top, int right, int bottom) {
+        Region_Delegate dstRegion = sManager.getDelegate(native_dst);
+        if (dstRegion == null) {
+            return true;
+        }
+
+        dstRegion.mArea = new Area(new Rectangle2D.Float(left, top, right - left, bottom - top));
+        return dstRegion.mArea.getBounds().isEmpty() == false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nativeSetPath(long native_dst, long native_path, long native_clip) {
+        Region_Delegate dstRegion = sManager.getDelegate(native_dst);
+        if (dstRegion == null) {
+            return true;
+        }
+
+        Path_Delegate path = Path_Delegate.getDelegate(native_path);
+        if (path == null) {
+            return true;
+        }
+
+        dstRegion.mArea = new Area(path.getJavaShape());
+
+        Region_Delegate clip = sManager.getDelegate(native_clip);
+        if (clip != null) {
+            dstRegion.mArea.subtract(clip.getJavaArea());
+        }
+
+        return dstRegion.mArea.getBounds().isEmpty() == false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nativeGetBounds(long native_region, Rect rect) {
+        Region_Delegate region = sManager.getDelegate(native_region);
+        if (region == null) {
+            return true;
+        }
+
+        Rectangle bounds = region.mArea.getBounds();
+        if (bounds.isEmpty()) {
+            rect.left = rect.top = rect.right = rect.bottom = 0;
+            return false;
+        }
+
+        rect.left = bounds.x;
+        rect.top = bounds.y;
+        rect.right = bounds.x + bounds.width;
+        rect.bottom = bounds.y + bounds.height;
+        return true;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nativeGetBoundaryPath(long native_region, long native_path) {
+        Region_Delegate region = sManager.getDelegate(native_region);
+        if (region == null) {
+            return false;
+        }
+
+        Path_Delegate path = Path_Delegate.getDelegate(native_path);
+        if (path == null) {
+            return false;
+        }
+
+        if (region.mArea.isEmpty()) {
+            path.reset();
+            return false;
+        }
+
+        path.setPathIterator(region.mArea.getPathIterator(new AffineTransform()));
+        return true;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nativeOp(long native_dst,
+            int left, int top, int right, int bottom, int op) {
+        Region_Delegate region = sManager.getDelegate(native_dst);
+        if (region == null) {
+            return false;
+        }
+
+        region.mArea = combineShapes(region.mArea,
+                new Rectangle2D.Float(left, top, right - left, bottom - top), op);
+
+        assert region.mArea != null;
+        if (region.mArea != null) {
+            region.mArea = new Area();
+        }
+
+        return region.mArea.getBounds().isEmpty() == false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nativeOp(long native_dst, Rect rect, long native_region, int op) {
+        Region_Delegate region = sManager.getDelegate(native_dst);
+        if (region == null) {
+            return false;
+        }
+
+        region.mArea = combineShapes(region.mArea,
+                new Rectangle2D.Float(rect.left, rect.top, rect.width(), rect.height()), op);
+
+        assert region.mArea != null;
+        if (region.mArea != null) {
+            region.mArea = new Area();
+        }
+
+        return region.mArea.getBounds().isEmpty() == false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nativeOp(long native_dst,
+            long native_region1, long native_region2, int op) {
+        Region_Delegate dstRegion = sManager.getDelegate(native_dst);
+        if (dstRegion == null) {
+            return true;
+        }
+
+        Region_Delegate region1 = sManager.getDelegate(native_region1);
+        if (region1 == null) {
+            return false;
+        }
+
+        Region_Delegate region2 = sManager.getDelegate(native_region2);
+        if (region2 == null) {
+            return false;
+        }
+
+        dstRegion.mArea = combineShapes(region1.mArea, region2.mArea, op);
+
+        assert dstRegion.mArea != null;
+        if (dstRegion.mArea != null) {
+            dstRegion.mArea = new Area();
+        }
+
+        return dstRegion.mArea.getBounds().isEmpty() == false;
+
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeCreateFromParcel(Parcel p) {
+        // This is only called by Region.CREATOR (Parcelable.Creator<Region>), which is only
+        // used during aidl call so really this should not be called.
+        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+                "AIDL is not suppored, and therefore Regions cannot be created from parcels.",
+                null /*data*/);
+        return 0;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nativeWriteToParcel(long native_region,
+                                                      Parcel p) {
+        // This is only called when sending a region through aidl, so really this should not
+        // be called.
+        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+                "AIDL is not suppored, and therefore Regions cannot be written to parcels.",
+                null /*data*/);
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nativeEquals(long native_r1, long native_r2) {
+        Region_Delegate region1 = sManager.getDelegate(native_r1);
+        if (region1 == null) {
+            return false;
+        }
+
+        Region_Delegate region2 = sManager.getDelegate(native_r2);
+        if (region2 == null) {
+            return false;
+        }
+
+        return region1.mArea.equals(region2.mArea);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static String nativeToString(long native_region) {
+        Region_Delegate region = sManager.getDelegate(native_region);
+        if (region == null) {
+            return "not found";
+        }
+
+        return region.mArea.toString();
+    }
+
+    // ---- Private delegate/helper methods ----
+
+}
diff --git a/android/graphics/RoundRectangle.java b/android/graphics/RoundRectangle.java
new file mode 100644
index 0000000..736f03e
--- /dev/null
+++ b/android/graphics/RoundRectangle.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2016 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;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RectangularShape;
+import java.awt.geom.RoundRectangle2D;
+import java.util.EnumSet;
+import java.util.NoSuchElementException;
+
+/**
+ * Defines a rectangle with rounded corners, where the sizes of the corners
+ * are potentially different.
+ */
+public class RoundRectangle extends RectangularShape {
+    public double x;
+    public double y;
+    public double width;
+    public double height;
+    public double ulWidth;
+    public double ulHeight;
+    public double urWidth;
+    public double urHeight;
+    public double lrWidth;
+    public double lrHeight;
+    public double llWidth;
+    public double llHeight;
+
+    private enum Zone {
+        CLOSE_OUTSIDE,
+        CLOSE_INSIDE,
+        MIDDLE,
+        FAR_INSIDE,
+        FAR_OUTSIDE
+    }
+
+    private final EnumSet<Zone> close = EnumSet.of(Zone.CLOSE_OUTSIDE, Zone.CLOSE_INSIDE);
+    private final EnumSet<Zone> far = EnumSet.of(Zone.FAR_OUTSIDE, Zone.FAR_INSIDE);
+
+    /**
+     * @param cornerDimensions array of 8 floating-point number corresponding to the width and
+     * the height of each corner in the following order: upper-left, upper-right, lower-right,
+     * lower-left. It assumes for the size the same convention as {@link RoundRectangle2D}, that
+     * is that the width and height of a corner correspond to the total width and height of the
+     * ellipse that corner is a quarter of.
+     */
+    public RoundRectangle(float x, float y, float width, float height, float[] cornerDimensions) {
+        assert cornerDimensions.length == 8 : "The array of corner dimensions must have eight " +
+                    "elements";
+
+        this.x = x;
+        this.y = y;
+        this.width = width;
+        this.height = height;
+
+        float[] dimensions = cornerDimensions.clone();
+        // If a value is negative, the corresponding corner is squared
+        for (int i = 0; i < dimensions.length; i += 2) {
+            if (dimensions[i] < 0 || dimensions[i + 1] < 0) {
+                dimensions[i] = 0;
+                dimensions[i + 1] = 0;
+            }
+        }
+
+        double topCornerWidth = (dimensions[0] + dimensions[2]) / 2d;
+        double bottomCornerWidth = (dimensions[4] + dimensions[6]) / 2d;
+        double leftCornerHeight = (dimensions[1] + dimensions[7]) / 2d;
+        double rightCornerHeight = (dimensions[3] + dimensions[5]) / 2d;
+
+        // Rescale the corner dimensions if they are bigger than the rectangle
+        double scale = Math.min(1.0, width / topCornerWidth);
+        scale = Math.min(scale, width / bottomCornerWidth);
+        scale = Math.min(scale, height / leftCornerHeight);
+        scale = Math.min(scale, height / rightCornerHeight);
+
+        this.ulWidth = dimensions[0] * scale;
+        this.ulHeight = dimensions[1] * scale;
+        this.urWidth = dimensions[2] * scale;
+        this.urHeight = dimensions[3] * scale;
+        this.lrWidth = dimensions[4] * scale;
+        this.lrHeight = dimensions[5] * scale;
+        this.llWidth = dimensions[6] * scale;
+        this.llHeight = dimensions[7] * scale;
+    }
+
+    @Override
+    public double getX() {
+        return x;
+    }
+
+    @Override
+    public double getY() {
+        return y;
+    }
+
+    @Override
+    public double getWidth() {
+        return width;
+    }
+
+    @Override
+    public double getHeight() {
+        return height;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return (width <= 0d) || (height <= 0d);
+    }
+
+    @Override
+    public void setFrame(double x, double y, double w, double h) {
+        this.x = x;
+        this.y = y;
+        this.width = w;
+        this.height = h;
+    }
+
+    @Override
+    public Rectangle2D getBounds2D() {
+        return new Rectangle2D.Double(x, y, width, height);
+    }
+
+    @Override
+    public boolean contains(double x, double y) {
+        if (isEmpty()) {
+            return false;
+        }
+
+        double x0 = getX();
+        double y0 = getY();
+        double x1 = x0 + getWidth();
+        double y1 = y0 + getHeight();
+        // Check for trivial rejection - point is outside bounding rectangle
+        if (x < x0 || y < y0 || x >= x1 || y >= y1) {
+            return false;
+        }
+
+        double insideTopX0 = x0 + ulWidth / 2d;
+        double insideLeftY0 = y0 + ulHeight / 2d;
+        if (x < insideTopX0 && y < insideLeftY0) {
+            // In the upper-left corner
+            return isInsideCorner(x - insideTopX0, y - insideLeftY0, ulWidth / 2d, ulHeight / 2d);
+        }
+
+        double insideTopX1 = x1 - urWidth / 2d;
+        double insideRightY0 = y0 + urHeight / 2d;
+        if (x > insideTopX1 && y < insideRightY0) {
+            // In the upper-right corner
+            return isInsideCorner(x - insideTopX1, y - insideRightY0, urWidth / 2d, urHeight / 2d);
+        }
+
+        double insideBottomX1 = x1 - lrWidth / 2d;
+        double insideRightY1 = y1 - lrHeight / 2d;
+        if (x > insideBottomX1 && y > insideRightY1) {
+            // In the lower-right corner
+            return isInsideCorner(x - insideBottomX1, y - insideRightY1, lrWidth / 2d,
+                    lrHeight / 2d);
+        }
+
+        double insideBottomX0 = x0 + llWidth / 2d;
+        double insideLeftY1 = y1 - llHeight / 2d;
+        if (x < insideBottomX0 && y > insideLeftY1) {
+            // In the lower-left corner
+            return isInsideCorner(x - insideBottomX0, y - insideLeftY1, llWidth / 2d,
+                    llHeight / 2d);
+        }
+
+        // In the central part of the rectangle
+        return true;
+    }
+
+    private boolean isInsideCorner(double x, double y, double width, double height) {
+        double squareDist = height * height * x * x + width * width * y * y;
+        return squareDist <= width * width * height * height;
+    }
+
+    private Zone classify(double coord, double side1, double arcSize1, double side2,
+            double arcSize2) {
+        if (coord < side1) {
+            return Zone.CLOSE_OUTSIDE;
+        } else if (coord < side1 + arcSize1) {
+            return Zone.CLOSE_INSIDE;
+        } else if (coord < side2 - arcSize2) {
+            return Zone.MIDDLE;
+        } else if (coord < side2) {
+            return Zone.FAR_INSIDE;
+        } else {
+            return Zone.FAR_OUTSIDE;
+        }
+    }
+
+    public boolean intersects(double x, double y, double w, double h) {
+        if (isEmpty() || w <= 0 || h <= 0) {
+            return false;
+        }
+        double x0 = getX();
+        double y0 = getY();
+        double x1 = x0 + getWidth();
+        double y1 = y0 + getHeight();
+        // Check for trivial rejection - bounding rectangles do not intersect
+        if (x + w <= x0 || x >= x1 || y + h <= y0 || y >= y1) {
+            return false;
+        }
+
+        double maxLeftCornerWidth = Math.max(ulWidth, llWidth) / 2d;
+        double maxRightCornerWidth = Math.max(urWidth, lrWidth) / 2d;
+        double maxUpperCornerHeight = Math.max(ulHeight, urHeight) / 2d;
+        double maxLowerCornerHeight = Math.max(llHeight, lrHeight) / 2d;
+        Zone x0class = classify(x, x0, maxLeftCornerWidth, x1, maxRightCornerWidth);
+        Zone x1class = classify(x + w, x0, maxLeftCornerWidth, x1, maxRightCornerWidth);
+        Zone y0class = classify(y, y0, maxUpperCornerHeight, y1, maxLowerCornerHeight);
+        Zone y1class = classify(y + h, y0, maxUpperCornerHeight, y1, maxLowerCornerHeight);
+
+        // Trivially accept if any point is inside inner rectangle
+        if (x0class == Zone.MIDDLE || x1class == Zone.MIDDLE || y0class == Zone.MIDDLE || y1class == Zone.MIDDLE) {
+            return true;
+        }
+        // Trivially accept if either edge spans inner rectangle
+        if ((close.contains(x0class) && far.contains(x1class)) || (close.contains(y0class) &&
+                far.contains(y1class))) {
+            return true;
+        }
+
+        // Since neither edge spans the center, then one of the corners
+        // must be in one of the rounded edges.  We detect this case if
+        // a [xy]0class is 3 or a [xy]1class is 1.  One of those two cases
+        // must be true for each direction.
+        // We now find a "nearest point" to test for being inside a rounded
+        // corner.
+        if (x1class == Zone.CLOSE_INSIDE && y1class == Zone.CLOSE_INSIDE) {
+            // Potentially in upper-left corner
+            x = x + w - x0 - ulWidth / 2d;
+            y = y + h - y0 - ulHeight / 2d;
+            return x > 0 || y > 0 || isInsideCorner(x, y, ulWidth / 2d, ulHeight / 2d);
+        }
+        if (x1class == Zone.CLOSE_INSIDE) {
+            // Potentially in lower-left corner
+            x = x + w - x0 - llWidth / 2d;
+            y = y - y1 + llHeight / 2d;
+            return x > 0 || y < 0 || isInsideCorner(x, y, llWidth / 2d, llHeight / 2d);
+        }
+        if (y1class == Zone.CLOSE_INSIDE) {
+            //Potentially in the upper-right corner
+            x = x - x1 + urWidth / 2d;
+            y = y + h - y0 - urHeight / 2d;
+            return x < 0 || y > 0 || isInsideCorner(x, y, urWidth / 2d, urHeight / 2d);
+        }
+        // Potentially in the lower-right corner
+        x = x - x1 + lrWidth / 2d;
+        y = y - y1 + lrHeight / 2d;
+        return x < 0 || y < 0 || isInsideCorner(x, y, lrWidth / 2d, lrHeight / 2d);
+    }
+
+    @Override
+    public boolean contains(double x, double y, double w, double h) {
+        if (isEmpty() || w <= 0 || h <= 0) {
+            return false;
+        }
+        return (contains(x, y) &&
+                contains(x + w, y) &&
+                contains(x, y + h) &&
+                contains(x + w, y + h));
+    }
+
+    @Override
+    public PathIterator getPathIterator(final AffineTransform at) {
+        return new PathIterator() {
+            int index;
+
+            // ArcIterator.btan(Math.PI/2)
+            public static final double CtrlVal = 0.5522847498307933;
+            private final double ncv = 1.0 - CtrlVal;
+
+            // Coordinates of control points for Bezier curves approximating the straight lines
+            // and corners of the rounded rectangle.
+            private final double[][] ctrlpts = {
+                    {0.0, 0.0, 0.0, ulHeight},
+                    {0.0, 0.0, 1.0, -llHeight},
+                    {0.0, 0.0, 1.0, -llHeight * ncv, 0.0, ncv * llWidth, 1.0, 0.0, 0.0, llWidth,
+                            1.0, 0.0},
+                    {1.0, -lrWidth, 1.0, 0.0},
+                    {1.0, -lrWidth * ncv, 1.0, 0.0, 1.0, 0.0, 1.0, -lrHeight * ncv, 1.0, 0.0, 1.0,
+                            -lrHeight},
+                    {1.0, 0.0, 0.0, urHeight},
+                    {1.0, 0.0, 0.0, ncv * urHeight, 1.0, -urWidth * ncv, 0.0, 0.0, 1.0, -urWidth,
+                            0.0, 0.0},
+                    {0.0, ulWidth, 0.0, 0.0},
+                    {0.0, ncv * ulWidth, 0.0, 0.0, 0.0, 0.0, 0.0, ncv * ulHeight, 0.0, 0.0, 0.0,
+                            ulHeight},
+                    {}
+            };
+            private final int[] types = {
+                    SEG_MOVETO,
+                    SEG_LINETO, SEG_CUBICTO,
+                    SEG_LINETO, SEG_CUBICTO,
+                    SEG_LINETO, SEG_CUBICTO,
+                    SEG_LINETO, SEG_CUBICTO,
+                    SEG_CLOSE,
+            };
+
+            @Override
+            public int getWindingRule() {
+                return WIND_NON_ZERO;
+            }
+
+            @Override
+            public boolean isDone() {
+                return index >= ctrlpts.length;
+            }
+
+            @Override
+            public void next() {
+                index++;
+            }
+
+            @Override
+            public int currentSegment(float[] coords) {
+                if (isDone()) {
+                    throw new NoSuchElementException("roundrect iterator out of bounds");
+                }
+                int nc = 0;
+                double ctrls[] = ctrlpts[index];
+                for (int i = 0; i < ctrls.length; i += 4) {
+                    coords[nc++] = (float) (x + ctrls[i] * width + ctrls[i + 1] / 2d);
+                    coords[nc++] = (float) (y + ctrls[i + 2] * height + ctrls[i + 3] / 2d);
+                }
+                if (at != null) {
+                    at.transform(coords, 0, coords, 0, nc / 2);
+                }
+                return types[index];
+            }
+
+            @Override
+            public int currentSegment(double[] coords) {
+                if (isDone()) {
+                    throw new NoSuchElementException("roundrect iterator out of bounds");
+                }
+                int nc = 0;
+                double ctrls[] = ctrlpts[index];
+                for (int i = 0; i < ctrls.length; i += 4) {
+                    coords[nc++] = x + ctrls[i] * width + ctrls[i + 1] / 2d;
+                    coords[nc++] = y + ctrls[i + 2] * height + ctrls[i + 3] / 2d;
+                }
+                if (at != null) {
+                    at.transform(coords, 0, coords, 0, nc / 2);
+                }
+                return types[index];
+            }
+        };
+    }
+}
diff --git a/android/graphics/Shader.java b/android/graphics/Shader.java
new file mode 100644
index 0000000..0209cea
--- /dev/null
+++ b/android/graphics/Shader.java
@@ -0,0 +1,171 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import libcore.util.NativeAllocationRegistry;
+
+/**
+ * Shader is the based class for objects that return horizontal spans of colors
+ * during drawing. A subclass of Shader is installed in a Paint calling
+ * paint.setShader(shader). After that any object (other than a bitmap) that is
+ * drawn with that paint will get its color(s) from the shader.
+ */
+public class Shader {
+
+    private static class NoImagePreloadHolder {
+        public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+                Shader.class.getClassLoader(), nativeGetFinalizer(), 50);
+    }
+
+    /**
+     * @deprecated Use subclass constructors directly instead.
+     */
+    @Deprecated
+    public Shader() {}
+
+    /**
+     * Current native shader instance. Created and updated lazily when {@link #getNativeInstance()}
+     * is called - otherwise may be out of date with java setters/properties.
+     */
+    private long mNativeInstance;
+    // Runnable to do immediate destruction
+    private Runnable mCleaner;
+
+    /**
+     * Current matrix - always set to null if local matrix is identity.
+     */
+    private Matrix mLocalMatrix;
+
+    public enum TileMode {
+        /**
+         * replicate the edge color if the shader draws outside of its
+         * original bounds
+         */
+        CLAMP   (0),
+        /**
+         * repeat the shader's image horizontally and vertically
+         */
+        REPEAT  (1),
+        /**
+         * repeat the shader's image horizontally and vertically, alternating
+         * mirror images so that adjacent images always seam
+         */
+        MIRROR  (2);
+    
+        TileMode(int nativeInt) {
+            this.nativeInt = nativeInt;
+        }
+        final int nativeInt;
+    }
+
+    /**
+     * Return true if the shader has a non-identity local matrix.
+     * @param localM Set to the local matrix of the shader, if the shader's matrix is non-null.
+     * @return true if the shader has a non-identity local matrix
+     */
+    public boolean getLocalMatrix(@NonNull Matrix localM) {
+        if (mLocalMatrix != null) {
+            localM.set(mLocalMatrix);
+            return true; // presence of mLocalMatrix means it's not identity
+        }
+        return false;
+    }
+
+    /**
+     * Set the shader's local matrix. Passing null will reset the shader's
+     * matrix to identity. If the matrix has scale value as 0, the drawing
+     * result is undefined.
+     *
+     * @param localM The shader's new local matrix, or null to specify identity
+     */
+    public void setLocalMatrix(@Nullable Matrix localM) {
+        if (localM == null || localM.isIdentity()) {
+            if (mLocalMatrix != null) {
+                mLocalMatrix = null;
+                discardNativeInstance();
+            }
+        } else {
+            if (mLocalMatrix == null) {
+                mLocalMatrix = new Matrix(localM);
+                discardNativeInstance();
+            } else if (!mLocalMatrix.equals(localM)) {
+                mLocalMatrix.set(localM);
+                discardNativeInstance();
+            }
+        }
+    }
+
+    long createNativeInstance(long nativeMatrix) {
+        return 0;
+    }
+
+    /** @hide */
+    protected final void discardNativeInstance() {
+        if (mNativeInstance != 0) {
+            mCleaner.run();
+            mCleaner = null;
+            mNativeInstance = 0;
+        }
+    }
+
+    /**
+     * Callback for subclasses to call {@link #discardNativeInstance()} if the most recently
+     * constructed native instance is no longer valid.
+     * @hide
+     */
+    protected void verifyNativeInstance() {
+    }
+
+    /**
+     * @hide
+     */
+    protected Shader copy() {
+        final Shader copy = new Shader();
+        copyLocalMatrix(copy);
+        return copy;
+    }
+
+    /**
+     * @hide
+     */
+    protected void copyLocalMatrix(Shader dest) {
+        dest.mLocalMatrix.set(mLocalMatrix);
+    }
+
+    /**
+     * @hide
+     */
+    public final long getNativeInstance() {
+        // verify mNativeInstance is valid
+        verifyNativeInstance();
+
+        if (mNativeInstance == 0) {
+            mNativeInstance = createNativeInstance(mLocalMatrix == null
+                    ? 0 : mLocalMatrix.native_instance);
+            mCleaner = NoImagePreloadHolder.sRegistry.registerNativeAllocation(
+                    this, mNativeInstance);
+        }
+        return mNativeInstance;
+    }
+
+    private static native long nativeGetFinalizer();
+
+}
+
diff --git a/android/graphics/Shader_Delegate.java b/android/graphics/Shader_Delegate.java
new file mode 100644
index 0000000..eefa929
--- /dev/null
+++ b/android/graphics/Shader_Delegate.java
@@ -0,0 +1,110 @@
+/*
+ * 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;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.graphics.Shader.TileMode;
+
+import libcore.util.NativeAllocationRegistry_Delegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.Shader
+ *
+ * Through the layoutlib_create tool, the original native methods of Shader have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original Shader class.
+ *
+ * This also serve as a base class for all Shader delegate classes.
+ *
+ * @see DelegateManager
+ *
+ */
+public abstract class Shader_Delegate {
+
+    // ---- delegate manager ----
+    protected static final DelegateManager<Shader_Delegate> sManager =
+            new DelegateManager<Shader_Delegate>(Shader_Delegate.class);
+    private static long sFinalizer = -1;
+
+    // ---- delegate helper data ----
+
+    // ---- delegate data ----
+    private Matrix_Delegate mLocalMatrix = null;
+
+    // ---- Public Helper methods ----
+
+    public static Shader_Delegate getDelegate(long nativeShader) {
+        return sManager.getDelegate(nativeShader);
+    }
+
+    /**
+     * Returns the {@link TileMode} matching the given int.
+     * @param tileMode the tile mode int value
+     * @return the TileMode enum.
+     */
+    public static TileMode getTileMode(int tileMode) {
+        for (TileMode tm : TileMode.values()) {
+            if (tm.nativeInt == tileMode) {
+                return tm;
+            }
+        }
+
+        assert false;
+        return TileMode.CLAMP;
+    }
+
+    public abstract java.awt.Paint getJavaPaint();
+    public abstract boolean isSupported();
+    public abstract String getSupportMessage();
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeGetFinalizer() {
+        synchronized (Shader_Delegate.class) {
+            if (sFinalizer == -1) {
+                sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
+                        sManager::removeJavaReferenceFor);
+            }
+        }
+        return sFinalizer;
+    }
+
+    // ---- Private delegate/helper methods ----
+
+    protected Shader_Delegate(long nativeMatrix) {
+        setLocalMatrix(nativeMatrix);
+    }
+
+    public void setLocalMatrix(long nativeMatrix) {
+        mLocalMatrix = Matrix_Delegate.getDelegate(nativeMatrix);
+    }
+
+    protected java.awt.geom.AffineTransform getLocalMatrix() {
+        if (mLocalMatrix != null) {
+            return mLocalMatrix.getAffineTransform();
+        }
+
+        return new java.awt.geom.AffineTransform();
+    }
+
+}
diff --git a/android/graphics/SumPathEffect.java b/android/graphics/SumPathEffect.java
new file mode 100644
index 0000000..8fedc31
--- /dev/null
+++ b/android/graphics/SumPathEffect.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2007 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;
+
+public class SumPathEffect extends PathEffect {
+
+    /**
+     * Construct a PathEffect whose effect is to apply two effects, in sequence.
+     * (e.g. first(path) + second(path))
+     */
+    public SumPathEffect(PathEffect first, PathEffect second) {
+        native_instance = nativeCreate(first.native_instance,
+                                       second.native_instance);
+    }
+    
+    private static native long nativeCreate(long first, long second);
+}
+
diff --git a/android/graphics/SumPathEffect_Delegate.java b/android/graphics/SumPathEffect_Delegate.java
new file mode 100644
index 0000000..6d2e9b4
--- /dev/null
+++ b/android/graphics/SumPathEffect_Delegate.java
@@ -0,0 +1,71 @@
+/*
+ * 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;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.awt.Stroke;
+
+/**
+ * Delegate implementing the native methods of android.graphics.SumPathEffect
+ *
+ * Through the layoutlib_create tool, the original native methods of SumPathEffect have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original SumPathEffect class.
+ *
+ * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager},
+ * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}.
+ *
+ * @see PathEffect_Delegate
+ *
+ */
+public class SumPathEffect_Delegate extends PathEffect_Delegate {
+
+    // ---- delegate data ----
+
+    // ---- Public Helper methods ----
+
+    @Override
+    public Stroke getStroke(Paint_Delegate paint) {
+        // FIXME
+        return null;
+    }
+
+    @Override
+    public boolean isSupported() {
+        return false;
+    }
+
+    @Override
+    public String getSupportMessage() {
+        return "Sum Path Effects are not supported in Layout Preview mode.";
+    }
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeCreate(long first, long second) {
+        SumPathEffect_Delegate newDelegate = new SumPathEffect_Delegate();
+        return sManager.addNewDelegate(newDelegate);
+    }
+
+    // ---- Private delegate/helper methods ----
+}
diff --git a/android/graphics/SurfaceTexture.java b/android/graphics/SurfaceTexture.java
new file mode 100644
index 0000000..97edf22
--- /dev/null
+++ b/android/graphics/SurfaceTexture.java
@@ -0,0 +1,408 @@
+/*
+ * 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;
+
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.view.Surface;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Captures frames from an image stream as an OpenGL ES texture.
+ *
+ * <p>The image stream may come from either camera preview or video decode. A
+ * {@link android.view.Surface} created from a SurfaceTexture can be used as an output
+ * destination for the {@link android.hardware.camera2}, {@link android.media.MediaCodec},
+ * {@link android.media.MediaPlayer}, and {@link android.renderscript.Allocation} APIs.
+ * When {@link #updateTexImage} is called, the contents of the texture object specified
+ * when the SurfaceTexture was created are updated to contain the most recent image from the image
+ * stream.  This may cause some frames of the stream to be skipped.
+ *
+ * <p>A SurfaceTexture may also be used in place of a SurfaceHolder when specifying the output
+ * destination of the older {@link android.hardware.Camera} API. Doing so will cause all the
+ * frames from the image stream to be sent to the SurfaceTexture object rather than to the device's
+ * display.
+ *
+ * <p>When sampling from the texture one should first transform the texture coordinates using the
+ * matrix queried via {@link #getTransformMatrix(float[])}.  The transform matrix may change each
+ * time {@link #updateTexImage} is called, so it should be re-queried each time the texture image
+ * is updated.
+ * This matrix transforms traditional 2D OpenGL ES texture coordinate column vectors of the form (s,
+ * t, 0, 1) where s and t are on the inclusive interval [0, 1] to the proper sampling location in
+ * the streamed texture.  This transform compensates for any properties of the image stream source
+ * that cause it to appear different from a traditional OpenGL ES texture.  For example, sampling
+ * from the bottom left corner of the image can be accomplished by transforming the column vector
+ * (0, 0, 0, 1) using the queried matrix, while sampling from the top right corner of the image can
+ * be done by transforming (1, 1, 0, 1).
+ *
+ * <p>The texture object uses the GL_TEXTURE_EXTERNAL_OES texture target, which is defined by the
+ * <a href="http://www.khronos.org/registry/gles/extensions/OES/OES_EGL_image_external.txt">
+ * GL_OES_EGL_image_external</a> OpenGL ES extension.  This limits how the texture may be used.
+ * Each time the texture is bound it must be bound to the GL_TEXTURE_EXTERNAL_OES target rather than
+ * the GL_TEXTURE_2D target.  Additionally, any OpenGL ES 2.0 shader that samples from the texture
+ * must declare its use of this extension using, for example, an "#extension
+ * GL_OES_EGL_image_external : require" directive.  Such shaders must also access the texture using
+ * the samplerExternalOES GLSL sampler type.
+ *
+ * <p>SurfaceTexture objects may be created on any thread.  {@link #updateTexImage} may only be
+ * called on the thread with the OpenGL ES context that contains the texture object.  The
+ * frame-available callback is called on an arbitrary thread, so unless special care is taken {@link
+ * #updateTexImage} should not be called directly from the callback.
+ */
+public class SurfaceTexture {
+    private final Looper mCreatorLooper;
+    private Handler mOnFrameAvailableHandler;
+
+    /**
+     * These fields are used by native code, do not access or modify.
+     */
+    private long mSurfaceTexture;
+    private long mProducer;
+    private long mFrameAvailableListener;
+
+    private boolean mIsSingleBuffered;
+
+    /**
+     * Callback interface for being notified that a new stream frame is available.
+     */
+    public interface OnFrameAvailableListener {
+        void onFrameAvailable(SurfaceTexture surfaceTexture);
+    }
+
+    /**
+     * Exception thrown when a SurfaceTexture couldn't be created or resized.
+     *
+     * @deprecated No longer thrown. {@link android.view.Surface.OutOfResourcesException}
+     * is used instead.
+     */
+    @SuppressWarnings("serial")
+    @Deprecated
+    public static class OutOfResourcesException extends Exception {
+        public OutOfResourcesException() {
+        }
+        public OutOfResourcesException(String name) {
+            super(name);
+        }
+    }
+
+    /**
+     * Construct a new SurfaceTexture to stream images to a given OpenGL texture.
+     *
+     * @param texName the OpenGL texture object name (e.g. generated via glGenTextures)
+     *
+     * @throws android.view.Surface.OutOfResourcesException If the SurfaceTexture cannot be created.
+     */
+    public SurfaceTexture(int texName) {
+        this(texName, false);
+    }
+
+    /**
+     * Construct a new SurfaceTexture to stream images to a given OpenGL texture.
+     *
+     * In single buffered mode the application is responsible for serializing access to the image
+     * content buffer. Each time the image content is to be updated, the
+     * {@link #releaseTexImage()} method must be called before the image content producer takes
+     * ownership of the buffer. For example, when producing image content with the NDK
+     * ANativeWindow_lock and ANativeWindow_unlockAndPost functions, {@link #releaseTexImage()}
+     * must be called before each ANativeWindow_lock, or that call will fail. When producing
+     * image content with OpenGL ES, {@link #releaseTexImage()} must be called before the first
+     * OpenGL ES function call each frame.
+     *
+     * @param texName the OpenGL texture object name (e.g. generated via glGenTextures)
+     * @param singleBufferMode whether the SurfaceTexture will be in single buffered mode.
+     *
+     * @throws android.view.Surface.OutOfResourcesException If the SurfaceTexture cannot be created.
+     */
+    public SurfaceTexture(int texName, boolean singleBufferMode) {
+        mCreatorLooper = Looper.myLooper();
+        mIsSingleBuffered = singleBufferMode;
+        nativeInit(false, texName, singleBufferMode, new WeakReference<SurfaceTexture>(this));
+    }
+
+    /**
+     * Construct a new SurfaceTexture to stream images to a given OpenGL texture.
+     *
+     * In single buffered mode the application is responsible for serializing access to the image
+     * content buffer. Each time the image content is to be updated, the
+     * {@link #releaseTexImage()} method must be called before the image content producer takes
+     * ownership of the buffer. For example, when producing image content with the NDK
+     * ANativeWindow_lock and ANativeWindow_unlockAndPost functions, {@link #releaseTexImage()}
+     * must be called before each ANativeWindow_lock, or that call will fail. When producing
+     * image content with OpenGL ES, {@link #releaseTexImage()} must be called before the first
+     * OpenGL ES function call each frame.
+     *
+     * Unlike {@link #SurfaceTexture(int, boolean)}, which takes an OpenGL texture object name,
+     * this constructor creates the SurfaceTexture in detached mode. A texture name must be passed
+     * in using {@link #attachToGLContext} before calling {@link #releaseTexImage()} and producing
+     * image content using OpenGL ES.
+     *
+     * @param singleBufferMode whether the SurfaceTexture will be in single buffered mode.
+     *
+     * @throws android.view.Surface.OutOfResourcesException If the SurfaceTexture cannot be created.
+     */
+    public SurfaceTexture(boolean singleBufferMode) {
+        mCreatorLooper = Looper.myLooper();
+        mIsSingleBuffered = singleBufferMode;
+        nativeInit(true, 0, singleBufferMode, new WeakReference<SurfaceTexture>(this));
+    }
+
+    /**
+     * Register a callback to be invoked when a new image frame becomes available to the
+     * SurfaceTexture.
+     * <p>
+     * The callback may be called on an arbitrary thread, so it is not
+     * safe to call {@link #updateTexImage} without first binding the OpenGL ES context to the
+     * thread invoking the callback.
+     * </p>
+     *
+     * @param listener The listener to use, or null to remove the listener.
+     */
+    public void setOnFrameAvailableListener(@Nullable OnFrameAvailableListener listener) {
+        setOnFrameAvailableListener(listener, null);
+    }
+
+    /**
+     * Register a callback to be invoked when a new image frame becomes available to the
+     * SurfaceTexture.
+     * <p>
+     * If a handler is specified, the callback will be invoked on that handler's thread.
+     * If no handler is specified, then the callback may be called on an arbitrary thread,
+     * so it is not safe to call {@link #updateTexImage} without first binding the OpenGL ES
+     * context to the thread invoking the callback.
+     * </p>
+     *
+     * @param listener The listener to use, or null to remove the listener.
+     * @param handler The handler on which the listener should be invoked, or null
+     * to use an arbitrary thread.
+     */
+    public void setOnFrameAvailableListener(@Nullable final OnFrameAvailableListener listener,
+            @Nullable Handler handler) {
+        if (listener != null) {
+            // Although we claim the thread is arbitrary, earlier implementation would
+            // prefer to send the callback on the creating looper or the main looper
+            // so we preserve this behavior here.
+            Looper looper = handler != null ? handler.getLooper() :
+                    mCreatorLooper != null ? mCreatorLooper : Looper.getMainLooper();
+            mOnFrameAvailableHandler = new Handler(looper, null, true /*async*/) {
+                @Override
+                public void handleMessage(Message msg) {
+                    listener.onFrameAvailable(SurfaceTexture.this);
+                }
+            };
+        } else {
+            mOnFrameAvailableHandler = null;
+        }
+    }
+
+    /**
+     * Set the default size of the image buffers.  The image producer may override the buffer size,
+     * in which case the producer-set buffer size will be used, not the default size set by this
+     * method.  Both video and camera based image producers do override the size.  This method may
+     * be used to set the image size when producing images with {@link android.graphics.Canvas} (via
+     * {@link android.view.Surface#lockCanvas}), or OpenGL ES (via an EGLSurface).
+     *
+     * The new default buffer size will take effect the next time the image producer requests a
+     * buffer to fill.  For {@link android.graphics.Canvas} this will be the next time {@link
+     * android.view.Surface#lockCanvas} is called.  For OpenGL ES, the EGLSurface should be
+     * destroyed (via eglDestroySurface), made not-current (via eglMakeCurrent), and then recreated
+     * (via eglCreateWindowSurface) to ensure that the new default size has taken effect.
+     *
+     * The width and height parameters must be no greater than the minimum of
+     * GL_MAX_VIEWPORT_DIMS and GL_MAX_TEXTURE_SIZE (see
+     * {@link javax.microedition.khronos.opengles.GL10#glGetIntegerv glGetIntegerv}).
+     * An error due to invalid dimensions might not be reported until
+     * updateTexImage() is called.
+     */
+    public void setDefaultBufferSize(int width, int height) {
+        nativeSetDefaultBufferSize(width, height);
+    }
+
+    /**
+     * Update the texture image to the most recent frame from the image stream.  This may only be
+     * called while the OpenGL ES context that owns the texture is current on the calling thread.
+     * It will implicitly bind its texture to the GL_TEXTURE_EXTERNAL_OES texture target.
+     */
+    public void updateTexImage() {
+        nativeUpdateTexImage();
+    }
+
+    /**
+     * Releases the the texture content. This is needed in single buffered mode to allow the image
+     * content producer to take ownership of the image buffer.
+     * For more information see {@link #SurfaceTexture(int, boolean)}.
+     */
+    public void releaseTexImage() {
+        nativeReleaseTexImage();
+    }
+
+    /**
+     * Detach the SurfaceTexture from the OpenGL ES context that owns the OpenGL ES texture object.
+     * This call must be made with the OpenGL ES context current on the calling thread.  The OpenGL
+     * ES texture object will be deleted as a result of this call.  After calling this method all
+     * calls to {@link #updateTexImage} will throw an {@link java.lang.IllegalStateException} until
+     * a successful call to {@link #attachToGLContext} is made.
+     *
+     * This can be used to access the SurfaceTexture image contents from multiple OpenGL ES
+     * contexts.  Note, however, that the image contents are only accessible from one OpenGL ES
+     * context at a time.
+     */
+    public void detachFromGLContext() {
+        int err = nativeDetachFromGLContext();
+        if (err != 0) {
+            throw new RuntimeException("Error during detachFromGLContext (see logcat for details)");
+        }
+    }
+
+    /**
+     * Attach the SurfaceTexture to the OpenGL ES context that is current on the calling thread.  A
+     * new OpenGL ES texture object is created and populated with the SurfaceTexture image frame
+     * that was current at the time of the last call to {@link #detachFromGLContext}.  This new
+     * texture is bound to the GL_TEXTURE_EXTERNAL_OES texture target.
+     *
+     * This can be used to access the SurfaceTexture image contents from multiple OpenGL ES
+     * contexts.  Note, however, that the image contents are only accessible from one OpenGL ES
+     * context at a time.
+     *
+     * @param texName The name of the OpenGL ES texture that will be created.  This texture name
+     * must be unusued in the OpenGL ES context that is current on the calling thread.
+     */
+    public void attachToGLContext(int texName) {
+        int err = nativeAttachToGLContext(texName);
+        if (err != 0) {
+            throw new RuntimeException("Error during attachToGLContext (see logcat for details)");
+        }
+    }
+
+    /**
+     * Retrieve the 4x4 texture coordinate transform matrix associated with the texture image set by
+     * the most recent call to updateTexImage.
+     *
+     * This transform matrix maps 2D homogeneous texture coordinates of the form (s, t, 0, 1) with s
+     * and t in the inclusive range [0, 1] to the texture coordinate that should be used to sample
+     * that location from the texture.  Sampling the texture outside of the range of this transform
+     * is undefined.
+     *
+     * The matrix is stored in column-major order so that it may be passed directly to OpenGL ES via
+     * the glLoadMatrixf or glUniformMatrix4fv functions.
+     *
+     * @param mtx the array into which the 4x4 matrix will be stored.  The array must have exactly
+     *     16 elements.
+     */
+    public void getTransformMatrix(float[] mtx) {
+        // Note we intentionally don't check mtx for null, so this will result in a
+        // NullPointerException. But it's safe because it happens before the call to native.
+        if (mtx.length != 16) {
+            throw new IllegalArgumentException();
+        }
+        nativeGetTransformMatrix(mtx);
+    }
+
+    /**
+     * Retrieve the timestamp associated with the texture image set by the most recent call to
+     * updateTexImage.
+     *
+     * This timestamp is in nanoseconds, and is normally monotonically increasing. The timestamp
+     * should be unaffected by time-of-day adjustments, and for a camera should be strictly
+     * monotonic but for a MediaPlayer may be reset when the position is set.  The
+     * specific meaning and zero point of the timestamp depends on the source providing images to
+     * the SurfaceTexture. Unless otherwise specified by the image source, timestamps cannot
+     * generally be compared across SurfaceTexture instances, or across multiple program
+     * invocations. It is mostly useful for determining time offsets between subsequent frames.
+     */
+
+    public long getTimestamp() {
+        return nativeGetTimestamp();
+    }
+
+    /**
+     * release() frees all the buffers and puts the SurfaceTexture into the
+     * 'abandoned' state. Once put in this state the SurfaceTexture can never
+     * leave it. When in the 'abandoned' state, all methods of the
+     * IGraphicBufferProducer interface will fail with the NO_INIT error.
+     *
+     * Note that while calling this method causes all the buffers to be freed
+     * from the perspective of the the SurfaceTexture, if there are additional
+     * references on the buffers (e.g. if a buffer is referenced by a client or
+     * by OpenGL ES as a texture) then those buffer will remain allocated.
+     *
+     * Always call this method when you are done with SurfaceTexture. Failing
+     * to do so may delay resource deallocation for a significant amount of
+     * time.
+     *
+     * @see #isReleased()
+     */
+    public void release() {
+        nativeRelease();
+    }
+
+    /**
+     * Returns true if the SurfaceTexture was released.
+     *
+     * @see #release()
+     */
+    public boolean isReleased() {
+        return nativeIsReleased();
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            nativeFinalize();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * This method is invoked from native code only.
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    private static void postEventFromNative(WeakReference<SurfaceTexture> weakSelf) {
+        SurfaceTexture st = weakSelf.get();
+        if (st != null) {
+            Handler handler = st.mOnFrameAvailableHandler;
+            if (handler != null) {
+                handler.sendEmptyMessage(0);
+            }
+        }
+    }
+
+    /**
+     * Returns true if the SurfaceTexture is single-buffered
+     * @hide
+     */
+    public boolean isSingleBuffered() {
+        return mIsSingleBuffered;
+    }
+
+    private native void nativeInit(boolean isDetached, int texName,
+            boolean singleBufferMode, WeakReference<SurfaceTexture> weakSelf)
+            throws Surface.OutOfResourcesException;
+    private native void nativeFinalize();
+    private native void nativeGetTransformMatrix(float[] mtx);
+    private native long nativeGetTimestamp();
+    private native void nativeSetDefaultBufferSize(int width, int height);
+    private native void nativeUpdateTexImage();
+    private native void nativeReleaseTexImage();
+    private native int nativeDetachFromGLContext();
+    private native int nativeAttachToGLContext(int texName);
+    private native void nativeRelease();
+    private native boolean nativeIsReleased();
+}
diff --git a/android/graphics/SweepGradient.java b/android/graphics/SweepGradient.java
new file mode 100644
index 0000000..b6b80b4
--- /dev/null
+++ b/android/graphics/SweepGradient.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2007 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;
+
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+public class SweepGradient extends Shader {
+
+    private static final int TYPE_COLORS_AND_POSITIONS = 1;
+    private static final int TYPE_COLOR_START_AND_COLOR_END = 2;
+
+    /**
+     * Type of the LinearGradient: can be either TYPE_COLORS_AND_POSITIONS or
+     * TYPE_COLOR_START_AND_COLOR_END.
+     */
+    private int mType;
+
+    private float mCx;
+    private float mCy;
+    private int[] mColors;
+    private float[] mPositions;
+    private int mColor0;
+    private int mColor1;
+
+    /**
+     * A Shader that draws a sweep gradient around a center point.
+     *
+     * @param cx       The x-coordinate of the center
+     * @param cy       The y-coordinate of the center
+     * @param colors   The colors to be distributed between around the center.
+     *                 There must be at least 2 colors in the array.
+     * @param positions May be NULL. The relative position of
+     *                 each corresponding color in the colors array, beginning
+     *                 with 0 and ending with 1.0. If the values are not
+     *                 monotonic, the drawing may produce unexpected results.
+     *                 If positions is NULL, then the colors are automatically
+     *                 spaced evenly.
+     */
+    public SweepGradient(float cx, float cy,
+            @NonNull @ColorInt int colors[], @Nullable float positions[]) {
+        if (colors.length < 2) {
+            throw new IllegalArgumentException("needs >= 2 number of colors");
+        }
+        if (positions != null && colors.length != positions.length) {
+            throw new IllegalArgumentException(
+                    "color and position arrays must be of equal length");
+        }
+        mType = TYPE_COLORS_AND_POSITIONS;
+        mCx = cx;
+        mCy = cy;
+        mColors = colors.clone();
+        mPositions = positions != null ? positions.clone() : null;
+    }
+
+    /**
+     * A Shader that draws a sweep gradient around a center point.
+     *
+     * @param cx       The x-coordinate of the center
+     * @param cy       The y-coordinate of the center
+     * @param color0   The color to use at the start of the sweep
+     * @param color1   The color to use at the end of the sweep
+     */
+    public SweepGradient(float cx, float cy, @ColorInt int color0, @ColorInt int color1) {
+        mType = TYPE_COLOR_START_AND_COLOR_END;
+        mCx = cx;
+        mCy = cy;
+        mColor0 = color0;
+        mColor1 = color1;
+        mColors = null;
+        mPositions = null;
+    }
+
+    @Override
+    long createNativeInstance(long nativeMatrix) {
+        if (mType == TYPE_COLORS_AND_POSITIONS) {
+            return nativeCreate1(nativeMatrix, mCx, mCy, mColors, mPositions);
+        } else { // TYPE_COLOR_START_AND_COLOR_END
+            return nativeCreate2(nativeMatrix, mCx, mCy, mColor0, mColor1);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    protected Shader copy() {
+        final SweepGradient copy;
+        if (mType == TYPE_COLORS_AND_POSITIONS) {
+            copy = new SweepGradient(mCx, mCy, mColors.clone(),
+                    mPositions != null ? mPositions.clone() : null);
+        } else { // TYPE_COLOR_START_AND_COLOR_END
+            copy = new SweepGradient(mCx, mCy, mColor0, mColor1);
+        }
+        copyLocalMatrix(copy);
+        return copy;
+    }
+
+    private static native long nativeCreate1(long matrix, float x, float y,
+            int colors[], float positions[]);
+    private static native long nativeCreate2(long matrix, float x, float y,
+            int color0, int color1);
+}
+
diff --git a/android/graphics/SweepGradient_Delegate.java b/android/graphics/SweepGradient_Delegate.java
new file mode 100644
index 0000000..6e8aca3
--- /dev/null
+++ b/android/graphics/SweepGradient_Delegate.java
@@ -0,0 +1,214 @@
+/*
+ * 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;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.awt.image.DataBufferInt;
+import java.awt.image.Raster;
+import java.awt.image.SampleModel;
+
+/**
+ * Delegate implementing the native methods of android.graphics.SweepGradient
+ *
+ * Through the layoutlib_create tool, the original native methods of SweepGradient have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original SweepGradient class.
+ *
+ * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager},
+ * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}.
+ *
+ * @see Shader_Delegate
+ *
+ */
+public class SweepGradient_Delegate extends Gradient_Delegate {
+
+    // ---- delegate data ----
+    private java.awt.Paint mJavaPaint;
+
+    // ---- Public Helper methods ----
+
+    @Override
+    public java.awt.Paint getJavaPaint() {
+        return mJavaPaint;
+    }
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeCreate1(long matrix, float x, float y, int colors[], float
+            positions[]) {
+        SweepGradient_Delegate newDelegate = new SweepGradient_Delegate(matrix, x, y, colors,
+                positions);
+        return sManager.addNewDelegate(newDelegate);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeCreate2(long matrix, float x, float y, int color0, int color1) {
+        return nativeCreate1(matrix, x, y, new int[] { color0, color1 },
+                null /*positions*/);
+    }
+
+    // ---- Private delegate/helper methods ----
+
+    /**
+     * A subclass of Shader that draws a sweep gradient around a center point.
+     *
+     * @param nativeMatrix reference to the shader's native transformation matrix
+     * @param cx       The x-coordinate of the center
+     * @param cy       The y-coordinate of the center
+     * @param colors   The colors to be distributed between around the center.
+     *                 There must be at least 2 colors in the array.
+     * @param positions May be NULL. The relative position of
+     *                 each corresponding color in the colors array, beginning
+     *                 with 0 and ending with 1.0. If the values are not
+     *                 monotonic, the drawing may produce unexpected results.
+     *                 If positions is NULL, then the colors are automatically
+     *                 spaced evenly.
+     */
+    private SweepGradient_Delegate(long nativeMatrix, float cx, float cy,
+            int colors[], float positions[]) {
+        super(nativeMatrix, colors, positions);
+        mJavaPaint = new SweepGradientPaint(cx, cy, mColors, mPositions);
+    }
+
+    private class SweepGradientPaint extends GradientPaint {
+
+        private final float mCx;
+        private final float mCy;
+
+        public SweepGradientPaint(float cx, float cy, int[] colors,
+                float[] positions) {
+            super(colors, positions, null /*tileMode*/);
+            mCx = cx;
+            mCy = cy;
+        }
+
+        @Override
+        public java.awt.PaintContext createContext(
+                java.awt.image.ColorModel     colorModel,
+                java.awt.Rectangle            deviceBounds,
+                java.awt.geom.Rectangle2D     userBounds,
+                java.awt.geom.AffineTransform xform,
+                java.awt.RenderingHints       hints) {
+            precomputeGradientColors();
+
+            java.awt.geom.AffineTransform canvasMatrix;
+            try {
+                canvasMatrix = xform.createInverse();
+            } catch (java.awt.geom.NoninvertibleTransformException e) {
+                Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE,
+                        "Unable to inverse matrix in SweepGradient", e, null /*data*/);
+                canvasMatrix = new java.awt.geom.AffineTransform();
+            }
+
+            java.awt.geom.AffineTransform localMatrix = getLocalMatrix();
+            try {
+                localMatrix = localMatrix.createInverse();
+            } catch (java.awt.geom.NoninvertibleTransformException e) {
+                Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE,
+                        "Unable to inverse matrix in SweepGradient", e, null /*data*/);
+                localMatrix = new java.awt.geom.AffineTransform();
+            }
+
+            return new SweepGradientPaintContext(canvasMatrix, localMatrix, colorModel);
+        }
+
+        private class SweepGradientPaintContext implements java.awt.PaintContext {
+
+            private final java.awt.geom.AffineTransform mCanvasMatrix;
+            private final java.awt.geom.AffineTransform mLocalMatrix;
+            private final java.awt.image.ColorModel mColorModel;
+
+            public SweepGradientPaintContext(
+                    java.awt.geom.AffineTransform canvasMatrix,
+                    java.awt.geom.AffineTransform localMatrix,
+                    java.awt.image.ColorModel colorModel) {
+                mCanvasMatrix = canvasMatrix;
+                mLocalMatrix = localMatrix;
+                mColorModel = colorModel;
+            }
+
+            @Override
+            public void dispose() {
+            }
+
+            @Override
+            public java.awt.image.ColorModel getColorModel() {
+                return mColorModel;
+            }
+
+            @Override
+            public java.awt.image.Raster getRaster(int x, int y, int w, int h) {
+
+                int[] data = new int[w*h];
+
+                // compute angle from each point to the center, and figure out the distance from
+                // it.
+                int index = 0;
+                float[] pt1 = new float[2];
+                float[] pt2 = new float[2];
+                for (int iy = 0 ; iy < h ; iy++) {
+                    for (int ix = 0 ; ix < w ; ix++) {
+                        // handle the canvas transform
+                        pt1[0] = x + ix;
+                        pt1[1] = y + iy;
+                        mCanvasMatrix.transform(pt1, 0, pt2, 0, 1);
+
+                        // handle the local matrix
+                        pt1[0] = pt2[0] - mCx;
+                        pt1[1] = pt2[1] - mCy;
+                        mLocalMatrix.transform(pt1, 0, pt2, 0, 1);
+
+                        float dx = pt2[0];
+                        float dy = pt2[1];
+
+                        float angle;
+                        if (dx == 0) {
+                            angle = (float) (dy < 0 ? 3 * Math.PI / 2 : Math.PI / 2);
+                        } else if (dy == 0) {
+                            angle = (float) (dx < 0 ? Math.PI : 0);
+                        } else {
+                            angle = (float) Math.atan(dy / dx);
+                            if (dx > 0) {
+                                if (dy < 0) {
+                                    angle += Math.PI * 2;
+                                }
+                            } else {
+                                angle += Math.PI;
+                            }
+                        }
+
+                        // convert to 0-1. value and get color
+                        data[index++] = getGradientColor((float) (angle / (2 * Math.PI)));
+                    }
+                }
+
+                DataBufferInt dataBuffer = new DataBufferInt(data, data.length);
+                SampleModel colorModel = mColorModel.createCompatibleSampleModel(w, h);
+                return Raster.createWritableRaster(colorModel, dataBuffer, null);
+            }
+
+        }
+    }
+}
diff --git a/android/graphics/TableMaskFilter.java b/android/graphics/TableMaskFilter.java
new file mode 100644
index 0000000..d0c1438
--- /dev/null
+++ b/android/graphics/TableMaskFilter.java
@@ -0,0 +1,46 @@
+/*
+ * 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;
+
+/**
+ * @hide
+ */
+public class TableMaskFilter extends MaskFilter {
+
+    public TableMaskFilter(byte[] table) {
+        if (table.length < 256) {
+            throw new RuntimeException("table.length must be >= 256");
+        }
+        native_instance = nativeNewTable(table);
+    }
+    
+    private TableMaskFilter(long ni) {
+        native_instance = ni;
+    }
+    
+    public static TableMaskFilter CreateClipTable(int min, int max) {
+        return new TableMaskFilter(nativeNewClip(min, max));
+    }
+    
+    public static TableMaskFilter CreateGammaTable(float gamma) {
+        return new TableMaskFilter(nativeNewGamma(gamma));
+    }
+
+    private static native long nativeNewTable(byte[] table);
+    private static native long nativeNewClip(int min, int max);
+    private static native long nativeNewGamma(float gamma);
+}
diff --git a/android/graphics/TemporaryBuffer.java b/android/graphics/TemporaryBuffer.java
new file mode 100644
index 0000000..36a2275
--- /dev/null
+++ b/android/graphics/TemporaryBuffer.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2007 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;
+
+import com.android.internal.util.ArrayUtils;
+
+/**
+ * @hide
+ */
+public class TemporaryBuffer {
+    public static char[] obtain(int len) {
+        char[] buf;
+
+        synchronized (TemporaryBuffer.class) {
+            buf = sTemp;
+            sTemp = null;
+        }
+
+        if (buf == null || buf.length < len) {
+            buf = ArrayUtils.newUnpaddedCharArray(len);
+        }
+
+        return buf;
+    }
+
+    public static void recycle(char[] temp) {
+        if (temp.length > 1000) return;
+
+        synchronized (TemporaryBuffer.class) {
+            sTemp = temp;
+        }
+    }
+
+    private static char[] sTemp = null;
+}
diff --git a/android/graphics/Typeface.java b/android/graphics/Typeface.java
new file mode 100644
index 0000000..cfc389f
--- /dev/null
+++ b/android/graphics/Typeface.java
@@ -0,0 +1,1204 @@
+/*
+ * 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;
+
+import static android.content.res.FontResourcesParser.ProviderResourceEntry;
+import static android.content.res.FontResourcesParser.FontFileResourceEntry;
+import static android.content.res.FontResourcesParser.FontFamilyFilesResourceEntry;
+import static android.content.res.FontResourcesParser.FamilyResourceEntry;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.AssetManager;
+import android.graphics.FontListParser;
+import android.graphics.fonts.FontVariationAxis;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.ParcelFileDescriptor;
+import android.os.ResultReceiver;
+import android.provider.FontRequest;
+import android.provider.FontsContract;
+import android.text.FontConfig;
+import android.util.ArrayMap;
+import android.util.Base64;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.LruCache;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * The Typeface class specifies the typeface and intrinsic style of a font.
+ * This is used in the paint, along with optionally Paint settings like
+ * textSize, textSkewX, textScaleX to specify
+ * how text appears when drawn (and measured).
+ */
+public class Typeface {
+
+    private static String TAG = "Typeface";
+
+    /** The default NORMAL typeface object */
+    public static final Typeface DEFAULT;
+    /**
+     * The default BOLD typeface object. Note: this may be not actually be
+     * bold, depending on what fonts are installed. Call getStyle() to know
+     * for sure.
+     */
+    public static final Typeface DEFAULT_BOLD;
+    /** The NORMAL style of the default sans serif typeface. */
+    public static final Typeface SANS_SERIF;
+    /** The NORMAL style of the default serif typeface. */
+    public static final Typeface SERIF;
+    /** The NORMAL style of the default monospace typeface. */
+    public static final Typeface MONOSPACE;
+
+    static Typeface[] sDefaults;
+
+    /**
+     * Cache for Typeface objects for style variant. Currently max size is 3.
+     */
+    @GuardedBy("sStyledCacheLock")
+    private static final LongSparseArray<SparseArray<Typeface>> sStyledTypefaceCache =
+            new LongSparseArray<>(3);
+    private static final Object sStyledCacheLock = new Object();
+
+    /**
+     * Cache for Typeface objects for weight variant. Currently max size is 3.
+     */
+    @GuardedBy("sWeightCacheLock")
+    private static final LongSparseArray<SparseArray<Typeface>> sWeightTypefaceCache =
+            new LongSparseArray<>(3);
+    private static final Object sWeightCacheLock = new Object();
+
+    /**
+     * Cache for Typeface objects dynamically loaded from assets. Currently max size is 16.
+     */
+    @GuardedBy("sDynamicCacheLock")
+    private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16);
+    private static final Object sDynamicCacheLock = new Object();
+
+    static Typeface sDefaultTypeface;
+    static final Map<String, Typeface> sSystemFontMap;
+    static final Map<String, FontFamily[]> sSystemFallbackMap;
+
+    /**
+     * @hide
+     */
+    public long native_instance;
+
+    // Style
+    public static final int NORMAL = 0;
+    public static final int BOLD = 1;
+    public static final int ITALIC = 2;
+    public static final int BOLD_ITALIC = 3;
+    /** @hide */ public static final int STYLE_MASK = 0x03;
+
+    private int mStyle = 0;
+    private int mWeight = 0;
+
+    // Value for weight and italic. Indicates the value is resolved by font metadata.
+    // Must be the same as the C++ constant in core/jni/android/graphics/FontFamily.cpp
+    /** @hide */
+    public static final int RESOLVE_BY_FONT_TABLE = -1;
+    private static final String DEFAULT_FAMILY = "sans-serif";
+
+    // Style value for building typeface.
+    private static final int STYLE_NORMAL = 0;
+    private static final int STYLE_ITALIC = 1;
+
+    private int[] mSupportedAxes;
+    private static final int[] EMPTY_AXES = {};
+
+    private static void setDefault(Typeface t) {
+        sDefaultTypeface = t;
+        nativeSetDefault(t.native_instance);
+    }
+
+    // TODO: Make this public API. (b/64852739)
+    /** @hide */
+    @VisibleForTesting
+    public int getWeight() {
+        return mWeight;
+    }
+
+    /** Returns the typeface's intrinsic style attributes */
+    public int getStyle() {
+        return mStyle;
+    }
+
+    /** Returns true if getStyle() has the BOLD bit set. */
+    public final boolean isBold() {
+        return (mStyle & BOLD) != 0;
+    }
+
+    /** Returns true if getStyle() has the ITALIC bit set. */
+    public final boolean isItalic() {
+        return (mStyle & ITALIC) != 0;
+    }
+
+    /**
+     * @hide
+     * Used by Resources to load a font resource of type font file.
+     */
+    @Nullable
+    public static Typeface createFromResources(AssetManager mgr, String path, int cookie) {
+        synchronized (sDynamicCacheLock) {
+            final String key = Builder.createAssetUid(
+                    mgr, path, 0 /* ttcIndex */, null /* axes */,
+                    RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */,
+                    DEFAULT_FAMILY);
+            Typeface typeface = sDynamicTypefaceCache.get(key);
+            if (typeface != null) return typeface;
+
+            FontFamily fontFamily = new FontFamily();
+            // TODO: introduce ttc index and variation settings to resource type font.
+            if (fontFamily.addFontFromAssetManager(mgr, path, cookie, false /* isAsset */,
+                    0 /* ttcIndex */, RESOLVE_BY_FONT_TABLE /* weight */,
+                    RESOLVE_BY_FONT_TABLE /* italic */, null /* axes */)) {
+                if (!fontFamily.freeze()) {
+                    return null;
+                }
+                FontFamily[] families = {fontFamily};
+                typeface = createFromFamiliesWithDefault(families, DEFAULT_FAMILY,
+                        RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
+                sDynamicTypefaceCache.put(key, typeface);
+                return typeface;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @hide
+     * Used by Resources to load a font resource of type xml.
+     */
+    @Nullable
+    public static Typeface createFromResources(
+            FamilyResourceEntry entry, AssetManager mgr, String path) {
+        if (entry instanceof ProviderResourceEntry) {
+            final ProviderResourceEntry providerEntry = (ProviderResourceEntry) entry;
+            // Downloadable font
+            List<List<String>> givenCerts = providerEntry.getCerts();
+            List<List<byte[]>> certs = new ArrayList<>();
+            if (givenCerts != null) {
+                for (int i = 0; i < givenCerts.size(); i++) {
+                    List<String> certSet = givenCerts.get(i);
+                    List<byte[]> byteArraySet = new ArrayList<>();
+                    for (int j = 0; j < certSet.size(); j++) {
+                        byteArraySet.add(Base64.decode(certSet.get(j), Base64.DEFAULT));
+                    }
+                    certs.add(byteArraySet);
+                }
+            }
+            // Downloaded font and it wasn't cached, request it again and return a
+            // default font instead (nothing we can do now).
+            FontRequest request = new FontRequest(providerEntry.getAuthority(),
+                    providerEntry.getPackage(), providerEntry.getQuery(), certs);
+            Typeface typeface = FontsContract.getFontSync(request);
+            return typeface == null ? DEFAULT : typeface;
+        }
+
+        Typeface typeface = findFromCache(mgr, path);
+        if (typeface != null) return typeface;
+
+        // family is FontFamilyFilesResourceEntry
+        final FontFamilyFilesResourceEntry filesEntry = (FontFamilyFilesResourceEntry) entry;
+
+        FontFamily fontFamily = new FontFamily();
+        for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) {
+            // TODO: Add ttc and variation font support. (b/37853920)
+            if (!fontFamily.addFontFromAssetManager(mgr, fontFile.getFileName(),
+                    0 /* resourceCookie */, false /* isAsset */, 0 /* ttcIndex */,
+                    fontFile.getWeight(), fontFile.getItalic(), null /* axes */)) {
+                return null;
+            }
+        }
+        if (!fontFamily.freeze()) {
+            return null;
+        }
+        FontFamily[] familyChain = { fontFamily };
+        typeface = createFromFamiliesWithDefault(familyChain, DEFAULT_FAMILY,
+                RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
+        synchronized (sDynamicCacheLock) {
+            final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */,
+                    null /* axes */, RESOLVE_BY_FONT_TABLE /* weight */,
+                    RESOLVE_BY_FONT_TABLE /* italic */, DEFAULT_FAMILY);
+            sDynamicTypefaceCache.put(key, typeface);
+        }
+        return typeface;
+    }
+
+    /**
+     * Used by resources for cached loading if the font is available.
+     * @hide
+     */
+    public static Typeface findFromCache(AssetManager mgr, String path) {
+        synchronized (sDynamicCacheLock) {
+            final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, null /* axes */,
+                    RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */,
+                    DEFAULT_FAMILY);
+            Typeface typeface = sDynamicTypefaceCache.get(key);
+            if (typeface != null) {
+                return typeface;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * A builder class for creating new Typeface instance.
+     *
+     * <p>
+     * Examples,
+     * 1) Create Typeface from ttf file.
+     * <pre>
+     * <code>
+     * Typeface.Builder buidler = new Typeface.Builder("your_font_file.ttf");
+     * Typeface typeface = builder.build();
+     * </code>
+     * </pre>
+     *
+     * 2) Create Typeface from ttc file in assets directory.
+     * <pre>
+     * <code>
+     * Typeface.Builder buidler = new Typeface.Builder(getAssets(), "your_font_file.ttc");
+     * builder.setTtcIndex(2);  // Set index of font collection.
+     * Typeface typeface = builder.build();
+     * </code>
+     * </pre>
+     *
+     * 3) Create Typeface with variation settings.
+     * <pre>
+     * <code>
+     * Typeface.Builder buidler = new Typeface.Builder("your_font_file.ttf");
+     * builder.setFontVariationSettings("'wght' 700, 'slnt' 20, 'ital' 1");
+     * builder.setWeight(700);  // Tell the system that this is a bold font.
+     * builder.setItalic(true);  // Tell the system that this is an italic style font.
+     * Typeface typeface = builder.build();
+     * </code>
+     * </pre>
+     * </p>
+     */
+    public static final class Builder {
+        /** @hide */
+        public static final int NORMAL_WEIGHT = 400;
+        /** @hide */
+        public static final int BOLD_WEIGHT = 700;
+
+        private int mTtcIndex;
+        private FontVariationAxis[] mAxes;
+
+        private AssetManager mAssetManager;
+        private String mPath;
+        private FileDescriptor mFd;
+
+        private FontsContract.FontInfo[] mFonts;
+        private Map<Uri, ByteBuffer> mFontBuffers;
+
+        private String mFallbackFamilyName;
+
+        private int mWeight = RESOLVE_BY_FONT_TABLE;
+        private int mItalic = RESOLVE_BY_FONT_TABLE;
+
+        /**
+         * Constructs a builder with a file path.
+         *
+         * @param path The file object refers to the font file.
+         */
+        public Builder(@NonNull File path) {
+            mPath = path.getAbsolutePath();
+        }
+
+        /**
+         * Constructs a builder with a file descriptor.
+         *
+         * Caller is responsible for closing the passed file descriptor after {@link #build} is
+         * called.
+         *
+         * @param fd The file descriptor. The passed fd must be mmap-able.
+         */
+        public Builder(@NonNull FileDescriptor fd) {
+            mFd = fd;
+        }
+
+        /**
+         * Constructs a builder with a file path.
+         *
+         * @param path The full path to the font file.
+         */
+        public Builder(@NonNull String path) {
+            mPath = path;
+        }
+
+        /**
+         * Constructs a builder from an asset manager and a file path in an asset directory.
+         *
+         * @param assetManager The application's asset manager
+         * @param path The file name of the font data in the asset directory
+         */
+        public Builder(@NonNull AssetManager assetManager, @NonNull String path) {
+            mAssetManager = Preconditions.checkNotNull(assetManager);
+            mPath = Preconditions.checkStringNotEmpty(path);
+        }
+
+        /**
+         * Constracts a builder from an array of FontsContract.FontInfo.
+         *
+         * Since {@link FontsContract.FontInfo} holds information about TTC indices and
+         * variation settings, there is no need to call {@link #setTtcIndex} or
+         * {@link #setFontVariationSettings}. Similary, {@link FontsContract.FontInfo} holds
+         * weight and italic information, so {@link #setWeight} and {@link #setItalic} are used
+         * for style matching during font selection.
+         *
+         * @param results The array of {@link FontsContract.FontInfo}
+         * @param buffers The mapping from URI to buffers to be used during building.
+         * @hide
+         */
+        public Builder(@NonNull FontsContract.FontInfo[] fonts,
+                @NonNull Map<Uri, ByteBuffer> buffers) {
+            mFonts = fonts;
+            mFontBuffers = buffers;
+        }
+
+        /**
+         * Sets weight of the font.
+         *
+         * Tells the system the weight of the given font. If not provided, the system will resolve
+         * the weight value by reading font tables.
+         * @param weight a weight value.
+         */
+        public Builder setWeight(@IntRange(from = 1, to = 1000) int weight) {
+            mWeight = weight;
+            return this;
+        }
+
+        /**
+         * Sets italic information of the font.
+         *
+         * Tells the system the style of the given font. If not provided, the system will resolve
+         * the style by reading font tables.
+         * @param italic {@code true} if the font is italic. Otherwise {@code false}.
+         */
+        public Builder setItalic(boolean italic) {
+            mItalic = italic ? STYLE_ITALIC : STYLE_NORMAL;
+            return this;
+        }
+
+        /**
+         * Sets an index of the font collection.
+         *
+         * Can not be used for Typeface source. build() method will return null for invalid index.
+         * @param ttcIndex An index of the font collection. If the font source is not font
+         *                 collection, do not call this method or specify 0.
+         */
+        public Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) {
+            if (mFonts != null) {
+                throw new IllegalArgumentException(
+                        "TTC index can not be specified for FontResult source.");
+            }
+            mTtcIndex = ttcIndex;
+            return this;
+        }
+
+        /**
+         * Sets a font variation settings.
+         *
+         * @param variationSettings See {@link android.widget.TextView#setFontVariationSettings}.
+         * @throws IllegalArgumentException If given string is not a valid font variation settings
+         *                                  format.
+         */
+        public Builder setFontVariationSettings(@Nullable String variationSettings) {
+            if (mFonts != null) {
+                throw new IllegalArgumentException(
+                        "Font variation settings can not be specified for FontResult source.");
+            }
+            if (mAxes != null) {
+                throw new IllegalStateException("Font variation settings are already set.");
+            }
+            mAxes = FontVariationAxis.fromFontVariationSettings(variationSettings);
+            return this;
+        }
+
+        /**
+         * Sets a font variation settings.
+         *
+         * @param axes An array of font variation axis tag-value pairs.
+         */
+        public Builder setFontVariationSettings(@Nullable FontVariationAxis[] axes) {
+            if (mFonts != null) {
+                throw new IllegalArgumentException(
+                        "Font variation settings can not be specified for FontResult source.");
+            }
+            if (mAxes != null) {
+                throw new IllegalStateException("Font variation settings are already set.");
+            }
+            mAxes = axes;
+            return this;
+        }
+
+        /**
+         * Sets a fallback family name.
+         *
+         * By specifying a fallback family name, a fallback Typeface will be returned if the
+         * {@link #build} method fails to create a Typeface from the provided font. The fallback
+         * family will be resolved with the provided weight and italic information specified by
+         * {@link #setWeight} and {@link #setItalic}.
+         *
+         * If {@link #setWeight} is not called, the fallback family keeps the default weight.
+         * Similary, if {@link #setItalic} is not called, the fallback family keeps the default
+         * italic information. For example, calling {@code builder.setFallback("sans-serif-light")}
+         * is equivalent to calling {@code builder.setFallback("sans-serif").setWeight(300)} in
+         * terms of fallback. The default weight and italic information are overridden by calling
+         * {@link #setWeight} and {@link #setItalic}. For example, if a Typeface is constructed
+         * using {@code builder.setFallback("sans-serif-light").setWeight(700)}, the fallback text
+         * will render as sans serif bold.
+         *
+         * @param familyName A family name to be used for fallback if the provided font can not be
+         *                   used. By passing {@code null}, build() returns {@code null}.
+         *                   If {@link #setFallback} is not called on the builder, {@code null}
+         *                   is assumed.
+         */
+        public Builder setFallback(@Nullable String familyName) {
+            mFallbackFamilyName = familyName;
+            return this;
+        }
+
+        /**
+         * Creates a unique id for a given AssetManager and asset path.
+         *
+         * @param mgr  AssetManager instance
+         * @param path The path for the asset.
+         * @param ttcIndex The TTC index for the font.
+         * @param axes The font variation settings.
+         * @return Unique id for a given AssetManager and asset path.
+         */
+        private static String createAssetUid(final AssetManager mgr, String path, int ttcIndex,
+                @Nullable FontVariationAxis[] axes, int weight, int italic, String fallback) {
+            final SparseArray<String> pkgs = mgr.getAssignedPackageIdentifiers();
+            final StringBuilder builder = new StringBuilder();
+            final int size = pkgs.size();
+            for (int i = 0; i < size; i++) {
+                builder.append(pkgs.valueAt(i));
+                builder.append("-");
+            }
+            builder.append(path);
+            builder.append("-");
+            builder.append(Integer.toString(ttcIndex));
+            builder.append("-");
+            builder.append(Integer.toString(weight));
+            builder.append("-");
+            builder.append(Integer.toString(italic));
+            // Family name may contain hyphen. Use double hyphen for avoiding key conflicts before
+            // and after appending falblack name.
+            builder.append("--");
+            builder.append(fallback);
+            builder.append("--");
+            if (axes != null) {
+                for (FontVariationAxis axis : axes) {
+                    builder.append(axis.getTag());
+                    builder.append("-");
+                    builder.append(Float.toString(axis.getStyleValue()));
+                }
+            }
+            return builder.toString();
+        }
+
+        private Typeface resolveFallbackTypeface() {
+            if (mFallbackFamilyName == null) {
+                return null;
+            }
+
+            Typeface base =  sSystemFontMap.get(mFallbackFamilyName);
+            if (base == null) {
+                base = sDefaultTypeface;
+            }
+
+            if (mWeight == RESOLVE_BY_FONT_TABLE && mItalic == RESOLVE_BY_FONT_TABLE) {
+                return base;
+            }
+
+            final int weight = (mWeight == RESOLVE_BY_FONT_TABLE) ? base.mWeight : mWeight;
+            final boolean italic =
+                    (mItalic == RESOLVE_BY_FONT_TABLE) ? (base.mStyle & ITALIC) != 0 : mItalic == 1;
+            return createWeightStyle(base, weight, italic);
+        }
+
+        /**
+         * Generates new Typeface from specified configuration.
+         *
+         * @return Newly created Typeface. May return null if some parameters are invalid.
+         */
+        public Typeface build() {
+            if (mFd != null) {  // Builder is created with file descriptor.
+                try (FileInputStream fis = new FileInputStream(mFd)) {
+                    FileChannel channel = fis.getChannel();
+                    long size = channel.size();
+                    ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, size);
+
+                    final FontFamily fontFamily = new FontFamily();
+                    if (!fontFamily.addFontFromBuffer(buffer, mTtcIndex, mAxes, mWeight, mItalic)) {
+                        fontFamily.abortCreation();
+                        return resolveFallbackTypeface();
+                    }
+                    if (!fontFamily.freeze()) {
+                        return resolveFallbackTypeface();
+                    }
+                    FontFamily[] families = { fontFamily };
+                    return createFromFamiliesWithDefault(families, mFallbackFamilyName, mWeight,
+                            mItalic);
+                } catch (IOException e) {
+                    return resolveFallbackTypeface();
+                }
+            } else if (mAssetManager != null) {  // Builder is created with asset manager.
+                final String key = createAssetUid(
+                        mAssetManager, mPath, mTtcIndex, mAxes, mWeight, mItalic,
+                        mFallbackFamilyName);
+                synchronized (sDynamicCacheLock) {
+                    Typeface typeface = sDynamicTypefaceCache.get(key);
+                    if (typeface != null) return typeface;
+                    final FontFamily fontFamily = new FontFamily();
+                    if (!fontFamily.addFontFromAssetManager(mAssetManager, mPath, mTtcIndex,
+                            true /* isAsset */, mTtcIndex, mWeight, mItalic, mAxes)) {
+                        fontFamily.abortCreation();
+                        return resolveFallbackTypeface();
+                    }
+                    if (!fontFamily.freeze()) {
+                        return resolveFallbackTypeface();
+                    }
+                    FontFamily[] families = { fontFamily };
+                    typeface = createFromFamiliesWithDefault(families, mFallbackFamilyName,
+                            mWeight, mItalic);
+                    sDynamicTypefaceCache.put(key, typeface);
+                    return typeface;
+                }
+            } else if (mPath != null) {  // Builder is created with file path.
+                final FontFamily fontFamily = new FontFamily();
+                if (!fontFamily.addFont(mPath, mTtcIndex, mAxes, mWeight, mItalic)) {
+                    fontFamily.abortCreation();
+                    return resolveFallbackTypeface();
+                }
+                if (!fontFamily.freeze()) {
+                    return resolveFallbackTypeface();
+                }
+                FontFamily[] families = { fontFamily };
+                return createFromFamiliesWithDefault(families, mFallbackFamilyName, mWeight,
+                        mItalic);
+            } else if (mFonts != null) {
+                final FontFamily fontFamily = new FontFamily();
+                boolean atLeastOneFont = false;
+                for (FontsContract.FontInfo font : mFonts) {
+                    final ByteBuffer fontBuffer = mFontBuffers.get(font.getUri());
+                    if (fontBuffer == null) {
+                        continue;  // skip
+                    }
+                    final boolean success = fontFamily.addFontFromBuffer(fontBuffer,
+                            font.getTtcIndex(), font.getAxes(), font.getWeight(),
+                            font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL);
+                    if (!success) {
+                        fontFamily.abortCreation();
+                        return null;
+                    }
+                    atLeastOneFont = true;
+                }
+                if (!atLeastOneFont) {
+                    // No fonts are avaialble. No need to create new Typeface and returns fallback
+                    // Typeface instead.
+                    fontFamily.abortCreation();
+                    return null;
+                }
+                fontFamily.freeze();
+                FontFamily[] families = { fontFamily };
+                return createFromFamiliesWithDefault(families, mFallbackFamilyName, mWeight,
+                        mItalic);
+            }
+
+            // Must not reach here.
+            throw new IllegalArgumentException("No source was set.");
+        }
+    }
+
+    /**
+     * Create a typeface object given a family name, and option style information.
+     * If null is passed for the name, then the "default" font will be chosen.
+     * The resulting typeface object can be queried (getStyle()) to discover what
+     * its "real" style characteristics are.
+     *
+     * @param familyName May be null. The name of the font family.
+     * @param style  The style (normal, bold, italic) of the typeface.
+     *               e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
+     * @return The best matching typeface.
+     */
+    public static Typeface create(String familyName, int style) {
+        return create(sSystemFontMap.get(familyName), style);
+    }
+
+    /**
+     * Create a typeface object that best matches the specified existing
+     * typeface and the specified Style. Use this call if you want to pick a new
+     * style from the same family of an existing typeface object. If family is
+     * null, this selects from the default font's family.
+     *
+     * <p>
+     * This method is not thread safe on API 27 or before.
+     * This method is thread safe on API 28 or after.
+     * </p>
+     *
+     * @param family An existing {@link Typeface} object. In case of {@code null}, the default
+     *               typeface is used instead.
+     * @param style  The style (normal, bold, italic) of the typeface.
+     *               e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
+     * @return The best matching typeface.
+     */
+    public static Typeface create(Typeface family, int style) {
+        if ((style & ~STYLE_MASK) != 0) {
+            style = NORMAL;
+        }
+        if (family == null) {
+            family = sDefaultTypeface;
+        }
+
+        // Return early if we're asked for the same face/style
+        if (family.mStyle == style) {
+            return family;
+        }
+
+        final long ni = family.native_instance;
+
+        Typeface typeface;
+        synchronized (sStyledCacheLock) {
+            SparseArray<Typeface> styles = sStyledTypefaceCache.get(ni);
+
+            if (styles == null) {
+                styles = new SparseArray<Typeface>(4);
+                sStyledTypefaceCache.put(ni, styles);
+            } else {
+                typeface = styles.get(style);
+                if (typeface != null) {
+                    return typeface;
+                }
+            }
+
+            typeface = new Typeface(nativeCreateFromTypeface(ni, style));
+            styles.put(style, typeface);
+        }
+        return typeface;
+    }
+
+    /**
+     * Creates a typeface object that best matches the specified existing typeface and the specified
+     * weight and italic style
+     *
+     * <p>
+     * This method is thread safe.
+     * </p>
+     *
+     * @param family An existing {@link Typeface} object. In case of {@code null}, the default
+     *               typeface is used instead.
+     * @param weight The desired weight to be drawn.
+     * @param italic {@code true} if italic style is desired to be drawn. Otherwise, {@code false}
+     * @return A {@link Typeface} object for drawing specified weight and italic style. Never
+     *         returns {@code null}
+     */
+    public static @NonNull Typeface create(@Nullable Typeface family,
+            @IntRange(from = 1, to = 1000) int weight, boolean italic) {
+        Preconditions.checkArgumentInRange(weight, 0, 1000, "weight");
+        if (family == null) {
+            family = sDefaultTypeface;
+        }
+        return createWeightStyle(family, weight, italic);
+    }
+
+    private static @NonNull Typeface createWeightStyle(@NonNull Typeface base,
+            @IntRange(from = 1, to = 1000) int weight, boolean italic) {
+        final int key = (weight << 1) | (italic ? 1 : 0);
+
+        Typeface typeface;
+        synchronized(sWeightCacheLock) {
+            SparseArray<Typeface> innerCache = sWeightTypefaceCache.get(base.native_instance);
+            if (innerCache == null) {
+                innerCache = new SparseArray<>(4);
+                sWeightTypefaceCache.put(base.native_instance, innerCache);
+            } else {
+                typeface = innerCache.get(key);
+                if (typeface != null) {
+                    return typeface;
+                }
+            }
+
+            typeface = new Typeface(
+                    nativeCreateFromTypefaceWithExactStyle(
+                            base.native_instance, weight, italic));
+            innerCache.put(key, typeface);
+        }
+        return typeface;
+    }
+
+    /** @hide */
+    public static Typeface createFromTypefaceWithVariation(@Nullable Typeface family,
+            @NonNull List<FontVariationAxis> axes) {
+        final long ni = family == null ? 0 : family.native_instance;
+        return new Typeface(nativeCreateFromTypefaceWithVariation(ni, axes));
+    }
+
+    /**
+     * Returns one of the default typeface objects, based on the specified style
+     *
+     * @return the default typeface that corresponds to the style
+     */
+    public static Typeface defaultFromStyle(int style) {
+        return sDefaults[style];
+    }
+
+    /**
+     * Create a new typeface from the specified font data.
+     *
+     * @param mgr  The application's asset manager
+     * @param path The file name of the font data in the assets directory
+     * @return The new typeface.
+     */
+    public static Typeface createFromAsset(AssetManager mgr, String path) {
+        if (path == null) {
+            throw new NullPointerException();  // for backward compatibility
+        }
+        synchronized (sDynamicCacheLock) {
+            Typeface typeface = new Builder(mgr, path).build();
+            if (typeface != null) return typeface;
+
+            final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */,
+                    null /* axes */, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE,
+                    DEFAULT_FAMILY);
+            typeface = sDynamicTypefaceCache.get(key);
+            if (typeface != null) return typeface;
+
+            final FontFamily fontFamily = new FontFamily();
+            if (fontFamily.addFontFromAssetManager(mgr, path, 0, true /* isAsset */,
+                    0 /* ttc index */, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE,
+                    null /* axes */)) {
+                // Due to backward compatibility, even if the font is not supported by our font
+                // stack, we need to place the empty font at the first place. The typeface with
+                // empty font behaves different from default typeface especially in fallback
+                // font selection.
+                fontFamily.allowUnsupportedFont();
+                fontFamily.freeze();
+                final FontFamily[] families = { fontFamily };
+                typeface = createFromFamiliesWithDefault(families, DEFAULT_FAMILY,
+                        RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
+                sDynamicTypefaceCache.put(key, typeface);
+                return typeface;
+            } else {
+                fontFamily.abortCreation();
+            }
+        }
+        throw new RuntimeException("Font asset not found " + path);
+    }
+
+    /**
+     * Creates a unique id for a given font provider and query.
+     */
+    private static String createProviderUid(String authority, String query) {
+        final StringBuilder builder = new StringBuilder();
+        builder.append("provider:");
+        builder.append(authority);
+        builder.append("-");
+        builder.append(query);
+        return builder.toString();
+    }
+
+    /**
+     * Create a new typeface from the specified font file.
+     *
+     * @param path The path to the font data.
+     * @return The new typeface.
+     */
+    public static Typeface createFromFile(@Nullable File path) {
+        // For the compatibility reasons, leaving possible NPE here.
+        // See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull
+        return createFromFile(path.getAbsolutePath());
+    }
+
+    /**
+     * Create a new typeface from the specified font file.
+     *
+     * @param path The full path to the font data.
+     * @return The new typeface.
+     */
+    public static Typeface createFromFile(@Nullable String path) {
+        final FontFamily fontFamily = new FontFamily();
+        if (fontFamily.addFont(path, 0 /* ttcIndex */, null /* axes */,
+                  RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)) {
+            // Due to backward compatibility, even if the font is not supported by our font
+            // stack, we need to place the empty font at the first place. The typeface with
+            // empty font behaves different from default typeface especially in fallback font
+            // selection.
+            fontFamily.allowUnsupportedFont();
+            fontFamily.freeze();
+            FontFamily[] families = { fontFamily };
+            return createFromFamiliesWithDefault(families, DEFAULT_FAMILY,
+                    RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
+        } else {
+            fontFamily.abortCreation();
+        }
+        throw new RuntimeException("Font not found " + path);
+    }
+
+    /**
+     * Create a new typeface from an array of font families.
+     *
+     * @param families array of font families
+     */
+    private static Typeface createFromFamilies(FontFamily[] families) {
+        long[] ptrArray = new long[families.length];
+        for (int i = 0; i < families.length; i++) {
+            ptrArray[i] = families[i].mNativePtr;
+        }
+        return new Typeface(nativeCreateFromArray(
+                ptrArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+    }
+
+    /**
+     * Create a new typeface from an array of font families, including
+     * also the font families in the fallback list.
+     * @param fallbackName the family name. If given families don't support characters, the
+     *               characters will be rendered with this family.
+     * @param weight the weight for this family. {@link RESOLVE_BY_FONT_TABLE} can be used. In that
+     *               case, the table information in the first family's font is used. If the first
+     *               family has multiple fonts, the closest to the regular weight and upright font
+     *               is used.
+     * @param italic the italic information for this family. {@link RESOLVE_BY_FONT_TABLE} can be
+     *               used. In that case, the table information in the first family's font is used.
+     *               If the first family has multiple fonts, the closest to the regular weight and
+     *               upright font is used.
+     * @param families array of font families
+     */
+    private static Typeface createFromFamiliesWithDefault(FontFamily[] families,
+                String fallbackName, int weight, int italic) {
+        FontFamily[] fallback = sSystemFallbackMap.get(fallbackName);
+        if (fallback == null) {
+            fallback = sSystemFallbackMap.get(DEFAULT_FAMILY);
+        }
+        long[] ptrArray = new long[families.length + fallback.length];
+        for (int i = 0; i < families.length; i++) {
+            ptrArray[i] = families[i].mNativePtr;
+        }
+        for (int i = 0; i < fallback.length; i++) {
+            ptrArray[i + families.length] = fallback[i].mNativePtr;
+        }
+        return new Typeface(nativeCreateFromArray(ptrArray, weight, italic));
+    }
+
+    // don't allow clients to call this directly
+    private Typeface(long ni) {
+        if (ni == 0) {
+            throw new RuntimeException("native typeface cannot be made");
+        }
+
+        native_instance = ni;
+        mStyle = nativeGetStyle(ni);
+        mWeight = nativeGetWeight(ni);
+    }
+
+    private static @Nullable ByteBuffer mmap(String fullPath) {
+        try (FileInputStream file = new FileInputStream(fullPath)) {
+            final FileChannel fileChannel = file.getChannel();
+            final long fontSize = fileChannel.size();
+            return fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
+        } catch (IOException e) {
+            Log.e(TAG, "Error mapping font file " + fullPath);
+            return null;
+        }
+    }
+
+    private static @Nullable FontFamily createFontFamily(
+            String familyName, List<FontConfig.Font> fonts, String[] languageTags, int variant,
+            Map<String, ByteBuffer> cache, String fontDir) {
+        final FontFamily family = new FontFamily(languageTags, variant);
+        for (int i = 0; i < fonts.size(); i++) {
+            final FontConfig.Font font = fonts.get(i);
+            final String fullPath = fontDir + font.getFontName();
+            ByteBuffer buffer = cache.get(fullPath);
+            if (buffer == null) {
+                if (cache.containsKey(fullPath)) {
+                    continue;  // Already failed to mmap. Skip it.
+                }
+                buffer = mmap(fullPath);
+                cache.put(fullPath, buffer);
+                if (buffer == null) {
+                    continue;
+                }
+            }
+            if (!family.addFontFromBuffer(buffer, font.getTtcIndex(), font.getAxes(),
+                    font.getWeight(), font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL)) {
+                Log.e(TAG, "Error creating font " + fullPath + "#" + font.getTtcIndex());
+            }
+        }
+        if (!family.freeze()) {
+            Log.e(TAG, "Unable to load Family: " + familyName + " : "
+                    + Arrays.toString(languageTags));
+            return null;
+        }
+        return family;
+    }
+
+    private static void pushFamilyToFallback(FontConfig.Family xmlFamily,
+            ArrayMap<String, ArrayList<FontFamily>> fallbackMap,
+            Map<String, ByteBuffer> cache,
+            String fontDir) {
+
+        final String[] languageTags = xmlFamily.getLanguages();
+        final int variant = xmlFamily.getVariant();
+
+        final ArrayList<FontConfig.Font> defaultFonts = new ArrayList<>();
+        final ArrayMap<String, ArrayList<FontConfig.Font>> specificFallbackFonts = new ArrayMap<>();
+
+        // Collect default fallback and specific fallback fonts.
+        for (final FontConfig.Font font : xmlFamily.getFonts()) {
+            final String fallbackName = font.getFallbackFor();
+            if (fallbackName == null) {
+                defaultFonts.add(font);
+            } else {
+                ArrayList<FontConfig.Font> fallback = specificFallbackFonts.get(fallbackName);
+                if (fallback == null) {
+                    fallback = new ArrayList<>();
+                    specificFallbackFonts.put(fallbackName, fallback);
+                }
+                fallback.add(font);
+            }
+        }
+
+        final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily(
+                xmlFamily.getName(), defaultFonts, languageTags, variant, cache, fontDir);
+
+        // Insert family into fallback map.
+        for (int i = 0; i < fallbackMap.size(); i++) {
+            final ArrayList<FontConfig.Font> fallback =
+                    specificFallbackFonts.get(fallbackMap.keyAt(i));
+            if (fallback == null) {
+                if (defaultFamily != null) {
+                    fallbackMap.valueAt(i).add(defaultFamily);
+                }
+            } else {
+                final FontFamily family = createFontFamily(
+                        xmlFamily.getName(), fallback, languageTags, variant, cache, fontDir);
+                if (family != null) {
+                    fallbackMap.valueAt(i).add(family);
+                }
+            }
+        }
+    }
+
+    /**
+     * Build the system fallback from xml file.
+     *
+     * @param xmlPath A full path string to the fonts.xml file.
+     * @param fontDir A full path string to the system font directory. This must end with
+     *                slash('/').
+     * @param fontMap An output system font map. Caller must pass empty map.
+     * @param fallbackMap An output system fallback map. Caller must pass empty map.
+     * @hide
+     */
+    @VisibleForTesting
+    public static void buildSystemFallback(String xmlPath, String fontDir,
+            ArrayMap<String, Typeface> fontMap, ArrayMap<String, FontFamily[]> fallbackMap) {
+        try {
+            final FileInputStream fontsIn = new FileInputStream(xmlPath);
+            final FontConfig fontConfig = FontListParser.parse(fontsIn);
+
+            final HashMap<String, ByteBuffer> bufferCache = new HashMap<String, ByteBuffer>();
+            final FontConfig.Family[] xmlFamilies = fontConfig.getFamilies();
+
+            final ArrayMap<String, ArrayList<FontFamily>> fallbackListMap = new ArrayMap<>();
+            // First traverse families which have a 'name' attribute to create fallback map.
+            for (final FontConfig.Family xmlFamily : xmlFamilies) {
+                final String familyName = xmlFamily.getName();
+                if (familyName == null) {
+                    continue;
+                }
+                final FontFamily family = createFontFamily(
+                        xmlFamily.getName(), Arrays.asList(xmlFamily.getFonts()),
+                        xmlFamily.getLanguages(), xmlFamily.getVariant(), bufferCache, fontDir);
+                if (family == null) {
+                    continue;
+                }
+                final ArrayList<FontFamily> fallback = new ArrayList<>();
+                fallback.add(family);
+                fallbackListMap.put(familyName, fallback);
+            }
+
+            // Then, add fallback fonts to the each fallback map.
+            for (int i = 0; i < xmlFamilies.length; i++) {
+                final FontConfig.Family xmlFamily = xmlFamilies[i];
+                // The first family (usually the sans-serif family) is always placed immediately
+                // after the primary family in the fallback.
+                if (i == 0 || xmlFamily.getName() == null) {
+                    pushFamilyToFallback(xmlFamily, fallbackListMap, bufferCache, fontDir);
+                }
+            }
+
+            // Build the font map and fallback map.
+            for (int i = 0; i < fallbackListMap.size(); i++) {
+                final String fallbackName = fallbackListMap.keyAt(i);
+                final List<FontFamily> familyList = fallbackListMap.valueAt(i);
+                final FontFamily[] families = familyList.toArray(new FontFamily[familyList.size()]);
+
+                fallbackMap.put(fallbackName, families);
+                final long[] ptrArray = new long[families.length];
+                for (int j = 0; j < families.length; j++) {
+                    ptrArray[j] = families[j].mNativePtr;
+                }
+                fontMap.put(fallbackName, new Typeface(nativeCreateFromArray(
+                        ptrArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)));
+            }
+
+            // Insert alias to font maps.
+            for (final FontConfig.Alias alias : fontConfig.getAliases()) {
+                Typeface base = fontMap.get(alias.getToName());
+                Typeface newFace = base;
+                int weight = alias.getWeight();
+                if (weight != 400) {
+                    newFace = new Typeface(nativeCreateWeightAlias(base.native_instance, weight));
+                }
+                fontMap.put(alias.getName(), newFace);
+            }
+        } catch (RuntimeException e) {
+            Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)", e);
+            // TODO: normal in non-Minikin case, remove or make error when Minikin-only
+        } catch (FileNotFoundException e) {
+            Log.e(TAG, "Error opening " + xmlPath, e);
+        } catch (IOException e) {
+            Log.e(TAG, "Error reading " + xmlPath, e);
+        } catch (XmlPullParserException e) {
+            Log.e(TAG, "XML parse exception for " + xmlPath, e);
+        }
+    }
+
+    static {
+        final ArrayMap<String, Typeface> systemFontMap = new ArrayMap<>();
+        final ArrayMap<String, FontFamily[]> systemFallbackMap = new ArrayMap<>();
+        buildSystemFallback("/system/etc/fonts.xml", "/system/fonts/", systemFontMap,
+                systemFallbackMap);
+        sSystemFontMap = Collections.unmodifiableMap(systemFontMap);
+        sSystemFallbackMap = Collections.unmodifiableMap(systemFallbackMap);
+
+        setDefault(sSystemFontMap.get(DEFAULT_FAMILY));
+
+        // Set up defaults and typefaces exposed in public API
+        DEFAULT         = create((String) null, 0);
+        DEFAULT_BOLD    = create((String) null, Typeface.BOLD);
+        SANS_SERIF      = create("sans-serif", 0);
+        SERIF           = create("serif", 0);
+        MONOSPACE       = create("monospace", 0);
+
+        sDefaults = new Typeface[] {
+            DEFAULT,
+            DEFAULT_BOLD,
+            create((String) null, Typeface.ITALIC),
+            create((String) null, Typeface.BOLD_ITALIC),
+        };
+
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            nativeUnref(native_instance);
+            native_instance = 0;  // Other finalizers can still call us.
+        } finally {
+            super.finalize();
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        Typeface typeface = (Typeface) o;
+
+        return mStyle == typeface.mStyle && native_instance == typeface.native_instance;
+    }
+
+    @Override
+    public int hashCode() {
+        /*
+         * Modified method for hashCode with long native_instance derived from
+         * http://developer.android.com/reference/java/lang/Object.html
+         */
+        int result = 17;
+        result = 31 * result + (int) (native_instance ^ (native_instance >>> 32));
+        result = 31 * result + mStyle;
+        return result;
+    }
+
+    /** @hide */
+    public boolean isSupportedAxes(int axis) {
+        if (mSupportedAxes == null) {
+            synchronized (this) {
+                if (mSupportedAxes == null) {
+                    mSupportedAxes = nativeGetSupportedAxes(native_instance);
+                    if (mSupportedAxes == null) {
+                        mSupportedAxes = EMPTY_AXES;
+                    }
+                }
+            }
+        }
+        return Arrays.binarySearch(mSupportedAxes, axis) >= 0;
+    }
+
+    private static native long nativeCreateFromTypeface(long native_instance, int style);
+    private static native long nativeCreateFromTypefaceWithExactStyle(
+            long native_instance, int weight, boolean italic);
+    // TODO: clean up: change List<FontVariationAxis> to FontVariationAxis[]
+    private static native long nativeCreateFromTypefaceWithVariation(
+            long native_instance, List<FontVariationAxis> axes);
+    private static native long nativeCreateWeightAlias(long native_instance, int weight);
+    private static native void nativeUnref(long native_instance);
+    private static native int  nativeGetStyle(long native_instance);
+    private static native int  nativeGetWeight(long native_instance);
+    private static native long nativeCreateFromArray(long[] familyArray, int weight, int italic);
+    private static native void nativeSetDefault(long native_instance);
+    private static native int[] nativeGetSupportedAxes(long native_instance);
+}
diff --git a/android/graphics/Typeface_Accessor.java b/android/graphics/Typeface_Accessor.java
new file mode 100644
index 0000000..ce669cb
--- /dev/null
+++ b/android/graphics/Typeface_Accessor.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.annotation.NonNull;
+
+/**
+ * Class allowing access to package-protected methods/fields.
+ */
+public class Typeface_Accessor {
+    public static boolean isSystemFont(@NonNull String fontName) {
+        return Typeface.sSystemFontMap.containsKey(fontName);
+    }
+}
diff --git a/android/graphics/Typeface_Delegate.java b/android/graphics/Typeface_Delegate.java
new file mode 100644
index 0000000..b9c0353
--- /dev/null
+++ b/android/graphics/Typeface_Delegate.java
@@ -0,0 +1,401 @@
+/*
+ * 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;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.FontFamily_Delegate.FontVariant;
+import android.graphics.fonts.FontVariationAxis;
+import android.text.FontConfig;
+import android.util.ArrayMap;
+
+import java.awt.Font;
+import java.lang.ref.SoftReference;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Spliterator;
+import java.util.Spliterators;
+
+import static android.graphics.FontFamily_Delegate.getFontLocation;
+
+/**
+ * Delegate implementing the native methods of android.graphics.Typeface
+ * <p>
+ * Through the layoutlib_create tool, the original native methods of Typeface have been replaced by
+ * calls to methods of the same name in this delegate class.
+ * <p>
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between it
+ * and the original Typeface class.
+ *
+ * @see DelegateManager
+ */
+public final class Typeface_Delegate {
+
+    public static final String SYSTEM_FONTS = "/system/fonts/";
+
+    // ---- delegate manager ----
+    private static final DelegateManager<Typeface_Delegate> sManager =
+            new DelegateManager<>(Typeface_Delegate.class);
+
+
+    // ---- delegate data ----
+    private static long sDefaultTypeface;
+    @NonNull
+    private final FontFamily_Delegate[] mFontFamilies;  // the reference to FontFamily_Delegate.
+    /** @see Font#getStyle() */
+    private final int mStyle;
+    private final int mWeight;
+    private SoftReference<EnumMap<FontVariant, List<Font>>> mFontsCache = new SoftReference<>(null);
+
+
+    // ---- Public Helper methods ----
+
+    public Typeface_Delegate(@NonNull FontFamily_Delegate[] fontFamilies, int style, int weight) {
+        mFontFamilies = fontFamilies;
+        mStyle = style;
+        mWeight = weight;
+    }
+
+    public static Typeface_Delegate getDelegate(long nativeTypeface) {
+        return sManager.getDelegate(nativeTypeface);
+    }
+
+    /**
+     * Clear the default typefaces when disposing bridge.
+     */
+    public static void resetDefaults() {
+        // Sometimes this is called before the Bridge is initialized. In that case, we don't want to
+        // initialize Typeface because the SDK fonts location hasn't been set.
+        if (FontFamily_Delegate.getFontLocation() != null) {
+            Typeface.sDefaults = null;
+        }
+    }
+
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static synchronized long nativeCreateFromTypeface(long native_instance, int style) {
+        Typeface_Delegate delegate = sManager.getDelegate(native_instance);
+        if (delegate == null) {
+            delegate = sManager.getDelegate(sDefaultTypeface);
+        }
+        if (delegate == null) {
+            return 0;
+        }
+
+        return sManager.addNewDelegate(
+                new Typeface_Delegate(delegate.mFontFamilies, style, delegate.mWeight));
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeCreateFromTypefaceWithExactStyle(long native_instance, int weight,
+            boolean italic) {
+        Typeface_Delegate delegate = sManager.getDelegate(native_instance);
+        if (delegate == null) {
+            delegate = sManager.getDelegate(sDefaultTypeface);
+        }
+        if (delegate == null) {
+            return 0;
+        }
+
+        int style = weight >= 600 ? (italic ? Typeface.BOLD_ITALIC : Typeface.BOLD) :
+                (italic ? Typeface.ITALIC : Typeface.NORMAL);
+        return sManager.addNewDelegate(
+                new Typeface_Delegate(delegate.mFontFamilies, style, weight));
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static synchronized long nativeCreateFromTypefaceWithVariation(long native_instance,
+            List<FontVariationAxis> axes) {
+        long newInstance = nativeCreateFromTypeface(native_instance, 0);
+
+        if (newInstance != 0) {
+            Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                    "nativeCreateFromTypefaceWithVariation is not supported", null, null);
+        }
+        return newInstance;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static synchronized int[] nativeGetSupportedAxes(long native_instance) {
+        // nativeCreateFromTypefaceWithVariation is not supported so we do not keep the axes
+        return null;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nativeCreateWeightAlias(long native_instance, int weight) {
+        Typeface_Delegate delegate = sManager.getDelegate(native_instance);
+        if (delegate == null) {
+            delegate = sManager.getDelegate(sDefaultTypeface);
+        }
+        if (delegate == null) {
+            return 0;
+        }
+        Typeface_Delegate weightAlias =
+                new Typeface_Delegate(delegate.mFontFamilies, delegate.mStyle, weight);
+        return sManager.addNewDelegate(weightAlias);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static synchronized long nativeCreateFromArray(long[] familyArray, int weight,
+            int italic) {
+        FontFamily_Delegate[] fontFamilies = new FontFamily_Delegate[familyArray.length];
+        for (int i = 0; i < familyArray.length; i++) {
+            fontFamilies[i] = FontFamily_Delegate.getDelegate(familyArray[i]);
+        }
+        if (weight == Typeface.RESOLVE_BY_FONT_TABLE) {
+            weight = 400;
+        }
+        if (italic == Typeface.RESOLVE_BY_FONT_TABLE) {
+            italic = 0;
+        }
+        int style = weight >= 600 ? (italic == 1 ? Typeface.BOLD_ITALIC : Typeface.BOLD) :
+                (italic == 1 ? Typeface.ITALIC : Typeface.NORMAL);
+        Typeface_Delegate delegate = new Typeface_Delegate(fontFamilies, style, weight);
+        return sManager.addNewDelegate(delegate);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nativeUnref(long native_instance) {
+        sManager.removeJavaReferenceFor(native_instance);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static int nativeGetStyle(long native_instance) {
+        Typeface_Delegate delegate = sManager.getDelegate(native_instance);
+        if (delegate == null) {
+            return 0;
+        }
+
+        return delegate.mStyle;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nativeSetDefault(long native_instance) {
+        sDefaultTypeface = native_instance;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static int nativeGetWeight(long native_instance) {
+        Typeface_Delegate delegate = sManager.getDelegate(native_instance);
+        if (delegate == null) {
+            return 0;
+        }
+        return delegate.mWeight;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void buildSystemFallback(String xmlPath, String fontDir,
+            ArrayMap<String, Typeface> fontMap, ArrayMap<String, FontFamily[]> fallbackMap) {
+        Typeface.buildSystemFallback_Original(getFontLocation() + "/fonts.xml", fontDir, fontMap,
+                fallbackMap);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static FontFamily createFontFamily(String familyName, List<FontConfig.Font> fonts,
+            String[] languageTags, int variant, Map<String, ByteBuffer> cache, String fontDir) {
+        FontFamily fontFamily = new FontFamily(languageTags, variant);
+        for (FontConfig.Font font : fonts) {
+            String fullPathName = fontDir + font.getFontName();
+            FontFamily_Delegate.addFont(fontFamily.mBuilderPtr, fullPathName, font.getWeight(),
+                    font.isItalic());
+        }
+        fontFamily.freeze();
+        return fontFamily;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static Typeface create(String familyName, int style) {
+        if (familyName != null && Files.exists(Paths.get(familyName))) {
+            // Workaround for b/64137851
+            // Support lib will call this method after failing to create the TypefaceCompat.
+            return Typeface.createFromFile(familyName);
+        }
+        return Typeface.create_Original(familyName, style);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static Typeface create(Typeface family, int style) {
+        return Typeface.create_Original(family, style);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static Typeface create(Typeface family, int style, boolean isItalic) {
+        return Typeface.create_Original(family, style, isItalic);
+    }
+
+    // ---- Private delegate/helper methods ----
+
+    private static List<Font> computeFonts(FontVariant variant, FontFamily_Delegate[] fontFamilies,
+            int inputWeight, int inputStyle) {
+        // Calculate the required weight based on style and weight of this typeface.
+        int weight = inputWeight + 50 +
+                ((inputStyle & Font.BOLD) == 0 ? 0 : FontFamily_Delegate.BOLD_FONT_WEIGHT_DELTA);
+        if (weight > 1000) {
+            weight = 1000;
+        } else if (weight < 100) {
+            weight = 100;
+        }
+        final boolean isItalic = (inputStyle & Font.ITALIC) != 0;
+        List<Font> fonts = new ArrayList<Font>(fontFamilies.length);
+        for (int i = 0; i < fontFamilies.length; i++) {
+            FontFamily_Delegate ffd = fontFamilies[i];
+            if (ffd != null && ffd.isValid()) {
+                Font font = ffd.getFont(weight, isItalic);
+                if (font != null) {
+                    FontVariant ffdVariant = ffd.getVariant();
+                    if (ffdVariant == FontVariant.NONE) {
+                        fonts.add(font);
+                        continue;
+                    }
+                    // We cannot open each font and get locales supported, etc to match the fonts.
+                    // As a workaround, we hardcode certain assumptions like Elegant and Compact
+                    // always appear in pairs.
+                    assert i < fontFamilies.length - 1;
+                    FontFamily_Delegate ffd2 = fontFamilies[++i];
+                    assert ffd2 != null;
+                    FontVariant ffd2Variant = ffd2.getVariant();
+                    Font font2 = ffd2.getFont(weight, isItalic);
+                    assert ffd2Variant != FontVariant.NONE && ffd2Variant != ffdVariant &&
+                            font2 != null;
+                    // Add the font with the matching variant to the list.
+                    if (variant == ffd.getVariant()) {
+                        fonts.add(font);
+                    } else {
+                        fonts.add(font2);
+                    }
+                } else {
+                    // The FontFamily is valid but doesn't contain any matching font. This means
+                    // that the font failed to load. We add null to the list of fonts. Don't throw
+                    // the warning just yet. If this is a non-english font, we don't want to warn
+                    // users who are trying to render only english text.
+                    fonts.add(null);
+                }
+            }
+        }
+
+        return fonts;
+    }
+
+    /**
+     * Return an Iterable of fonts that match the style and variant. The list is ordered
+     * according to preference of fonts.
+     * <p>
+     * The Iterator may contain null when the font failed to load. If null is reached when trying to
+     * render with this list of fonts, then a warning should be logged letting the user know that
+     * some font failed to load.
+     *
+     * @param variant The variant preferred. Can only be {@link FontVariant#COMPACT} or {@link
+     * FontVariant#ELEGANT}
+     */
+    @NonNull
+    public Iterable<Font> getFonts(final FontVariant variant) {
+        assert variant != FontVariant.NONE;
+
+        return new FontsIterator(mFontFamilies, variant, mWeight, mStyle);
+    }
+
+    private static class FontsIterator implements Iterator<Font>, Iterable<Font> {
+        private final FontFamily_Delegate[] fontFamilies;
+        private final int weight;
+        private final boolean isItalic;
+        private final FontVariant variant;
+
+        private int index = 0;
+
+        private FontsIterator(@NonNull FontFamily_Delegate[] fontFamilies,
+                @NonNull FontVariant variant, int weight, int style) {
+            // Calculate the required weight based on style and weight of this typeface.
+            int boldExtraWeight =
+                    ((style & Font.BOLD) == 0 ? 0 : FontFamily_Delegate.BOLD_FONT_WEIGHT_DELTA);
+            this.weight = Math.min(Math.max(100, weight + 50 + boldExtraWeight), 1000);
+            this.isItalic = (style & Font.ITALIC) != 0;
+            this.fontFamilies = fontFamilies;
+            this.variant = variant;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return index < fontFamilies.length;
+        }
+
+        @Override
+        @Nullable
+        public Font next() {
+            FontFamily_Delegate ffd = fontFamilies[index++];
+            if (ffd == null || !ffd.isValid()) {
+                return null;
+            }
+
+            Font font = ffd.getFont(weight, isItalic);
+            if (font == null) {
+                // The FontFamily is valid but doesn't contain any matching font. This means
+                // that the font failed to load. We add null to the list of fonts. Don't throw
+                // the warning just yet. If this is a non-english font, we don't want to warn
+                // users who are trying to render only english text.
+                return null;
+            }
+
+            FontVariant ffdVariant = ffd.getVariant();
+            if (ffdVariant == FontVariant.NONE) {
+                return font;
+            }
+
+            // We cannot open each font and get locales supported, etc to match the fonts.
+            // As a workaround, we hardcode certain assumptions like Elegant and Compact
+            // always appear in pairs.
+            assert index < fontFamilies.length - 1;
+            FontFamily_Delegate ffd2 = fontFamilies[index++];
+            assert ffd2 != null;
+
+            if (ffdVariant == variant) {
+                return font;
+            }
+
+            FontVariant ffd2Variant = ffd2.getVariant();
+            Font font2 = ffd2.getFont(weight, isItalic);
+            assert ffd2Variant != FontVariant.NONE && ffd2Variant != ffdVariant && font2 != null;
+            // Add the font with the matching variant to the list.
+            return variant == ffd.getVariant() ? font : font2;
+        }
+
+        @NonNull
+        @Override
+        public Iterator<Font> iterator() {
+            return this;
+        }
+
+        @Override
+        public Spliterator<Font> spliterator() {
+            return Spliterators.spliterator(iterator(), fontFamilies.length,
+                    Spliterator.IMMUTABLE | Spliterator.SIZED);
+        }
+    }
+}
diff --git a/android/graphics/Xfermode.java b/android/graphics/Xfermode.java
new file mode 100644
index 0000000..a5da5d0
--- /dev/null
+++ b/android/graphics/Xfermode.java
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+// This file was generated from the C++ include file: SkXfermode.h
+// Any changes made to this file will be discarded by the build.
+// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, 
+// or one of the auxilary file specifications in device/tools/gluemaker.
+
+package android.graphics;
+
+/**
+ * Xfermode is the base class for objects that are called to implement custom
+ * "transfer-modes" in the drawing pipeline. The static function Create(Modes)
+ * can be called to return an instance of any of the predefined subclasses as
+ * specified in the Modes enum. When an Xfermode is assigned to an Paint, then
+ * objects drawn with that paint have the xfermode applied.
+ */
+public class Xfermode {
+    static final int DEFAULT = PorterDuff.Mode.SRC_OVER.nativeInt;
+    int porterDuffMode = DEFAULT;
+}
diff --git a/android/graphics/YuvImage.java b/android/graphics/YuvImage.java
new file mode 100644
index 0000000..af3f276
--- /dev/null
+++ b/android/graphics/YuvImage.java
@@ -0,0 +1,240 @@
+/*
+ * 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;
+
+import java.io.OutputStream;
+
+/**
+ * YuvImage contains YUV data and provides a method that compresses a region of
+ * the YUV data to a Jpeg. The YUV data should be provided as a single byte
+ * array irrespective of the number of image planes in it.
+ * Currently only ImageFormat.NV21 and ImageFormat.YUY2 are supported.
+ *
+ * To compress a rectangle region in the YUV data, users have to specify the
+ * region by left, top, width and height.
+ */
+public class YuvImage {
+
+    /**
+     * Number of bytes of temp storage we use for communicating between the
+     * native compressor and the java OutputStream.
+     */
+    private final static int WORKING_COMPRESS_STORAGE = 4096;
+
+   /**
+     * The YUV format as defined in {@link ImageFormat}.
+     */
+    private int mFormat;
+
+    /**
+     * The raw YUV data.
+     * In the case of more than one image plane, the image planes must be
+     * concatenated into a single byte array.
+     */
+    private byte[] mData;
+
+    /**
+     * The number of row bytes in each image plane.
+     */
+    private int[] mStrides;
+
+    /**
+     * The width of the image.
+     */
+    private int mWidth;
+
+    /**
+     * The height of the the image.
+     */
+    private int mHeight;
+
+    /**
+     * Construct an YuvImage.
+     *
+     * @param yuv     The YUV data. In the case of more than one image plane, all the planes must be
+     *                concatenated into a single byte array.
+     * @param format  The YUV data format as defined in {@link ImageFormat}.
+     * @param width   The width of the YuvImage.
+     * @param height  The height of the YuvImage.
+     * @param strides (Optional) Row bytes of each image plane. If yuv contains padding, the stride
+     *                of each image must be provided. If strides is null, the method assumes no
+     *                padding and derives the row bytes by format and width itself.
+     * @throws IllegalArgumentException if format is not support; width or height <= 0; or yuv is
+     *                null.
+     */
+    public YuvImage(byte[] yuv, int format, int width, int height, int[] strides) {
+        if (format != ImageFormat.NV21 &&
+                format != ImageFormat.YUY2) {
+            throw new IllegalArgumentException(
+                    "only support ImageFormat.NV21 " +
+                    "and ImageFormat.YUY2 for now");
+        }
+
+        if (width <= 0  || height <= 0) {
+            throw new IllegalArgumentException(
+                    "width and height must large than 0");
+        }
+
+        if (yuv == null) {
+            throw new IllegalArgumentException("yuv cannot be null");
+        }
+
+        if (strides == null) {
+            mStrides = calculateStrides(width, format);
+        } else {
+            mStrides = strides;
+        }
+
+        mData = yuv;
+        mFormat = format;
+        mWidth = width;
+        mHeight = height;
+    }
+
+    /**
+     * Compress a rectangle region in the YuvImage to a jpeg.
+     * Only ImageFormat.NV21 and ImageFormat.YUY2
+     * are supported for now.
+     *
+     * @param rectangle The rectangle region to be compressed. The medthod checks if rectangle is
+     *                  inside the image. Also, the method modifies rectangle if the chroma pixels
+     *                  in it are not matched with the luma pixels in it.
+     * @param quality   Hint to the compressor, 0-100. 0 meaning compress for
+     *                  small size, 100 meaning compress for max quality.
+     * @param stream    OutputStream to write the compressed data.
+     * @return          True if the compression is successful.
+     * @throws IllegalArgumentException if rectangle is invalid; quality is not within [0,
+     *                  100]; or stream is null.
+     */
+    public boolean compressToJpeg(Rect rectangle, int quality, OutputStream stream) {
+        Rect wholeImage = new Rect(0, 0, mWidth, mHeight);
+        if (!wholeImage.contains(rectangle)) {
+            throw new IllegalArgumentException(
+                    "rectangle is not inside the image");
+        }
+
+        if (quality < 0 || quality > 100) {
+            throw new IllegalArgumentException("quality must be 0..100");
+        }
+
+        if (stream == null) {
+            throw new IllegalArgumentException("stream cannot be null");
+        }
+
+        adjustRectangle(rectangle);
+        int[] offsets = calculateOffsets(rectangle.left, rectangle.top);
+
+        return nativeCompressToJpeg(mData, mFormat, rectangle.width(),
+                rectangle.height(), offsets, mStrides, quality, stream,
+                new byte[WORKING_COMPRESS_STORAGE]);
+    }
+
+
+   /**
+     * @return the YUV data.
+     */
+    public byte[] getYuvData() {
+        return mData;
+    }
+
+    /**
+     * @return the YUV format as defined in {@link ImageFormat}.
+     */
+    public int getYuvFormat() {
+        return mFormat;
+    }
+
+    /**
+     * @return the number of row bytes in each image plane.
+     */
+    public int[] getStrides() {
+        return mStrides;
+    }
+
+    /**
+     * @return the width of the image.
+     */
+    public int getWidth() {
+        return mWidth;
+    }
+
+    /**
+     * @return the height of the image.
+     */
+    public int getHeight() {
+        return mHeight;
+    }
+
+    int[] calculateOffsets(int left, int top) {
+        int[] offsets = null;
+        if (mFormat == ImageFormat.NV21) {
+            offsets = new int[] {top * mStrides[0] + left,
+                  mHeight * mStrides[0] + top / 2 * mStrides[1]
+                  + left / 2 * 2 };
+            return offsets;
+        }
+
+        if (mFormat == ImageFormat.YUY2) {
+            offsets = new int[] {top * mStrides[0] + left / 2 * 4};
+            return offsets;
+        }
+
+        return offsets;
+    }
+
+    private int[] calculateStrides(int width, int format) {
+        int[] strides = null;
+        if (format == ImageFormat.NV21) {
+            strides = new int[] {width, width};
+            return strides;
+        }
+
+        if (format == ImageFormat.YUY2) {
+            strides = new int[] {width * 2};
+            return strides;
+        }
+
+        return strides;
+    }
+
+   private void adjustRectangle(Rect rect) {
+       int width = rect.width();
+       int height = rect.height();
+       if (mFormat == ImageFormat.NV21) {
+           // Make sure left, top, width and height are all even.
+           width &= ~1;
+           height &= ~1;
+           rect.left &= ~1;
+           rect.top &= ~1;
+           rect.right = rect.left + width;
+           rect.bottom = rect.top + height;
+        }
+
+        if (mFormat == ImageFormat.YUY2) {
+            // Make sure left and width are both even.
+            width &= ~1;
+            rect.left &= ~1;
+            rect.right = rect.left + width;
+        }
+    }
+
+    //////////// native methods
+
+    private static native boolean nativeCompressToJpeg(byte[] oriYuv,
+            int format, int width, int height, int[] offsets, int[] strides,
+            int quality, OutputStream stream, byte[] tempStorage);
+}
diff --git a/android/graphics/drawable/AdaptiveIconDrawable.java b/android/graphics/drawable/AdaptiveIconDrawable.java
new file mode 100644
index 0000000..1d0cfa5
--- /dev/null
+++ b/android/graphics/drawable/AdaptiveIconDrawable.java
@@ -0,0 +1,1115 @@
+/*
+ * Copyright (C) 2017 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.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.content.pm.ActivityInfo.Config;
+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.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff.Mode;
+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;
+
+import com.android.internal.R;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * <p>This class can also be created via XML inflation using <code>&lt;adaptive-icon></code> tag
+ * in addition to dynamic creation.
+ *
+ * <p>This drawable supports two drawable layers: foreground and background. The layers are clipped
+ * when rendering using the mask defined in the device configuration.
+ *
+ * <ul>
+ * <li>Both foreground and background layers should be sized at 108 x 108 dp.</li>
+ * <li>The inner 72 x 72 dp  of the icon appears within the masked viewport.</li>
+ * <li>The outer 18 dp on each of the 4 sides of the layers is reserved for use by the system UI
+ * surfaces to create interesting visual effects, such as parallax or pulsing.</li>
+ * </ul>
+ *
+ * Such motion effect is achieved by internally setting the bounds of the foreground and
+ * background layer as following:
+ * <pre>
+ * Rect(getBounds().left - getBounds().getWidth() * #getExtraInsetFraction(),
+ *      getBounds().top - getBounds().getHeight() * #getExtraInsetFraction(),
+ *      getBounds().right + getBounds().getWidth() * #getExtraInsetFraction(),
+ *      getBounds().bottom + getBounds().getHeight() * #getExtraInsetFraction())
+ * </pre>
+ */
+public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback {
+
+    /**
+     * Mask path is defined inside device configuration in following dimension: [100 x 100]
+     * @hide
+     */
+    @TestApi
+    public static final float MASK_SIZE = 100f;
+
+    /**
+     * Launcher icons design guideline
+     */
+    private static final float SAFEZONE_SCALE = 66f/72f;
+
+    /**
+     * All four sides of the layers are padded with extra inset so as to provide
+     * extra content to reveal within the clip path when performing affine transformations on the
+     * layers.
+     *
+     * Each layers will reserve 25% of it's width and height.
+     *
+     * As a result, the view port of the layers is smaller than their intrinsic width and height.
+     */
+    private static final float EXTRA_INSET_PERCENTAGE = 1 / 4f;
+    private static final float DEFAULT_VIEW_PORT_SCALE = 1f / (1 + 2 * EXTRA_INSET_PERCENTAGE);
+
+    /**
+     * Clip path defined in R.string.config_icon_mask.
+     */
+    private static Path sMask;
+
+    /**
+     * Scaled mask based on the view bounds.
+     */
+    private final Path mMask;
+    private final Matrix mMaskMatrix;
+    private final Region mTransparentRegion;
+
+    private Bitmap mMaskBitmap;
+
+    /**
+     * Indices used to access {@link #mLayerState.mChildDrawable} array for foreground and
+     * background layer.
+     */
+    private static final int BACKGROUND_ID = 0;
+    private static final int FOREGROUND_ID = 1;
+
+    /**
+     * State variable that maintains the {@link ChildDrawable} array.
+     */
+    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.
+     */
+    AdaptiveIconDrawable() {
+        this((LayerState) null, null);
+    }
+
+    /**
+     * The one constructor to rule them all. This is called by all public
+     * constructors to set the state and initialize local properties.
+     */
+    AdaptiveIconDrawable(@Nullable LayerState state, @Nullable Resources res) {
+        mLayerState = createConstantState(state, res);
+
+        if (sMask == null) {
+            sMask = PathParser.createPathFromPathData(
+                Resources.getSystem().getString(R.string.config_icon_mask));
+        }
+        mMask = PathParser.createPathFromPathData(
+            Resources.getSystem().getString(R.string.config_icon_mask));
+        mMaskMatrix = new Matrix();
+        mCanvas = new Canvas();
+        mTransparentRegion = new Region();
+    }
+
+    private ChildDrawable createChildDrawable(Drawable drawable) {
+        final ChildDrawable layer = new ChildDrawable(mLayerState.mDensity);
+        layer.mDrawable = drawable;
+        layer.mDrawable.setCallback(this);
+        mLayerState.mChildrenChangingConfigurations |=
+            layer.mDrawable.getChangingConfigurations();
+        return layer;
+    }
+
+    LayerState createConstantState(@Nullable LayerState state, @Nullable Resources res) {
+        return new LayerState(state, this, res);
+    }
+
+    /**
+     * Constructor used to dynamically create this drawable.
+     *
+     * @param backgroundDrawable drawable that should be rendered in the background
+     * @param foregroundDrawable drawable that should be rendered in the foreground
+     */
+    public AdaptiveIconDrawable(Drawable backgroundDrawable,
+            Drawable foregroundDrawable) {
+        this((LayerState)null, null);
+        if (backgroundDrawable != null) {
+            addLayer(BACKGROUND_ID, createChildDrawable(backgroundDrawable));
+        }
+        if (foregroundDrawable != null) {
+            addLayer(FOREGROUND_ID, createChildDrawable(foregroundDrawable));
+        }
+    }
+
+    /**
+     * Sets the layer to the {@param index} and invalidates cache.
+     *
+     * @param index The index of the layer.
+     * @param layer The layer to add.
+     */
+    private void addLayer(int index, @NonNull ChildDrawable layer) {
+        mLayerState.mChildren[index] = layer;
+        mLayerState.invalidateCache();
+    }
+
+    @Override
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
+        super.inflate(r, parser, attrs, theme);
+
+        final LayerState state = mLayerState;
+        if (state == null) {
+            return;
+        }
+
+        // The density may have changed since the last update. This will
+        // apply scaling to any existing constant state properties.
+        final int deviceDensity = Drawable.resolveDensity(r, 0);
+        state.setDensity(deviceDensity);
+        state.mSrcDensityOverride = mSrcDensityOverride;
+
+        final ChildDrawable[] array = state.mChildren;
+        for (int i = 0; i < state.mChildren.length; i++) {
+            final ChildDrawable layer = array[i];
+            layer.setDensity(deviceDensity);
+        }
+
+        inflateLayers(r, parser, attrs, theme);
+    }
+
+    /**
+     * All four sides of the layers are padded with extra inset so as to provide
+     * extra content to reveal within the clip path when performing affine transformations on the
+     * layers.
+     *
+     * @see #getForeground() and #getBackground() for more info on how this value is used
+     */
+    public static float getExtraInsetFraction() {
+        return EXTRA_INSET_PERCENTAGE;
+    }
+
+    /**
+     * @hide
+     */
+    public static float getExtraInsetPercentage() {
+        return EXTRA_INSET_PERCENTAGE;
+    }
+
+    /**
+     * When called before the bound is set, the returned path is identical to
+     * R.string.config_icon_mask. After the bound is set, the
+     * returned path's computed bound is same as the #getBounds().
+     *
+     * @return the mask path object used to clip the drawable
+     */
+    public Path getIconMask() {
+        return mMask;
+    }
+
+    /**
+     * Returns the foreground drawable managed by this class. The bound of this drawable is
+     * extended by {@link #getExtraInsetFraction()} * getBounds().width on left/right sides and by
+     * {@link #getExtraInsetFraction()} * getBounds().height on top/bottom sides.
+     *
+     * @return the foreground drawable managed by this drawable
+     */
+    public Drawable getForeground() {
+        return mLayerState.mChildren[FOREGROUND_ID].mDrawable;
+    }
+
+    /**
+     * Returns the foreground drawable managed by this class. The bound of this drawable is
+     * extended by {@link #getExtraInsetFraction()} * getBounds().width on left/right sides and by
+     * {@link #getExtraInsetFraction()} * getBounds().height on top/bottom sides.
+     *
+     * @return the background drawable managed by this drawable
+     */
+    public Drawable getBackground() {
+        return mLayerState.mChildren[BACKGROUND_ID].mDrawable;
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        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;
+
+        for (int i = 0, count = mLayerState.N_CHILDREN; i < count; i++) {
+            final ChildDrawable r = mLayerState.mChildren[i];
+            if (r == null) {
+                continue;
+            }
+            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);
+        }
+    }
+
+    private void updateMaskBoundsInternal(Rect b) {
+        mMaskMatrix.setScale(b.width() / MASK_SIZE, b.height() / MASK_SIZE);
+        sMask.transform(mMaskMatrix, mMask);
+
+        if (mMaskBitmap == null || mMaskBitmap.getWidth() != b.width() ||
+            mMaskBitmap.getHeight() != b.height()) {
+            mMaskBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ALPHA_8);
+            mLayersBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ARGB_8888);
+        }
+        // mMaskBitmap bound [0, w] x [0, h]
+        mCanvas.setBitmap(mMaskBitmap);
+        mPaint.setShader(null);
+        mCanvas.drawPath(mMask, mPaint);
+
+        // mMask bound [left, top, right, bottom]
+        mMaskMatrix.postTranslate(b.left, b.top);
+        mMask.reset();
+        sMask.transform(mMaskMatrix, mMask);
+        // reset everything that depends on the view bounds
+        mTransparentRegion.setEmpty();
+        mLayersShader = null;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (mLayersBitmap == null) {
+            return;
+        }
+        if (mLayersShader == null) {
+            mCanvas.setBitmap(mLayersBitmap);
+            mCanvas.drawColor(Color.BLACK);
+            for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+                if (mLayerState.mChildren[i] == null) {
+                    continue;
+                }
+                final Drawable dr = mLayerState.mChildren[i].mDrawable;
+                if (dr != null) {
+                    dr.draw(mCanvas);
+                }
+            }
+            mLayersShader = new BitmapShader(mLayersBitmap, TileMode.CLAMP, TileMode.CLAMP);
+            mPaint.setShader(mLayersShader);
+        }
+        if (mMaskBitmap != null) {
+            Rect bounds = getBounds();
+            canvas.drawBitmap(mMaskBitmap, bounds.left, bounds.top, mPaint);
+        }
+    }
+
+    @Override
+    public void invalidateSelf() {
+        mLayersShader = null;
+        super.invalidateSelf();
+    }
+
+    @Override
+    public void getOutline(@NonNull Outline outline) {
+        outline.setConvexPath(mMask);
+    }
+
+    /** @hide */
+    @TestApi
+    public Region getSafeZone() {
+        mMaskMatrix.reset();
+        mMaskMatrix.setScale(SAFEZONE_SCALE, SAFEZONE_SCALE, getBounds().centerX(), getBounds().centerY());
+        Path p = new Path();
+        mMask.transform(mMaskMatrix, p);
+        Region safezoneRegion = new Region(getBounds());
+        safezoneRegion.setPath(p, safezoneRegion);
+        return safezoneRegion;
+    }
+
+    @Override
+    public @Nullable Region getTransparentRegion() {
+        if (mTransparentRegion.isEmpty()) {
+            mMask.toggleInverseFillType();
+            mTransparentRegion.set(getBounds());
+            mTransparentRegion.setPath(mMask, mTransparentRegion);
+            mMask.toggleInverseFillType();
+        }
+        return mTransparentRegion;
+    }
+
+    @Override
+    public void applyTheme(@NonNull Theme t) {
+        super.applyTheme(t);
+
+        final LayerState state = mLayerState;
+        if (state == null) {
+            return;
+        }
+
+        final int density = Drawable.resolveDensity(t.getResources(), 0);
+        state.setDensity(density);
+
+        final ChildDrawable[] array = state.mChildren;
+        for (int i = 0; i < state.N_CHILDREN; i++) {
+            final ChildDrawable layer = array[i];
+            layer.setDensity(density);
+
+            if (layer.mThemeAttrs != null) {
+                final TypedArray a = t.resolveAttributes(
+                    layer.mThemeAttrs, R.styleable.AdaptiveIconDrawableLayer);
+                updateLayerFromTypedArray(layer, a);
+                a.recycle();
+            }
+
+            final Drawable d = layer.mDrawable;
+            if (d != null && d.canApplyTheme()) {
+                d.applyTheme(t);
+
+                // Update cached mask of child changing configurations.
+                state.mChildrenChangingConfigurations |= d.getChangingConfigurations();
+            }
+        }
+    }
+
+    /**
+     * Inflates child layers using the specified parser.
+     */
+    private void inflateLayers(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
+        final LayerState state = mLayerState;
+
+        final int innerDepth = parser.getDepth() + 1;
+        int type;
+        int depth;
+        int childIndex = 0;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            if (depth > innerDepth) {
+                continue;
+            }
+            String tagName = parser.getName();
+            if (tagName.equals("background")) {
+                childIndex = BACKGROUND_ID;
+            } else if (tagName.equals("foreground")) {
+                childIndex = FOREGROUND_ID;
+            } else {
+                continue;
+            }
+
+            final ChildDrawable layer = new ChildDrawable(state.mDensity);
+            final TypedArray a = obtainAttributes(r, theme, attrs,
+                R.styleable.AdaptiveIconDrawableLayer);
+            updateLayerFromTypedArray(layer, a);
+            a.recycle();
+
+            // If the layer doesn't have a drawable or unresolved theme
+            // attribute for a drawable, attempt to parse one from the child
+            // element. If multiple child elements exist, we'll only use the
+            // first one.
+            if (layer.mDrawable == null && (layer.mThemeAttrs == null)) {
+                while ((type = parser.next()) == XmlPullParser.TEXT) {
+                }
+                if (type != XmlPullParser.START_TAG) {
+                    throw new XmlPullParserException(parser.getPositionDescription()
+                            + ": <foreground> or <background> tag requires a 'drawable'"
+                            + "attribute or child tag defining a drawable");
+                }
+
+                // We found a child drawable. Take ownership.
+                layer.mDrawable = Drawable.createFromXmlInnerForDensity(r, parser, attrs,
+                        mLayerState.mSrcDensityOverride, theme);
+                layer.mDrawable.setCallback(this);
+                state.mChildrenChangingConfigurations |=
+                        layer.mDrawable.getChangingConfigurations();
+            }
+            addLayer(childIndex, layer);
+        }
+    }
+
+    private void updateLayerFromTypedArray(@NonNull ChildDrawable layer, @NonNull TypedArray a) {
+        final LayerState state = mLayerState;
+
+        // Account for any configuration changes.
+        state.mChildrenChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        layer.mThemeAttrs = a.extractThemeAttrs();
+
+        Drawable dr = a.getDrawableForDensity(R.styleable.AdaptiveIconDrawableLayer_drawable,
+                state.mSrcDensityOverride);
+        if (dr != null) {
+            if (layer.mDrawable != null) {
+                // It's possible that a drawable was already set, in which case
+                // we should clear the callback. We may have also integrated the
+                // drawable's changing configurations, but we don't have enough
+                // information to revert that change.
+                layer.mDrawable.setCallback(null);
+            }
+
+            // Take ownership of the new drawable.
+            layer.mDrawable = dr;
+            layer.mDrawable.setCallback(this);
+            state.mChildrenChangingConfigurations |=
+                layer.mDrawable.getChangingConfigurations();
+        }
+    }
+
+    @Override
+    public boolean canApplyTheme() {
+        return (mLayerState != null && mLayerState.canApplyTheme()) || super.canApplyTheme();
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public boolean isProjected() {
+        if (super.isProjected()) {
+            return true;
+        }
+
+        final ChildDrawable[] layers = mLayerState.mChildren;
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            if (layers[i].mDrawable.isProjected()) {
+                return true;
+            }
+        }
+        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) {
+        scheduleSelf(what, when);
+    }
+
+    @Override
+    public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
+        unscheduleSelf(what);
+    }
+
+    @Override
+    public @Config int getChangingConfigurations() {
+        return super.getChangingConfigurations() | mLayerState.getChangingConfigurations();
+    }
+
+    @Override
+    public void setHotspot(float x, float y) {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.setHotspot(x, y);
+            }
+        }
+    }
+
+    @Override
+    public void setHotspotBounds(int left, int top, int right, int bottom) {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.setHotspotBounds(left, top, right, bottom);
+            }
+        }
+
+        if (mHotspotBounds == null) {
+            mHotspotBounds = new Rect(left, top, right, bottom);
+        } else {
+            mHotspotBounds.set(left, top, right, bottom);
+        }
+    }
+
+    @Override
+    public void getHotspotBounds(Rect outRect) {
+        if (mHotspotBounds != null) {
+            outRect.set(mHotspotBounds);
+        } else {
+            super.getHotspotBounds(outRect);
+        }
+    }
+
+    @Override
+    public boolean setVisible(boolean visible, boolean restart) {
+        final boolean changed = super.setVisible(visible, restart);
+        final ChildDrawable[] array = mLayerState.mChildren;
+
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.setVisible(visible, restart);
+            }
+        }
+
+        return changed;
+    }
+
+    @Override
+    public void setDither(boolean dither) {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.setDither(dither);
+            }
+        }
+    }
+
+    @Override
+    public void setAlpha(int 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
+    public int getAlpha() {
+        return PixelFormat.TRANSLUCENT;
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.setColorFilter(colorFilter);
+            }
+        }
+    }
+
+    @Override
+    public void setTintList(ColorStateList tint) {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int N = mLayerState.N_CHILDREN;
+        for (int i = 0; i < N; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.setTintList(tint);
+            }
+        }
+    }
+
+    @Override
+    public void setTintMode(Mode tintMode) {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int N = mLayerState.N_CHILDREN;
+        for (int i = 0; i < N; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.setTintMode(tintMode);
+            }
+        }
+    }
+
+    public void setOpacity(int opacity) {
+        mLayerState.mOpacityOverride = opacity;
+    }
+
+    @Override
+    public int getOpacity() {
+        if (mLayerState.mOpacityOverride != PixelFormat.UNKNOWN) {
+            return mLayerState.mOpacityOverride;
+        }
+        return mLayerState.getOpacity();
+    }
+
+    @Override
+    public void setAutoMirrored(boolean mirrored) {
+        mLayerState.mAutoMirrored = mirrored;
+
+        final ChildDrawable[] array = mLayerState.mChildren;
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.setAutoMirrored(mirrored);
+            }
+        }
+    }
+
+    @Override
+    public boolean isAutoMirrored() {
+        return mLayerState.mAutoMirrored;
+    }
+
+    @Override
+    public void jumpToCurrentState() {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.jumpToCurrentState();
+            }
+        }
+    }
+
+    @Override
+    public boolean isStateful() {
+        return mLayerState.isStateful();
+    }
+
+    /** @hide */
+    @Override
+    public boolean hasFocusStateSpecified() {
+        return mLayerState.hasFocusStateSpecified();
+    }
+
+    @Override
+    protected boolean onStateChange(int[] state) {
+        boolean changed = false;
+
+        final ChildDrawable[] array = mLayerState.mChildren;
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null && dr.isStateful() && dr.setState(state)) {
+                changed = true;
+            }
+        }
+
+        if (changed) {
+            updateLayerBounds(getBounds());
+        }
+
+        return changed;
+    }
+
+    @Override
+    protected boolean onLevelChange(int level) {
+        boolean changed = false;
+
+        final ChildDrawable[] array = mLayerState.mChildren;
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null && dr.setLevel(level)) {
+                changed = true;
+            }
+        }
+
+        if (changed) {
+            updateLayerBounds(getBounds());
+        }
+
+        return changed;
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return (int)(getMaxIntrinsicWidth() * DEFAULT_VIEW_PORT_SCALE);
+    }
+
+    private int getMaxIntrinsicWidth() {
+        int width = -1;
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            final ChildDrawable r = mLayerState.mChildren[i];
+            if (r.mDrawable == null) {
+                continue;
+            }
+            final int w = r.mDrawable.getIntrinsicWidth();
+            if (w > width) {
+                width = w;
+            }
+        }
+        return width;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return (int)(getMaxIntrinsicHeight() * DEFAULT_VIEW_PORT_SCALE);
+    }
+
+    private int getMaxIntrinsicHeight() {
+        int height = -1;
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            final ChildDrawable r = mLayerState.mChildren[i];
+            if (r.mDrawable == null) {
+                continue;
+            }
+            final int h = r.mDrawable.getIntrinsicHeight();
+            if (h > height) {
+                height = h;
+            }
+        }
+        return height;
+    }
+
+    @Override
+    public ConstantState getConstantState() {
+        if (mLayerState.canConstantState()) {
+            mLayerState.mChangingConfigurations = getChangingConfigurations();
+            return mLayerState;
+        }
+        return null;
+    }
+
+    @Override
+    public Drawable mutate() {
+        if (!mMutated && super.mutate() == this) {
+            mLayerState = createConstantState(mLayerState, null);
+            for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+                final Drawable dr = mLayerState.mChildren[i].mDrawable;
+                if (dr != null) {
+                    dr.mutate();
+                }
+            }
+            mMutated = true;
+        }
+        return this;
+    }
+
+    /**
+     * @hide
+     */
+    public void clearMutated() {
+        super.clearMutated();
+        final ChildDrawable[] array = mLayerState.mChildren;
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.clearMutated();
+            }
+        }
+        mMutated = false;
+    }
+
+    static class ChildDrawable {
+        public Drawable mDrawable;
+        public int[] mThemeAttrs;
+        public int mDensity = DisplayMetrics.DENSITY_DEFAULT;
+
+        ChildDrawable(int density) {
+            mDensity = density;
+        }
+
+        ChildDrawable(@NonNull ChildDrawable orig, @NonNull AdaptiveIconDrawable owner,
+                @Nullable Resources res) {
+
+            final Drawable dr = orig.mDrawable;
+            final Drawable clone;
+            if (dr != null) {
+                final ConstantState cs = dr.getConstantState();
+                if (cs == null) {
+                    clone = dr;
+                } else if (res != null) {
+                    clone = cs.newDrawable(res);
+                } else {
+                    clone = cs.newDrawable();
+                }
+                clone.setCallback(owner);
+                clone.setBounds(dr.getBounds());
+                clone.setLevel(dr.getLevel());
+            } else {
+                clone = null;
+            }
+
+            mDrawable = clone;
+            mThemeAttrs = orig.mThemeAttrs;
+
+            mDensity = Drawable.resolveDensity(res, orig.mDensity);
+        }
+
+        public boolean canApplyTheme() {
+            return mThemeAttrs != null
+                    || (mDrawable != null && mDrawable.canApplyTheme());
+        }
+
+        public final void setDensity(int targetDensity) {
+            if (mDensity != targetDensity) {
+                mDensity = targetDensity;
+            }
+        }
+    }
+
+    static class LayerState extends ConstantState {
+        private int[] mThemeAttrs;
+
+        final static int N_CHILDREN = 2;
+        ChildDrawable[] mChildren;
+
+        // The density at which to render the drawable and its children.
+        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;
+
+        int mOpacityOverride = PixelFormat.UNKNOWN;
+
+        @Config int mChangingConfigurations;
+        @Config int mChildrenChangingConfigurations;
+
+        private boolean mCheckedOpacity;
+        private int mOpacity;
+
+        private boolean mCheckedStateful;
+        private boolean mIsStateful;
+        private boolean mAutoMirrored = false;
+
+        LayerState(@Nullable LayerState orig, @NonNull AdaptiveIconDrawable owner,
+                @Nullable Resources res) {
+            mDensity = Drawable.resolveDensity(res, orig != null ? orig.mDensity : 0);
+            mChildren = new ChildDrawable[N_CHILDREN];
+            if (orig != null) {
+                final ChildDrawable[] origChildDrawable = orig.mChildren;
+
+                mChangingConfigurations = orig.mChangingConfigurations;
+                mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
+
+                for (int i = 0; i < N_CHILDREN; i++) {
+                    final ChildDrawable or = origChildDrawable[i];
+                    mChildren[i] = new ChildDrawable(or, owner, res);
+                }
+
+                mCheckedOpacity = orig.mCheckedOpacity;
+                mOpacity = orig.mOpacity;
+                mCheckedStateful = orig.mCheckedStateful;
+                mIsStateful = orig.mIsStateful;
+                mAutoMirrored = orig.mAutoMirrored;
+                mThemeAttrs = orig.mThemeAttrs;
+                mOpacityOverride = orig.mOpacityOverride;
+                mSrcDensityOverride = orig.mSrcDensityOverride;
+            } else {
+                for (int i = 0; i < N_CHILDREN; i++) {
+                    mChildren[i] = new ChildDrawable(mDensity);
+                }
+            }
+        }
+
+        public final void setDensity(int targetDensity) {
+            if (mDensity != targetDensity) {
+                mDensity = targetDensity;
+            }
+        }
+
+        @Override
+        public boolean canApplyTheme() {
+            if (mThemeAttrs != null || super.canApplyTheme()) {
+                return true;
+            }
+
+            final ChildDrawable[] array = mChildren;
+            for (int i = 0; i < N_CHILDREN; i++) {
+                final ChildDrawable layer = array[i];
+                if (layer.canApplyTheme()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return new AdaptiveIconDrawable(this, null);
+        }
+
+        @Override
+        public Drawable newDrawable(@Nullable Resources res) {
+            return new AdaptiveIconDrawable(this, res);
+        }
+
+        @Override
+        public @Config int getChangingConfigurations() {
+            return mChangingConfigurations
+                    | mChildrenChangingConfigurations;
+        }
+
+        public final int getOpacity() {
+            if (mCheckedOpacity) {
+                return mOpacity;
+            }
+
+            final ChildDrawable[] array = mChildren;
+
+            // Seek to the first non-null drawable.
+            int firstIndex = -1;
+            for (int i = 0; i < N_CHILDREN; i++) {
+                if (array[i].mDrawable != null) {
+                    firstIndex = i;
+                    break;
+                }
+            }
+
+            int op;
+            if (firstIndex >= 0) {
+                op = array[firstIndex].mDrawable.getOpacity();
+            } else {
+                op = PixelFormat.TRANSPARENT;
+            }
+
+            // Merge all remaining non-null drawables.
+            for (int i = firstIndex + 1; i < N_CHILDREN; i++) {
+                final Drawable dr = array[i].mDrawable;
+                if (dr != null) {
+                    op = Drawable.resolveOpacity(op, dr.getOpacity());
+                }
+            }
+
+            mOpacity = op;
+            mCheckedOpacity = true;
+            return op;
+        }
+
+        public final boolean isStateful() {
+            if (mCheckedStateful) {
+                return mIsStateful;
+            }
+
+            final ChildDrawable[] array = mChildren;
+            boolean isStateful = false;
+            for (int i = 0; i < N_CHILDREN; i++) {
+                final Drawable dr = array[i].mDrawable;
+                if (dr != null && dr.isStateful()) {
+                    isStateful = true;
+                    break;
+                }
+            }
+
+            mIsStateful = isStateful;
+            mCheckedStateful = true;
+            return isStateful;
+        }
+
+        public final boolean hasFocusStateSpecified() {
+            final ChildDrawable[] array = mChildren;
+            for (int i = 0; i < N_CHILDREN; i++) {
+                final Drawable dr = array[i].mDrawable;
+                if (dr != null && dr.hasFocusStateSpecified()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public final boolean canConstantState() {
+            final ChildDrawable[] array = mChildren;
+            for (int i = 0; i < N_CHILDREN; i++) {
+                final Drawable dr = array[i].mDrawable;
+                if (dr != null && dr.getConstantState() == null) {
+                    return false;
+                }
+            }
+
+            // Don't cache the result, this method is not called very often.
+            return true;
+        }
+
+        public void invalidateCache() {
+            mCheckedOpacity = false;
+            mCheckedStateful = false;
+        }
+    }
+}
diff --git a/android/graphics/drawable/AdaptiveIconDrawable_Delegate.java b/android/graphics/drawable/AdaptiveIconDrawable_Delegate.java
new file mode 100644
index 0000000..ca6bc85
--- /dev/null
+++ b/android/graphics/drawable/AdaptiveIconDrawable_Delegate.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 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 com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.content.res.Resources;
+import android.content.res.Resources_Delegate;
+import android.util.PathParser;
+
+import static com.android.layoutlib.bridge.android.RenderParamsFlags.FLAG_KEY_ADAPTIVE_ICON_MASK_PATH;
+
+public class AdaptiveIconDrawable_Delegate {
+
+    @LayoutlibDelegate
+    /*package*/ static void constructor_after(AdaptiveIconDrawable icon) {
+        String pathString = Resources_Delegate.getLayoutlibCallback(Resources.getSystem()).getFlag(
+                FLAG_KEY_ADAPTIVE_ICON_MASK_PATH);
+        if (pathString != null) {
+            AdaptiveIconDrawable.sMask = PathParser.createPathFromPathData(pathString);
+        }
+    }
+}
diff --git a/android/graphics/drawable/Animatable.java b/android/graphics/drawable/Animatable.java
new file mode 100644
index 0000000..983d650
--- /dev/null
+++ b/android/graphics/drawable/Animatable.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2009 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;
+
+/**
+ * Interface that drawables supporting animations should implement.
+ */
+public interface Animatable {
+    /**
+     * Starts the drawable's animation.
+     */
+    void start();
+
+    /**
+     * Stops the drawable's animation.
+     */
+    void stop();
+
+    /**
+     * Indicates whether the animation is running.
+     *
+     * @return True if the animation is running, false otherwise.
+     */
+    boolean isRunning();
+}
diff --git a/android/graphics/drawable/Animatable2.java b/android/graphics/drawable/Animatable2.java
new file mode 100644
index 0000000..7c7e60e
--- /dev/null
+++ b/android/graphics/drawable/Animatable2.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 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.NonNull;
+
+/**
+ * Abstract class that drawables supporting animations and callbacks should extend.
+ */
+public interface Animatable2 extends Animatable {
+
+    /**
+     * Adds a callback to listen to the animation events.
+     *
+     * @param callback Callback to add.
+     */
+    void registerAnimationCallback(@NonNull AnimationCallback callback);
+
+    /**
+     * Removes the specified animation callback.
+     *
+     * @param callback Callback to remove.
+     * @return {@code false} if callback didn't exist in the call back list, or {@code true} if
+     *         callback has been removed successfully.
+     */
+    boolean unregisterAnimationCallback(@NonNull AnimationCallback callback);
+
+    /**
+     * Removes all existing animation callbacks.
+     */
+    void clearAnimationCallbacks();
+
+    public static abstract class AnimationCallback {
+        /**
+         * Called when the animation starts.
+         *
+         * @param drawable The drawable started the animation.
+         */
+        public void onAnimationStart(Drawable drawable) {};
+        /**
+         * Called when the animation ends.
+         *
+         * @param drawable The drawable finished the animation.
+         */
+        public void onAnimationEnd(Drawable drawable) {};
+    }
+}
diff --git a/android/graphics/drawable/AnimatedRotateDrawable.java b/android/graphics/drawable/AnimatedRotateDrawable.java
new file mode 100644
index 0000000..d714ca8
--- /dev/null
+++ b/android/graphics/drawable/AnimatedRotateDrawable.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2009 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.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.Resources.Theme;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.os.SystemClock;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+import com.android.internal.R;
+
+/**
+ * @hide
+ */
+public class AnimatedRotateDrawable extends DrawableWrapper implements Animatable {
+    private AnimatedRotateState mState;
+
+    private float mCurrentDegrees;
+    private float mIncrement;
+
+    /** Whether this drawable is currently animating. */
+    private boolean mRunning;
+
+    /**
+     * Creates a new animated rotating drawable with no wrapped drawable.
+     */
+    public AnimatedRotateDrawable() {
+        this(new AnimatedRotateState(null, null), null);
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        final Drawable drawable = getDrawable();
+        final Rect bounds = drawable.getBounds();
+        final int w = bounds.right - bounds.left;
+        final int h = bounds.bottom - bounds.top;
+
+        final AnimatedRotateState st = mState;
+        final float px = st.mPivotXRel ? (w * st.mPivotX) : st.mPivotX;
+        final float py = st.mPivotYRel ? (h * st.mPivotY) : st.mPivotY;
+
+        final int saveCount = canvas.save();
+        canvas.rotate(mCurrentDegrees, px + bounds.left, py + bounds.top);
+        drawable.draw(canvas);
+        canvas.restoreToCount(saveCount);
+    }
+
+    /**
+     * Starts the rotation animation.
+     * <p>
+     * The animation will run until {@link #stop()} is called. Calling this
+     * method while the animation is already running has no effect.
+     *
+     * @see #stop()
+     */
+    @Override
+    public void start() {
+        if (!mRunning) {
+            mRunning = true;
+            nextFrame();
+        }
+    }
+
+    /**
+     * Stops the rotation animation.
+     *
+     * @see #start()
+     */
+    @Override
+    public void stop() {
+        mRunning = false;
+        unscheduleSelf(mNextFrame);
+    }
+
+    @Override
+    public boolean isRunning() {
+        return mRunning;
+    }
+
+    private void nextFrame() {
+        unscheduleSelf(mNextFrame);
+        scheduleSelf(mNextFrame, SystemClock.uptimeMillis() + mState.mFrameDuration);
+    }
+
+    @Override
+    public boolean setVisible(boolean visible, boolean restart) {
+        final boolean changed = super.setVisible(visible, restart);
+        if (visible) {
+            if (changed || restart) {
+                mCurrentDegrees = 0.0f;
+                nextFrame();
+            }
+        } else {
+            unscheduleSelf(mNextFrame);
+        }
+        return changed;
+    }
+
+    @Override
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.AnimatedRotateDrawable);
+
+        // Inflation will advance the XmlPullParser and AttributeSet.
+        super.inflate(r, parser, attrs, theme);
+
+        updateStateFromTypedArray(a);
+        verifyRequiredAttributes(a);
+        a.recycle();
+
+        updateLocalState();
+    }
+
+    @Override
+    public void applyTheme(@NonNull Theme t) {
+        super.applyTheme(t);
+
+        final AnimatedRotateState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        if (state.mThemeAttrs != null) {
+            final TypedArray a = t.resolveAttributes(
+                    state.mThemeAttrs, R.styleable.AnimatedRotateDrawable);
+            try {
+                updateStateFromTypedArray(a);
+                verifyRequiredAttributes(a);
+            } catch (XmlPullParserException e) {
+                rethrowAsRuntimeException(e);
+            } finally {
+                a.recycle();
+            }
+        }
+
+        updateLocalState();
+    }
+
+    private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
+        // If we're not waiting on a theme, verify required attributes.
+        if (getDrawable() == null && (mState.mThemeAttrs == null
+                || mState.mThemeAttrs[R.styleable.AnimatedRotateDrawable_drawable] == 0)) {
+            throw new XmlPullParserException(a.getPositionDescription()
+                    + ": <animated-rotate> tag requires a 'drawable' attribute or "
+                    + "child tag defining a drawable");
+        }
+    }
+
+    private void updateStateFromTypedArray(@NonNull TypedArray a) {
+        final AnimatedRotateState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        // Account for any configuration changes.
+        state.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        state.mThemeAttrs = a.extractThemeAttrs();
+
+        if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotX)) {
+            final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotX);
+            state.mPivotXRel = tv.type == TypedValue.TYPE_FRACTION;
+            state.mPivotX = state.mPivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
+        }
+
+        if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotY)) {
+            final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotY);
+            state.mPivotYRel = tv.type == TypedValue.TYPE_FRACTION;
+            state.mPivotY = state.mPivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
+        }
+
+        setFramesCount(a.getInt(
+                R.styleable.AnimatedRotateDrawable_framesCount, state.mFramesCount));
+        setFramesDuration(a.getInt(
+                R.styleable.AnimatedRotateDrawable_frameDuration, state.mFrameDuration));
+    }
+
+    public void setFramesCount(int framesCount) {
+        mState.mFramesCount = framesCount;
+        mIncrement = 360.0f / mState.mFramesCount;
+    }
+
+    public void setFramesDuration(int framesDuration) {
+        mState.mFrameDuration = framesDuration;
+    }
+
+    @Override
+    DrawableWrapperState mutateConstantState() {
+        mState = new AnimatedRotateState(mState, null);
+        return mState;
+    }
+
+    static final class AnimatedRotateState extends DrawableWrapper.DrawableWrapperState {
+        private int[] mThemeAttrs;
+
+        boolean mPivotXRel = false;
+        float mPivotX = 0;
+        boolean mPivotYRel = false;
+        float mPivotY = 0;
+        int mFrameDuration = 150;
+        int mFramesCount = 12;
+
+        public AnimatedRotateState(AnimatedRotateState orig, Resources res) {
+            super(orig, res);
+
+            if (orig != null) {
+                mPivotXRel = orig.mPivotXRel;
+                mPivotX = orig.mPivotX;
+                mPivotYRel = orig.mPivotYRel;
+                mPivotY = orig.mPivotY;
+                mFramesCount = orig.mFramesCount;
+                mFrameDuration = orig.mFrameDuration;
+            }
+        }
+
+        @Override
+        public Drawable newDrawable(Resources res) {
+            return new AnimatedRotateDrawable(this, res);
+        }
+    }
+
+    private AnimatedRotateDrawable(AnimatedRotateState state, Resources res) {
+        super(state, res);
+
+        mState = state;
+
+        updateLocalState();
+    }
+
+    private void updateLocalState() {
+        final AnimatedRotateState state = mState;
+        mIncrement = 360.0f / state.mFramesCount;
+
+        // Force the wrapped drawable to use filtering and AA, if applicable,
+        // so that it looks smooth when rotated.
+        final Drawable drawable = getDrawable();
+        if (drawable != null) {
+            drawable.setFilterBitmap(true);
+            if (drawable instanceof BitmapDrawable) {
+                ((BitmapDrawable) drawable).setAntiAlias(true);
+            }
+        }
+    }
+
+    private final Runnable mNextFrame = new Runnable() {
+        @Override
+        public void run() {
+            // TODO: This should be computed in draw(Canvas), based on the amount
+            // of time since the last frame drawn
+            mCurrentDegrees += mIncrement;
+            if (mCurrentDegrees > (360.0f - mIncrement)) {
+                mCurrentDegrees = 0.0f;
+            }
+            invalidateSelf();
+            nextFrame();
+        }
+    };
+}
diff --git a/android/graphics/drawable/AnimatedStateListDrawable.java b/android/graphics/drawable/AnimatedStateListDrawable.java
new file mode 100644
index 0000000..3ed6a78
--- /dev/null
+++ b/android/graphics/drawable/AnimatedStateListDrawable.java
@@ -0,0 +1,735 @@
+/*
+ * Copyright (C) 2014 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.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.LongSparseLongArray;
+import android.util.SparseIntArray;
+import android.util.StateSet;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * Drawable containing a set of Drawable keyframes where the currently displayed
+ * keyframe is chosen based on the current state set. Animations between
+ * keyframes may optionally be defined using transition elements.
+ * <p>
+ * This drawable can be defined in an XML file with the <code>
+ * &lt;animated-selector></code> element. Each keyframe Drawable is defined in a
+ * nested <code>&lt;item></code> element. Transitions are defined in a nested
+ * <code>&lt;transition></code> element.
+ *
+ * @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 AnimatedStateListDrawable extends StateListDrawable {
+    private static final String LOGTAG = AnimatedStateListDrawable.class.getSimpleName();
+
+    private static final String ELEMENT_TRANSITION = "transition";
+    private static final String ELEMENT_ITEM = "item";
+
+    private AnimatedStateListState mState;
+
+    /** The currently running transition, if any. */
+    private Transition mTransition;
+
+    /** Index to be set after the transition ends. */
+    private int mTransitionToIndex = -1;
+
+    /** Index away from which we are transitioning. */
+    private int mTransitionFromIndex = -1;
+
+    private boolean mMutated;
+
+    public AnimatedStateListDrawable() {
+        this(null, null);
+    }
+
+    @Override
+    public boolean setVisible(boolean visible, boolean restart) {
+        final boolean changed = super.setVisible(visible, restart);
+
+        if (mTransition != null && (changed || restart)) {
+            if (visible) {
+                mTransition.start();
+            } else {
+                // Ensure we're showing the correct state when visible.
+                jumpToCurrentState();
+            }
+        }
+
+        return changed;
+    }
+
+    /**
+     * Add a new drawable to the set of keyframes.
+     *
+     * @param stateSet An array of resource IDs to associate with the keyframe
+     * @param drawable The drawable to show when in the specified state, may not be null
+     * @param id The unique identifier for the keyframe
+     */
+    public void addState(@NonNull int[] stateSet, @NonNull Drawable drawable, int id) {
+        if (drawable == null) {
+            throw new IllegalArgumentException("Drawable must not be null");
+        }
+
+        mState.addStateSet(stateSet, drawable, id);
+        onStateChange(getState());
+    }
+
+    /**
+     * Adds a new transition between keyframes.
+     *
+     * @param fromId Unique identifier of the starting keyframe
+     * @param toId Unique identifier of the ending keyframe
+     * @param transition An {@link Animatable} drawable to use as a transition, may not be null
+     * @param reversible Whether the transition can be reversed
+     */
+    public <T extends Drawable & Animatable> void addTransition(int fromId, int toId,
+            @NonNull T transition, boolean reversible) {
+        if (transition == null) {
+            throw new IllegalArgumentException("Transition drawable must not be null");
+        }
+
+        mState.addTransition(fromId, toId, transition, reversible);
+    }
+
+    @Override
+    public boolean isStateful() {
+        return true;
+    }
+
+    @Override
+    protected boolean onStateChange(int[] stateSet) {
+        // If we're not already at the target index, either attempt to find a
+        // valid transition to it or jump directly there.
+        final int targetIndex = mState.indexOfKeyframe(stateSet);
+        boolean changed = targetIndex != getCurrentIndex()
+                && (selectTransition(targetIndex) || selectDrawable(targetIndex));
+
+        // We need to propagate the state change to the current drawable, but
+        // we can't call StateListDrawable.onStateChange() without changing the
+        // current drawable.
+        final Drawable current = getCurrent();
+        if (current != null) {
+            changed |= current.setState(stateSet);
+        }
+
+        return changed;
+    }
+
+    private boolean selectTransition(int toIndex) {
+        final int fromIndex;
+        final Transition currentTransition = mTransition;
+        if (currentTransition != null) {
+            if (toIndex == mTransitionToIndex) {
+                // Already animating to that keyframe.
+                return true;
+            } else if (toIndex == mTransitionFromIndex && currentTransition.canReverse()) {
+                // Reverse the current animation.
+                currentTransition.reverse();
+                mTransitionToIndex = mTransitionFromIndex;
+                mTransitionFromIndex = toIndex;
+                return true;
+            }
+
+            // Start the next transition from the end of the current one.
+            fromIndex = mTransitionToIndex;
+
+            // Changing animation, end the current animation.
+            currentTransition.stop();
+        } else {
+            fromIndex = getCurrentIndex();
+        }
+
+        // Reset state.
+        mTransition = null;
+        mTransitionFromIndex = -1;
+        mTransitionToIndex = -1;
+
+        final AnimatedStateListState state = mState;
+        final int fromId = state.getKeyframeIdAt(fromIndex);
+        final int toId = state.getKeyframeIdAt(toIndex);
+        if (toId == 0 || fromId == 0) {
+            // Missing a keyframe ID.
+            return false;
+        }
+
+        final int transitionIndex = state.indexOfTransition(fromId, toId);
+        if (transitionIndex < 0) {
+            // Couldn't select a transition.
+            return false;
+        }
+
+        boolean hasReversibleFlag = state.transitionHasReversibleFlag(fromId, toId);
+
+        // This may fail if we're already on the transition, but that's okay!
+        selectDrawable(transitionIndex);
+
+        final Transition transition;
+        final Drawable d = getCurrent();
+        if (d instanceof AnimationDrawable) {
+            final boolean reversed = state.isTransitionReversed(fromId, toId);
+
+            transition = new AnimationDrawableTransition((AnimationDrawable) d,
+                    reversed, hasReversibleFlag);
+        } else if (d instanceof AnimatedVectorDrawable) {
+            final boolean reversed = state.isTransitionReversed(fromId, toId);
+
+            transition = new AnimatedVectorDrawableTransition((AnimatedVectorDrawable) d,
+                    reversed, hasReversibleFlag);
+        } else if (d instanceof Animatable) {
+            transition = new AnimatableTransition((Animatable) d);
+        } else {
+            // We don't know how to animate this transition.
+            return false;
+        }
+
+        transition.start();
+
+        mTransition = transition;
+        mTransitionFromIndex = fromIndex;
+        mTransitionToIndex = toIndex;
+        return true;
+    }
+
+    private static abstract class Transition {
+        public abstract void start();
+        public abstract void stop();
+
+        public void reverse() {
+            // Not supported by default.
+        }
+
+        public boolean canReverse() {
+            return false;
+        }
+    }
+
+    private static class AnimatableTransition  extends Transition {
+        private final Animatable mA;
+
+        public AnimatableTransition(Animatable a) {
+            mA = a;
+        }
+
+        @Override
+        public void start() {
+            mA.start();
+        }
+
+        @Override
+        public void stop() {
+            mA.stop();
+        }
+    }
+
+
+    private static class AnimationDrawableTransition  extends Transition {
+        private final ObjectAnimator mAnim;
+
+        // Even AnimationDrawable is always reversible technically, but
+        // we should obey the XML's android:reversible flag.
+        private final boolean mHasReversibleFlag;
+
+        public AnimationDrawableTransition(AnimationDrawable ad,
+                boolean reversed, boolean hasReversibleFlag) {
+            final int frameCount = ad.getNumberOfFrames();
+            final int fromFrame = reversed ? frameCount - 1 : 0;
+            final int toFrame = reversed ? 0 : frameCount - 1;
+            final FrameInterpolator interp = new FrameInterpolator(ad, reversed);
+            final ObjectAnimator anim = ObjectAnimator.ofInt(ad, "currentIndex", fromFrame, toFrame);
+            anim.setAutoCancel(true);
+            anim.setDuration(interp.getTotalDuration());
+            anim.setInterpolator(interp);
+            mHasReversibleFlag = hasReversibleFlag;
+            mAnim = anim;
+        }
+
+        @Override
+        public boolean canReverse() {
+            return mHasReversibleFlag;
+        }
+
+        @Override
+        public void start() {
+            mAnim.start();
+        }
+
+        @Override
+        public void reverse() {
+            mAnim.reverse();
+        }
+
+        @Override
+        public void stop() {
+            mAnim.cancel();
+        }
+    }
+
+    private static class AnimatedVectorDrawableTransition  extends Transition {
+        private final AnimatedVectorDrawable mAvd;
+
+        // mReversed is indicating the current transition's direction.
+        private final boolean mReversed;
+
+        // mHasReversibleFlag is indicating whether the whole transition has
+        // reversible flag set to true.
+        // If mHasReversibleFlag is false, then mReversed is always false.
+        private final boolean mHasReversibleFlag;
+
+        public AnimatedVectorDrawableTransition(AnimatedVectorDrawable avd,
+                boolean reversed, boolean hasReversibleFlag) {
+            mAvd = avd;
+            mReversed = reversed;
+            mHasReversibleFlag = hasReversibleFlag;
+        }
+
+        @Override
+        public boolean canReverse() {
+            // When the transition's XML says it is not reversible, then we obey
+            // it, even if the AVD itself is reversible.
+            // This will help the single direction transition.
+            return mAvd.canReverse() && mHasReversibleFlag;
+        }
+
+        @Override
+        public void start() {
+            if (mReversed) {
+                reverse();
+            } else {
+                mAvd.start();
+            }
+        }
+
+        @Override
+        public void reverse() {
+            if (canReverse()) {
+                mAvd.reverse();
+            } else {
+                Log.w(LOGTAG, "Can't reverse, either the reversible is set to false,"
+                        + " or the AnimatedVectorDrawable can't reverse");
+            }
+        }
+
+        @Override
+        public void stop() {
+            mAvd.stop();
+        }
+    }
+
+
+    @Override
+    public void jumpToCurrentState() {
+        super.jumpToCurrentState();
+
+        if (mTransition != null) {
+            mTransition.stop();
+            mTransition = null;
+
+            selectDrawable(mTransitionToIndex);
+            mTransitionToIndex = -1;
+            mTransitionFromIndex = -1;
+        }
+    }
+
+    @Override
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
+        final TypedArray a = obtainAttributes(
+                r, theme, attrs, R.styleable.AnimatedStateListDrawable);
+        super.inflateWithAttributes(r, parser, a, R.styleable.AnimatedStateListDrawable_visible);
+        updateStateFromTypedArray(a);
+        updateDensity(r);
+        a.recycle();
+
+        inflateChildElements(r, parser, attrs, theme);
+
+        init();
+    }
+
+    @Override
+    public void applyTheme(@Nullable Theme theme) {
+        super.applyTheme(theme);
+
+        final AnimatedStateListState state = mState;
+        if (state == null || state.mAnimThemeAttrs == null) {
+            return;
+        }
+
+        final TypedArray a = theme.resolveAttributes(
+                state.mAnimThemeAttrs, R.styleable.AnimatedRotateDrawable);
+        updateStateFromTypedArray(a);
+        a.recycle();
+
+        init();
+    }
+
+    private void updateStateFromTypedArray(TypedArray a) {
+        final AnimatedStateListState state = mState;
+
+        // Account for any configuration changes.
+        state.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        state.mAnimThemeAttrs = a.extractThemeAttrs();
+
+        state.setVariablePadding(a.getBoolean(
+                R.styleable.AnimatedStateListDrawable_variablePadding, state.mVariablePadding));
+        state.setConstantSize(a.getBoolean(
+                R.styleable.AnimatedStateListDrawable_constantSize, state.mConstantSize));
+        state.setEnterFadeDuration(a.getInt(
+                R.styleable.AnimatedStateListDrawable_enterFadeDuration, state.mEnterFadeDuration));
+        state.setExitFadeDuration(a.getInt(
+                R.styleable.AnimatedStateListDrawable_exitFadeDuration, state.mExitFadeDuration));
+
+        setDither(a.getBoolean(
+                R.styleable.AnimatedStateListDrawable_dither, state.mDither));
+        setAutoMirrored(a.getBoolean(
+                R.styleable.AnimatedStateListDrawable_autoMirrored, state.mAutoMirrored));
+    }
+
+    private void init() {
+        onStateChange(getState());
+    }
+
+    private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
+            Theme theme) throws XmlPullParserException, IOException {
+        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) {
+                continue;
+            }
+
+            if (parser.getName().equals(ELEMENT_ITEM)) {
+                parseItem(r, parser, attrs, theme);
+            } else if (parser.getName().equals(ELEMENT_TRANSITION)) {
+                parseTransition(r, parser, attrs, theme);
+            }
+        }
+    }
+
+    private int parseTransition(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
+        // 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 = obtainAttributes(r, theme, attrs,
+                R.styleable.AnimatedStateListDrawableTransition);
+        final int fromId = a.getResourceId(
+                R.styleable.AnimatedStateListDrawableTransition_fromId, 0);
+        final int toId = a.getResourceId(
+                R.styleable.AnimatedStateListDrawableTransition_toId, 0);
+        final boolean reversible = a.getBoolean(
+                R.styleable.AnimatedStateListDrawableTransition_reversible, false);
+        Drawable dr = a.getDrawable(
+                R.styleable.AnimatedStateListDrawableTransition_drawable);
+        a.recycle();
+
+        // 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) {
+            int type;
+            while ((type = parser.next()) == XmlPullParser.TEXT) {
+            }
+            if (type != XmlPullParser.START_TAG) {
+                throw new XmlPullParserException(
+                        parser.getPositionDescription()
+                                + ": <transition> tag requires a 'drawable' attribute or "
+                                + "child tag defining a drawable");
+            }
+            dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
+        }
+
+        return mState.addTransition(fromId, toId, dr, reversible);
+    }
+
+    private int parseItem(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
+        // 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 = obtainAttributes(r, theme, attrs,
+                R.styleable.AnimatedStateListDrawableItem);
+        final int keyframeId = a.getResourceId(R.styleable.AnimatedStateListDrawableItem_id, 0);
+        Drawable dr = a.getDrawable(R.styleable.AnimatedStateListDrawableItem_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) {
+            int type;
+            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, theme);
+        }
+
+        return mState.addStateSet(states, dr, keyframeId);
+    }
+
+    @Override
+    public Drawable mutate() {
+        if (!mMutated && super.mutate() == this) {
+            mState.mutate();
+            mMutated = true;
+        }
+
+        return this;
+    }
+
+    @Override
+    AnimatedStateListState cloneConstantState() {
+        return new AnimatedStateListState(mState, this, null);
+    }
+
+    /**
+     * @hide
+     */
+    public void clearMutated() {
+        super.clearMutated();
+        mMutated = false;
+    }
+
+    static class AnimatedStateListState extends StateListState {
+        // REVERSED_BIT is indicating the current transition's direction.
+        private static final long REVERSED_BIT = 0x100000000l;
+
+        // REVERSIBLE_FLAG_BIT is indicating whether the whole transition has
+        // reversible flag set to true.
+        private static final long REVERSIBLE_FLAG_BIT = 0x200000000l;
+
+        int[] mAnimThemeAttrs;
+
+        LongSparseLongArray mTransitions;
+        SparseIntArray mStateIds;
+
+        AnimatedStateListState(@Nullable AnimatedStateListState orig,
+                @NonNull AnimatedStateListDrawable owner, @Nullable Resources res) {
+            super(orig, owner, res);
+
+            if (orig != null) {
+                // Perform a shallow copy and rely on mutate() to deep-copy.
+                mAnimThemeAttrs = orig.mAnimThemeAttrs;
+                mTransitions = orig.mTransitions;
+                mStateIds = orig.mStateIds;
+            } else {
+                mTransitions = new LongSparseLongArray();
+                mStateIds = new SparseIntArray();
+            }
+        }
+
+        void mutate() {
+            mTransitions = mTransitions.clone();
+            mStateIds = mStateIds.clone();
+        }
+
+        int addTransition(int fromId, int toId, @NonNull Drawable anim, boolean reversible) {
+            final int pos = super.addChild(anim);
+            final long keyFromTo = generateTransitionKey(fromId, toId);
+            long reversibleBit = 0;
+            if (reversible) {
+                reversibleBit = REVERSIBLE_FLAG_BIT;
+            }
+            mTransitions.append(keyFromTo, pos | reversibleBit);
+
+            if (reversible) {
+                final long keyToFrom = generateTransitionKey(toId, fromId);
+                mTransitions.append(keyToFrom, pos | REVERSED_BIT | reversibleBit);
+            }
+
+            return pos;
+        }
+
+        int addStateSet(@NonNull int[] stateSet, @NonNull Drawable drawable, int id) {
+            final int index = super.addStateSet(stateSet, drawable);
+            mStateIds.put(index, id);
+            return index;
+        }
+
+        int indexOfKeyframe(@NonNull int[] stateSet) {
+            final int index = super.indexOfStateSet(stateSet);
+            if (index >= 0) {
+                return index;
+            }
+
+            return super.indexOfStateSet(StateSet.WILD_CARD);
+        }
+
+        int getKeyframeIdAt(int index) {
+            return index < 0 ? 0 : mStateIds.get(index, 0);
+        }
+
+        int indexOfTransition(int fromId, int toId) {
+            final long keyFromTo = generateTransitionKey(fromId, toId);
+            return (int) mTransitions.get(keyFromTo, -1);
+        }
+
+        boolean isTransitionReversed(int fromId, int toId) {
+            final long keyFromTo = generateTransitionKey(fromId, toId);
+            return (mTransitions.get(keyFromTo, -1) & REVERSED_BIT) != 0;
+        }
+
+        boolean transitionHasReversibleFlag(int fromId, int toId) {
+            final long keyFromTo = generateTransitionKey(fromId, toId);
+            return (mTransitions.get(keyFromTo, -1) & REVERSIBLE_FLAG_BIT) != 0;
+        }
+
+        @Override
+        public boolean canApplyTheme() {
+            return mAnimThemeAttrs != null || super.canApplyTheme();
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return new AnimatedStateListDrawable(this, null);
+        }
+
+        @Override
+        public Drawable newDrawable(Resources res) {
+            return new AnimatedStateListDrawable(this, res);
+        }
+
+        private static long generateTransitionKey(int fromId, int toId) {
+            return (long) fromId << 32 | toId;
+        }
+    }
+
+    @Override
+    protected void setConstantState(@NonNull DrawableContainerState state) {
+        super.setConstantState(state);
+
+        if (state instanceof AnimatedStateListState) {
+            mState = (AnimatedStateListState) state;
+        }
+    }
+
+    private AnimatedStateListDrawable(@Nullable AnimatedStateListState state, @Nullable Resources res) {
+        super(null);
+
+        // Every animated state list drawable has its own constant state.
+        final AnimatedStateListState newState = new AnimatedStateListState(state, this, res);
+        setConstantState(newState);
+        onStateChange(getState());
+        jumpToCurrentState();
+    }
+
+    /**
+     * Interpolates between frames with respect to their individual durations.
+     */
+    private static class FrameInterpolator implements TimeInterpolator {
+        private int[] mFrameTimes;
+        private int mFrames;
+        private int mTotalDuration;
+
+        public FrameInterpolator(AnimationDrawable d, boolean reversed) {
+            updateFrames(d, reversed);
+        }
+
+        public int updateFrames(AnimationDrawable d, boolean reversed) {
+            final int N = d.getNumberOfFrames();
+            mFrames = N;
+
+            if (mFrameTimes == null || mFrameTimes.length < N) {
+                mFrameTimes = new int[N];
+            }
+
+            final int[] frameTimes = mFrameTimes;
+            int totalDuration = 0;
+            for (int i = 0; i < N; i++) {
+                final int duration = d.getDuration(reversed ? N - i - 1 : i);
+                frameTimes[i] = duration;
+                totalDuration += duration;
+            }
+
+            mTotalDuration = totalDuration;
+            return totalDuration;
+        }
+
+        public int getTotalDuration() {
+            return mTotalDuration;
+        }
+
+        @Override
+        public float getInterpolation(float input) {
+            final int elapsed = (int) (input * mTotalDuration + 0.5f);
+            final int N = mFrames;
+            final int[] frameTimes = mFrameTimes;
+
+            // Find the current frame and remaining time within that frame.
+            int remaining = elapsed;
+            int i = 0;
+            while (i < N && remaining >= frameTimes[i]) {
+                remaining -= frameTimes[i];
+                i++;
+            }
+
+            // Remaining time is relative of total duration.
+            final float frameElapsed;
+            if (i < N) {
+                frameElapsed = remaining / (float) mTotalDuration;
+            } else {
+                frameElapsed = 0;
+            }
+
+            return i / (float) N + frameElapsed;
+        }
+    }
+}
diff --git a/android/graphics/drawable/AnimatedVectorDrawable.java b/android/graphics/drawable/AnimatedVectorDrawable.java
new file mode 100644
index 0000000..90d6ab8
--- /dev/null
+++ b/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -0,0 +1,1826 @@
+/*
+ * Copyright (C) 2014 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.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorInflater;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityThread;
+import android.app.Application;
+import android.content.pm.ActivityInfo.Config;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Insets;
+import android.graphics.Outline;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.os.Build;
+import android.util.ArrayMap;
+import android.util.AttributeSet;
+import android.util.IntArray;
+import android.util.Log;
+import android.util.LongArray;
+import android.util.PathParser;
+import android.util.Property;
+import android.util.TimeUtils;
+import android.view.Choreographer;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+import android.view.RenderNodeAnimatorSetHelper;
+import android.view.View;
+
+import com.android.internal.R;
+import com.android.internal.util.VirtualRefBasePtr;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+import dalvik.annotation.optimization.FastNative;
+
+/**
+ * This class animates properties of a {@link android.graphics.drawable.VectorDrawable} with
+ * animations defined using {@link android.animation.ObjectAnimator} or
+ * {@link android.animation.AnimatorSet}.
+ * <p>
+ * Starting from API 25, AnimatedVectorDrawable runs on RenderThread (as opposed to on UI thread for
+ * earlier APIs). This means animations in AnimatedVectorDrawable can remain smooth even when there
+ * is heavy workload on the UI thread. Note: If the UI thread is unresponsive, RenderThread may
+ * continue animating until the UI thread is capable of pushing another frame. Therefore, it is not
+ * possible to precisely coordinate a RenderThread-enabled AnimatedVectorDrawable with UI thread
+ * animations. Additionally,
+ * {@link android.graphics.drawable.Animatable2.AnimationCallback#onAnimationEnd(Drawable)} will be
+ * called the frame after the AnimatedVectorDrawable finishes on the RenderThread.
+ * </p>
+ * <p>
+ * AnimatedVectorDrawable can be defined in either <a href="#ThreeXML">three separate XML files</a>,
+ * or <a href="#OneXML">one XML</a>.
+ * </p>
+ * <a name="ThreeXML"></a>
+ * <h3>Define an AnimatedVectorDrawable in three separate XML files</h3>
+ * <ul>
+ * <a name="VDExample"></a>
+ * <li><h4>XML for the VectorDrawable containing properties to be animated</h4>
+ * <p>
+ * Animations can be performed on the animatable attributes in
+ * {@link android.graphics.drawable.VectorDrawable}. These attributes will be animated by
+ * {@link android.animation.ObjectAnimator}. The ObjectAnimator's target can be the root element,
+ * a group element or a path element. The targeted elements need to be named uniquely within
+ * the same VectorDrawable. Elements without animation do not need to be named.
+ * </p>
+ * <p>
+ * Here are all the animatable attributes in {@link android.graphics.drawable.VectorDrawable}:
+ * <table border="2" align="center" cellpadding="5">
+ *     <thead>
+ *         <tr>
+ *             <th>Element Name</th>
+ *             <th>Animatable attribute name</th>
+ *         </tr>
+ *     </thead>
+ *     <tr>
+ *         <td>&lt;vector&gt;</td>
+ *         <td>alpha</td>
+ *     </tr>
+ *     <tr>
+ *         <td rowspan="7">&lt;group&gt;</td>
+ *         <td>rotation</td>
+ *     </tr>
+ *     <tr>
+ *         <td>pivotX</td>
+ *     </tr>
+ *     <tr>
+ *         <td>pivotY</td>
+ *     </tr>
+ *     <tr>
+ *         <td>scaleX</td>
+ *     </tr>
+ *     <tr>
+ *         <td>scaleY</td>
+ *     </tr>
+ *     <tr>
+ *         <td>translateX</td>
+ *     </tr>
+ *     <tr>
+ *         <td>translateY</td>
+ *     </tr>
+ *     <tr>
+ *         <td rowspan="8">&lt;path&gt;</td>
+ *         <td>pathData</td>
+ *     </tr>
+ *     <tr>
+ *         <td>fillColor</td>
+ *     </tr>
+ *     <tr>
+ *         <td>strokeColor</td>
+ *     </tr>
+ *     <tr>
+ *         <td>strokeWidth</td>
+ *     </tr>
+ *     <tr>
+ *         <td>strokeAlpha</td>
+ *     </tr>
+ *     <tr>
+ *         <td>fillAlpha</td>
+ *     </tr>
+ *     <tr>
+ *         <td>trimPathStart</td>
+ *     </tr>
+ *     <tr>
+ *         <td>trimPathOffset</td>
+ *     </tr>
+ *     <tr>
+ *         <td>&lt;clip-path&gt;</td>
+ *         <td>pathData</td>
+ *     </tr>
+ * </table>
+ * </p>
+ * Below is an example of a VectorDrawable defined in vectordrawable.xml. This VectorDrawable is
+ * referred to by its file name (not including file suffix) in the
+ * <a href="AVDExample">AnimatedVectorDrawable XML example</a>.
+ * <pre>
+ * &lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
+ *     android:height=&quot;64dp&quot;
+ *     android:width=&quot;64dp&quot;
+ *     android:viewportHeight=&quot;600&quot;
+ *     android:viewportWidth=&quot;600&quot; &gt;
+ *     &lt;group
+ *         android:name=&quot;rotationGroup&quot;
+ *         android:pivotX=&quot;300.0&quot;
+ *         android:pivotY=&quot;300.0&quot;
+ *         android:rotation=&quot;45.0&quot; &gt;
+ *         &lt;path
+ *             android:name=&quot;v&quot;
+ *             android:fillColor=&quot;#000000&quot;
+ *             android:pathData=&quot;M300,70 l 0,-70 70,70 0,0 -70,70z&quot; /&gt;
+ *     &lt;/group&gt;
+ * &lt;/vector&gt;
+ * </pre></li>
+ *
+ * <a name="AVDExample"></a>
+ * <li><h4>XML for AnimatedVectorDrawable</h4>
+ * <p>
+ * An AnimatedVectorDrawable element has a VectorDrawable attribute, and one or more target
+ * element(s). The target element can specify its target by android:name attribute, and link the
+ * target with the proper ObjectAnimator or AnimatorSet by android:animation attribute.
+ * </p>
+ * The following code sample defines an AnimatedVectorDrawable. Note that the names refer to the
+ * groups and paths in the <a href="#VDExample">VectorDrawable XML above</a>.
+ * <pre>
+ * &lt;animated-vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
+ *     android:drawable=&quot;@drawable/vectordrawable&quot; &gt;
+ *     &lt;target
+ *         android:name=&quot;rotationGroup&quot;
+ *         android:animation=&quot;@anim/rotation&quot; /&gt;
+ *     &lt;target
+ *         android:name=&quot;v&quot;
+ *         android:animation=&quot;@anim/path_morph&quot; /&gt;
+ * &lt;/animated-vector&gt;
+ * </pre>
+ * </li>
+ *
+ * <li><h4>XML for Animations defined using ObjectAnimator or AnimatorSet</h4>
+ * <p>
+ * From the previous <a href="#AVDExample">example of AnimatedVectorDrawable</a>, two animations
+ * were used: rotation.xml and path_morph.xml.
+ * </p>
+ * rotation.xml rotates the target group from 0 degree to 360 degrees over 6000ms:
+ * <pre>
+ * &lt;objectAnimator
+ *     android:duration=&quot;6000&quot;
+ *     android:propertyName=&quot;rotation&quot;
+ *     android:valueFrom=&quot;0&quot;
+ *     android:valueTo=&quot;360&quot; /&gt;
+ * </pre>
+ *
+ * path_morph.xml morphs the path from one shape into the other. Note that the paths must be
+ * compatible for morphing. Specifically, the paths must have the same commands, in the same order,
+ * and must have the same number of parameters for each command. It is recommended to store path
+ * strings as string resources for reuse.
+ * <pre>
+ * &lt;set xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;&gt;
+ *     &lt;objectAnimator
+ *         android:duration=&quot;3000&quot;
+ *         android:propertyName=&quot;pathData&quot;
+ *         android:valueFrom=&quot;M300,70 l 0,-70 70,70 0,0 -70,70z&quot;
+ *         android:valueTo=&quot;M300,70 l 0,-70 70,0  0,140 -70,0 z&quot;
+ *         android:valueType=&quot;pathType&quot;/&gt;
+ * &lt;/set&gt;
+ * </pre>
+ * </ul>
+ * <a name="OneXML"></a>
+ * <h3>Define an AnimatedVectorDrawable all in one XML file</h3>
+ * <p>
+ * Since the AAPT tool supports a new format that bundles several related XML files together, we can
+ * merge the XML files from the previous examples into one XML file:
+ * </p>
+ * <pre>
+ * &lt;animated-vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
+ *                  xmlns:aapt=&quothttp://schemas.android.com/aapt&quot; &gt;
+ *     &lt;aapt:attr name="android:drawable"&gt;
+ *         &lt;vector
+ *             android:height=&quot;64dp&quot;
+ *             android:width=&quot;64dp&quot;
+ *             android:viewportHeight=&quot;600&quot;
+ *             android:viewportWidth=&quot;600&quot; &gt;
+ *             &lt;group
+ *                 android:name=&quot;rotationGroup&quot;
+ *                 android:pivotX=&quot;300.0&quot;
+ *                 android:pivotY=&quot;300.0&quot;
+ *                 android:rotation=&quot;45.0&quot; &gt;
+ *                 &lt;path
+ *                     android:name=&quot;v&quot;
+ *                     android:fillColor=&quot;#000000&quot;
+ *                     android:pathData=&quot;M300,70 l 0,-70 70,70 0,0 -70,70z&quot; /&gt;
+ *             &lt;/group&gt;
+ *         &lt;/vector&gt;
+ *     &lt;/aapt:attr&gt;
+ *
+ *     &lt;target android:name=&quot;rotationGroup&quot;&gt; *
+ *         &lt;aapt:attr name="android:animation"&gt;
+ *             &lt;objectAnimator
+ *             android:duration=&quot;6000&quot;
+ *             android:propertyName=&quot;rotation&quot;
+ *             android:valueFrom=&quot;0&quot;
+ *             android:valueTo=&quot;360&quot; /&gt;
+ *         &lt;/aapt:attr&gt;
+ *     &lt;/target&gt;
+ *
+ *     &lt;target android:name=&quot;v&quot; &gt;
+ *         &lt;aapt:attr name="android:animation"&gt;
+ *             &lt;set&gt;
+ *                 &lt;objectAnimator
+ *                     android:duration=&quot;3000&quot;
+ *                     android:propertyName=&quot;pathData&quot;
+ *                     android:valueFrom=&quot;M300,70 l 0,-70 70,70 0,0 -70,70z&quot;
+ *                     android:valueTo=&quot;M300,70 l 0,-70 70,0  0,140 -70,0 z&quot;
+ *                     android:valueType=&quot;pathType&quot;/&gt;
+ *             &lt;/set&gt;
+ *         &lt;/aapt:attr&gt;
+ *      &lt;/target&gt;
+ * &lt;/animated-vector&gt;
+ * </pre>
+ *
+ * @attr ref android.R.styleable#AnimatedVectorDrawable_drawable
+ * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_name
+ * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_animation
+ */
+public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
+    private static final String LOGTAG = "AnimatedVectorDrawable";
+
+    private static final String ANIMATED_VECTOR = "animated-vector";
+    private static final String TARGET = "target";
+
+    private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false;
+
+    /** Local, mutable animator set. */
+    private VectorDrawableAnimator mAnimatorSet;
+
+    /**
+     * The resources against which this drawable was created. Used to attempt
+     * to inflate animators if applyTheme() doesn't get called.
+     */
+    private Resources mRes;
+
+    private AnimatedVectorDrawableState mAnimatedVectorState;
+
+    /** The animator set that is parsed from the xml. */
+    private AnimatorSet mAnimatorSetFromXml = null;
+
+    private boolean mMutated;
+
+    /** Use a internal AnimatorListener to support callbacks during animation events. */
+    private ArrayList<Animatable2.AnimationCallback> mAnimationCallbacks = null;
+    private AnimatorListener mAnimatorListener = null;
+
+    public AnimatedVectorDrawable() {
+        this(null, null);
+    }
+
+    private AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res) {
+        mAnimatedVectorState = new AnimatedVectorDrawableState(state, mCallback, res);
+        mAnimatorSet = new VectorDrawableAnimatorRT(this);
+        mRes = res;
+    }
+
+    @Override
+    public Drawable mutate() {
+        if (!mMutated && super.mutate() == this) {
+            mAnimatedVectorState = new AnimatedVectorDrawableState(
+                    mAnimatedVectorState, mCallback, mRes);
+            mMutated = true;
+        }
+        return this;
+    }
+
+    /**
+     * @hide
+     */
+    public void clearMutated() {
+        super.clearMutated();
+        if (mAnimatedVectorState.mVectorDrawable != null) {
+            mAnimatedVectorState.mVectorDrawable.clearMutated();
+        }
+        mMutated = false;
+    }
+
+    /**
+     * In order to avoid breaking old apps, we only throw exception on invalid VectorDrawable
+     * animations for apps targeting N and later. For older apps, we ignore (i.e. quietly skip)
+     * these animations.
+     *
+     * @return whether invalid animations for vector drawable should be ignored.
+     */
+    private static boolean shouldIgnoreInvalidAnimation() {
+        Application app = ActivityThread.currentApplication();
+        if (app == null || app.getApplicationInfo() == null) {
+            return true;
+        }
+        if (app.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public ConstantState getConstantState() {
+        mAnimatedVectorState.mChangingConfigurations = getChangingConfigurations();
+        return mAnimatedVectorState;
+    }
+
+    @Override
+    public @Config int getChangingConfigurations() {
+        return super.getChangingConfigurations() | mAnimatedVectorState.getChangingConfigurations();
+    }
+
+    /**
+     * Draws the AnimatedVectorDrawable into the given canvas.
+     * <p>
+     * <strong>Note:</strong> Calling this method with a software canvas when the
+     * AnimatedVectorDrawable is being animated on RenderThread (for API 25 and later) may yield
+     * outdated result, as the UI thread is not guaranteed to be in sync with RenderThread on
+     * VectorDrawable's property changes during RenderThread animations.
+     * </p>
+     *
+     * @param canvas The canvas to draw into
+     */
+    @Override
+    public void draw(Canvas canvas) {
+        if (!canvas.isHardwareAccelerated() && mAnimatorSet instanceof VectorDrawableAnimatorRT) {
+            // If we have SW canvas and the RT animation is waiting to start, We need to fallback
+            // to UI thread animation for AVD.
+            if (!mAnimatorSet.isRunning() &&
+                    ((VectorDrawableAnimatorRT) mAnimatorSet).mPendingAnimationActions.size() > 0) {
+                fallbackOntoUI();
+            }
+        }
+        mAnimatorSet.onDraw(canvas);
+        mAnimatedVectorState.mVectorDrawable.draw(canvas);
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        mAnimatedVectorState.mVectorDrawable.setBounds(bounds);
+    }
+
+    @Override
+    protected boolean onStateChange(int[] state) {
+        return mAnimatedVectorState.mVectorDrawable.setState(state);
+    }
+
+    @Override
+    protected boolean onLevelChange(int level) {
+        return mAnimatedVectorState.mVectorDrawable.setLevel(level);
+    }
+
+    @Override
+    public boolean onLayoutDirectionChanged(@View.ResolvedLayoutDir int layoutDirection) {
+        return mAnimatedVectorState.mVectorDrawable.setLayoutDirection(layoutDirection);
+    }
+
+    /**
+     * For API 25 and later, AnimatedVectorDrawable runs on RenderThread. Therefore, when the
+     * root alpha is being animated, this getter does not guarantee to return an up-to-date alpha
+     * value.
+     *
+     * @return the containing vector drawable's root alpha value.
+     */
+    @Override
+    public int getAlpha() {
+        return mAnimatedVectorState.mVectorDrawable.getAlpha();
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        mAnimatedVectorState.mVectorDrawable.setAlpha(alpha);
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter);
+    }
+
+    @Override
+    public ColorFilter getColorFilter() {
+        return mAnimatedVectorState.mVectorDrawable.getColorFilter();
+    }
+
+    @Override
+    public void setTintList(ColorStateList tint) {
+        mAnimatedVectorState.mVectorDrawable.setTintList(tint);
+    }
+
+    @Override
+    public void setHotspot(float x, float y) {
+        mAnimatedVectorState.mVectorDrawable.setHotspot(x, y);
+    }
+
+    @Override
+    public void setHotspotBounds(int left, int top, int right, int bottom) {
+        mAnimatedVectorState.mVectorDrawable.setHotspotBounds(left, top, right, bottom);
+    }
+
+    @Override
+    public void setTintMode(PorterDuff.Mode tintMode) {
+        mAnimatedVectorState.mVectorDrawable.setTintMode(tintMode);
+    }
+
+    @Override
+    public boolean setVisible(boolean visible, boolean restart) {
+        if (mAnimatorSet.isInfinite() && mAnimatorSet.isStarted()) {
+            if (visible) {
+                // Resume the infinite animation when the drawable becomes visible again.
+                mAnimatorSet.resume();
+            } else {
+                // Pause the infinite animation once the drawable is no longer visible.
+                mAnimatorSet.pause();
+            }
+        }
+        mAnimatedVectorState.mVectorDrawable.setVisible(visible, restart);
+        return super.setVisible(visible, restart);
+    }
+
+    @Override
+    public boolean isStateful() {
+        return mAnimatedVectorState.mVectorDrawable.isStateful();
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.TRANSLUCENT;
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth();
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight();
+    }
+
+    @Override
+    public void getOutline(@NonNull Outline outline) {
+        mAnimatedVectorState.mVectorDrawable.getOutline(outline);
+    }
+
+    /** @hide */
+    @Override
+    public Insets getOpticalInsets() {
+        return mAnimatedVectorState.mVectorDrawable.getOpticalInsets();
+    }
+
+    @Override
+    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
+            throws XmlPullParserException, IOException {
+        final AnimatedVectorDrawableState state = mAnimatedVectorState;
+
+        int eventType = parser.getEventType();
+        float pathErrorScale = 1;
+        final int innerDepth = parser.getDepth() + 1;
+
+        // Parse everything until the end of the animated-vector element.
+        while (eventType != XmlPullParser.END_DOCUMENT
+                && (parser.getDepth() >= innerDepth || eventType != XmlPullParser.END_TAG)) {
+            if (eventType == XmlPullParser.START_TAG) {
+                final String tagName = parser.getName();
+                if (ANIMATED_VECTOR.equals(tagName)) {
+                    final TypedArray a = obtainAttributes(res, theme, attrs,
+                            R.styleable.AnimatedVectorDrawable);
+                    int drawableRes = a.getResourceId(
+                            R.styleable.AnimatedVectorDrawable_drawable, 0);
+                    if (drawableRes != 0) {
+                        VectorDrawable vectorDrawable = (VectorDrawable) res.getDrawable(
+                                drawableRes, theme).mutate();
+                        vectorDrawable.setAllowCaching(false);
+                        vectorDrawable.setCallback(mCallback);
+                        pathErrorScale = vectorDrawable.getPixelSize();
+                        if (state.mVectorDrawable != null) {
+                            state.mVectorDrawable.setCallback(null);
+                        }
+                        state.mVectorDrawable = vectorDrawable;
+                    }
+                    a.recycle();
+                } else if (TARGET.equals(tagName)) {
+                    final TypedArray a = obtainAttributes(res, theme, attrs,
+                            R.styleable.AnimatedVectorDrawableTarget);
+                    final String target = a.getString(
+                            R.styleable.AnimatedVectorDrawableTarget_name);
+                    final int animResId = a.getResourceId(
+                            R.styleable.AnimatedVectorDrawableTarget_animation, 0);
+                    if (animResId != 0) {
+                        if (theme != null) {
+                            // The animator here could be ObjectAnimator or AnimatorSet.
+                            final Animator animator = AnimatorInflater.loadAnimator(
+                                    res, theme, animResId, pathErrorScale);
+                            updateAnimatorProperty(animator, target, state.mVectorDrawable,
+                                    state.mShouldIgnoreInvalidAnim);
+                            state.addTargetAnimator(target, animator);
+                        } else {
+                            // The animation may be theme-dependent. As a
+                            // workaround until Animator has full support for
+                            // applyTheme(), postpone loading the animator
+                            // until we have a theme in applyTheme().
+                            state.addPendingAnimator(animResId, pathErrorScale, target);
+
+                        }
+                    }
+                    a.recycle();
+                }
+            }
+
+            eventType = parser.next();
+        }
+
+        // If we don't have any pending animations, we don't need to hold a
+        // reference to the resources.
+        mRes = state.mPendingAnims == null ? null : res;
+    }
+
+    private static void updateAnimatorProperty(Animator animator, String targetName,
+            VectorDrawable vectorDrawable, boolean ignoreInvalidAnim) {
+        if (animator instanceof ObjectAnimator) {
+            // Change the property of the Animator from using reflection based on the property
+            // name to a Property object that wraps the setter and getter for modifying that
+            // specific property for a given object. By replacing the reflection with a direct call,
+            // we can largely reduce the time it takes for a animator to modify a VD property.
+            PropertyValuesHolder[] holders = ((ObjectAnimator) animator).getValues();
+            for (int i = 0; i < holders.length; i++) {
+                PropertyValuesHolder pvh = holders[i];
+                String propertyName = pvh.getPropertyName();
+                Object targetNameObj = vectorDrawable.getTargetByName(targetName);
+                Property property = null;
+                if (targetNameObj instanceof VectorDrawable.VObject) {
+                    property = ((VectorDrawable.VObject) targetNameObj).getProperty(propertyName);
+                } else if (targetNameObj instanceof VectorDrawable.VectorDrawableState) {
+                    property = ((VectorDrawable.VectorDrawableState) targetNameObj)
+                            .getProperty(propertyName);
+                }
+                if (property != null) {
+                    if (containsSameValueType(pvh, property)) {
+                        pvh.setProperty(property);
+                    } else if (!ignoreInvalidAnim) {
+                        throw new RuntimeException("Wrong valueType for Property: " + propertyName
+                                + ".  Expected type: " + property.getType().toString() + ". Actual "
+                                + "type defined in resources: " + pvh.getValueType().toString());
+
+                    }
+                }
+            }
+        } else if (animator instanceof AnimatorSet) {
+            for (Animator anim : ((AnimatorSet) animator).getChildAnimations()) {
+                updateAnimatorProperty(anim, targetName, vectorDrawable, ignoreInvalidAnim);
+            }
+        }
+    }
+
+    private static boolean containsSameValueType(PropertyValuesHolder holder, Property property) {
+        Class type1 = holder.getValueType();
+        Class type2 = property.getType();
+        if (type1 == float.class || type1 == Float.class) {
+            return type2 == float.class || type2 == Float.class;
+        } else if (type1 == int.class || type1 == Integer.class) {
+            return type2 == int.class || type2 == Integer.class;
+        } else {
+            return type1 == type2;
+        }
+    }
+
+    /**
+     * Force to animate on UI thread.
+     * @hide
+     */
+    public void forceAnimationOnUI() {
+        if (mAnimatorSet instanceof VectorDrawableAnimatorRT) {
+            VectorDrawableAnimatorRT animator = (VectorDrawableAnimatorRT) mAnimatorSet;
+            if (animator.isRunning()) {
+                throw new UnsupportedOperationException("Cannot force Animated Vector Drawable to" +
+                        " run on UI thread when the animation has started on RenderThread.");
+            }
+            fallbackOntoUI();
+        }
+    }
+
+    private void fallbackOntoUI() {
+        if (mAnimatorSet instanceof VectorDrawableAnimatorRT) {
+            VectorDrawableAnimatorRT oldAnim = (VectorDrawableAnimatorRT) mAnimatorSet;
+            mAnimatorSet = new VectorDrawableAnimatorUI(this);
+            if (mAnimatorSetFromXml != null) {
+                mAnimatorSet.init(mAnimatorSetFromXml);
+            }
+            // Transfer the listener from RT animator to UI animator
+            if (oldAnim.mListener != null) {
+                mAnimatorSet.setListener(oldAnim.mListener);
+            }
+            oldAnim.transferPendingActions(mAnimatorSet);
+        }
+    }
+
+    @Override
+    public boolean canApplyTheme() {
+        return (mAnimatedVectorState != null && mAnimatedVectorState.canApplyTheme())
+                || super.canApplyTheme();
+    }
+
+    @Override
+    public void applyTheme(Theme t) {
+        super.applyTheme(t);
+
+        final VectorDrawable vectorDrawable = mAnimatedVectorState.mVectorDrawable;
+        if (vectorDrawable != null && vectorDrawable.canApplyTheme()) {
+            vectorDrawable.applyTheme(t);
+        }
+
+        if (t != null) {
+            mAnimatedVectorState.inflatePendingAnimators(t.getResources(), t);
+        }
+
+        // If we don't have any pending animations, we don't need to hold a
+        // reference to the resources.
+        if (mAnimatedVectorState.mPendingAnims == null) {
+            mRes = null;
+        }
+    }
+
+    private static class AnimatedVectorDrawableState extends ConstantState {
+        @Config int mChangingConfigurations;
+        VectorDrawable mVectorDrawable;
+
+        private final boolean mShouldIgnoreInvalidAnim;
+
+        /** Animators that require a theme before inflation. */
+        ArrayList<PendingAnimator> mPendingAnims;
+
+        /** Fully inflated animators awaiting cloning into an AnimatorSet. */
+        ArrayList<Animator> mAnimators;
+
+        /** Map of animators to their target object names */
+        ArrayMap<Animator, String> mTargetNameMap;
+
+        public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy,
+                Callback owner, Resources res) {
+            mShouldIgnoreInvalidAnim = shouldIgnoreInvalidAnimation();
+            if (copy != null) {
+                mChangingConfigurations = copy.mChangingConfigurations;
+
+                if (copy.mVectorDrawable != null) {
+                    final ConstantState cs = copy.mVectorDrawable.getConstantState();
+                    if (res != null) {
+                        mVectorDrawable = (VectorDrawable) cs.newDrawable(res);
+                    } else {
+                        mVectorDrawable = (VectorDrawable) cs.newDrawable();
+                    }
+                    mVectorDrawable = (VectorDrawable) mVectorDrawable.mutate();
+                    mVectorDrawable.setCallback(owner);
+                    mVectorDrawable.setLayoutDirection(copy.mVectorDrawable.getLayoutDirection());
+                    mVectorDrawable.setBounds(copy.mVectorDrawable.getBounds());
+                    mVectorDrawable.setAllowCaching(false);
+                }
+
+                if (copy.mAnimators != null) {
+                    mAnimators = new ArrayList<>(copy.mAnimators);
+                }
+
+                if (copy.mTargetNameMap != null) {
+                    mTargetNameMap = new ArrayMap<>(copy.mTargetNameMap);
+                }
+
+                if (copy.mPendingAnims != null) {
+                    mPendingAnims = new ArrayList<>(copy.mPendingAnims);
+                }
+            } else {
+                mVectorDrawable = new VectorDrawable();
+            }
+        }
+
+        @Override
+        public boolean canApplyTheme() {
+            return (mVectorDrawable != null && mVectorDrawable.canApplyTheme())
+                    || mPendingAnims != null || super.canApplyTheme();
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return new AnimatedVectorDrawable(this, null);
+        }
+
+        @Override
+        public Drawable newDrawable(Resources res) {
+            return new AnimatedVectorDrawable(this, res);
+        }
+
+        @Override
+        public @Config int getChangingConfigurations() {
+            return mChangingConfigurations;
+        }
+
+        public void addPendingAnimator(int resId, float pathErrorScale, String target) {
+            if (mPendingAnims == null) {
+                mPendingAnims = new ArrayList<>(1);
+            }
+            mPendingAnims.add(new PendingAnimator(resId, pathErrorScale, target));
+        }
+
+        public void addTargetAnimator(String targetName, Animator animator) {
+            if (mAnimators == null) {
+                mAnimators = new ArrayList<>(1);
+                mTargetNameMap = new ArrayMap<>(1);
+            }
+            mAnimators.add(animator);
+            mTargetNameMap.put(animator, targetName);
+
+            if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+                Log.v(LOGTAG, "add animator  for target " + targetName + " " + animator);
+            }
+        }
+
+        /**
+         * Prepares a local set of mutable animators based on the constant
+         * state.
+         * <p>
+         * If there are any pending uninflated animators, attempts to inflate
+         * them immediately against the provided resources object.
+         *
+         * @param animatorSet the animator set to which the animators should
+         *                    be added
+         * @param res the resources against which to inflate any pending
+         *            animators, or {@code null} if not available
+         */
+        public void prepareLocalAnimators(@NonNull AnimatorSet animatorSet,
+                @Nullable Resources res) {
+            // Check for uninflated animators. We can remove this after we add
+            // support for Animator.applyTheme(). See comments in inflate().
+            if (mPendingAnims != null) {
+                // Attempt to load animators without applying a theme.
+                if (res != null) {
+                    inflatePendingAnimators(res, null);
+                } else {
+                    Log.e(LOGTAG, "Failed to load animators. Either the AnimatedVectorDrawable"
+                            + " must be created using a Resources object or applyTheme() must be"
+                            + " called with a non-null Theme object.");
+                }
+
+                mPendingAnims = null;
+            }
+
+            // Perform a deep copy of the constant state's animators.
+            final int count = mAnimators == null ? 0 : mAnimators.size();
+            if (count > 0) {
+                final Animator firstAnim = prepareLocalAnimator(0);
+                final AnimatorSet.Builder builder = animatorSet.play(firstAnim);
+                for (int i = 1; i < count; ++i) {
+                    final Animator nextAnim = prepareLocalAnimator(i);
+                    builder.with(nextAnim);
+                }
+            }
+        }
+
+        /**
+         * Prepares a local animator for the given index within the constant
+         * state's list of animators.
+         *
+         * @param index the index of the animator within the constant state
+         */
+        private Animator prepareLocalAnimator(int index) {
+            final Animator animator = mAnimators.get(index);
+            final Animator localAnimator = animator.clone();
+            final String targetName = mTargetNameMap.get(animator);
+            final Object target = mVectorDrawable.getTargetByName(targetName);
+            if (!mShouldIgnoreInvalidAnim) {
+                if (target == null) {
+                    throw new IllegalStateException("Target with the name \"" + targetName
+                            + "\" cannot be found in the VectorDrawable to be animated.");
+                } else if (!(target instanceof VectorDrawable.VectorDrawableState)
+                        && !(target instanceof VectorDrawable.VObject)) {
+                    throw new UnsupportedOperationException("Target should be either VGroup, VPath,"
+                            + " or ConstantState, " + target.getClass() + " is not supported");
+                }
+            }
+            localAnimator.setTarget(target);
+            return localAnimator;
+        }
+
+        /**
+         * Inflates pending animators, if any, against a theme. Clears the list of
+         * pending animators.
+         *
+         * @param t the theme against which to inflate the animators
+         */
+        public void inflatePendingAnimators(@NonNull Resources res, @Nullable Theme t) {
+            final ArrayList<PendingAnimator> pendingAnims = mPendingAnims;
+            if (pendingAnims != null) {
+                mPendingAnims = null;
+
+                for (int i = 0, count = pendingAnims.size(); i < count; i++) {
+                    final PendingAnimator pendingAnimator = pendingAnims.get(i);
+                    final Animator animator = pendingAnimator.newInstance(res, t);
+                    updateAnimatorProperty(animator, pendingAnimator.target, mVectorDrawable,
+                            mShouldIgnoreInvalidAnim);
+                    addTargetAnimator(pendingAnimator.target, animator);
+                }
+            }
+        }
+
+        /**
+         * Basically a constant state for Animators until we actually implement
+         * constant states for Animators.
+         */
+        private static class PendingAnimator {
+            public final int animResId;
+            public final float pathErrorScale;
+            public final String target;
+
+            public PendingAnimator(int animResId, float pathErrorScale, String target) {
+                this.animResId = animResId;
+                this.pathErrorScale = pathErrorScale;
+                this.target = target;
+            }
+
+            public Animator newInstance(Resources res, Theme theme) {
+                return AnimatorInflater.loadAnimator(res, theme, animResId, pathErrorScale);
+            }
+        }
+    }
+
+    @Override
+    public boolean isRunning() {
+        return mAnimatorSet.isRunning();
+    }
+
+    /**
+     * Resets the AnimatedVectorDrawable to the start state as specified in the animators.
+     */
+    public void reset() {
+        ensureAnimatorSet();
+        if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+            Log.w(LOGTAG, "calling reset on AVD: " +
+                    ((VectorDrawable.VectorDrawableState) ((AnimatedVectorDrawableState)
+                    getConstantState()).mVectorDrawable.getConstantState()).mRootName
+                    + ", at: " + this);
+        }
+        mAnimatorSet.reset();
+    }
+
+    @Override
+    public void start() {
+        ensureAnimatorSet();
+        if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+            Log.w(LOGTAG, "calling start on AVD: " +
+                    ((VectorDrawable.VectorDrawableState) ((AnimatedVectorDrawableState)
+                    getConstantState()).mVectorDrawable.getConstantState()).mRootName
+                    + ", at: " + this);
+        }
+        mAnimatorSet.start();
+    }
+
+    @NonNull
+    private void ensureAnimatorSet() {
+        if (mAnimatorSetFromXml == null) {
+            // TODO: Skip the AnimatorSet creation and init the VectorDrawableAnimator directly
+            // with a list of LocalAnimators.
+            mAnimatorSetFromXml = new AnimatorSet();
+            mAnimatedVectorState.prepareLocalAnimators(mAnimatorSetFromXml, mRes);
+            mAnimatorSet.init(mAnimatorSetFromXml);
+            mRes = null;
+        }
+    }
+
+    @Override
+    public void stop() {
+        if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+            Log.w(LOGTAG, "calling stop on AVD: " +
+                    ((VectorDrawable.VectorDrawableState) ((AnimatedVectorDrawableState)
+                            getConstantState()).mVectorDrawable.getConstantState())
+                            .mRootName + ", at: " + this);
+        }
+        mAnimatorSet.end();
+    }
+
+    /**
+     * Reverses ongoing animations or starts pending animations in reverse.
+     * <p>
+     * NOTE: Only works if all animations support reverse. Otherwise, this will
+     * do nothing.
+     * @hide
+     */
+    public void reverse() {
+        ensureAnimatorSet();
+
+        // Only reverse when all the animators can be reversed.
+        if (!canReverse()) {
+            Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()");
+            return;
+        }
+
+        mAnimatorSet.reverse();
+    }
+
+    /**
+     * @hide
+     */
+    public boolean canReverse() {
+        return mAnimatorSet.canReverse();
+    }
+
+    private final Callback mCallback = new Callback() {
+        @Override
+        public void invalidateDrawable(@NonNull Drawable who) {
+            invalidateSelf();
+        }
+
+        @Override
+        public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
+            scheduleSelf(what, when);
+        }
+
+        @Override
+        public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
+            unscheduleSelf(what);
+        }
+    };
+
+    @Override
+    public void registerAnimationCallback(@NonNull AnimationCallback callback) {
+        if (callback == null) {
+            return;
+        }
+
+        // Add listener accordingly.
+        if (mAnimationCallbacks == null) {
+            mAnimationCallbacks = new ArrayList<>();
+        }
+
+        mAnimationCallbacks.add(callback);
+
+        if (mAnimatorListener == null) {
+            // Create a animator listener and trigger the callback events when listener is
+            // triggered.
+            mAnimatorListener = new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    ArrayList<AnimationCallback> tmpCallbacks = new ArrayList<>(mAnimationCallbacks);
+                    int size = tmpCallbacks.size();
+                    for (int i = 0; i < size; i ++) {
+                        tmpCallbacks.get(i).onAnimationStart(AnimatedVectorDrawable.this);
+                    }
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    ArrayList<AnimationCallback> tmpCallbacks = new ArrayList<>(mAnimationCallbacks);
+                    int size = tmpCallbacks.size();
+                    for (int i = 0; i < size; i ++) {
+                        tmpCallbacks.get(i).onAnimationEnd(AnimatedVectorDrawable.this);
+                    }
+                }
+            };
+        }
+        mAnimatorSet.setListener(mAnimatorListener);
+    }
+
+    // A helper function to clean up the animator listener in the mAnimatorSet.
+    private void removeAnimatorSetListener() {
+        if (mAnimatorListener != null) {
+            mAnimatorSet.removeListener(mAnimatorListener);
+            mAnimatorListener = null;
+        }
+    }
+
+    @Override
+    public boolean unregisterAnimationCallback(@NonNull AnimationCallback callback) {
+        if (mAnimationCallbacks == null || callback == null) {
+            // Nothing to be removed.
+            return false;
+        }
+        boolean removed = mAnimationCallbacks.remove(callback);
+
+        //  When the last call back unregistered, remove the listener accordingly.
+        if (mAnimationCallbacks.size() == 0) {
+            removeAnimatorSetListener();
+        }
+        return removed;
+    }
+
+    @Override
+    public void clearAnimationCallbacks() {
+        removeAnimatorSetListener();
+        if (mAnimationCallbacks == null) {
+            return;
+        }
+
+        mAnimationCallbacks.clear();
+    }
+
+    private interface VectorDrawableAnimator {
+        void init(@NonNull AnimatorSet set);
+        void start();
+        void end();
+        void reset();
+        void reverse();
+        boolean canReverse();
+        void setListener(AnimatorListener listener);
+        void removeListener(AnimatorListener listener);
+        void onDraw(Canvas canvas);
+        boolean isStarted();
+        boolean isRunning();
+        boolean isInfinite();
+        void pause();
+        void resume();
+    }
+
+    private static class VectorDrawableAnimatorUI implements VectorDrawableAnimator {
+        // mSet is only initialized in init(). So we need to check whether it is null before any
+        // operation.
+        private AnimatorSet mSet = null;
+        private final Drawable mDrawable;
+        // Caching the listener in the case when listener operation is called before the mSet is
+        // setup by init().
+        private ArrayList<AnimatorListener> mListenerArray = null;
+        private boolean mIsInfinite = false;
+
+        VectorDrawableAnimatorUI(@NonNull AnimatedVectorDrawable drawable) {
+            mDrawable = drawable;
+        }
+
+        @Override
+        public void init(@NonNull AnimatorSet set) {
+            if (mSet != null) {
+                // Already initialized
+                throw new UnsupportedOperationException("VectorDrawableAnimator cannot be " +
+                        "re-initialized");
+            }
+            // Keep a deep copy of the set, such that set can be still be constantly representing
+            // the static content from XML file.
+            mSet = set.clone();
+            mIsInfinite = mSet.getTotalDuration() == Animator.DURATION_INFINITE;
+
+            // If there are listeners added before calling init(), now they should be setup.
+            if (mListenerArray != null && !mListenerArray.isEmpty()) {
+                for (int i = 0; i < mListenerArray.size(); i++) {
+                    mSet.addListener(mListenerArray.get(i));
+                }
+                mListenerArray.clear();
+                mListenerArray = null;
+            }
+        }
+
+        // Although start(), reset() and reverse() should call init() already, it is better to
+        // protect these functions from NPE in any situation.
+        @Override
+        public void start() {
+            if (mSet == null || mSet.isStarted()) {
+                return;
+            }
+            mSet.start();
+            invalidateOwningView();
+        }
+
+        @Override
+        public void end() {
+            if (mSet == null) {
+                return;
+            }
+            mSet.end();
+        }
+
+        @Override
+        public void reset() {
+            if (mSet == null) {
+                return;
+            }
+            start();
+            mSet.cancel();
+        }
+
+        @Override
+        public void reverse() {
+            if (mSet == null) {
+                return;
+            }
+            mSet.reverse();
+            invalidateOwningView();
+        }
+
+        @Override
+        public boolean canReverse() {
+            return mSet != null && mSet.canReverse();
+        }
+
+        @Override
+        public void setListener(AnimatorListener listener) {
+            if (mSet == null) {
+                if (mListenerArray == null) {
+                    mListenerArray = new ArrayList<AnimatorListener>();
+                }
+                mListenerArray.add(listener);
+            } else {
+                mSet.addListener(listener);
+            }
+        }
+
+        @Override
+        public void removeListener(AnimatorListener listener) {
+            if (mSet == null) {
+                if (mListenerArray == null) {
+                    return;
+                }
+                mListenerArray.remove(listener);
+            } else {
+                mSet.removeListener(listener);
+            }
+        }
+
+        @Override
+        public void onDraw(Canvas canvas) {
+            if (mSet != null && mSet.isStarted()) {
+                invalidateOwningView();
+            }
+        }
+
+        @Override
+        public boolean isStarted() {
+            return mSet != null && mSet.isStarted();
+        }
+
+        @Override
+        public boolean isRunning() {
+            return mSet != null && mSet.isRunning();
+        }
+
+        @Override
+        public boolean isInfinite() {
+            return mIsInfinite;
+        }
+
+        @Override
+        public void pause() {
+            if (mSet == null) {
+                return;
+            }
+            mSet.pause();
+        }
+
+        @Override
+        public void resume() {
+            if (mSet == null) {
+                return;
+            }
+            mSet.resume();
+        }
+
+        private void invalidateOwningView() {
+            mDrawable.invalidateSelf();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public static class VectorDrawableAnimatorRT implements VectorDrawableAnimator {
+        private static final int START_ANIMATION = 1;
+        private static final int REVERSE_ANIMATION = 2;
+        private static final int RESET_ANIMATION = 3;
+        private static final int END_ANIMATION = 4;
+
+        // If the duration of an animation is more than 300 frames, we cap the sample size to 300.
+        private static final int MAX_SAMPLE_POINTS = 300;
+        private AnimatorListener mListener = null;
+        private final LongArray mStartDelays = new LongArray();
+        private PropertyValuesHolder.PropertyValues mTmpValues =
+                new PropertyValuesHolder.PropertyValues();
+        private long mSetPtr = 0;
+        private boolean mContainsSequentialAnimators = false;
+        private boolean mStarted = false;
+        private boolean mInitialized = false;
+        private boolean mIsReversible = false;
+        private boolean mIsInfinite = false;
+        // TODO: Consider using NativeAllocationRegistery to track native allocation
+        private final VirtualRefBasePtr mSetRefBasePtr;
+        private WeakReference<RenderNode> mLastSeenTarget = null;
+        private int mLastListenerId = 0;
+        private final IntArray mPendingAnimationActions = new IntArray();
+        private final AnimatedVectorDrawable mDrawable;
+
+        VectorDrawableAnimatorRT(AnimatedVectorDrawable drawable) {
+            mDrawable = drawable;
+            mSetPtr = nCreateAnimatorSet();
+            // Increment ref count on native AnimatorSet, so it doesn't get released before Java
+            // side is done using it.
+            mSetRefBasePtr = new VirtualRefBasePtr(mSetPtr);
+        }
+
+        @Override
+        public void init(@NonNull AnimatorSet set) {
+            if (mInitialized) {
+                // Already initialized
+                throw new UnsupportedOperationException("VectorDrawableAnimator cannot be " +
+                        "re-initialized");
+            }
+            parseAnimatorSet(set, 0);
+            long vectorDrawableTreePtr = mDrawable.mAnimatedVectorState.mVectorDrawable
+                    .getNativeTree();
+            nSetVectorDrawableTarget(mSetPtr, vectorDrawableTreePtr);
+            mInitialized = true;
+            mIsInfinite = set.getTotalDuration() == Animator.DURATION_INFINITE;
+
+            // Check reversible.
+            mIsReversible = true;
+            if (mContainsSequentialAnimators) {
+                mIsReversible = false;
+            } else {
+                // Check if there's any start delay set on child
+                for (int i = 0; i < mStartDelays.size(); i++) {
+                    if (mStartDelays.get(i) > 0) {
+                        mIsReversible = false;
+                        return;
+                    }
+                }
+            }
+        }
+
+        private void parseAnimatorSet(AnimatorSet set, long startTime) {
+            ArrayList<Animator> animators = set.getChildAnimations();
+
+            boolean playTogether = set.shouldPlayTogether();
+            // Convert AnimatorSet to VectorDrawableAnimatorRT
+            for (int i = 0; i < animators.size(); i++) {
+                Animator animator = animators.get(i);
+                // Here we only support ObjectAnimator
+                if (animator instanceof AnimatorSet) {
+                    parseAnimatorSet((AnimatorSet) animator, startTime);
+                } else if (animator instanceof ObjectAnimator) {
+                    createRTAnimator((ObjectAnimator) animator, startTime);
+                } // ignore ValueAnimators and others because they don't directly modify VD
+                  // therefore will be useless to AVD.
+
+                if (!playTogether) {
+                    // Assume not play together means play sequentially
+                    startTime += animator.getTotalDuration();
+                    mContainsSequentialAnimators = true;
+                }
+            }
+        }
+
+        // TODO: This method reads animation data from already parsed Animators. We need to move
+        // this step further up the chain in the parser to avoid the detour.
+        private void createRTAnimator(ObjectAnimator animator, long startTime) {
+            PropertyValuesHolder[] values = animator.getValues();
+            Object target = animator.getTarget();
+            if (target instanceof VectorDrawable.VGroup) {
+                createRTAnimatorForGroup(values, animator, (VectorDrawable.VGroup) target,
+                        startTime);
+            } else if (target instanceof VectorDrawable.VPath) {
+                for (int i = 0; i < values.length; i++) {
+                    values[i].getPropertyValues(mTmpValues);
+                    if (mTmpValues.endValue instanceof PathParser.PathData &&
+                            mTmpValues.propertyName.equals("pathData")) {
+                        createRTAnimatorForPath(animator, (VectorDrawable.VPath) target,
+                                startTime);
+                    }  else if (target instanceof VectorDrawable.VFullPath) {
+                        createRTAnimatorForFullPath(animator, (VectorDrawable.VFullPath) target,
+                                startTime);
+                    } else if (!mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) {
+                        throw new IllegalArgumentException("ClipPath only supports PathData " +
+                                "property");
+                    }
+                }
+            } else if (target instanceof VectorDrawable.VectorDrawableState) {
+                createRTAnimatorForRootGroup(values, animator,
+                        (VectorDrawable.VectorDrawableState) target, startTime);
+            }
+        }
+
+        private void createRTAnimatorForGroup(PropertyValuesHolder[] values,
+                ObjectAnimator animator, VectorDrawable.VGroup target,
+                long startTime) {
+
+            long nativePtr = target.getNativePtr();
+            int propertyId;
+            for (int i = 0; i < values.length; i++) {
+                // TODO: We need to support the rare case in AVD where no start value is provided
+                values[i].getPropertyValues(mTmpValues);
+                propertyId = VectorDrawable.VGroup.getPropertyIndex(mTmpValues.propertyName);
+                if (mTmpValues.type != Float.class && mTmpValues.type != float.class) {
+                    if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+                        Log.e(LOGTAG, "Unsupported type: " +
+                                mTmpValues.type + ". Only float value is supported for Groups.");
+                    }
+                    continue;
+                }
+                if (propertyId < 0) {
+                    if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+                        Log.e(LOGTAG, "Unsupported property: " +
+                                mTmpValues.propertyName + " for Vector Drawable Group");
+                    }
+                    continue;
+                }
+                long propertyPtr = nCreateGroupPropertyHolder(nativePtr, propertyId,
+                        (Float) mTmpValues.startValue, (Float) mTmpValues.endValue);
+                if (mTmpValues.dataSource != null) {
+                    float[] dataPoints = createFloatDataPoints(mTmpValues.dataSource,
+                            animator.getDuration());
+                    nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length);
+                }
+                createNativeChildAnimator(propertyPtr, startTime, animator);
+            }
+        }
+        private void createRTAnimatorForPath( ObjectAnimator animator, VectorDrawable.VPath target,
+                long startTime) {
+
+            long nativePtr = target.getNativePtr();
+            long startPathDataPtr = ((PathParser.PathData) mTmpValues.startValue)
+                    .getNativePtr();
+            long endPathDataPtr = ((PathParser.PathData) mTmpValues.endValue)
+                    .getNativePtr();
+            long propertyPtr = nCreatePathDataPropertyHolder(nativePtr, startPathDataPtr,
+                    endPathDataPtr);
+            createNativeChildAnimator(propertyPtr, startTime, animator);
+        }
+
+        private void createRTAnimatorForFullPath(ObjectAnimator animator,
+                VectorDrawable.VFullPath target, long startTime) {
+
+            int propertyId = target.getPropertyIndex(mTmpValues.propertyName);
+            long propertyPtr;
+            long nativePtr = target.getNativePtr();
+            if (mTmpValues.type == Float.class || mTmpValues.type == float.class) {
+                if (propertyId < 0) {
+                    if (mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) {
+                        return;
+                    } else {
+                        throw new IllegalArgumentException("Property: " + mTmpValues.propertyName
+                                + " is not supported for FullPath");
+                    }
+                }
+                propertyPtr = nCreatePathPropertyHolder(nativePtr, propertyId,
+                        (Float) mTmpValues.startValue, (Float) mTmpValues.endValue);
+                if (mTmpValues.dataSource != null) {
+                    // Pass keyframe data to native, if any.
+                    float[] dataPoints = createFloatDataPoints(mTmpValues.dataSource,
+                            animator.getDuration());
+                    nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length);
+                }
+
+            } else if (mTmpValues.type == Integer.class || mTmpValues.type == int.class) {
+                propertyPtr = nCreatePathColorPropertyHolder(nativePtr, propertyId,
+                        (Integer) mTmpValues.startValue, (Integer) mTmpValues.endValue);
+                if (mTmpValues.dataSource != null) {
+                    // Pass keyframe data to native, if any.
+                    int[] dataPoints = createIntDataPoints(mTmpValues.dataSource,
+                            animator.getDuration());
+                    nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length);
+                }
+            } else {
+                if (mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) {
+                    return;
+                } else {
+                    throw new UnsupportedOperationException("Unsupported type: " +
+                            mTmpValues.type + ". Only float, int or PathData value is " +
+                            "supported for Paths.");
+                }
+            }
+            createNativeChildAnimator(propertyPtr, startTime, animator);
+        }
+
+        private void createRTAnimatorForRootGroup(PropertyValuesHolder[] values,
+                ObjectAnimator animator, VectorDrawable.VectorDrawableState target,
+                long startTime) {
+            long nativePtr = target.getNativeRenderer();
+            if (!animator.getPropertyName().equals("alpha")) {
+                if (mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) {
+                    return;
+                } else {
+                    throw new UnsupportedOperationException("Only alpha is supported for root "
+                            + "group");
+                }
+            }
+            Float startValue = null;
+            Float endValue = null;
+            for (int i = 0; i < values.length; i++) {
+                values[i].getPropertyValues(mTmpValues);
+                if (mTmpValues.propertyName.equals("alpha")) {
+                    startValue = (Float) mTmpValues.startValue;
+                    endValue = (Float) mTmpValues.endValue;
+                    break;
+                }
+            }
+            if (startValue == null && endValue == null) {
+                if (mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) {
+                    return;
+                } else {
+                    throw new UnsupportedOperationException("No alpha values are specified");
+                }
+            }
+            long propertyPtr = nCreateRootAlphaPropertyHolder(nativePtr, startValue, endValue);
+            if (mTmpValues.dataSource != null) {
+                // Pass keyframe data to native, if any.
+                float[] dataPoints = createFloatDataPoints(mTmpValues.dataSource,
+                        animator.getDuration());
+                nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length);
+            }
+            createNativeChildAnimator(propertyPtr, startTime, animator);
+        }
+
+        /**
+         * Calculate the amount of frames an animation will run based on duration.
+         */
+        private static int getFrameCount(long duration) {
+            long frameIntervalNanos = Choreographer.getInstance().getFrameIntervalNanos();
+            int animIntervalMs = (int) (frameIntervalNanos / TimeUtils.NANOS_PER_MS);
+            int numAnimFrames = (int) Math.ceil(((double) duration) / animIntervalMs);
+            // We need 2 frames of data minimum.
+            numAnimFrames = Math.max(2, numAnimFrames);
+            if (numAnimFrames > MAX_SAMPLE_POINTS) {
+                Log.w("AnimatedVectorDrawable", "Duration for the animation is too long :" +
+                        duration + ", the animation will subsample the keyframe or path data.");
+                numAnimFrames = MAX_SAMPLE_POINTS;
+            }
+            return numAnimFrames;
+        }
+
+        // These are the data points that define the value of the animating properties.
+        // e.g. translateX and translateY can animate along a Path, at any fraction in [0, 1]
+        // a point on the path corresponds to the values of translateX and translateY.
+        // TODO: (Optimization) We should pass the path down in native and chop it into segments
+        // in native.
+        private static float[] createFloatDataPoints(
+                PropertyValuesHolder.PropertyValues.DataSource dataSource, long duration) {
+            int numAnimFrames = getFrameCount(duration);
+            float values[] = new float[numAnimFrames];
+            float lastFrame = numAnimFrames - 1;
+            for (int i = 0; i < numAnimFrames; i++) {
+                float fraction = i / lastFrame;
+                values[i] = (Float) dataSource.getValueAtFraction(fraction);
+            }
+            return values;
+        }
+
+        private static int[] createIntDataPoints(
+                PropertyValuesHolder.PropertyValues.DataSource dataSource, long duration) {
+            int numAnimFrames = getFrameCount(duration);
+            int values[] = new int[numAnimFrames];
+            float lastFrame = numAnimFrames - 1;
+            for (int i = 0; i < numAnimFrames; i++) {
+                float fraction = i / lastFrame;
+                values[i] = (Integer) dataSource.getValueAtFraction(fraction);
+            }
+            return values;
+        }
+
+        private void createNativeChildAnimator(long propertyPtr, long extraDelay,
+                                               ObjectAnimator animator) {
+            long duration = animator.getDuration();
+            int repeatCount = animator.getRepeatCount();
+            long startDelay = extraDelay + animator.getStartDelay();
+            TimeInterpolator interpolator = animator.getInterpolator();
+            long nativeInterpolator =
+                    RenderNodeAnimatorSetHelper.createNativeInterpolator(interpolator, duration);
+
+            startDelay *= ValueAnimator.getDurationScale();
+            duration *= ValueAnimator.getDurationScale();
+
+            mStartDelays.add(startDelay);
+            nAddAnimator(mSetPtr, propertyPtr, nativeInterpolator, startDelay, duration,
+                    repeatCount, animator.getRepeatMode());
+        }
+
+        /**
+         * Holds a weak reference to the target that was last seen (through the DisplayListCanvas
+         * in the last draw call), so that when animator set needs to start, we can add the animator
+         * to the last seen RenderNode target and start right away.
+         */
+        protected void recordLastSeenTarget(DisplayListCanvas canvas) {
+            final RenderNode node = RenderNodeAnimatorSetHelper.getTarget(canvas);
+            mLastSeenTarget = new WeakReference<RenderNode>(node);
+            // Add the animator to the list of animators on every draw
+            if (mInitialized || mPendingAnimationActions.size() > 0) {
+                if (useTarget(node)) {
+                    if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+                        Log.d(LOGTAG, "Target is set in the next frame");
+                    }
+                    for (int i = 0; i < mPendingAnimationActions.size(); i++) {
+                        handlePendingAction(mPendingAnimationActions.get(i));
+                    }
+                    mPendingAnimationActions.clear();
+                }
+            }
+        }
+
+        private void handlePendingAction(int pendingAnimationAction) {
+            if (pendingAnimationAction == START_ANIMATION) {
+                startAnimation();
+            } else if (pendingAnimationAction == REVERSE_ANIMATION) {
+                reverseAnimation();
+            } else if (pendingAnimationAction == RESET_ANIMATION) {
+                resetAnimation();
+            } else if (pendingAnimationAction == END_ANIMATION) {
+                endAnimation();
+            } else {
+                throw new UnsupportedOperationException("Animation action " +
+                        pendingAnimationAction + "is not supported");
+            }
+        }
+
+        private boolean useLastSeenTarget() {
+            if (mLastSeenTarget != null) {
+                final RenderNode target = mLastSeenTarget.get();
+                return useTarget(target);
+            }
+            return false;
+        }
+
+        private boolean useTarget(RenderNode target) {
+            if (target != null && target.isAttached()) {
+                target.registerVectorDrawableAnimator(this);
+                return true;
+            }
+            return false;
+        }
+
+        private void invalidateOwningView() {
+            mDrawable.invalidateSelf();
+        }
+
+        private void addPendingAction(int pendingAnimationAction) {
+            invalidateOwningView();
+            mPendingAnimationActions.add(pendingAnimationAction);
+        }
+
+        @Override
+        public void start() {
+            if (!mInitialized) {
+                return;
+            }
+
+            if (useLastSeenTarget()) {
+                if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+                    Log.d(LOGTAG, "Target is set. Starting VDAnimatorSet from java");
+                }
+                startAnimation();
+            } else {
+                addPendingAction(START_ANIMATION);
+            }
+        }
+
+        @Override
+        public void end() {
+            if (!mInitialized) {
+                return;
+            }
+
+            if (useLastSeenTarget()) {
+                endAnimation();
+            } else {
+                addPendingAction(END_ANIMATION);
+            }
+        }
+
+        @Override
+        public void reset() {
+            if (!mInitialized) {
+                return;
+            }
+
+            if (useLastSeenTarget()) {
+                resetAnimation();
+            } else {
+                addPendingAction(RESET_ANIMATION);
+            }
+        }
+
+        // Current (imperfect) Java AnimatorSet cannot be reversed when the set contains sequential
+        // animators or when the animator set has a start delay
+        @Override
+        public void reverse() {
+            if (!mIsReversible || !mInitialized) {
+                return;
+            }
+            if (useLastSeenTarget()) {
+                if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+                    Log.d(LOGTAG, "Target is set. Reversing VDAnimatorSet from java");
+                }
+                reverseAnimation();
+            } else {
+                addPendingAction(REVERSE_ANIMATION);
+            }
+        }
+
+        // This should only be called after animator has been added to the RenderNode target.
+        private void startAnimation() {
+            if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+                Log.w(LOGTAG, "starting animation on VD: " +
+                        ((VectorDrawable.VectorDrawableState) ((AnimatedVectorDrawableState)
+                                mDrawable.getConstantState()).mVectorDrawable.getConstantState())
+                                .mRootName);
+            }
+            mStarted = true;
+            nStart(mSetPtr, this, ++mLastListenerId);
+            invalidateOwningView();
+            if (mListener != null) {
+                mListener.onAnimationStart(null);
+            }
+        }
+
+        // This should only be called after animator has been added to the RenderNode target.
+        private void endAnimation() {
+            if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+                Log.w(LOGTAG, "ending animation on VD: " +
+                        ((VectorDrawable.VectorDrawableState) ((AnimatedVectorDrawableState)
+                                mDrawable.getConstantState()).mVectorDrawable.getConstantState())
+                                .mRootName);
+            }
+            nEnd(mSetPtr);
+            invalidateOwningView();
+        }
+
+        // This should only be called after animator has been added to the RenderNode target.
+        private void resetAnimation() {
+            nReset(mSetPtr);
+            invalidateOwningView();
+        }
+
+        // This should only be called after animator has been added to the RenderNode target.
+        private void reverseAnimation() {
+            mStarted = true;
+            nReverse(mSetPtr, this, ++mLastListenerId);
+            invalidateOwningView();
+            if (mListener != null) {
+                mListener.onAnimationStart(null);
+            }
+        }
+
+        public long getAnimatorNativePtr() {
+            return mSetPtr;
+        }
+
+        @Override
+        public boolean canReverse() {
+            return mIsReversible;
+        }
+
+        @Override
+        public boolean isStarted() {
+            return mStarted;
+        }
+
+        @Override
+        public boolean isRunning() {
+            if (!mInitialized) {
+                return false;
+            }
+            return mStarted;
+        }
+
+        @Override
+        public void setListener(AnimatorListener listener) {
+            mListener = listener;
+        }
+
+        @Override
+        public void removeListener(AnimatorListener listener) {
+            mListener = null;
+        }
+
+        @Override
+        public void onDraw(Canvas canvas) {
+            if (canvas.isHardwareAccelerated()) {
+                recordLastSeenTarget((DisplayListCanvas) canvas);
+            }
+        }
+
+        @Override
+        public boolean isInfinite() {
+            return mIsInfinite;
+        }
+
+        @Override
+        public void pause() {
+            // TODO: Implement pause for Animator On RT.
+        }
+
+        @Override
+        public void resume() {
+            // TODO: Implement resume for Animator On RT.
+        }
+
+        private void onAnimationEnd(int listenerId) {
+            if (listenerId != mLastListenerId) {
+                return;
+            }
+            if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+                Log.d(LOGTAG, "on finished called from native");
+            }
+            mStarted = false;
+            // Invalidate in the end of the animation to make sure the data in
+            // RT thread is synced back to UI thread.
+            invalidateOwningView();
+            if (mListener != null) {
+                mListener.onAnimationEnd(null);
+            }
+        }
+
+        // onFinished: should be called from native
+        private static void callOnFinished(VectorDrawableAnimatorRT set, int id) {
+            set.onAnimationEnd(id);
+        }
+
+        private void transferPendingActions(VectorDrawableAnimator animatorSet) {
+            for (int i = 0; i < mPendingAnimationActions.size(); i++) {
+                int pendingAction = mPendingAnimationActions.get(i);
+                if (pendingAction == START_ANIMATION) {
+                    animatorSet.start();
+                } else if (pendingAction == END_ANIMATION) {
+                    animatorSet.end();
+                } else if (pendingAction == REVERSE_ANIMATION) {
+                    animatorSet.reverse();
+                } else if (pendingAction == RESET_ANIMATION) {
+                    animatorSet.reset();
+                } else {
+                    throw new UnsupportedOperationException("Animation action " +
+                            pendingAction + "is not supported");
+                }
+            }
+            mPendingAnimationActions.clear();
+        }
+    }
+
+    private static native long nCreateAnimatorSet();
+    private static native void nSetVectorDrawableTarget(long animatorPtr, long vectorDrawablePtr);
+    private static native void nAddAnimator(long setPtr, long propertyValuesHolder,
+            long nativeInterpolator, long startDelay, long duration, int repeatCount,
+            int repeatMode);
+    private static native void nSetPropertyHolderData(long nativePtr, float[] data, int length);
+    private static native void nSetPropertyHolderData(long nativePtr, int[] data, int length);
+    private static native void nStart(long animatorSetPtr, VectorDrawableAnimatorRT set, int id);
+    private static native void nReverse(long animatorSetPtr, VectorDrawableAnimatorRT set, int id);
+
+    // ------------- @FastNative -------------------
+
+    @FastNative
+    private static native long nCreateGroupPropertyHolder(long nativePtr, int propertyId,
+            float startValue, float endValue);
+    @FastNative
+    private static native long nCreatePathDataPropertyHolder(long nativePtr, long startValuePtr,
+            long endValuePtr);
+    @FastNative
+    private static native long nCreatePathColorPropertyHolder(long nativePtr, int propertyId,
+            int startValue, int endValue);
+    @FastNative
+    private static native long nCreatePathPropertyHolder(long nativePtr, int propertyId,
+            float startValue, float endValue);
+    @FastNative
+    private static native long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue,
+            float endValue);
+    @FastNative
+    private static native void nEnd(long animatorSetPtr);
+    @FastNative
+    private static native void nReset(long animatorSetPtr);
+}
diff --git a/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java b/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java
new file mode 100644
index 0000000..ad2c564
--- /dev/null
+++ b/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2016 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 com.android.ide.common.rendering.api.LayoutLog;
+import com.android.internal.view.animation.NativeInterpolatorFactoryHelper_Delegate;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.drawable.AnimatedVectorDrawable.VectorDrawableAnimatorRT;
+import android.graphics.drawable.VectorDrawable_Delegate.VFullPath_Delegate;
+import android.graphics.drawable.VectorDrawable_Delegate.VGroup_Delegate;
+import android.graphics.drawable.VectorDrawable_Delegate.VNativeObject;
+import android.graphics.drawable.VectorDrawable_Delegate.VPathRenderer_Delegate;
+
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link
+ * AnimatedVectorDrawable}
+ * <p>
+ * Through the layoutlib_create tool, the original  methods of AnimatedVectorDrawable have been
+ * replaced by calls to methods of the same name in this delegate class.
+ */
+@SuppressWarnings("unused")
+public class AnimatedVectorDrawable_Delegate {
+    private static DelegateManager<AnimatorSetHolder> sAnimatorSets = new
+            DelegateManager<>(AnimatorSetHolder.class);
+    private static DelegateManager<PropertySetter> sHolders = new
+            DelegateManager<>(PropertySetter.class);
+
+
+    @LayoutlibDelegate
+    /*package*/ static long nCreateAnimatorSet() {
+        return sAnimatorSets.addNewDelegate(new AnimatorSetHolder());
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetVectorDrawableTarget(long animatorPtr, long vectorDrawablePtr) {
+        // TODO: implement
+    }
+    @LayoutlibDelegate
+    /*package*/ static void nAddAnimator(long setPtr, long propertyValuesHolder,
+            long nativeInterpolator, long startDelay, long duration, int repeatCount,
+            int repeatMode) {
+        PropertySetter holder = sHolders.getDelegate(propertyValuesHolder);
+        if (holder == null || holder.getValues() == null) {
+            return;
+        }
+
+        ObjectAnimator animator = new ObjectAnimator();
+        animator.setValues(holder.getValues());
+        animator.setInterpolator(
+                NativeInterpolatorFactoryHelper_Delegate.getDelegate(nativeInterpolator));
+        animator.setStartDelay(startDelay);
+        animator.setDuration(duration);
+        animator.setRepeatCount(repeatCount);
+        animator.setRepeatMode(repeatMode);
+        animator.setTarget(holder);
+        animator.setPropertyName(holder.getValues().getPropertyName());
+
+        AnimatorSetHolder set = sAnimatorSets.getDelegate(setPtr);
+        assert set != null;
+        set.addAnimator(animator);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nCreateGroupPropertyHolder(long nativePtr, int propertyId,
+            float startValue, float endValue) {
+        VGroup_Delegate group = VNativeObject.getDelegate(nativePtr);
+        Consumer<Float> setter = group.getPropertySetter(propertyId);
+
+        return sHolders.addNewDelegate(FloatPropertySetter.of(setter, startValue,
+                endValue));
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nCreatePathDataPropertyHolder(long nativePtr, long startValuePtr,
+            long endValuePtr) {
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, "AnimatedVectorDrawable path " +
+                "animations are not supported.", null, null);
+        return 0;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nCreatePathColorPropertyHolder(long nativePtr, int propertyId,
+            int startValue, int endValue) {
+        VFullPath_Delegate path = VNativeObject.getDelegate(nativePtr);
+        Consumer<Integer> setter = path.getIntPropertySetter(propertyId);
+
+        return sHolders.addNewDelegate(IntPropertySetter.of(setter, startValue,
+                endValue));
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nCreatePathPropertyHolder(long nativePtr, int propertyId,
+            float startValue, float endValue) {
+        VFullPath_Delegate path = VNativeObject.getDelegate(nativePtr);
+        Consumer<Float> setter = path.getFloatPropertySetter(propertyId);
+
+        return sHolders.addNewDelegate(FloatPropertySetter.of(setter, startValue,
+                endValue));
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue,
+            float endValue) {
+        VPathRenderer_Delegate renderer = VNativeObject.getDelegate(nativePtr);
+
+        return sHolders.addNewDelegate(FloatPropertySetter.of(renderer::setRootAlpha,
+                startValue,
+                endValue));
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetPropertyHolderData(long nativePtr, float[] data, int length) {
+        PropertySetter setter = sHolders.getDelegate(nativePtr);
+        assert setter != null;
+
+        setter.setValues(data);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetPropertyHolderData(long nativePtr, int[] data, int length) {
+        PropertySetter setter = sHolders.getDelegate(nativePtr);
+        assert setter != null;
+
+        setter.setValues(data);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nStart(long animatorSetPtr, VectorDrawableAnimatorRT set, int id) {
+        AnimatorSetHolder animatorSet = sAnimatorSets.getDelegate(animatorSetPtr);
+        assert animatorSet != null;
+
+        animatorSet.start();
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nReverse(long animatorSetPtr, VectorDrawableAnimatorRT set, int id) {
+        AnimatorSetHolder animatorSet = sAnimatorSets.getDelegate(animatorSetPtr);
+        assert animatorSet != null;
+
+        animatorSet.reverse();
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nEnd(long animatorSetPtr) {
+        AnimatorSetHolder animatorSet = sAnimatorSets.getDelegate(animatorSetPtr);
+        assert animatorSet != null;
+
+        animatorSet.end();
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nReset(long animatorSetPtr) {
+        AnimatorSetHolder animatorSet = sAnimatorSets.getDelegate(animatorSetPtr);
+        assert animatorSet != null;
+
+        animatorSet.end();
+        animatorSet.start();
+    }
+
+    private static class AnimatorSetHolder {
+        private ArrayList<Animator> mAnimators = new ArrayList<>();
+        private AnimatorSet mAnimatorSet = null;
+
+        private void addAnimator(@NonNull Animator animator) {
+            mAnimators.add(animator);
+        }
+
+        private void ensureAnimatorSet() {
+            if (mAnimatorSet == null) {
+                mAnimatorSet = new AnimatorSet();
+                mAnimatorSet.playTogether(mAnimators);
+            }
+        }
+
+        private void start() {
+            ensureAnimatorSet();
+
+            mAnimatorSet.start();
+        }
+
+        private void end() {
+            mAnimatorSet.end();
+        }
+
+        private void reset() {
+            end();
+            start();
+        }
+
+        private void reverse() {
+            mAnimatorSet.reverse();
+        }
+    }
+
+    /**
+     * Class that allows setting a value and holds the range of values for the given property.
+     *
+     * @param <T> the type of the property
+     */
+    private static class PropertySetter<T> {
+        final Consumer<T> mValueSetter;
+        private PropertyValuesHolder mValues;
+
+        private PropertySetter(@NonNull Consumer<T> valueSetter) {
+            mValueSetter = valueSetter;
+        }
+
+        /**
+         * Method to set an {@link Integer} value for this property. The default implementation of
+         * this method doesn't do anything. This method is accessed via reflection by the
+         * PropertyValuesHolder.
+         */
+        public void setIntValue(Integer value) {
+        }
+
+        /**
+         * Method to set an {@link Integer} value for this property. The default implementation of
+         * this method doesn't do anything. This method is accessed via reflection by the
+         * PropertyValuesHolder.
+         */
+        public void setFloatValue(Float value) {
+        }
+
+        void setValues(float... values) {
+            mValues = PropertyValuesHolder.ofFloat("floatValue", values);
+        }
+
+        @Nullable
+        PropertyValuesHolder getValues() {
+            return mValues;
+        }
+
+        void setValues(int... values) {
+            mValues = PropertyValuesHolder.ofInt("intValue", values);
+        }
+    }
+
+    private static class IntPropertySetter extends PropertySetter<Integer> {
+        private IntPropertySetter(Consumer<Integer> valueSetter) {
+            super(valueSetter);
+        }
+
+        private static PropertySetter of(Consumer<Integer> valueSetter, int... values) {
+            PropertySetter setter = new IntPropertySetter(valueSetter);
+            setter.setValues(values);
+
+            return setter;
+        }
+
+        public void setIntValue(Integer value) {
+            mValueSetter.accept(value);
+        }
+    }
+
+    private static class FloatPropertySetter extends PropertySetter<Float> {
+        private FloatPropertySetter(Consumer<Float> valueSetter) {
+            super(valueSetter);
+        }
+
+        private static PropertySetter of(Consumer<Float> valueSetter, float... values) {
+            PropertySetter setter = new FloatPropertySetter(valueSetter);
+            setter.setValues(values);
+
+            return setter;
+        }
+
+        public void setFloatValue(Float value) {
+            mValueSetter.accept(value);
+        }
+
+    }
+}
diff --git a/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java b/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java
new file mode 100644
index 0000000..fc848d9
--- /dev/null
+++ b/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2016 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 com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.graphics.Canvas;
+import android.graphics.drawable.AnimatedVectorDrawable.VectorDrawableAnimatorRT;
+
+public class AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate {
+    @LayoutlibDelegate
+    /*package*/ static boolean useLastSeenTarget(VectorDrawableAnimatorRT thisDrawableAnimator) {
+        return true;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void onDraw(VectorDrawableAnimatorRT thisDrawableAnimator, Canvas canvas) {
+        // Do not attempt to record as we are not using a DisplayListCanvas
+    }
+}
diff --git a/android/graphics/drawable/AnimationDrawable.java b/android/graphics/drawable/AnimationDrawable.java
new file mode 100644
index 0000000..0fd1741
--- /dev/null
+++ b/android/graphics/drawable/AnimationDrawable.java
@@ -0,0 +1,444 @@
+/*
+ * 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 com.android.internal.R;
+
+import java.io.IOException;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.annotation.NonNull;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.Resources.Theme;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+
+/**
+ * An object used to create frame-by-frame animations, defined by a series of
+ * Drawable objects, which can be used as a View object's background.
+ * <p>
+ * The simplest way to create a frame-by-frame animation is to define the
+ * animation in an XML file, placed in the res/drawable/ folder, and set it as
+ * the background to a View object. Then, call {@link #start()} to run the
+ * animation.
+ * <p>
+ * An AnimationDrawable defined in XML consists of a single
+ * {@code <animation-list>} element and a series of nested
+ * {@code <item>} tags. Each item defines a frame of the animation. See
+ * the example below.
+ * <p>
+ * spin_animation.xml file in res/drawable/ folder:
+ * <pre>
+ * &lt;!-- Animation frames are wheel0.png through wheel5.png
+ *     files inside the res/drawable/ folder --&gt;
+ * &lt;animation-list android:id=&quot;@+id/selected&quot; android:oneshot=&quot;false&quot;&gt;
+ *    &lt;item android:drawable=&quot;@drawable/wheel0&quot; android:duration=&quot;50&quot; /&gt;
+ *    &lt;item android:drawable=&quot;@drawable/wheel1&quot; android:duration=&quot;50&quot; /&gt;
+ *    &lt;item android:drawable=&quot;@drawable/wheel2&quot; android:duration=&quot;50&quot; /&gt;
+ *    &lt;item android:drawable=&quot;@drawable/wheel3&quot; android:duration=&quot;50&quot; /&gt;
+ *    &lt;item android:drawable=&quot;@drawable/wheel4&quot; android:duration=&quot;50&quot; /&gt;
+ *    &lt;item android:drawable=&quot;@drawable/wheel5&quot; android:duration=&quot;50&quot; /&gt;
+ * &lt;/animation-list&gt;</pre>
+ * <p>
+ * Here is the code to load and play this animation.
+ * <pre>
+ * // Load the ImageView that will host the animation and
+ * // set its background to our AnimationDrawable XML resource.
+ * ImageView img = (ImageView)findViewById(R.id.spinning_wheel_image);
+ * img.setBackgroundResource(R.drawable.spin_animation);
+ *
+ * // Get the background, which has been compiled to an AnimationDrawable object.
+ * AnimationDrawable frameAnimation = (AnimationDrawable) img.getBackground();
+ *
+ * // Start the animation (looped playback by default).
+ * frameAnimation.start();
+ * </pre>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about animating with {@code AnimationDrawable}, read the
+ * <a href="{@docRoot}guide/topics/graphics/drawable-animation.html">Drawable Animation</a>
+ * developer guide.</p>
+ * </div>
+ *
+ * @attr ref android.R.styleable#AnimationDrawable_visible
+ * @attr ref android.R.styleable#AnimationDrawable_variablePadding
+ * @attr ref android.R.styleable#AnimationDrawable_oneshot
+ * @attr ref android.R.styleable#AnimationDrawableItem_duration
+ * @attr ref android.R.styleable#AnimationDrawableItem_drawable
+ */
+public class AnimationDrawable extends DrawableContainer implements Runnable, Animatable {
+    private AnimationState mAnimationState;
+
+    /** The current frame, ranging from 0 to {@link #mAnimationState#getChildCount() - 1} */
+    private int mCurFrame = 0;
+
+    /** Whether the drawable has an animation callback posted. */
+    private boolean mRunning;
+
+    /** Whether the drawable should animate when visible. */
+    private boolean mAnimating;
+
+    private boolean mMutated;
+
+    public AnimationDrawable() {
+        this(null, null);
+    }
+
+    /**
+     * Sets whether this AnimationDrawable is visible.
+     * <p>
+     * When the drawable becomes invisible, it will pause its animation. A subsequent change to
+     * visible with <code>restart</code> set to true will restart the animation from the
+     * first frame. If <code>restart</code> is false, the drawable will resume from the most recent
+     * frame. If the drawable has already reached the last frame, it will then loop back to the
+     * first frame, unless it's a one shot drawable (set through {@link #setOneShot(boolean)}),
+     * in which case, it will stay on the last frame.
+     *
+     * @param visible true if visible, false otherwise
+     * @param restart when visible, true to force the animation to restart
+     *                from the first frame
+     * @return true if the new visibility is different than its previous state
+     */
+    @Override
+    public boolean setVisible(boolean visible, boolean restart) {
+        final boolean changed = super.setVisible(visible, restart);
+        if (visible) {
+            if (restart || changed) {
+                boolean startFromZero = restart || (!mRunning && !mAnimationState.mOneShot) ||
+                        mCurFrame >= mAnimationState.getChildCount();
+                setFrame(startFromZero ? 0 : mCurFrame, true, mAnimating);
+            }
+        } else {
+            unscheduleSelf(this);
+        }
+        return changed;
+    }
+
+    /**
+     * Starts the animation from the first frame, looping if necessary. This method has no effect
+     * if the animation is running.
+     * <p>
+     * <strong>Note:</strong> Do not call this in the
+     * {@link android.app.Activity#onCreate} method of your activity, because
+     * the {@link AnimationDrawable} is not yet fully attached to the window.
+     * If you want to play the animation immediately without requiring
+     * interaction, then you might want to call it from the
+     * {@link android.app.Activity#onWindowFocusChanged} method in your
+     * activity, which will get called when Android brings your window into
+     * focus.
+     *
+     * @see #isRunning()
+     * @see #stop()
+     */
+    @Override
+    public void start() {
+        mAnimating = true;
+
+        if (!isRunning()) {
+            // Start from 0th frame.
+            setFrame(0, false, mAnimationState.getChildCount() > 1
+                    || !mAnimationState.mOneShot);
+        }
+    }
+
+    /**
+     * Stops the animation at the current frame. This method has no effect if the animation is not
+     * running.
+     *
+     * @see #isRunning()
+     * @see #start()
+     */
+    @Override
+    public void stop() {
+        mAnimating = false;
+
+        if (isRunning()) {
+            mCurFrame = 0;
+            unscheduleSelf(this);
+        }
+    }
+
+    /**
+     * Indicates whether the animation is currently running or not.
+     *
+     * @return true if the animation is running, false otherwise
+     */
+    @Override
+    public boolean isRunning() {
+        return mRunning;
+    }
+
+    /**
+     * This method exists for implementation purpose only and should not be
+     * called directly. Invoke {@link #start()} instead.
+     *
+     * @see #start()
+     */
+    @Override
+    public void run() {
+        nextFrame(false);
+    }
+
+    @Override
+    public void unscheduleSelf(Runnable what) {
+        mRunning = false;
+        super.unscheduleSelf(what);
+    }
+
+    /**
+     * @return The number of frames in the animation
+     */
+    public int getNumberOfFrames() {
+        return mAnimationState.getChildCount();
+    }
+
+    /**
+     * @return The Drawable at the specified frame index
+     */
+    public Drawable getFrame(int index) {
+        return mAnimationState.getChild(index);
+    }
+
+    /**
+     * @return The duration in milliseconds of the frame at the
+     *         specified index
+     */
+    public int getDuration(int i) {
+        return mAnimationState.mDurations[i];
+    }
+
+    /**
+     * @return True of the animation will play once, false otherwise
+     */
+    public boolean isOneShot() {
+        return mAnimationState.mOneShot;
+    }
+
+    /**
+     * Sets whether the animation should play once or repeat.
+     *
+     * @param oneShot Pass true if the animation should only play once
+     */
+    public void setOneShot(boolean oneShot) {
+        mAnimationState.mOneShot = oneShot;
+    }
+
+    /**
+     * Adds a frame to the animation
+     *
+     * @param frame The frame to add
+     * @param duration How long in milliseconds the frame should appear
+     */
+    public void addFrame(@NonNull Drawable frame, int duration) {
+        mAnimationState.addFrame(frame, duration);
+        if (!mRunning) {
+            setFrame(0, true, false);
+        }
+    }
+
+    private void nextFrame(boolean unschedule) {
+        int nextFrame = mCurFrame + 1;
+        final int numFrames = mAnimationState.getChildCount();
+        final boolean isLastFrame = mAnimationState.mOneShot && nextFrame >= (numFrames - 1);
+
+        // Loop if necessary. One-shot animations should never hit this case.
+        if (!mAnimationState.mOneShot && nextFrame >= numFrames) {
+            nextFrame = 0;
+        }
+
+        setFrame(nextFrame, unschedule, !isLastFrame);
+    }
+
+    private void setFrame(int frame, boolean unschedule, boolean animate) {
+        if (frame >= mAnimationState.getChildCount()) {
+            return;
+        }
+        mAnimating = animate;
+        mCurFrame = frame;
+        selectDrawable(frame);
+        if (unschedule || animate) {
+            unscheduleSelf(this);
+        }
+        if (animate) {
+            // Unscheduling may have clobbered these values; restore them
+            mCurFrame = frame;
+            mRunning = true;
+            scheduleSelf(this, SystemClock.uptimeMillis() + mAnimationState.mDurations[frame]);
+        }
+    }
+
+    @Override
+    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+            throws XmlPullParserException, IOException {
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.AnimationDrawable);
+        super.inflateWithAttributes(r, parser, a, R.styleable.AnimationDrawable_visible);
+        updateStateFromTypedArray(a);
+        updateDensity(r);
+        a.recycle();
+
+        inflateChildElements(r, parser, attrs, theme);
+
+        setFrame(0, true, false);
+    }
+
+    private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
+            Theme theme) throws XmlPullParserException, IOException {
+        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;
+            }
+
+            final TypedArray a = obtainAttributes(r, theme, attrs,
+                    R.styleable.AnimationDrawableItem);
+
+            final int duration = a.getInt(R.styleable.AnimationDrawableItem_duration, -1);
+            if (duration < 0) {
+                throw new XmlPullParserException(parser.getPositionDescription()
+                        + ": <item> tag requires a 'duration' attribute");
+            }
+
+            Drawable dr = a.getDrawable(R.styleable.AnimationDrawableItem_drawable);
+
+            a.recycle();
+
+            if (dr == null) {
+                while ((type=parser.next()) == XmlPullParser.TEXT) {
+                    // Empty
+                }
+                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, theme);
+            }
+
+            mAnimationState.addFrame(dr, duration);
+            if (dr != null) {
+                dr.setCallback(this);
+            }
+        }
+    }
+
+    private void updateStateFromTypedArray(TypedArray a) {
+        mAnimationState.mVariablePadding = a.getBoolean(
+                R.styleable.AnimationDrawable_variablePadding, mAnimationState.mVariablePadding);
+
+        mAnimationState.mOneShot = a.getBoolean(
+                R.styleable.AnimationDrawable_oneshot, mAnimationState.mOneShot);
+    }
+
+    @Override
+    @NonNull
+    public Drawable mutate() {
+        if (!mMutated && super.mutate() == this) {
+            mAnimationState.mutate();
+            mMutated = true;
+        }
+        return this;
+    }
+
+    @Override
+    AnimationState cloneConstantState() {
+        return new AnimationState(mAnimationState, this, null);
+    }
+
+    /**
+     * @hide
+     */
+    public void clearMutated() {
+        super.clearMutated();
+        mMutated = false;
+    }
+
+    private final static class AnimationState extends DrawableContainerState {
+        private int[] mDurations;
+        private boolean mOneShot = false;
+
+        AnimationState(AnimationState orig, AnimationDrawable owner, Resources res) {
+            super(orig, owner, res);
+
+            if (orig != null) {
+                mDurations = orig.mDurations;
+                mOneShot = orig.mOneShot;
+            } else {
+                mDurations = new int[getCapacity()];
+                mOneShot = false;
+            }
+        }
+
+        private void mutate() {
+            mDurations = mDurations.clone();
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return new AnimationDrawable(this, null);
+        }
+
+        @Override
+        public Drawable newDrawable(Resources res) {
+            return new AnimationDrawable(this, res);
+        }
+
+        public void addFrame(Drawable dr, int dur) {
+            // Do not combine the following. The array index must be evaluated before
+            // the array is accessed because super.addChild(dr) has a side effect on mDurations.
+            int pos = super.addChild(dr);
+            mDurations[pos] = dur;
+        }
+
+        @Override
+        public void growArray(int oldSize, int newSize) {
+            super.growArray(oldSize, newSize);
+            int[] newDurations = new int[newSize];
+            System.arraycopy(mDurations, 0, newDurations, 0, oldSize);
+            mDurations = newDurations;
+        }
+    }
+
+    @Override
+    protected void setConstantState(@NonNull DrawableContainerState state) {
+        super.setConstantState(state);
+
+        if (state instanceof AnimationState) {
+            mAnimationState = (AnimationState) state;
+        }
+    }
+
+    private AnimationDrawable(AnimationState state, Resources res) {
+        final AnimationState as = new AnimationState(state, this, res);
+        setConstantState(as);
+        if (state != null) {
+            setFrame(0, true, false);
+        }
+    }
+}
+
diff --git a/android/graphics/drawable/BitmapDrawable.java b/android/graphics/drawable/BitmapDrawable.java
new file mode 100644
index 0000000..e3740e3
--- /dev/null
+++ b/android/graphics/drawable/BitmapDrawable.java
@@ -0,0 +1,1036 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.content.pm.ActivityInfo.Config;
+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.BitmapFactory;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Insets;
+import android.graphics.Matrix;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.graphics.Xfermode;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.LayoutDirection;
+import android.util.TypedValue;
+import android.view.Gravity;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A Drawable that wraps a bitmap and can be tiled, stretched, or aligned. You can create a
+ * BitmapDrawable from a file path, an input stream, through XML inflation, or from
+ * a {@link android.graphics.Bitmap} object.
+ * <p>It can be defined in an XML file with the <code>&lt;bitmap></code> element.  For more
+ * information, see the guide to <a
+ * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
+ * <p>
+ * Also see the {@link android.graphics.Bitmap} class, which handles the management and
+ * transformation of raw bitmap graphics, and should be used when drawing to a
+ * {@link android.graphics.Canvas}.
+ * </p>
+ *
+ * @attr ref android.R.styleable#BitmapDrawable_src
+ * @attr ref android.R.styleable#BitmapDrawable_antialias
+ * @attr ref android.R.styleable#BitmapDrawable_filter
+ * @attr ref android.R.styleable#BitmapDrawable_dither
+ * @attr ref android.R.styleable#BitmapDrawable_gravity
+ * @attr ref android.R.styleable#BitmapDrawable_mipMap
+ * @attr ref android.R.styleable#BitmapDrawable_tileMode
+ */
+public class BitmapDrawable extends Drawable {
+    private static final int DEFAULT_PAINT_FLAGS =
+            Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG;
+
+    // Constants for {@link android.R.styleable#BitmapDrawable_tileMode}.
+    private static final int TILE_MODE_UNDEFINED = -2;
+    private static final int TILE_MODE_DISABLED = -1;
+    private static final int TILE_MODE_CLAMP = 0;
+    private static final int TILE_MODE_REPEAT = 1;
+    private static final int TILE_MODE_MIRROR = 2;
+
+    private final Rect mDstRect = new Rect();   // #updateDstRectAndInsetsIfDirty() sets this
+
+    private BitmapState mBitmapState;
+    private PorterDuffColorFilter mTintFilter;
+
+    private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
+
+    private boolean mDstRectAndInsetsDirty = true;
+    private boolean mMutated;
+
+     // These are scaled to match the target density.
+    private int mBitmapWidth;
+    private int mBitmapHeight;
+
+    /** Optical insets due to gravity. */
+    private Insets mOpticalInsets = Insets.NONE;
+
+    // Mirroring matrix for using with Shaders
+    private Matrix mMirrorMatrix;
+
+    /**
+     * Create an empty drawable, not dealing with density.
+     * @deprecated Use {@link #BitmapDrawable(android.content.res.Resources, android.graphics.Bitmap)}
+     * instead to specify a bitmap to draw with and ensure the correct density is set.
+     */
+    @Deprecated
+    public BitmapDrawable() {
+        mBitmapState = new BitmapState((Bitmap) null);
+    }
+
+    /**
+     * Create an empty drawable, setting initial target density based on
+     * the display metrics of the resources.
+     *
+     * @deprecated Use {@link #BitmapDrawable(android.content.res.Resources, android.graphics.Bitmap)}
+     * instead to specify a bitmap to draw with.
+     */
+    @SuppressWarnings("unused")
+    @Deprecated
+    public BitmapDrawable(Resources res) {
+        mBitmapState = new BitmapState((Bitmap) null);
+        mBitmapState.mTargetDensity = mTargetDensity;
+    }
+
+    /**
+     * Create drawable from a bitmap, not dealing with density.
+     * @deprecated Use {@link #BitmapDrawable(Resources, Bitmap)} to ensure
+     * that the drawable has correctly set its target density.
+     */
+    @Deprecated
+    public BitmapDrawable(Bitmap bitmap) {
+        this(new BitmapState(bitmap), null);
+    }
+
+    /**
+     * Create drawable from a bitmap, setting initial target density based on
+     * the display metrics of the resources.
+     */
+    public BitmapDrawable(Resources res, Bitmap bitmap) {
+        this(new BitmapState(bitmap), res);
+        mBitmapState.mTargetDensity = mTargetDensity;
+    }
+
+    /**
+     * Create a drawable by opening a given file path and decoding the bitmap.
+     * @deprecated Use {@link #BitmapDrawable(Resources, String)} to ensure
+     * that the drawable has correctly set its target density.
+     */
+    @Deprecated
+    public BitmapDrawable(String filepath) {
+        this(new BitmapState(BitmapFactory.decodeFile(filepath)), null);
+        if (mBitmapState.mBitmap == null) {
+            android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
+        }
+    }
+
+    /**
+     * Create a drawable by opening a given file path and decoding the bitmap.
+     */
+    @SuppressWarnings("unused")
+    public BitmapDrawable(Resources res, String filepath) {
+        this(new BitmapState(BitmapFactory.decodeFile(filepath)), null);
+        mBitmapState.mTargetDensity = mTargetDensity;
+        if (mBitmapState.mBitmap == null) {
+            android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
+        }
+    }
+
+    /**
+     * Create a drawable by decoding a bitmap from the given input stream.
+     * @deprecated Use {@link #BitmapDrawable(Resources, java.io.InputStream)} to ensure
+     * that the drawable has correctly set its target density.
+     */
+    @Deprecated
+    public BitmapDrawable(java.io.InputStream is) {
+        this(new BitmapState(BitmapFactory.decodeStream(is)), null);
+        if (mBitmapState.mBitmap == null) {
+            android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
+        }
+    }
+
+    /**
+     * Create a drawable by decoding a bitmap from the given input stream.
+     */
+    @SuppressWarnings("unused")
+    public BitmapDrawable(Resources res, java.io.InputStream is) {
+        this(new BitmapState(BitmapFactory.decodeStream(is)), null);
+        mBitmapState.mTargetDensity = mTargetDensity;
+        if (mBitmapState.mBitmap == null) {
+            android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
+        }
+    }
+
+    /**
+     * Returns the paint used to render this drawable.
+     */
+    public final Paint getPaint() {
+        return mBitmapState.mPaint;
+    }
+
+    /**
+     * Returns the bitmap used by this drawable to render. May be null.
+     */
+    public final Bitmap getBitmap() {
+        return mBitmapState.mBitmap;
+    }
+
+    private void computeBitmapSize() {
+        final Bitmap bitmap = mBitmapState.mBitmap;
+        if (bitmap != null) {
+            mBitmapWidth = bitmap.getScaledWidth(mTargetDensity);
+            mBitmapHeight = bitmap.getScaledHeight(mTargetDensity);
+        } else {
+            mBitmapWidth = mBitmapHeight = -1;
+        }
+    }
+
+    /** @hide */
+    public void setBitmap(Bitmap bitmap) {
+        if (mBitmapState.mBitmap != bitmap) {
+            mBitmapState.mBitmap = bitmap;
+            computeBitmapSize();
+            invalidateSelf();
+        }
+    }
+
+    /**
+     * Set the density scale at which this drawable will be rendered. This
+     * method assumes the drawable will be rendered at the same density as the
+     * specified canvas.
+     *
+     * @param canvas The Canvas from which the density scale must be obtained.
+     *
+     * @see android.graphics.Bitmap#setDensity(int)
+     * @see android.graphics.Bitmap#getDensity()
+     */
+    public void setTargetDensity(Canvas canvas) {
+        setTargetDensity(canvas.getDensity());
+    }
+
+    /**
+     * Set the density scale at which this drawable will be rendered.
+     *
+     * @param metrics The DisplayMetrics indicating the density scale for this drawable.
+     *
+     * @see android.graphics.Bitmap#setDensity(int)
+     * @see android.graphics.Bitmap#getDensity()
+     */
+    public void setTargetDensity(DisplayMetrics metrics) {
+        setTargetDensity(metrics.densityDpi);
+    }
+
+    /**
+     * Set the density at which this drawable will be rendered.
+     *
+     * @param density The density scale for this drawable.
+     *
+     * @see android.graphics.Bitmap#setDensity(int)
+     * @see android.graphics.Bitmap#getDensity()
+     */
+    public void setTargetDensity(int density) {
+        if (mTargetDensity != density) {
+            mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density;
+            if (mBitmapState.mBitmap != null) {
+                computeBitmapSize();
+            }
+            invalidateSelf();
+        }
+    }
+
+    /** Get the gravity used to position/stretch the bitmap within its bounds.
+     * See android.view.Gravity
+     * @return the gravity applied to the bitmap
+     */
+    public int getGravity() {
+        return mBitmapState.mGravity;
+    }
+
+    /** Set the gravity used to position/stretch the bitmap within its bounds.
+        See android.view.Gravity
+     * @param gravity the gravity
+     */
+    public void setGravity(int gravity) {
+        if (mBitmapState.mGravity != gravity) {
+            mBitmapState.mGravity = gravity;
+            mDstRectAndInsetsDirty = true;
+            invalidateSelf();
+        }
+    }
+
+    /**
+     * Enables or disables the mipmap hint for this drawable's bitmap.
+     * See {@link Bitmap#setHasMipMap(boolean)} for more information.
+     *
+     * If the bitmap is null calling this method has no effect.
+     *
+     * @param mipMap True if the bitmap should use mipmaps, false otherwise.
+     *
+     * @see #hasMipMap()
+     */
+    public void setMipMap(boolean mipMap) {
+        if (mBitmapState.mBitmap != null) {
+            mBitmapState.mBitmap.setHasMipMap(mipMap);
+            invalidateSelf();
+        }
+    }
+
+    /**
+     * Indicates whether the mipmap hint is enabled on this drawable's bitmap.
+     *
+     * @return True if the mipmap hint is set, false otherwise. If the bitmap
+     *         is null, this method always returns false.
+     *
+     * @see #setMipMap(boolean)
+     * @attr ref android.R.styleable#BitmapDrawable_mipMap
+     */
+    public boolean hasMipMap() {
+        return mBitmapState.mBitmap != null && mBitmapState.mBitmap.hasMipMap();
+    }
+
+    /**
+     * Enables or disables anti-aliasing for this drawable. Anti-aliasing affects
+     * the edges of the bitmap only so it applies only when the drawable is rotated.
+     *
+     * @param aa True if the bitmap should be anti-aliased, false otherwise.
+     *
+     * @see #hasAntiAlias()
+     */
+    public void setAntiAlias(boolean aa) {
+        mBitmapState.mPaint.setAntiAlias(aa);
+        invalidateSelf();
+    }
+
+    /**
+     * Indicates whether anti-aliasing is enabled for this drawable.
+     *
+     * @return True if anti-aliasing is enabled, false otherwise.
+     *
+     * @see #setAntiAlias(boolean)
+     */
+    public boolean hasAntiAlias() {
+        return mBitmapState.mPaint.isAntiAlias();
+    }
+
+    @Override
+    public void setFilterBitmap(boolean filter) {
+        mBitmapState.mPaint.setFilterBitmap(filter);
+        invalidateSelf();
+    }
+
+    @Override
+    public boolean isFilterBitmap() {
+        return mBitmapState.mPaint.isFilterBitmap();
+    }
+
+    @Override
+    public void setDither(boolean dither) {
+        mBitmapState.mPaint.setDither(dither);
+        invalidateSelf();
+    }
+
+    /**
+     * Indicates the repeat behavior of this drawable on the X axis.
+     *
+     * @return {@link android.graphics.Shader.TileMode#CLAMP} if the bitmap does not repeat,
+     *         {@link android.graphics.Shader.TileMode#REPEAT} or
+     *         {@link android.graphics.Shader.TileMode#MIRROR} otherwise.
+     */
+    public Shader.TileMode getTileModeX() {
+        return mBitmapState.mTileModeX;
+    }
+
+    /**
+     * Indicates the repeat behavior of this drawable on the Y axis.
+     *
+     * @return {@link android.graphics.Shader.TileMode#CLAMP} if the bitmap does not repeat,
+     *         {@link android.graphics.Shader.TileMode#REPEAT} or
+     *         {@link android.graphics.Shader.TileMode#MIRROR} otherwise.
+     */
+    public Shader.TileMode getTileModeY() {
+        return mBitmapState.mTileModeY;
+    }
+
+    /**
+     * Sets the repeat behavior of this drawable on the X axis. By default, the drawable
+     * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or
+     * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled)
+     * if the bitmap is smaller than this drawable.
+     *
+     * @param mode The repeat mode for this drawable.
+     *
+     * @see #setTileModeY(android.graphics.Shader.TileMode)
+     * @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode)
+     * @attr ref android.R.styleable#BitmapDrawable_tileModeX
+     */
+    public void setTileModeX(Shader.TileMode mode) {
+        setTileModeXY(mode, mBitmapState.mTileModeY);
+    }
+
+    /**
+     * Sets the repeat behavior of this drawable on the Y axis. By default, the drawable
+     * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or
+     * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled)
+     * if the bitmap is smaller than this drawable.
+     *
+     * @param mode The repeat mode for this drawable.
+     *
+     * @see #setTileModeX(android.graphics.Shader.TileMode)
+     * @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode)
+     * @attr ref android.R.styleable#BitmapDrawable_tileModeY
+     */
+    public final void setTileModeY(Shader.TileMode mode) {
+        setTileModeXY(mBitmapState.mTileModeX, mode);
+    }
+
+    /**
+     * Sets the repeat behavior of this drawable on both axis. By default, the drawable
+     * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or
+     * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled)
+     * if the bitmap is smaller than this drawable.
+     *
+     * @param xmode The X repeat mode for this drawable.
+     * @param ymode The Y repeat mode for this drawable.
+     *
+     * @see #setTileModeX(android.graphics.Shader.TileMode)
+     * @see #setTileModeY(android.graphics.Shader.TileMode)
+     */
+    public void setTileModeXY(Shader.TileMode xmode, Shader.TileMode ymode) {
+        final BitmapState state = mBitmapState;
+        if (state.mTileModeX != xmode || state.mTileModeY != ymode) {
+            state.mTileModeX = xmode;
+            state.mTileModeY = ymode;
+            state.mRebuildShader = true;
+            mDstRectAndInsetsDirty = true;
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public void setAutoMirrored(boolean mirrored) {
+        if (mBitmapState.mAutoMirrored != mirrored) {
+            mBitmapState.mAutoMirrored = mirrored;
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public final boolean isAutoMirrored() {
+        return mBitmapState.mAutoMirrored;
+    }
+
+    @Override
+    public @Config int getChangingConfigurations() {
+        return super.getChangingConfigurations() | mBitmapState.getChangingConfigurations();
+    }
+
+    private boolean needMirroring() {
+        return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        mDstRectAndInsetsDirty = true;
+
+        final Bitmap bitmap = mBitmapState.mBitmap;
+        final Shader shader = mBitmapState.mPaint.getShader();
+        if (bitmap != null && shader != null) {
+            updateShaderMatrix(bitmap, mBitmapState.mPaint, shader, needMirroring());
+        }
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        final Bitmap bitmap = mBitmapState.mBitmap;
+        if (bitmap == null) {
+            return;
+        }
+
+        final BitmapState state = mBitmapState;
+        final Paint paint = state.mPaint;
+        if (state.mRebuildShader) {
+            final Shader.TileMode tmx = state.mTileModeX;
+            final Shader.TileMode tmy = state.mTileModeY;
+            if (tmx == null && tmy == null) {
+                paint.setShader(null);
+            } else {
+                paint.setShader(new BitmapShader(bitmap,
+                        tmx == null ? Shader.TileMode.CLAMP : tmx,
+                        tmy == null ? Shader.TileMode.CLAMP : tmy));
+            }
+
+            state.mRebuildShader = false;
+        }
+
+        final int restoreAlpha;
+        if (state.mBaseAlpha != 1.0f) {
+            final Paint p = getPaint();
+            restoreAlpha = p.getAlpha();
+            p.setAlpha((int) (restoreAlpha * state.mBaseAlpha + 0.5f));
+        } else {
+            restoreAlpha = -1;
+        }
+
+        final boolean clearColorFilter;
+        if (mTintFilter != null && paint.getColorFilter() == null) {
+            paint.setColorFilter(mTintFilter);
+            clearColorFilter = true;
+        } else {
+            clearColorFilter = false;
+        }
+
+        updateDstRectAndInsetsIfDirty();
+        final Shader shader = paint.getShader();
+        final boolean needMirroring = needMirroring();
+        if (shader == null) {
+            if (needMirroring) {
+                canvas.save();
+                // Mirror the bitmap
+                canvas.translate(mDstRect.right - mDstRect.left, 0);
+                canvas.scale(-1.0f, 1.0f);
+            }
+
+            canvas.drawBitmap(bitmap, null, mDstRect, paint);
+
+            if (needMirroring) {
+                canvas.restore();
+            }
+        } else {
+            updateShaderMatrix(bitmap, paint, shader, needMirroring);
+            canvas.drawRect(mDstRect, paint);
+        }
+
+        if (clearColorFilter) {
+            paint.setColorFilter(null);
+        }
+
+        if (restoreAlpha >= 0) {
+            paint.setAlpha(restoreAlpha);
+        }
+    }
+
+    /**
+     * Updates the {@code paint}'s shader matrix to be consistent with the
+     * destination size and layout direction.
+     *
+     * @param bitmap the bitmap to be drawn
+     * @param paint the paint used to draw the bitmap
+     * @param shader the shader to set on the paint
+     * @param needMirroring whether the bitmap should be mirrored
+     */
+    private void updateShaderMatrix(@NonNull Bitmap bitmap, @NonNull Paint paint,
+            @NonNull Shader shader, boolean needMirroring) {
+        final int sourceDensity = bitmap.getDensity();
+        final int targetDensity = mTargetDensity;
+        final boolean needScaling = sourceDensity != 0 && sourceDensity != targetDensity;
+        if (needScaling || needMirroring) {
+            final Matrix matrix = getOrCreateMirrorMatrix();
+            matrix.reset();
+
+            if (needMirroring) {
+                final int dx = mDstRect.right - mDstRect.left;
+                matrix.setTranslate(dx, 0);
+                matrix.setScale(-1, 1);
+            }
+
+            if (needScaling) {
+                final float densityScale = targetDensity / (float) sourceDensity;
+                matrix.postScale(densityScale, densityScale);
+            }
+
+            shader.setLocalMatrix(matrix);
+        } else {
+            mMirrorMatrix = null;
+            shader.setLocalMatrix(Matrix.IDENTITY_MATRIX);
+        }
+
+        paint.setShader(shader);
+    }
+
+    private Matrix getOrCreateMirrorMatrix() {
+        if (mMirrorMatrix == null) {
+            mMirrorMatrix = new Matrix();
+        }
+        return mMirrorMatrix;
+    }
+
+    private void updateDstRectAndInsetsIfDirty() {
+        if (mDstRectAndInsetsDirty) {
+            if (mBitmapState.mTileModeX == null && mBitmapState.mTileModeY == null) {
+                final Rect bounds = getBounds();
+                final int layoutDirection = getLayoutDirection();
+                Gravity.apply(mBitmapState.mGravity, mBitmapWidth, mBitmapHeight,
+                        bounds, mDstRect, layoutDirection);
+
+                final int left = mDstRect.left - bounds.left;
+                final int top = mDstRect.top - bounds.top;
+                final int right = bounds.right - mDstRect.right;
+                final int bottom = bounds.bottom - mDstRect.bottom;
+                mOpticalInsets = Insets.of(left, top, right, bottom);
+            } else {
+                copyBounds(mDstRect);
+                mOpticalInsets = Insets.NONE;
+            }
+        }
+        mDstRectAndInsetsDirty = false;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public Insets getOpticalInsets() {
+        updateDstRectAndInsetsIfDirty();
+        return mOpticalInsets;
+    }
+
+    @Override
+    public void getOutline(@NonNull Outline outline) {
+        updateDstRectAndInsetsIfDirty();
+        outline.setRect(mDstRect);
+
+        // Only opaque Bitmaps can report a non-0 alpha,
+        // since only they are guaranteed to fill their bounds
+        boolean opaqueOverShape = mBitmapState.mBitmap != null
+                && !mBitmapState.mBitmap.hasAlpha();
+        outline.setAlpha(opaqueOverShape ? getAlpha() / 255.0f : 0.0f);
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        final int oldAlpha = mBitmapState.mPaint.getAlpha();
+        if (alpha != oldAlpha) {
+            mBitmapState.mPaint.setAlpha(alpha);
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public int getAlpha() {
+        return mBitmapState.mPaint.getAlpha();
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        mBitmapState.mPaint.setColorFilter(colorFilter);
+        invalidateSelf();
+    }
+
+    @Override
+    public ColorFilter getColorFilter() {
+        return mBitmapState.mPaint.getColorFilter();
+    }
+
+    @Override
+    public void setTintList(ColorStateList tint) {
+        final BitmapState state = mBitmapState;
+        if (state.mTint != tint) {
+            state.mTint = tint;
+            mTintFilter = updateTintFilter(mTintFilter, tint, mBitmapState.mTintMode);
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public void setTintMode(PorterDuff.Mode tintMode) {
+        final BitmapState state = mBitmapState;
+        if (state.mTintMode != tintMode) {
+            state.mTintMode = tintMode;
+            mTintFilter = updateTintFilter(mTintFilter, mBitmapState.mTint, tintMode);
+            invalidateSelf();
+        }
+    }
+
+    /**
+     * @hide only needed by a hack within ProgressBar
+     */
+    public ColorStateList getTint() {
+        return mBitmapState.mTint;
+    }
+
+    /**
+     * @hide only needed by a hack within ProgressBar
+     */
+    public Mode getTintMode() {
+        return mBitmapState.mTintMode;
+    }
+
+    /**
+     * @hide Candidate for future API inclusion
+     */
+    @Override
+    public void setXfermode(Xfermode xfermode) {
+        mBitmapState.mPaint.setXfermode(xfermode);
+        invalidateSelf();
+    }
+
+    /**
+     * A mutable BitmapDrawable still shares its Bitmap with any other Drawable
+     * that comes from the same resource.
+     *
+     * @return This drawable.
+     */
+    @Override
+    public Drawable mutate() {
+        if (!mMutated && super.mutate() == this) {
+            mBitmapState = new BitmapState(mBitmapState);
+            mMutated = true;
+        }
+        return this;
+    }
+
+    /**
+     * @hide
+     */
+    public void clearMutated() {
+        super.clearMutated();
+        mMutated = false;
+    }
+
+    @Override
+    protected boolean onStateChange(int[] stateSet) {
+        final BitmapState state = mBitmapState;
+        if (state.mTint != null && state.mTintMode != null) {
+            mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isStateful() {
+        return (mBitmapState.mTint != null && mBitmapState.mTint.isStateful())
+                || super.isStateful();
+    }
+
+    /** @hide */
+    @Override
+    public boolean hasFocusStateSpecified() {
+        return mBitmapState.mTint != null && mBitmapState.mTint.hasFocusStateSpecified();
+    }
+
+    @Override
+    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+            throws XmlPullParserException, IOException {
+        super.inflate(r, parser, attrs, theme);
+
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.BitmapDrawable);
+        updateStateFromTypedArray(a, mSrcDensityOverride);
+        verifyRequiredAttributes(a);
+        a.recycle();
+
+        // Update local properties.
+        updateLocalState(r);
+    }
+
+    /**
+     * Ensures all required attributes are set.
+     *
+     * @throws XmlPullParserException if any required attributes are missing
+     */
+    private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
+        // If we're not waiting on a theme, verify required attributes.
+        final BitmapState state = mBitmapState;
+        if (state.mBitmap == null && (state.mThemeAttrs == null
+                || state.mThemeAttrs[R.styleable.BitmapDrawable_src] == 0)) {
+            throw new XmlPullParserException(a.getPositionDescription() +
+                    ": <bitmap> requires a valid 'src' attribute");
+        }
+    }
+
+    /**
+     * Updates the constant state from the values in the typed array.
+     */
+    private void updateStateFromTypedArray(TypedArray a, int srcDensityOverride)
+            throws XmlPullParserException {
+        final Resources r = a.getResources();
+        final BitmapState state = mBitmapState;
+
+        // Account for any configuration changes.
+        state.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        state.mThemeAttrs = a.extractThemeAttrs();
+
+        state.mSrcDensityOverride = srcDensityOverride;
+
+        state.mTargetDensity = Drawable.resolveDensity(r, 0);
+
+        final int srcResId = a.getResourceId(R.styleable.BitmapDrawable_src, 0);
+        if (srcResId != 0) {
+            final TypedValue value = new TypedValue();
+            r.getValueForDensity(srcResId, srcDensityOverride, value, true);
+
+            // Pretend the requested density is actually the display density. If
+            // the drawable returned is not the requested density, then force it
+            // to be scaled later by dividing its density by the ratio of
+            // requested density to actual device density. Drawables that have
+            // undefined density or no density don't need to be handled here.
+            if (srcDensityOverride > 0 && value.density > 0
+                    && value.density != TypedValue.DENSITY_NONE) {
+                if (value.density == srcDensityOverride) {
+                    value.density = r.getDisplayMetrics().densityDpi;
+                } else {
+                    value.density =
+                            (value.density * r.getDisplayMetrics().densityDpi) / srcDensityOverride;
+                }
+            }
+
+            Bitmap bitmap = null;
+            try (InputStream is = r.openRawResource(srcResId, value)) {
+                bitmap = BitmapFactory.decodeResourceStream(r, value, is, null, null);
+            } catch (Exception e) {
+                // Do nothing and pick up the error below.
+            }
+
+            if (bitmap == null) {
+                throw new XmlPullParserException(a.getPositionDescription() +
+                        ": <bitmap> requires a valid 'src' attribute");
+            }
+
+            state.mBitmap = bitmap;
+        }
+
+        final boolean defMipMap = state.mBitmap != null ? state.mBitmap.hasMipMap() : false;
+        setMipMap(a.getBoolean(R.styleable.BitmapDrawable_mipMap, defMipMap));
+
+        state.mAutoMirrored = a.getBoolean(
+                R.styleable.BitmapDrawable_autoMirrored, state.mAutoMirrored);
+        state.mBaseAlpha = a.getFloat(R.styleable.BitmapDrawable_alpha, state.mBaseAlpha);
+
+        final int tintMode = a.getInt(R.styleable.BitmapDrawable_tintMode, -1);
+        if (tintMode != -1) {
+            state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
+        }
+
+        final ColorStateList tint = a.getColorStateList(R.styleable.BitmapDrawable_tint);
+        if (tint != null) {
+            state.mTint = tint;
+        }
+
+        final Paint paint = mBitmapState.mPaint;
+        paint.setAntiAlias(a.getBoolean(
+                R.styleable.BitmapDrawable_antialias, paint.isAntiAlias()));
+        paint.setFilterBitmap(a.getBoolean(
+                R.styleable.BitmapDrawable_filter, paint.isFilterBitmap()));
+        paint.setDither(a.getBoolean(R.styleable.BitmapDrawable_dither, paint.isDither()));
+
+        setGravity(a.getInt(R.styleable.BitmapDrawable_gravity, state.mGravity));
+
+        final int tileMode = a.getInt(R.styleable.BitmapDrawable_tileMode, TILE_MODE_UNDEFINED);
+        if (tileMode != TILE_MODE_UNDEFINED) {
+            final Shader.TileMode mode = parseTileMode(tileMode);
+            setTileModeXY(mode, mode);
+        }
+
+        final int tileModeX = a.getInt(R.styleable.BitmapDrawable_tileModeX, TILE_MODE_UNDEFINED);
+        if (tileModeX != TILE_MODE_UNDEFINED) {
+            setTileModeX(parseTileMode(tileModeX));
+        }
+
+        final int tileModeY = a.getInt(R.styleable.BitmapDrawable_tileModeY, TILE_MODE_UNDEFINED);
+        if (tileModeY != TILE_MODE_UNDEFINED) {
+            setTileModeY(parseTileMode(tileModeY));
+        }
+    }
+
+    @Override
+    public void applyTheme(Theme t) {
+        super.applyTheme(t);
+
+        final BitmapState state = mBitmapState;
+        if (state == null) {
+            return;
+        }
+
+        if (state.mThemeAttrs != null) {
+            final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.BitmapDrawable);
+            try {
+                updateStateFromTypedArray(a, state.mSrcDensityOverride);
+            } catch (XmlPullParserException e) {
+                rethrowAsRuntimeException(e);
+            } finally {
+                a.recycle();
+            }
+        }
+
+        // Apply theme to contained color state list.
+        if (state.mTint != null && state.mTint.canApplyTheme()) {
+            state.mTint = state.mTint.obtainForTheme(t);
+        }
+
+        // Update local properties.
+        updateLocalState(t.getResources());
+    }
+
+    private static Shader.TileMode parseTileMode(int tileMode) {
+        switch (tileMode) {
+            case TILE_MODE_CLAMP:
+                return Shader.TileMode.CLAMP;
+            case TILE_MODE_REPEAT:
+                return Shader.TileMode.REPEAT;
+            case TILE_MODE_MIRROR:
+                return Shader.TileMode.MIRROR;
+            default:
+                return null;
+        }
+    }
+
+    @Override
+    public boolean canApplyTheme() {
+        return mBitmapState != null && mBitmapState.canApplyTheme();
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return mBitmapWidth;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return mBitmapHeight;
+    }
+
+    @Override
+    public int getOpacity() {
+        if (mBitmapState.mGravity != Gravity.FILL) {
+            return PixelFormat.TRANSLUCENT;
+        }
+
+        final Bitmap bitmap = mBitmapState.mBitmap;
+        return (bitmap == null || bitmap.hasAlpha() || mBitmapState.mPaint.getAlpha() < 255) ?
+                PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
+    }
+
+    @Override
+    public final ConstantState getConstantState() {
+        mBitmapState.mChangingConfigurations |= getChangingConfigurations();
+        return mBitmapState;
+    }
+
+    final static class BitmapState extends ConstantState {
+        final Paint mPaint;
+
+        // Values loaded during inflation.
+        int[] mThemeAttrs = null;
+        Bitmap mBitmap = null;
+        ColorStateList mTint = null;
+        Mode mTintMode = DEFAULT_TINT_MODE;
+        int mGravity = Gravity.FILL;
+        float mBaseAlpha = 1.0f;
+        Shader.TileMode mTileModeX = null;
+        Shader.TileMode mTileModeY = null;
+
+        // The density to use when looking up the bitmap in Resources. A value of 0 means use
+        // the system's density.
+        int mSrcDensityOverride = 0;
+
+        // The density at which to render the bitmap.
+        int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
+
+        boolean mAutoMirrored = false;
+
+        @Config int mChangingConfigurations;
+        boolean mRebuildShader;
+
+        BitmapState(Bitmap bitmap) {
+            mBitmap = bitmap;
+            mPaint = new Paint(DEFAULT_PAINT_FLAGS);
+        }
+
+        BitmapState(BitmapState bitmapState) {
+            mBitmap = bitmapState.mBitmap;
+            mTint = bitmapState.mTint;
+            mTintMode = bitmapState.mTintMode;
+            mThemeAttrs = bitmapState.mThemeAttrs;
+            mChangingConfigurations = bitmapState.mChangingConfigurations;
+            mGravity = bitmapState.mGravity;
+            mTileModeX = bitmapState.mTileModeX;
+            mTileModeY = bitmapState.mTileModeY;
+            mSrcDensityOverride = bitmapState.mSrcDensityOverride;
+            mTargetDensity = bitmapState.mTargetDensity;
+            mBaseAlpha = bitmapState.mBaseAlpha;
+            mPaint = new Paint(bitmapState.mPaint);
+            mRebuildShader = bitmapState.mRebuildShader;
+            mAutoMirrored = bitmapState.mAutoMirrored;
+        }
+
+        @Override
+        public boolean canApplyTheme() {
+            return mThemeAttrs != null || mTint != null && mTint.canApplyTheme();
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return new BitmapDrawable(this, null);
+        }
+
+        @Override
+        public Drawable newDrawable(Resources res) {
+            return new BitmapDrawable(this, res);
+        }
+
+        @Override
+        public @Config int getChangingConfigurations() {
+            return mChangingConfigurations
+                    | (mTint != null ? mTint.getChangingConfigurations() : 0);
+        }
+    }
+
+    /**
+     * The one constructor to rule them all. This is called by all public
+     * constructors to set the state and initialize local properties.
+     */
+    private BitmapDrawable(BitmapState state, Resources res) {
+        mBitmapState = state;
+
+        updateLocalState(res);
+    }
+
+    /**
+     * Initializes local dynamic properties from state. This should be called
+     * after significant state changes, e.g. from the One True Constructor and
+     * after inflating or applying a theme.
+     */
+    private void updateLocalState(Resources res) {
+        mTargetDensity = resolveDensity(res, mBitmapState.mTargetDensity);
+        mTintFilter = updateTintFilter(mTintFilter, mBitmapState.mTint, mBitmapState.mTintMode);
+        computeBitmapSize();
+    }
+}
diff --git a/android/graphics/drawable/ClipDrawable.java b/android/graphics/drawable/ClipDrawable.java
new file mode 100644
index 0000000..d925b6b
--- /dev/null
+++ b/android/graphics/drawable/ClipDrawable.java
@@ -0,0 +1,240 @@
+/*
+ * 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 com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.Resources.Theme;
+import android.graphics.*;
+import android.view.Gravity;
+import android.util.AttributeSet;
+
+import java.io.IOException;
+
+/**
+ * A Drawable that clips another Drawable based on this Drawable's current
+ * level value.  You can control how much the child Drawable gets clipped in width
+ * and height based on the level, as well as a gravity to control where it is
+ * placed in its overall container.  Most often used to implement things like
+ * progress bars, by increasing the drawable's level with {@link
+ * android.graphics.drawable.Drawable#setLevel(int) setLevel()}.
+ * <p class="note"><strong>Note:</strong> The drawable is clipped completely and not visible when
+ * the level is 0 and fully revealed when the level is 10,000.</p>
+ *
+ * <p>It can be defined in an XML file with the <code>&lt;clip></code> element.  For more
+ * information, see the guide to <a
+ * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
+ *
+ * @attr ref android.R.styleable#ClipDrawable_clipOrientation
+ * @attr ref android.R.styleable#ClipDrawable_gravity
+ * @attr ref android.R.styleable#ClipDrawable_drawable
+ */
+public class ClipDrawable extends DrawableWrapper {
+    public static final int HORIZONTAL = 1;
+    public static final int VERTICAL = 2;
+
+    private static final int MAX_LEVEL = 10000;
+
+    private final Rect mTmpRect = new Rect();
+
+    private ClipState mState;
+
+    ClipDrawable() {
+        this(new ClipState(null, null), null);
+    }
+
+    /**
+     * Creates a new clip drawable with the specified gravity and orientation.
+     *
+     * @param drawable the drawable to clip
+     * @param gravity gravity constant (see {@link Gravity} used to position
+     *                the clipped drawable within the parent container
+     * @param orientation bitwise-or of {@link #HORIZONTAL} and/or
+     *                   {@link #VERTICAL}
+     */
+    public ClipDrawable(Drawable drawable, int gravity, int orientation) {
+        this(new ClipState(null, null), null);
+
+        mState.mGravity = gravity;
+        mState.mOrientation = orientation;
+
+        setDrawable(drawable);
+    }
+
+    @Override
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ClipDrawable);
+
+        // Inflation will advance the XmlPullParser and AttributeSet.
+        super.inflate(r, parser, attrs, theme);
+
+        updateStateFromTypedArray(a);
+        verifyRequiredAttributes(a);
+        a.recycle();
+    }
+
+    @Override
+    public void applyTheme(@NonNull Theme t) {
+        super.applyTheme(t);
+
+        final ClipState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        if (state.mThemeAttrs != null) {
+            final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ClipDrawable);
+            try {
+                updateStateFromTypedArray(a);
+                verifyRequiredAttributes(a);
+            } catch (XmlPullParserException e) {
+                rethrowAsRuntimeException(e);
+            } finally {
+                a.recycle();
+            }
+        }
+    }
+
+    private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
+        // If we're not waiting on a theme, verify required attributes.
+        if (getDrawable() == null && (mState.mThemeAttrs == null
+                || mState.mThemeAttrs[R.styleable.ClipDrawable_drawable] == 0)) {
+            throw new XmlPullParserException(a.getPositionDescription()
+                    + ": <clip> tag requires a 'drawable' attribute or "
+                    + "child tag defining a drawable");
+        }
+    }
+
+    private void updateStateFromTypedArray(@NonNull TypedArray a) {
+        final ClipState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        // Account for any configuration changes.
+        state.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        state.mThemeAttrs = a.extractThemeAttrs();
+
+        state.mOrientation = a.getInt(
+                R.styleable.ClipDrawable_clipOrientation, state.mOrientation);
+        state.mGravity = a.getInt(
+                R.styleable.ClipDrawable_gravity, state.mGravity);
+    }
+
+    @Override
+    protected boolean onLevelChange(int level) {
+        super.onLevelChange(level);
+        invalidateSelf();
+        return true;
+    }
+
+    @Override
+    public int getOpacity() {
+        final Drawable dr = getDrawable();
+        final int opacity = dr.getOpacity();
+        if (opacity == PixelFormat.TRANSPARENT || dr.getLevel() == 0) {
+            return PixelFormat.TRANSPARENT;
+        }
+
+        final int level = getLevel();
+        if (level >= MAX_LEVEL) {
+            return dr.getOpacity();
+        }
+
+        // Some portion of non-transparent drawable is showing.
+        return PixelFormat.TRANSLUCENT;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        final Drawable dr = getDrawable();
+        if (dr.getLevel() == 0) {
+            return;
+        }
+
+        final Rect r = mTmpRect;
+        final Rect bounds = getBounds();
+        final int level = getLevel();
+
+        int w = bounds.width();
+        final int iw = 0; //mState.mDrawable.getIntrinsicWidth();
+        if ((mState.mOrientation & HORIZONTAL) != 0) {
+            w -= (w - iw) * (MAX_LEVEL - level) / MAX_LEVEL;
+        }
+
+        int h = bounds.height();
+        final int ih = 0; //mState.mDrawable.getIntrinsicHeight();
+        if ((mState.mOrientation & VERTICAL) != 0) {
+            h -= (h - ih) * (MAX_LEVEL - level) / MAX_LEVEL;
+        }
+
+        final int layoutDirection = getLayoutDirection();
+        Gravity.apply(mState.mGravity, w, h, bounds, r, layoutDirection);
+
+        if (w > 0 && h > 0) {
+            canvas.save();
+            canvas.clipRect(r);
+            dr.draw(canvas);
+            canvas.restore();
+        }
+    }
+
+    @Override
+    DrawableWrapperState mutateConstantState() {
+        mState = new ClipState(mState, null);
+        return mState;
+    }
+
+    static final class ClipState extends DrawableWrapper.DrawableWrapperState {
+        private int[] mThemeAttrs;
+
+        int mOrientation = HORIZONTAL;
+        int mGravity = Gravity.LEFT;
+
+        ClipState(ClipState orig, Resources res) {
+            super(orig, res);
+
+            if (orig != null) {
+                mOrientation = orig.mOrientation;
+                mGravity = orig.mGravity;
+            }
+        }
+
+        @Override
+        public Drawable newDrawable(Resources res) {
+            return new ClipDrawable(this, res);
+        }
+    }
+
+    private ClipDrawable(ClipState state, Resources res) {
+        super(state, res);
+
+        mState = state;
+    }
+}
+
diff --git a/android/graphics/drawable/ColorDrawable.java b/android/graphics/drawable/ColorDrawable.java
new file mode 100644
index 0000000..9ae747d
--- /dev/null
+++ b/android/graphics/drawable/ColorDrawable.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2008 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.ColorInt;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.content.pm.ActivityInfo.Config;
+import android.graphics.*;
+import android.graphics.PorterDuff.Mode;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.ViewDebug;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * A specialized Drawable that fills the Canvas with a specified color.
+ * Note that a ColorDrawable ignores the ColorFilter.
+ *
+ * <p>It can be defined in an XML file with the <code>&lt;color></code> element.</p>
+ *
+ * @attr ref android.R.styleable#ColorDrawable_color
+ */
+public class ColorDrawable extends Drawable {
+    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+    @ViewDebug.ExportedProperty(deepExport = true, prefix = "state_")
+    private ColorState mColorState;
+    private PorterDuffColorFilter mTintFilter;
+
+    private boolean mMutated;
+
+    /**
+     * Creates a new black ColorDrawable.
+     */
+    public ColorDrawable() {
+        mColorState = new ColorState();
+    }
+
+    /**
+     * Creates a new ColorDrawable with the specified color.
+     *
+     * @param color The color to draw.
+     */
+    public ColorDrawable(@ColorInt int color) {
+        mColorState = new ColorState();
+
+        setColor(color);
+    }
+
+    @Override
+    public @Config int getChangingConfigurations() {
+        return super.getChangingConfigurations() | mColorState.getChangingConfigurations();
+    }
+
+    /**
+     * A mutable BitmapDrawable still shares its Bitmap with any other Drawable
+     * that comes from the same resource.
+     *
+     * @return This drawable.
+     */
+    @Override
+    public Drawable mutate() {
+        if (!mMutated && super.mutate() == this) {
+            mColorState = new ColorState(mColorState);
+            mMutated = true;
+        }
+        return this;
+    }
+
+    /**
+     * @hide
+     */
+    public void clearMutated() {
+        super.clearMutated();
+        mMutated = false;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        final ColorFilter colorFilter = mPaint.getColorFilter();
+        if ((mColorState.mUseColor >>> 24) != 0 || colorFilter != null || mTintFilter != null) {
+            if (colorFilter == null) {
+                mPaint.setColorFilter(mTintFilter);
+            }
+
+            mPaint.setColor(mColorState.mUseColor);
+            canvas.drawRect(getBounds(), mPaint);
+
+            // Restore original color filter.
+            mPaint.setColorFilter(colorFilter);
+        }
+    }
+
+    /**
+     * Gets the drawable's color value.
+     *
+     * @return int The color to draw.
+     */
+    @ColorInt
+    public int getColor() {
+        return mColorState.mUseColor;
+    }
+
+    /**
+     * Sets the drawable's color value. This action will clobber the results of
+     * prior calls to {@link #setAlpha(int)} on this object, which side-affected
+     * the underlying color.
+     *
+     * @param color The color to draw.
+     */
+    public void setColor(@ColorInt int color) {
+        if (mColorState.mBaseColor != color || mColorState.mUseColor != color) {
+            mColorState.mBaseColor = mColorState.mUseColor = color;
+            invalidateSelf();
+        }
+    }
+
+    /**
+     * Returns the alpha value of this drawable's color.
+     *
+     * @return A value between 0 and 255.
+     */
+    @Override
+    public int getAlpha() {
+        return mColorState.mUseColor >>> 24;
+    }
+
+    /**
+     * Sets the color's alpha value.
+     *
+     * @param alpha The alpha value to set, between 0 and 255.
+     */
+    @Override
+    public void setAlpha(int alpha) {
+        alpha += alpha >> 7;   // make it 0..256
+        final int baseAlpha = mColorState.mBaseColor >>> 24;
+        final int useAlpha = baseAlpha * alpha >> 8;
+        final int useColor = (mColorState.mBaseColor << 8 >>> 8) | (useAlpha << 24);
+        if (mColorState.mUseColor != useColor) {
+            mColorState.mUseColor = useColor;
+            invalidateSelf();
+        }
+    }
+
+    /**
+     * Sets the color filter applied to this color.
+     * <p>
+     * Only supported on version {@link android.os.Build.VERSION_CODES#LOLLIPOP} and
+     * above. Calling this method has no effect on earlier versions.
+     *
+     * @see android.graphics.drawable.Drawable#setColorFilter(ColorFilter)
+     */
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        mPaint.setColorFilter(colorFilter);
+    }
+
+    @Override
+    public void setTintList(ColorStateList tint) {
+        mColorState.mTint = tint;
+        mTintFilter = updateTintFilter(mTintFilter, tint, mColorState.mTintMode);
+        invalidateSelf();
+    }
+
+    @Override
+    public void setTintMode(Mode tintMode) {
+        mColorState.mTintMode = tintMode;
+        mTintFilter = updateTintFilter(mTintFilter, mColorState.mTint, tintMode);
+        invalidateSelf();
+    }
+
+    @Override
+    protected boolean onStateChange(int[] stateSet) {
+        final ColorState state = mColorState;
+        if (state.mTint != null && state.mTintMode != null) {
+            mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isStateful() {
+        return mColorState.mTint != null && mColorState.mTint.isStateful();
+    }
+
+    /** @hide */
+    @Override
+    public boolean hasFocusStateSpecified() {
+        return mColorState.mTint != null && mColorState.mTint.hasFocusStateSpecified();
+    }
+
+    /**
+     * @hide
+     * @param mode new transfer mode
+     */
+    @Override
+    public void setXfermode(@Nullable Xfermode mode) {
+        mPaint.setXfermode(mode);
+        invalidateSelf();
+    }
+
+    /**
+     * @hide
+     * @return current transfer mode
+     */
+    @TestApi
+    public Xfermode getXfermode() {
+        return mPaint.getXfermode();
+    }
+
+    @Override
+    public int getOpacity() {
+        if (mTintFilter != null || mPaint.getColorFilter() != null) {
+            return PixelFormat.TRANSLUCENT;
+        }
+
+        switch (mColorState.mUseColor >>> 24) {
+            case 255:
+                return PixelFormat.OPAQUE;
+            case 0:
+                return PixelFormat.TRANSPARENT;
+        }
+        return PixelFormat.TRANSLUCENT;
+    }
+
+    @Override
+    public void getOutline(@NonNull Outline outline) {
+        outline.setRect(getBounds());
+        outline.setAlpha(getAlpha() / 255.0f);
+    }
+
+    @Override
+    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+            throws XmlPullParserException, IOException {
+        super.inflate(r, parser, attrs, theme);
+
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ColorDrawable);
+        updateStateFromTypedArray(a);
+        a.recycle();
+
+        updateLocalState(r);
+    }
+
+    /**
+     * Updates the constant state from the values in the typed array.
+     */
+    private void updateStateFromTypedArray(TypedArray a) {
+        final ColorState state = mColorState;
+
+        // Account for any configuration changes.
+        state.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        state.mThemeAttrs = a.extractThemeAttrs();
+
+        state.mBaseColor = a.getColor(R.styleable.ColorDrawable_color, state.mBaseColor);
+        state.mUseColor = state.mBaseColor;
+    }
+
+    @Override
+    public boolean canApplyTheme() {
+        return mColorState.canApplyTheme() || super.canApplyTheme();
+    }
+
+    @Override
+    public void applyTheme(Theme t) {
+        super.applyTheme(t);
+
+        final ColorState state = mColorState;
+        if (state == null) {
+            return;
+        }
+
+        if (state.mThemeAttrs != null) {
+            final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ColorDrawable);
+            updateStateFromTypedArray(a);
+            a.recycle();
+        }
+
+        if (state.mTint != null && state.mTint.canApplyTheme()) {
+            state.mTint = state.mTint.obtainForTheme(t);
+        }
+
+        updateLocalState(t.getResources());
+    }
+
+    @Override
+    public ConstantState getConstantState() {
+        return mColorState;
+    }
+
+    final static class ColorState extends ConstantState {
+        int[] mThemeAttrs;
+        int mBaseColor; // base color, independent of setAlpha()
+        @ViewDebug.ExportedProperty
+        int mUseColor;  // basecolor modulated by setAlpha()
+        @Config int mChangingConfigurations;
+        ColorStateList mTint = null;
+        Mode mTintMode = DEFAULT_TINT_MODE;
+
+        ColorState() {
+            // Empty constructor.
+        }
+
+        ColorState(ColorState state) {
+            mThemeAttrs = state.mThemeAttrs;
+            mBaseColor = state.mBaseColor;
+            mUseColor = state.mUseColor;
+            mChangingConfigurations = state.mChangingConfigurations;
+            mTint = state.mTint;
+            mTintMode = state.mTintMode;
+        }
+
+        @Override
+        public boolean canApplyTheme() {
+            return mThemeAttrs != null
+                    || (mTint != null && mTint.canApplyTheme());
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return new ColorDrawable(this, null);
+        }
+
+        @Override
+        public Drawable newDrawable(Resources res) {
+            return new ColorDrawable(this, res);
+        }
+
+        @Override
+        public @Config int getChangingConfigurations() {
+            return mChangingConfigurations
+                    | (mTint != null ? mTint.getChangingConfigurations() : 0);
+        }
+    }
+
+    private ColorDrawable(ColorState state, Resources res) {
+        mColorState = state;
+
+        updateLocalState(res);
+    }
+
+    /**
+     * Initializes local dynamic properties from state. This should be called
+     * after significant state changes, e.g. from the One True Constructor and
+     * after inflating or applying a theme.
+     */
+    private void updateLocalState(Resources r) {
+        mTintFilter = updateTintFilter(mTintFilter, mColorState.mTint, mColorState.mTintMode);
+    }
+}
diff --git a/android/graphics/drawable/Drawable.java b/android/graphics/drawable/Drawable.java
new file mode 100644
index 0000000..f17cd76
--- /dev/null
+++ b/android/graphics/drawable/Drawable.java
@@ -0,0 +1,1594 @@
+/*
+ * 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 com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.annotation.AttrRes;
+import android.annotation.ColorInt;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.content.pm.ActivityInfo.Config;
+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.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Insets;
+import android.graphics.NinePatch;
+import android.graphics.Outline;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.Xfermode;
+import android.os.Trace;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.StateSet;
+import android.util.TypedValue;
+import android.util.Xml;
+import android.view.View;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
+
+/**
+ * A Drawable is a general abstraction for "something that can be drawn."  Most
+ * often you will deal with Drawable as the type of resource retrieved for
+ * drawing things to the screen; the Drawable class provides a generic API for
+ * dealing with an underlying visual resource that may take a variety of forms.
+ * Unlike a {@link android.view.View}, a Drawable does not have any facility to
+ * receive events or otherwise interact with the user.
+ *
+ * <p>In addition to simple drawing, Drawable provides a number of generic
+ * mechanisms for its client to interact with what is being drawn:
+ *
+ * <ul>
+ *     <li> The {@link #setBounds} method <var>must</var> be called to tell the
+ *     Drawable where it is drawn and how large it should be.  All Drawables
+ *     should respect the requested size, often simply by scaling their
+ *     imagery.  A client can find the preferred size for some Drawables with
+ *     the {@link #getIntrinsicHeight} and {@link #getIntrinsicWidth} methods.
+ *
+ *     <li> The {@link #getPadding} method can return from some Drawables
+ *     information about how to frame content that is placed inside of them.
+ *     For example, a Drawable that is intended to be the frame for a button
+ *     widget would need to return padding that correctly places the label
+ *     inside of itself.
+ *
+ *     <li> The {@link #setState} method allows the client to tell the Drawable
+ *     in which state it is to be drawn, such as "focused", "selected", etc.
+ *     Some drawables may modify their imagery based on the selected state.
+ *
+ *     <li> The {@link #setLevel} method allows the client to supply a single
+ *     continuous controller that can modify the Drawable is displayed, such as
+ *     a battery level or progress level.  Some drawables may modify their
+ *     imagery based on the current level.
+ *
+ *     <li> A Drawable can perform animations by calling back to its client
+ *     through the {@link Callback} interface.  All clients should support this
+ *     interface (via {@link #setCallback}) so that animations will work.  A
+ *     simple way to do this is through the system facilities such as
+ *     {@link android.view.View#setBackground(Drawable)} and
+ *     {@link android.widget.ImageView}.
+ * </ul>
+ *
+ * Though usually not visible to the application, Drawables may take a variety
+ * of forms:
+ *
+ * <ul>
+ *     <li> <b>Bitmap</b>: the simplest Drawable, a PNG or JPEG image.
+ *     <li> <b>Nine Patch</b>: an extension to the PNG format allows it to
+ *     specify information about how to stretch it and place things inside of
+ *     it.
+ *     <li><b>Vector</b>: a drawable defined in an XML file as a set of points,
+ *     lines, and curves along with its associated color information. This type
+ *     of drawable can be scaled without loss of display quality.
+ *     <li> <b>Shape</b>: contains simple drawing commands instead of a raw
+ *     bitmap, allowing it to resize better in some cases.
+ *     <li> <b>Layers</b>: a compound drawable, which draws multiple underlying
+ *     drawables on top of each other.
+ *     <li> <b>States</b>: a compound drawable that selects one of a set of
+ *     drawables based on its state.
+ *     <li> <b>Levels</b>: a compound drawable that selects one of a set of
+ *     drawables based on its level.
+ *     <li> <b>Scale</b>: a compound drawable with a single child drawable,
+ *     whose overall size is modified based on the current level.
+ * </ul>
+ *
+ * <a name="Custom"></a>
+ * <h3>Custom drawables</h3>
+ *
+ * <p>
+ * All versions of Android allow the Drawable class to be extended and used at
+ * run time in place of framework-provided drawable classes. Starting in
+ * {@link android.os.Build.VERSION_CODES#N API 24}, custom drawables classes
+ * may also be used in XML.
+ * <p>
+ * <strong>Note:</strong> Custom drawable classes are only accessible from
+ * within your application package. Other applications will not be able to load
+ * them.
+ * <p>
+ * At a minimum, custom drawable classes must implement the abstract methods on
+ * Drawable and should override the {@link Drawable#draw(Canvas)} method to
+ * draw content.
+ * <p>
+ * Custom drawables classes may be used in XML in multiple ways:
+ * <ul>
+ *     <li>
+ *         Using the fully-qualified class name as the XML element name. For
+ *         this method, the custom drawable class must be a public top-level
+ *         class.
+ * <pre>
+ * &lt;com.myapp.MyCustomDrawable xmlns:android="http://schemas.android.com/apk/res/android"
+ *     android:color="#ffff0000" /&gt;
+ * </pre>
+ *     </li>
+ *     <li>
+ *         Using <em>drawable</em> as the XML element name and specifying the
+ *         fully-qualified class name from the <em>class</em> attribute. This
+ *         method may be used for both public top-level classes and public
+ *         static inner classes.
+ * <pre>
+ * &lt;drawable xmlns:android="http://schemas.android.com/apk/res/android"
+ *     class="com.myapp.MyTopLevelClass$InnerCustomDrawable"
+ *     android:color="#ffff0000" /&gt;
+ * </pre>
+ *     </li>
+ * </ul>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about how to use drawables, read the
+ * <a href="{@docRoot}guide/topics/graphics/2d-graphics.html">Canvas and Drawables</a> developer
+ * guide. For information and examples of creating drawable resources (XML or bitmap files that
+ * can be loaded in code), read the
+ * <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>
+ * document.</p></div>
+ */
+public abstract class Drawable {
+    private static final Rect ZERO_BOUNDS_RECT = new Rect();
+
+    static final PorterDuff.Mode DEFAULT_TINT_MODE = PorterDuff.Mode.SRC_IN;
+
+    private int[] mStateSet = StateSet.WILD_CARD;
+    private int mLevel = 0;
+    private @Config int mChangingConfigurations = 0;
+    private Rect mBounds = ZERO_BOUNDS_RECT;  // lazily becomes a new Rect()
+    private WeakReference<Callback> mCallback = null;
+    private boolean mVisible = true;
+
+    private int mLayoutDirection;
+
+    /**
+     * The source density to use when looking up resources using
+     * {@link Resources#getDrawableForDensity(int, int, Theme)}. A value of 0 means there is no
+     * override and the system density will be used.
+     *
+     * NOTE(adamlesinski): This is transient state used to get around the public API that does not
+     * account for source density overrides. Custom drawables implemented by developers do not need
+     * to be aware of the source density override, as it is only used by Launcher to load higher
+     * resolution icons from external Resources packages, which do not execute custom code.
+     * This is all to support the {@link Resources#getDrawableForDensity(int, int, Theme)} API.
+     *
+     * @hide
+     */
+    protected int mSrcDensityOverride = 0;
+
+    /**
+     * Draw in its bounds (set via setBounds) respecting optional effects such
+     * as alpha (set via setAlpha) and color filter (set via setColorFilter).
+     *
+     * @param canvas The canvas to draw into
+     */
+    public abstract void draw(@NonNull Canvas canvas);
+
+    /**
+     * Specify a bounding rectangle for the Drawable. This is where the drawable
+     * will draw when its draw() method is called.
+     */
+    public void setBounds(int left, int top, int right, int bottom) {
+        Rect oldBounds = mBounds;
+
+        if (oldBounds == ZERO_BOUNDS_RECT) {
+            oldBounds = mBounds = new Rect();
+        }
+
+        if (oldBounds.left != left || oldBounds.top != top ||
+                oldBounds.right != right || oldBounds.bottom != bottom) {
+            if (!oldBounds.isEmpty()) {
+                // first invalidate the previous bounds
+                invalidateSelf();
+            }
+            mBounds.set(left, top, right, bottom);
+            onBoundsChange(mBounds);
+        }
+    }
+
+    /**
+     * Specify a bounding rectangle for the Drawable. This is where the drawable
+     * will draw when its draw() method is called.
+     */
+    public void setBounds(@NonNull Rect bounds) {
+        setBounds(bounds.left, bounds.top, bounds.right, bounds.bottom);
+    }
+
+    /**
+     * Return a copy of the drawable's bounds in the specified Rect (allocated
+     * by the caller). The bounds specify where this will draw when its draw()
+     * method is called.
+     *
+     * @param bounds Rect to receive the drawable's bounds (allocated by the
+     *               caller).
+     */
+    public final void copyBounds(@NonNull Rect bounds) {
+        bounds.set(mBounds);
+    }
+
+    /**
+     * Return a copy of the drawable's bounds in a new Rect. This returns the
+     * same values as getBounds(), but the returned object is guaranteed to not
+     * be changed later by the drawable (i.e. it retains no reference to this
+     * rect). If the caller already has a Rect allocated, call copyBounds(rect).
+     *
+     * @return A copy of the drawable's bounds
+     */
+    @NonNull
+    public final Rect copyBounds() {
+        return new Rect(mBounds);
+    }
+
+    /**
+     * Return the drawable's bounds Rect. Note: for efficiency, the returned
+     * object may be the same object stored in the drawable (though this is not
+     * guaranteed), so if a persistent copy of the bounds is needed, call
+     * copyBounds(rect) instead.
+     * You should also not change the object returned by this method as it may
+     * be the same object stored in the drawable.
+     *
+     * @return The bounds of the drawable (which may change later, so caller
+     *         beware). DO NOT ALTER the returned object as it may change the
+     *         stored bounds of this drawable.
+     *
+     * @see #copyBounds()
+     * @see #copyBounds(android.graphics.Rect)
+     */
+    @NonNull
+    public final Rect getBounds() {
+        if (mBounds == ZERO_BOUNDS_RECT) {
+            mBounds = new Rect();
+        }
+
+        return mBounds;
+    }
+
+    /**
+     * Return the drawable's dirty bounds Rect. Note: for efficiency, the
+     * returned object may be the same object stored in the drawable (though
+     * this is not guaranteed).
+     * <p>
+     * By default, this returns the full drawable bounds. Custom drawables may
+     * override this method to perform more precise invalidation.
+     *
+     * @return The dirty bounds of this drawable
+     */
+    @NonNull
+    public Rect getDirtyBounds() {
+        return getBounds();
+    }
+
+    /**
+     * Set a mask of the configuration parameters for which this drawable
+     * may change, requiring that it be re-created.
+     *
+     * @param configs A mask of the changing configuration parameters, as
+     * defined by {@link android.content.pm.ActivityInfo}.
+     *
+     * @see android.content.pm.ActivityInfo
+     */
+    public void setChangingConfigurations(@Config int configs) {
+        mChangingConfigurations = configs;
+    }
+
+    /**
+     * Return a mask of the configuration parameters for which this drawable
+     * may change, requiring that it be re-created.  The default implementation
+     * returns whatever was provided through
+     * {@link #setChangingConfigurations(int)} or 0 by default.  Subclasses
+     * may extend this to or in the changing configurations of any other
+     * drawables they hold.
+     *
+     * @return Returns a mask of the changing configuration parameters, as
+     * defined by {@link android.content.pm.ActivityInfo}.
+     *
+     * @see android.content.pm.ActivityInfo
+     */
+    public @Config int getChangingConfigurations() {
+        return mChangingConfigurations;
+    }
+
+    /**
+     * Set to true to have the drawable dither its colors when drawn to a
+     * device with fewer than 8-bits per color component.
+     *
+     * @see android.graphics.Paint#setDither(boolean);
+     * @deprecated This property is ignored.
+     */
+    @Deprecated
+    public void setDither(boolean dither) {}
+
+    /**
+     * Set to true to have the drawable filter its bitmaps with bilinear
+     * sampling when they are scaled or rotated.
+     *
+     * <p>This can improve appearance when bitmaps are rotated. If the drawable
+     * does not use bitmaps, this call is ignored.</p>
+     *
+     * @see #isFilterBitmap()
+     * @see android.graphics.Paint#setFilterBitmap(boolean);
+     */
+    public void setFilterBitmap(boolean filter) {}
+
+    /**
+     * @return whether this drawable filters its bitmaps
+     * @see #setFilterBitmap(boolean)
+     */
+    public boolean isFilterBitmap() {
+        return false;
+    }
+
+    /**
+     * Implement this interface if you want to create an animated drawable that
+     * extends {@link android.graphics.drawable.Drawable Drawable}.
+     * Upon retrieving a drawable, use
+     * {@link Drawable#setCallback(android.graphics.drawable.Drawable.Callback)}
+     * to supply your implementation of the interface to the drawable; it uses
+     * this interface to schedule and execute animation changes.
+     */
+    public interface Callback {
+        /**
+         * Called when the drawable needs to be redrawn.  A view at this point
+         * should invalidate itself (or at least the part of itself where the
+         * drawable appears).
+         *
+         * @param who The drawable that is requesting the update.
+         */
+        void invalidateDrawable(@NonNull Drawable who);
+
+        /**
+         * A Drawable can call this to schedule the next frame of its
+         * animation.  An implementation can generally simply call
+         * {@link android.os.Handler#postAtTime(Runnable, Object, long)} with
+         * the parameters <var>(what, who, when)</var> to perform the
+         * scheduling.
+         *
+         * @param who The drawable being scheduled.
+         * @param what The action to execute.
+         * @param when The time (in milliseconds) to run.  The timebase is
+         *             {@link android.os.SystemClock#uptimeMillis}
+         */
+        void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when);
+
+        /**
+         * A Drawable can call this to unschedule an action previously
+         * scheduled with {@link #scheduleDrawable}.  An implementation can
+         * generally simply call
+         * {@link android.os.Handler#removeCallbacks(Runnable, Object)} with
+         * the parameters <var>(what, who)</var> to unschedule the drawable.
+         *
+         * @param who The drawable being unscheduled.
+         * @param what The action being unscheduled.
+         */
+        void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what);
+    }
+
+    /**
+     * Bind a {@link Callback} object to this Drawable.  Required for clients
+     * that want to support animated drawables.
+     *
+     * @param cb The client's Callback implementation.
+     *
+     * @see #getCallback()
+     */
+    public final void setCallback(@Nullable Callback cb) {
+        mCallback = cb != null ? new WeakReference<>(cb) : null;
+    }
+
+    /**
+     * Return the current {@link Callback} implementation attached to this
+     * Drawable.
+     *
+     * @return A {@link Callback} instance or null if no callback was set.
+     *
+     * @see #setCallback(android.graphics.drawable.Drawable.Callback)
+     */
+    @Nullable
+    public Callback getCallback() {
+        return mCallback != null ? mCallback.get() : null;
+    }
+
+    /**
+     * Use the current {@link Callback} implementation to have this Drawable
+     * redrawn.  Does nothing if there is no Callback attached to the
+     * Drawable.
+     *
+     * @see Callback#invalidateDrawable
+     * @see #getCallback()
+     * @see #setCallback(android.graphics.drawable.Drawable.Callback)
+     */
+    public void invalidateSelf() {
+        final Callback callback = getCallback();
+        if (callback != null) {
+            callback.invalidateDrawable(this);
+        }
+    }
+
+    /**
+     * Use the current {@link Callback} implementation to have this Drawable
+     * scheduled.  Does nothing if there is no Callback attached to the
+     * Drawable.
+     *
+     * @param what The action being scheduled.
+     * @param when The time (in milliseconds) to run.
+     *
+     * @see Callback#scheduleDrawable
+     */
+    public void scheduleSelf(@NonNull Runnable what, long when) {
+        final Callback callback = getCallback();
+        if (callback != null) {
+            callback.scheduleDrawable(this, what, when);
+        }
+    }
+
+    /**
+     * Use the current {@link Callback} implementation to have this Drawable
+     * unscheduled.  Does nothing if there is no Callback attached to the
+     * Drawable.
+     *
+     * @param what The runnable that you no longer want called.
+     *
+     * @see Callback#unscheduleDrawable
+     */
+    public void unscheduleSelf(@NonNull Runnable what) {
+        final Callback callback = getCallback();
+        if (callback != null) {
+            callback.unscheduleDrawable(this, what);
+        }
+    }
+
+    /**
+     * Returns the resolved layout direction for this Drawable.
+     *
+     * @return One of {@link android.view.View#LAYOUT_DIRECTION_LTR},
+     *         {@link android.view.View#LAYOUT_DIRECTION_RTL}
+     * @see #setLayoutDirection(int)
+     */
+    public @View.ResolvedLayoutDir int getLayoutDirection() {
+        return mLayoutDirection;
+    }
+
+    /**
+     * Set the layout direction for this drawable. Should be a resolved
+     * layout direction, as the Drawable has no capacity to do the resolution on
+     * its own.
+     *
+     * @param layoutDirection the resolved layout direction for the drawable,
+     *                        either {@link android.view.View#LAYOUT_DIRECTION_LTR}
+     *                        or {@link android.view.View#LAYOUT_DIRECTION_RTL}
+     * @return {@code true} if the layout direction change has caused the
+     *         appearance of the drawable to change such that it needs to be
+     *         re-drawn, {@code false} otherwise
+     * @see #getLayoutDirection()
+     */
+    public final boolean setLayoutDirection(@View.ResolvedLayoutDir int layoutDirection) {
+        if (mLayoutDirection != layoutDirection) {
+            mLayoutDirection = layoutDirection;
+            return onLayoutDirectionChanged(layoutDirection);
+        }
+        return false;
+    }
+
+    /**
+     * Called when the drawable's resolved layout direction changes.
+     *
+     * @param layoutDirection the new resolved layout direction
+     * @return {@code true} if the layout direction change has caused the
+     *         appearance of the drawable to change such that it needs to be
+     *         re-drawn, {@code false} otherwise
+     * @see #setLayoutDirection(int)
+     */
+    public boolean onLayoutDirectionChanged(@View.ResolvedLayoutDir int layoutDirection) {
+        return false;
+    }
+
+    /**
+     * Specify an alpha value for the drawable. 0 means fully transparent, and
+     * 255 means fully opaque.
+     */
+    public abstract void setAlpha(@IntRange(from=0,to=255) int alpha);
+
+    /**
+     * Gets the current alpha value for the drawable. 0 means fully transparent,
+     * 255 means fully opaque. This method is implemented by
+     * Drawable subclasses and the value returned is specific to how that class treats alpha.
+     * The default return value is 255 if the class does not override this method to return a value
+     * specific to its use of alpha.
+     */
+    @IntRange(from=0,to=255)
+    public int getAlpha() {
+        return 0xFF;
+    }
+
+    /**
+     * @hide
+     *
+     * Internal-only method for setting xfermode on certain supported drawables.
+     *
+     * Should not be made public since the layers and drawing area with which
+     * Drawables draw is private implementation detail, and not something apps
+     * should rely upon.
+     */
+    public void setXfermode(@Nullable Xfermode mode) {
+        // Base implementation drops it on the floor for compatibility. Whee!
+    }
+
+    /**
+     * Specify an optional color filter for the drawable.
+     * <p>
+     * If a Drawable has a ColorFilter, each output pixel of the Drawable's
+     * drawing contents will be modified by the color filter before it is
+     * blended onto the render target of a Canvas.
+     * </p>
+     * <p>
+     * Pass {@code null} to remove any existing color filter.
+     * </p>
+     * <p class="note"><strong>Note:</strong> Setting a non-{@code null} color
+     * filter disables {@link #setTintList(ColorStateList) tint}.
+     * </p>
+     *
+     * @param colorFilter The color filter to apply, or {@code null} to remove the
+     *            existing color filter
+     */
+    public abstract void setColorFilter(@Nullable ColorFilter colorFilter);
+
+    /**
+     * Specify a color and Porter-Duff mode to be the color filter for this
+     * drawable.
+     * <p>
+     * Convenience for {@link #setColorFilter(ColorFilter)} which constructs a
+     * {@link PorterDuffColorFilter}.
+     * </p>
+     * <p class="note"><strong>Note:</strong> Setting a color filter disables
+     * {@link #setTintList(ColorStateList) tint}.
+     * </p>
+     */
+    public void setColorFilter(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
+        if (getColorFilter() instanceof PorterDuffColorFilter) {
+            PorterDuffColorFilter existing = (PorterDuffColorFilter) getColorFilter();
+            if (existing.getColor() == color && existing.getMode() == mode) {
+                return;
+            }
+        }
+        setColorFilter(new PorterDuffColorFilter(color, mode));
+    }
+
+    /**
+     * Specifies tint color for this drawable.
+     * <p>
+     * A Drawable's drawing content will be blended together with its tint
+     * before it is drawn to the screen. This functions similarly to
+     * {@link #setColorFilter(int, PorterDuff.Mode)}.
+     * </p>
+     * <p>
+     * To clear the tint, pass {@code null} to
+     * {@link #setTintList(ColorStateList)}.
+     * </p>
+     * <p class="note"><strong>Note:</strong> Setting a color filter via
+     * {@link #setColorFilter(ColorFilter)} or
+     * {@link #setColorFilter(int, PorterDuff.Mode)} overrides tint.
+     * </p>
+     *
+     * @param tintColor Color to use for tinting this drawable
+     * @see #setTintList(ColorStateList)
+     * @see #setTintMode(PorterDuff.Mode)
+     */
+    public void setTint(@ColorInt int tintColor) {
+        setTintList(ColorStateList.valueOf(tintColor));
+    }
+
+    /**
+     * Specifies tint color for this drawable as a color state list.
+     * <p>
+     * A Drawable's drawing content will be blended together with its tint
+     * before it is drawn to the screen. This functions similarly to
+     * {@link #setColorFilter(int, PorterDuff.Mode)}.
+     * </p>
+     * <p class="note"><strong>Note:</strong> Setting a color filter via
+     * {@link #setColorFilter(ColorFilter)} or
+     * {@link #setColorFilter(int, PorterDuff.Mode)} overrides tint.
+     * </p>
+     *
+     * @param tint Color state list to use for tinting this drawable, or
+     *            {@code null} to clear the tint
+     * @see #setTint(int)
+     * @see #setTintMode(PorterDuff.Mode)
+     */
+    public void setTintList(@Nullable ColorStateList tint) {}
+
+    /**
+     * Specifies a tint blending mode for this drawable.
+     * <p>
+     * Defines how this drawable's tint color should be blended into the drawable
+     * before it is drawn to screen. Default tint mode is {@link PorterDuff.Mode#SRC_IN}.
+     * </p>
+     * <p class="note"><strong>Note:</strong> Setting a color filter via
+     * {@link #setColorFilter(ColorFilter)} or
+     * {@link #setColorFilter(int, PorterDuff.Mode)} overrides tint.
+     * </p>
+     *
+     * @param tintMode A Porter-Duff blending mode
+     * @see #setTint(int)
+     * @see #setTintList(ColorStateList)
+     */
+    public void setTintMode(@NonNull PorterDuff.Mode tintMode) {}
+
+    /**
+     * Returns the current color filter, or {@code null} if none set.
+     *
+     * @return the current color filter, or {@code null} if none set
+     */
+    public @Nullable ColorFilter getColorFilter() {
+        return null;
+    }
+
+    /**
+     * Removes the color filter for this drawable.
+     */
+    public void clearColorFilter() {
+        setColorFilter(null);
+    }
+
+    /**
+     * Specifies the hotspot's location within the drawable.
+     *
+     * @param x The X coordinate of the center of the hotspot
+     * @param y The Y coordinate of the center of the hotspot
+     */
+    public void setHotspot(float x, float y) {}
+
+    /**
+     * Sets the bounds to which the hotspot is constrained, if they should be
+     * different from the drawable bounds.
+     *
+     * @param left position in pixels of the left bound
+     * @param top position in pixels of the top bound
+     * @param right position in pixels of the right bound
+     * @param bottom position in pixels of the bottom bound
+     * @see #getHotspotBounds(android.graphics.Rect)
+     */
+    public void setHotspotBounds(int left, int top, int right, int bottom) {}
+
+    /**
+     * Populates {@code outRect} with the hotspot bounds.
+     *
+     * @param outRect the rect to populate with the hotspot bounds
+     * @see #setHotspotBounds(int, int, int, int)
+     */
+    public void getHotspotBounds(@NonNull Rect outRect) {
+        outRect.set(getBounds());
+    }
+
+    /**
+     * Whether this drawable requests projection.
+     *
+     * @hide magic!
+     */
+    public boolean isProjected() {
+        return false;
+    }
+
+    /**
+     * Indicates whether this drawable will change its appearance based on
+     * state. Clients can use this to determine whether it is necessary to
+     * calculate their state and call setState.
+     *
+     * @return True if this drawable changes its appearance based on state,
+     *         false otherwise.
+     * @see #setState(int[])
+     */
+    public boolean isStateful() {
+        return false;
+    }
+
+    /**
+     * Indicates whether this drawable has at least one state spec explicitly
+     * specifying {@link android.R.attr#state_focused}.
+     *
+     * <p>Note: A View uses a {@link Drawable} instance as its background and it
+     * changes its appearance based on a state. On keyboard devices, it should
+     * specify its {@link android.R.attr#state_focused} to make sure the user
+     * knows which view is holding the focus.</p>
+     *
+     * @return {@code true} if {@link android.R.attr#state_focused} is specified
+     * for this drawable.
+     *
+     * @hide
+     */
+    @TestApi
+    public boolean hasFocusStateSpecified() {
+        return false;
+    }
+
+    /**
+     * Specify a set of states for the drawable. These are use-case specific,
+     * so see the relevant documentation. As an example, the background for
+     * widgets like Button understand the following states:
+     * [{@link android.R.attr#state_focused},
+     *  {@link android.R.attr#state_pressed}].
+     *
+     * <p>If the new state you are supplying causes the appearance of the
+     * Drawable to change, then it is responsible for calling
+     * {@link #invalidateSelf} in order to have itself redrawn, <em>and</em>
+     * true will be returned from this function.
+     *
+     * <p>Note: The Drawable holds a reference on to <var>stateSet</var>
+     * until a new state array is given to it, so you must not modify this
+     * array during that time.</p>
+     *
+     * @param stateSet The new set of states to be displayed.
+     *
+     * @return Returns true if this change in state has caused the appearance
+     * of the Drawable to change (hence requiring an invalidate), otherwise
+     * returns false.
+     */
+    public boolean setState(@NonNull final int[] stateSet) {
+        if (!Arrays.equals(mStateSet, stateSet)) {
+            mStateSet = stateSet;
+            return onStateChange(stateSet);
+        }
+        return false;
+    }
+
+    /**
+     * Describes the current state, as a union of primitve states, such as
+     * {@link android.R.attr#state_focused},
+     * {@link android.R.attr#state_selected}, etc.
+     * Some drawables may modify their imagery based on the selected state.
+     * @return An array of resource Ids describing the current state.
+     */
+    public @NonNull int[] getState() {
+        return mStateSet;
+    }
+
+    /**
+     * If this Drawable does transition animations between states, ask that
+     * it immediately jump to the current state and skip any active animations.
+     */
+    public void jumpToCurrentState() {
+    }
+
+    /**
+     * @return The current drawable that will be used by this drawable. For simple drawables, this
+     *         is just the drawable itself. For drawables that change state like
+     *         {@link StateListDrawable} and {@link LevelListDrawable} this will be the child drawable
+     *         currently in use.
+     */
+    public @NonNull Drawable getCurrent() {
+        return this;
+    }
+
+    /**
+     * Specify the level for the drawable.  This allows a drawable to vary its
+     * imagery based on a continuous controller, for example to show progress
+     * or volume level.
+     *
+     * <p>If the new level you are supplying causes the appearance of the
+     * Drawable to change, then it is responsible for calling
+     * {@link #invalidateSelf} in order to have itself redrawn, <em>and</em>
+     * true will be returned from this function.
+     *
+     * @param level The new level, from 0 (minimum) to 10000 (maximum).
+     *
+     * @return Returns true if this change in level has caused the appearance
+     * of the Drawable to change (hence requiring an invalidate), otherwise
+     * returns false.
+     */
+    public final boolean setLevel(@IntRange(from=0,to=10000) int level) {
+        if (mLevel != level) {
+            mLevel = level;
+            return onLevelChange(level);
+        }
+        return false;
+    }
+
+    /**
+     * Retrieve the current level.
+     *
+     * @return int Current level, from 0 (minimum) to 10000 (maximum).
+     */
+    public final @IntRange(from=0,to=10000) int getLevel() {
+        return mLevel;
+    }
+
+    /**
+     * Set whether this Drawable is visible.  This generally does not impact
+     * the Drawable's behavior, but is a hint that can be used by some
+     * Drawables, for example, to decide whether run animations.
+     *
+     * @param visible Set to true if visible, false if not.
+     * @param restart You can supply true here to force the drawable to behave
+     *                as if it has just become visible, even if it had last
+     *                been set visible.  Used for example to force animations
+     *                to restart.
+     *
+     * @return boolean Returns true if the new visibility is different than
+     *         its previous state.
+     */
+    public boolean setVisible(boolean visible, boolean restart) {
+        boolean changed = mVisible != visible;
+        if (changed) {
+            mVisible = visible;
+            invalidateSelf();
+        }
+        return changed;
+    }
+
+    public final boolean isVisible() {
+        return mVisible;
+    }
+
+    /**
+     * Set whether this Drawable is automatically mirrored when its layout direction is RTL
+     * (right-to left). See {@link android.util.LayoutDirection}.
+     *
+     * @param mirrored Set to true if the Drawable should be mirrored, false if not.
+     */
+    public void setAutoMirrored(boolean mirrored) {
+    }
+
+    /**
+     * Tells if this Drawable will be automatically mirrored  when its layout direction is RTL
+     * right-to-left. See {@link android.util.LayoutDirection}.
+     *
+     * @return boolean Returns true if this Drawable will be automatically mirrored.
+     */
+    public boolean isAutoMirrored() {
+        return false;
+    }
+
+    /**
+     * Applies the specified theme to this Drawable and its children.
+     *
+     * @param t the theme to apply
+     */
+    public void applyTheme(@NonNull @SuppressWarnings("unused") Theme t) {
+    }
+
+    public boolean canApplyTheme() {
+        return false;
+    }
+
+    /**
+     * Return the opacity/transparency of this Drawable.  The returned value is
+     * one of the abstract format constants in
+     * {@link android.graphics.PixelFormat}:
+     * {@link android.graphics.PixelFormat#UNKNOWN},
+     * {@link android.graphics.PixelFormat#TRANSLUCENT},
+     * {@link android.graphics.PixelFormat#TRANSPARENT}, or
+     * {@link android.graphics.PixelFormat#OPAQUE}.
+     *
+     * <p>An OPAQUE drawable is one that draws all all content within its bounds, completely
+     * covering anything behind the drawable. A TRANSPARENT drawable is one that draws nothing
+     * within its bounds, allowing everything behind it to show through. A TRANSLUCENT drawable
+     * is a drawable in any other state, where the drawable will draw some, but not all,
+     * of the content within its bounds and at least some content behind the drawable will
+     * be visible. If the visibility of the drawable's contents cannot be determined, the
+     * safest/best return value is TRANSLUCENT.
+     *
+     * <p>Generally a Drawable should be as conservative as possible with the
+     * value it returns.  For example, if it contains multiple child drawables
+     * and only shows one of them at a time, if only one of the children is
+     * TRANSLUCENT and the others are OPAQUE then TRANSLUCENT should be
+     * returned.  You can use the method {@link #resolveOpacity} to perform a
+     * standard reduction of two opacities to the appropriate single output.
+     *
+     * <p>Note that the returned value does not necessarily take into account a
+     * custom alpha or color filter that has been applied by the client through
+     * the {@link #setAlpha} or {@link #setColorFilter} methods. Some subclasses,
+     * such as {@link BitmapDrawable}, {@link ColorDrawable}, and {@link GradientDrawable},
+     * do account for the value of {@link #setAlpha}, but the general behavior is dependent
+     * upon the implementation of the subclass.
+     *
+     * @return int The opacity class of the Drawable.
+     *
+     * @see android.graphics.PixelFormat
+     */
+    public abstract @PixelFormat.Opacity int getOpacity();
+
+    /**
+     * Return the appropriate opacity value for two source opacities.  If
+     * either is UNKNOWN, that is returned; else, if either is TRANSLUCENT,
+     * that is returned; else, if either is TRANSPARENT, that is returned;
+     * else, OPAQUE is returned.
+     *
+     * <p>This is to help in implementing {@link #getOpacity}.
+     *
+     * @param op1 One opacity value.
+     * @param op2 Another opacity value.
+     *
+     * @return int The combined opacity value.
+     *
+     * @see #getOpacity
+     */
+    public static @PixelFormat.Opacity int resolveOpacity(@PixelFormat.Opacity int op1,
+            @PixelFormat.Opacity int op2) {
+        if (op1 == op2) {
+            return op1;
+        }
+        if (op1 == PixelFormat.UNKNOWN || op2 == PixelFormat.UNKNOWN) {
+            return PixelFormat.UNKNOWN;
+        }
+        if (op1 == PixelFormat.TRANSLUCENT || op2 == PixelFormat.TRANSLUCENT) {
+            return PixelFormat.TRANSLUCENT;
+        }
+        if (op1 == PixelFormat.TRANSPARENT || op2 == PixelFormat.TRANSPARENT) {
+            return PixelFormat.TRANSPARENT;
+        }
+        return PixelFormat.OPAQUE;
+    }
+
+    /**
+     * Returns a Region representing the part of the Drawable that is completely
+     * transparent.  This can be used to perform drawing operations, identifying
+     * which parts of the target will not change when rendering the Drawable.
+     * The default implementation returns null, indicating no transparent
+     * region; subclasses can optionally override this to return an actual
+     * Region if they want to supply this optimization information, but it is
+     * not required that they do so.
+     *
+     * @return Returns null if the Drawables has no transparent region to
+     * report, else a Region holding the parts of the Drawable's bounds that
+     * are transparent.
+     */
+    public @Nullable Region getTransparentRegion() {
+        return null;
+    }
+
+    /**
+     * Override this in your subclass to change appearance if you recognize the
+     * specified state.
+     *
+     * @return Returns true if the state change has caused the appearance of
+     * the Drawable to change (that is, it needs to be drawn), else false
+     * if it looks the same and there is no need to redraw it since its
+     * last state.
+     */
+    protected boolean onStateChange(int[] state) {
+        return false;
+    }
+
+    /** Override this in your subclass to change appearance if you vary based
+     *  on level.
+     * @return Returns true if the level change has caused the appearance of
+     * the Drawable to change (that is, it needs to be drawn), else false
+     * if it looks the same and there is no need to redraw it since its
+     * last level.
+     */
+    protected boolean onLevelChange(int level) {
+        return false;
+    }
+
+    /**
+     * Override this in your subclass to change appearance if you vary based on
+     * the bounds.
+     */
+    protected void onBoundsChange(Rect bounds) {
+        // Stub method.
+    }
+
+    /**
+     * Returns the drawable's intrinsic width.
+     * <p>
+     * Intrinsic width is the width at which the drawable would like to be laid
+     * out, including any inherent padding. If the drawable has no intrinsic
+     * width, such as a solid color, this method returns -1.
+     *
+     * @return the intrinsic width, or -1 if no intrinsic width
+     */
+    public int getIntrinsicWidth() {
+        return -1;
+    }
+
+    /**
+     * Returns the drawable's intrinsic height.
+     * <p>
+     * Intrinsic height is the height at which the drawable would like to be
+     * laid out, including any inherent padding. If the drawable has no
+     * intrinsic height, such as a solid color, this method returns -1.
+     *
+     * @return the intrinsic height, or -1 if no intrinsic height
+     */
+    public int getIntrinsicHeight() {
+        return -1;
+    }
+
+    /**
+     * Returns the minimum width suggested by this Drawable. If a View uses this
+     * Drawable as a background, it is suggested that the View use at least this
+     * value for its width. (There will be some scenarios where this will not be
+     * possible.) This value should INCLUDE any padding.
+     *
+     * @return The minimum width suggested by this Drawable. If this Drawable
+     *         doesn't have a suggested minimum width, 0 is returned.
+     */
+    public int getMinimumWidth() {
+        final int intrinsicWidth = getIntrinsicWidth();
+        return intrinsicWidth > 0 ? intrinsicWidth : 0;
+    }
+
+    /**
+     * Returns the minimum height suggested by this Drawable. If a View uses this
+     * Drawable as a background, it is suggested that the View use at least this
+     * value for its height. (There will be some scenarios where this will not be
+     * possible.) This value should INCLUDE any padding.
+     *
+     * @return The minimum height suggested by this Drawable. If this Drawable
+     *         doesn't have a suggested minimum height, 0 is returned.
+     */
+    public int getMinimumHeight() {
+        final int intrinsicHeight = getIntrinsicHeight();
+        return intrinsicHeight > 0 ? intrinsicHeight : 0;
+    }
+
+    /**
+     * Return in padding the insets suggested by this Drawable for placing
+     * content inside the drawable's bounds. Positive values move toward the
+     * center of the Drawable (set Rect.inset).
+     *
+     * @return true if this drawable actually has a padding, else false. When false is returned,
+     * the padding is always set to 0.
+     */
+    public boolean getPadding(@NonNull Rect padding) {
+        padding.set(0, 0, 0, 0);
+        return false;
+    }
+
+    /**
+     * Return in insets the layout insets suggested by this Drawable for use with alignment
+     * operations during layout.
+     *
+     * @hide
+     */
+    public @NonNull Insets getOpticalInsets() {
+        return Insets.NONE;
+    }
+
+    /**
+     * Called to get the drawable to populate the Outline that defines its drawing area.
+     * <p>
+     * This method is called by the default {@link android.view.ViewOutlineProvider} to define
+     * the outline of the View.
+     * <p>
+     * The default behavior defines the outline to be the bounding rectangle of 0 alpha.
+     * Subclasses that wish to convey a different shape or alpha value must override this method.
+     *
+     * @see android.view.View#setOutlineProvider(android.view.ViewOutlineProvider)
+     */
+    public void getOutline(@NonNull Outline outline) {
+        outline.setRect(getBounds());
+        outline.setAlpha(0);
+    }
+
+    /**
+     * Make this drawable mutable. This operation cannot be reversed. A mutable
+     * drawable is guaranteed to not share its state with any other drawable.
+     * This is especially useful when you need to modify properties of drawables
+     * loaded from resources. By default, all drawables instances loaded from
+     * the same resource share a common state; if you modify the state of one
+     * instance, all the other instances will receive the same modification.
+     *
+     * Calling this method on a mutable Drawable will have no effect.
+     *
+     * @return This drawable.
+     * @see ConstantState
+     * @see #getConstantState()
+     */
+    public @NonNull Drawable mutate() {
+        return this;
+    }
+
+    /**
+     * Clears the mutated state, allowing this drawable to be cached and
+     * mutated again.
+     * <p>
+     * This is hidden because only framework drawables can be cached, so
+     * custom drawables don't need to support constant state, mutate(), or
+     * clearMutated().
+     *
+     * @hide
+     */
+    public void clearMutated() {
+        // Default implementation is no-op.
+    }
+
+    /**
+     * Create a drawable from an inputstream
+     */
+    public static Drawable createFromStream(InputStream is, String srcName) {
+        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, srcName != null ? srcName : "Unknown drawable");
+        try {
+            return createFromResourceStream(null, null, is, srcName);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+        }
+    }
+
+    /**
+     * Create a drawable from an inputstream, using the given resources and
+     * value to determine density information.
+     */
+    public static Drawable createFromResourceStream(Resources res, TypedValue value,
+            InputStream is, String srcName) {
+        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, srcName != null ? srcName : "Unknown drawable");
+        try {
+            return createFromResourceStream(res, value, is, srcName, null);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+        }
+    }
+
+    /**
+     * Create a drawable from an inputstream, using the given resources and
+     * value to determine density information.
+     */
+    public static Drawable createFromResourceStream(Resources res, TypedValue value,
+            InputStream is, String srcName, BitmapFactory.Options opts) {
+        if (is == null) {
+            return null;
+        }
+
+        /*  ugh. The decodeStream contract is that we have already allocated
+            the pad rect, but if the bitmap does not had a ninepatch chunk,
+            then the pad will be ignored. If we could change this to lazily
+            alloc/assign the rect, we could avoid the GC churn of making new
+            Rects only to drop them on the floor.
+        */
+        Rect pad = new Rect();
+
+        // Special stuff for compatibility mode: if the target density is not
+        // the same as the display density, but the resource -is- the same as
+        // the display density, then don't scale it down to the target density.
+        // This allows us to load the system's density-correct resources into
+        // an application in compatibility mode, without scaling those down
+        // to the compatibility density only to have them scaled back up when
+        // drawn to the screen.
+        if (opts == null) opts = new BitmapFactory.Options();
+        opts.inScreenDensity = Drawable.resolveDensity(res, 0);
+        Bitmap  bm = BitmapFactory.decodeResourceStream(res, value, is, pad, opts);
+        if (bm != null) {
+            byte[] np = bm.getNinePatchChunk();
+            if (np == null || !NinePatch.isNinePatchChunk(np)) {
+                np = null;
+                pad = null;
+            }
+
+            final Rect opticalInsets = new Rect();
+            bm.getOpticalInsets(opticalInsets);
+            return drawableFromBitmap(res, bm, np, pad, opticalInsets, srcName);
+        }
+        return null;
+    }
+
+    /**
+     * Create a drawable from an XML document. For more information on how to
+     * create resources in XML, see
+     * <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.
+     */
+    @NonNull
+    public static Drawable createFromXml(@NonNull Resources r, @NonNull XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        return createFromXml(r, parser, null);
+    }
+
+    /**
+     * Create a drawable from an XML document using an optional {@link Theme}.
+     * For more information on how to create resources in XML, see
+     * <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.
+     */
+    @NonNull
+    public static Drawable createFromXml(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @Nullable Theme theme) throws XmlPullParserException, IOException {
+        return createFromXmlForDensity(r, parser, 0, theme);
+    }
+
+    /**
+     * Version of {@link #createFromXml(Resources, XmlPullParser, Theme)} that accepts a density
+     * override.
+     * @hide
+     */
+    @NonNull
+    public static Drawable createFromXmlForDensity(@NonNull Resources r,
+            @NonNull XmlPullParser parser, int density, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
+        AttributeSet attrs = Xml.asAttributeSet(parser);
+
+        int type;
+        //noinspection StatementWithEmptyBody
+        while ((type=parser.next()) != XmlPullParser.START_TAG
+                && type != XmlPullParser.END_DOCUMENT) {
+            // Empty loop.
+        }
+
+        if (type != XmlPullParser.START_TAG) {
+            throw new XmlPullParserException("No start tag found");
+        }
+
+        Drawable drawable = createFromXmlInnerForDensity(r, parser, attrs, density, theme);
+
+        if (drawable == null) {
+            throw new RuntimeException("Unknown initial tag: " + parser.getName());
+        }
+
+        return drawable;
+    }
+
+    /**
+     * Create from inside an XML document.  Called on a parser positioned at
+     * a tag in an XML document, tries to create a Drawable from that tag.
+     * Returns null if the tag is not a valid drawable.
+     */
+    @NonNull
+    public static Drawable createFromXmlInner(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs) throws XmlPullParserException, IOException {
+        return createFromXmlInner(r, parser, attrs, null);
+    }
+
+    /**
+     * Create a drawable from inside an XML document using an optional
+     * {@link Theme}. Called on a parser positioned at a tag in an XML
+     * document, tries to create a Drawable from that tag. Returns {@code null}
+     * if the tag is not a valid drawable.
+     */
+    @NonNull
+    public static Drawable createFromXmlInner(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
+        return createFromXmlInnerForDensity(r, parser, attrs, 0, theme);
+    }
+
+    /**
+     * Version of {@link #createFromXmlInner(Resources, XmlPullParser, AttributeSet, Theme)} that
+     * accepts an override density.
+     */
+    @NonNull
+    static Drawable createFromXmlInnerForDensity(@NonNull Resources r,
+            @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, int density,
+            @Nullable Theme theme) throws XmlPullParserException, IOException {
+        return r.getDrawableInflater().inflateFromXmlForDensity(parser.getName(), parser, attrs,
+                density, theme);
+    }
+
+    /**
+     * Create a drawable from file path name.
+     */
+    @Nullable
+    public static Drawable createFromPath(String pathName) {
+        if (pathName == null) {
+            return null;
+        }
+
+        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, pathName);
+        try {
+            Bitmap bm = BitmapFactory.decodeFile(pathName);
+            if (bm != null) {
+                return drawableFromBitmap(null, bm, null, null, null, pathName);
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+        }
+
+        return null;
+    }
+
+    /**
+     * Inflate this Drawable from an XML resource. Does not apply a theme.
+     *
+     * @see #inflate(Resources, XmlPullParser, AttributeSet, Theme)
+     */
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs) throws XmlPullParserException, IOException {
+        inflate(r, parser, attrs, null);
+    }
+
+    /**
+     * Inflate this Drawable from an XML resource optionally styled by a theme.
+     * This can't be called more than once for each Drawable. Note that framework may have called
+     * this once to create the Drawable instance from XML resource.
+     *
+     * @param r Resources used to resolve attribute values
+     * @param parser XML parser from which to inflate this Drawable
+     * @param attrs Base set of attribute values
+     * @param theme Theme to apply, may be null
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.Drawable);
+        mVisible = a.getBoolean(R.styleable.Drawable_visible, mVisible);
+        a.recycle();
+    }
+
+    /**
+     * Inflate a Drawable from an XML resource.
+     *
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    void inflateWithAttributes(@NonNull @SuppressWarnings("unused") Resources r,
+            @NonNull @SuppressWarnings("unused") XmlPullParser parser, @NonNull TypedArray attrs,
+            @AttrRes int visibleAttr) throws XmlPullParserException, IOException {
+        mVisible = attrs.getBoolean(visibleAttr, mVisible);
+    }
+
+    /**
+     * Sets the source override density for this Drawable. If non-zero, this density is to be used
+     * for any calls to {@link Resources#getDrawableForDensity(int, int, Theme)} or
+     * {@link Resources#getValueForDensity(int, int, TypedValue, boolean)}.
+     * @hide
+     */
+    final void setSrcDensityOverride(int density) {
+        mSrcDensityOverride = density;
+    }
+
+    /**
+     * This abstract class is used by {@link Drawable}s to store shared constant state and data
+     * between Drawables. {@link BitmapDrawable}s created from the same resource will for instance
+     * share a unique bitmap stored in their ConstantState.
+     *
+     * <p>
+     * {@link #newDrawable(Resources)} can be used as a factory to create new Drawable instances
+     * from this ConstantState.
+     * </p>
+     *
+     * Use {@link Drawable#getConstantState()} to retrieve the ConstantState of a Drawable. Calling
+     * {@link Drawable#mutate()} on a Drawable should typically create a new ConstantState for that
+     * Drawable.
+     */
+    public static abstract class ConstantState {
+        /**
+         * Creates a new Drawable instance from its constant state.
+         * <p>
+         * <strong>Note:</strong> Using this method means density-dependent
+         * properties, such as pixel dimensions or bitmap images, will not be
+         * updated to match the density of the target display. To ensure
+         * correct scaling, use {@link #newDrawable(Resources)} instead to
+         * provide an appropriate Resources object.
+         *
+         * @return a new drawable object based on this constant state
+         * @see #newDrawable(Resources)
+         */
+        public abstract @NonNull Drawable newDrawable();
+
+        /**
+         * Creates a new Drawable instance from its constant state using the
+         * specified resources. This method should be implemented for drawables
+         * that have density-dependent properties.
+         * <p>
+         * The default implementation for this method calls through to
+         * {@link #newDrawable()}.
+         *
+         * @param res the resources of the context in which the drawable will
+         *            be displayed
+         * @return a new drawable object based on this constant state
+         */
+        public @NonNull Drawable newDrawable(@Nullable Resources res) {
+            return newDrawable();
+        }
+
+        /**
+         * Creates a new Drawable instance from its constant state using the
+         * specified resources and theme. This method should be implemented for
+         * drawables that have theme-dependent properties.
+         * <p>
+         * The default implementation for this method calls through to
+         * {@link #newDrawable(Resources)}.
+         *
+         * @param res the resources of the context in which the drawable will
+         *            be displayed
+         * @param theme the theme of the context in which the drawable will be
+         *              displayed
+         * @return a new drawable object based on this constant state
+         */
+        public @NonNull Drawable newDrawable(@Nullable Resources res,
+                @Nullable @SuppressWarnings("unused") Theme theme) {
+            return newDrawable(res);
+        }
+
+        /**
+         * Return a bit mask of configuration changes that will impact
+         * this drawable (and thus require completely reloading it).
+         */
+        public abstract @Config int getChangingConfigurations();
+
+        /**
+         * Return whether this constant state can have a theme applied.
+         */
+        public boolean canApplyTheme() {
+            return false;
+        }
+    }
+
+    /**
+     * Return a {@link ConstantState} instance that holds the shared state of this Drawable.
+     *
+     * @return The ConstantState associated to that Drawable.
+     * @see ConstantState
+     * @see Drawable#mutate()
+     */
+    public @Nullable ConstantState getConstantState() {
+        return null;
+    }
+
+    private static Drawable drawableFromBitmap(Resources res, Bitmap bm, byte[] np,
+            Rect pad, Rect layoutBounds, String srcName) {
+
+        if (np != null) {
+            return new NinePatchDrawable(res, bm, np, pad, layoutBounds, srcName);
+        }
+
+        return new BitmapDrawable(res, bm);
+    }
+
+    /**
+     * Ensures the tint filter is consistent with the current tint color and
+     * mode.
+     */
+    @Nullable PorterDuffColorFilter updateTintFilter(@Nullable PorterDuffColorFilter tintFilter,
+            @Nullable ColorStateList tint, @Nullable PorterDuff.Mode tintMode) {
+        if (tint == null || tintMode == null) {
+            return null;
+        }
+
+        final int color = tint.getColorForState(getState(), Color.TRANSPARENT);
+        if (tintFilter == null) {
+            return new PorterDuffColorFilter(color, tintMode);
+        }
+
+        tintFilter.setColor(color);
+        tintFilter.setMode(tintMode);
+        return tintFilter;
+    }
+
+    /**
+     * Obtains styled attributes from the theme, if available, or unstyled
+     * resources if the theme is null.
+     * @hide
+     */
+    protected static @NonNull TypedArray obtainAttributes(@NonNull Resources res,
+            @Nullable Theme theme, @NonNull AttributeSet set, @NonNull int[] attrs) {
+        if (theme == null) {
+            return res.obtainAttributes(set, attrs);
+        }
+        return theme.obtainStyledAttributes(set, attrs, 0, 0);
+    }
+
+    /**
+     * Scales a floating-point pixel value from the source density to the
+     * target density.
+     *
+     * @param pixels the pixel value for use in source density
+     * @param sourceDensity the source density
+     * @param targetDensity the target density
+     * @return the scaled pixel value for use in target density
+     */
+    static float scaleFromDensity(float pixels, int sourceDensity, int targetDensity) {
+        return pixels * targetDensity / sourceDensity;
+    }
+
+    /**
+     * Scales a pixel value from the source density to the target density,
+     * optionally handling the resulting pixel value as a size rather than an
+     * offset.
+     * <p>
+     * A size conversion involves rounding the base value and ensuring that
+     * a non-zero base value is at least one pixel in size.
+     * <p>
+     * An offset conversion involves simply truncating the base value to an
+     * integer.
+     *
+     * @param pixels the pixel value for use in source density
+     * @param sourceDensity the source density
+     * @param targetDensity the target density
+     * @param isSize {@code true} to handle the resulting scaled value as a
+     *               size, or {@code false} to handle it as an offset
+     * @return the scaled pixel value for use in target density
+     */
+    static int scaleFromDensity(
+            int pixels, int sourceDensity, int targetDensity, boolean isSize) {
+        if (pixels == 0 || sourceDensity == targetDensity) {
+            return pixels;
+        }
+
+        final float result = pixels * targetDensity / (float) sourceDensity;
+        if (!isSize) {
+            return (int) result;
+        }
+
+        final int rounded = Math.round(result);
+        if (rounded != 0) {
+            return rounded;
+        } else if (pixels > 0) {
+            return 1;
+        } else {
+            return -1;
+        }
+    }
+
+    static int resolveDensity(@Nullable Resources r, int parentDensity) {
+        final int densityDpi = r == null ? parentDensity : r.getDisplayMetrics().densityDpi;
+        return densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+    }
+
+    /**
+     * Re-throws an exception as a {@link RuntimeException} with an empty stack
+     * trace to avoid cluttering the log. The original exception's stack trace
+     * will still be included.
+     *
+     * @param cause the exception to re-throw
+     * @throws RuntimeException
+     */
+    static void rethrowAsRuntimeException(@NonNull Exception cause) throws RuntimeException {
+        final RuntimeException e = new RuntimeException(cause);
+        e.setStackTrace(new StackTraceElement[0]);
+        throw e;
+    }
+
+    /**
+     * Parses a {@link android.graphics.PorterDuff.Mode} from a tintMode
+     * attribute's enum value.
+     *
+     * @hide
+     */
+    public static PorterDuff.Mode parseTintMode(int value, Mode defaultMode) {
+        switch (value) {
+            case 3: return Mode.SRC_OVER;
+            case 5: return Mode.SRC_IN;
+            case 9: return Mode.SRC_ATOP;
+            case 14: return Mode.MULTIPLY;
+            case 15: return Mode.SCREEN;
+            case 16: return Mode.ADD;
+            default: return defaultMode;
+        }
+    }
+}
+
diff --git a/android/graphics/drawable/DrawableContainer.java b/android/graphics/drawable/DrawableContainer.java
new file mode 100644
index 0000000..aa4cd9c
--- /dev/null
+++ b/android/graphics/drawable/DrawableContainer.java
@@ -0,0 +1,1277 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.content.pm.ActivityInfo.Config;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Insets;
+import android.graphics.Outline;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.util.DisplayMetrics;
+import android.util.LayoutDirection;
+import android.util.SparseArray;
+import android.view.View;
+
+/**
+ * A helper class that contains several {@link Drawable}s and selects which one to use.
+ *
+ * You can subclass it to create your own DrawableContainers or directly use one its child classes.
+ */
+public class DrawableContainer extends Drawable implements Drawable.Callback {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "DrawableContainer";
+
+    /**
+     * To be proper, we should have a getter for dither (and alpha, etc.)
+     * so that proxy classes like this can save/restore their delegates'
+     * values, but we don't have getters. Since we do have setters
+     * (e.g. setDither), which this proxy forwards on, we have to have some
+     * default/initial setting.
+     *
+     * The initial setting for dither is now true, since it almost always seems
+     * to improve the quality at negligible cost.
+     */
+    private static final boolean DEFAULT_DITHER = true;
+    private DrawableContainerState mDrawableContainerState;
+    private Rect mHotspotBounds;
+    private Drawable mCurrDrawable;
+    private Drawable mLastDrawable;
+    private int mAlpha = 0xFF;
+
+    /** Whether setAlpha() has been called at least once. */
+    private boolean mHasAlpha;
+
+    private int mCurIndex = -1;
+    private int mLastIndex = -1;
+    private boolean mMutated;
+
+    // Animations.
+    private Runnable mAnimationRunnable;
+    private long mEnterAnimationEnd;
+    private long mExitAnimationEnd;
+
+    /** Callback that blocks invalidation. Used for drawable initialization. */
+    private BlockInvalidateCallback mBlockInvalidateCallback;
+
+    // overrides from Drawable
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (mCurrDrawable != null) {
+            mCurrDrawable.draw(canvas);
+        }
+        if (mLastDrawable != null) {
+            mLastDrawable.draw(canvas);
+        }
+    }
+
+    @Override
+    public @Config int getChangingConfigurations() {
+        return super.getChangingConfigurations()
+                | mDrawableContainerState.getChangingConfigurations();
+    }
+
+    private boolean needsMirroring() {
+        return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
+    }
+
+    @Override
+    public boolean getPadding(Rect padding) {
+        final Rect r = mDrawableContainerState.getConstantPadding();
+        boolean result;
+        if (r != null) {
+            padding.set(r);
+            result = (r.left | r.top | r.bottom | r.right) != 0;
+        } else {
+            if (mCurrDrawable != null) {
+                result = mCurrDrawable.getPadding(padding);
+            } else {
+                result = super.getPadding(padding);
+            }
+        }
+        if (needsMirroring()) {
+            final int left = padding.left;
+            final int right = padding.right;
+            padding.left = right;
+            padding.right = left;
+        }
+        return result;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public Insets getOpticalInsets() {
+        if (mCurrDrawable != null) {
+            return mCurrDrawable.getOpticalInsets();
+        }
+        return Insets.NONE;
+    }
+
+    @Override
+    public void getOutline(@NonNull Outline outline) {
+        if (mCurrDrawable != null) {
+            mCurrDrawable.getOutline(outline);
+        }
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        if (!mHasAlpha || mAlpha != alpha) {
+            mHasAlpha = true;
+            mAlpha = alpha;
+            if (mCurrDrawable != null) {
+                if (mEnterAnimationEnd == 0) {
+                    mCurrDrawable.setAlpha(alpha);
+                } else {
+                    animate(false);
+                }
+            }
+        }
+    }
+
+    @Override
+    public int getAlpha() {
+        return mAlpha;
+    }
+
+    @Override
+    public void setDither(boolean dither) {
+        if (mDrawableContainerState.mDither != dither) {
+            mDrawableContainerState.mDither = dither;
+            if (mCurrDrawable != null) {
+                mCurrDrawable.setDither(mDrawableContainerState.mDither);
+            }
+        }
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        mDrawableContainerState.mHasColorFilter = true;
+
+        if (mDrawableContainerState.mColorFilter != colorFilter) {
+            mDrawableContainerState.mColorFilter = colorFilter;
+
+            if (mCurrDrawable != null) {
+                mCurrDrawable.setColorFilter(colorFilter);
+            }
+        }
+    }
+
+    @Override
+    public void setTintList(ColorStateList tint) {
+        mDrawableContainerState.mHasTintList = true;
+
+        if (mDrawableContainerState.mTintList != tint) {
+            mDrawableContainerState.mTintList = tint;
+
+            if (mCurrDrawable != null) {
+                mCurrDrawable.setTintList(tint);
+            }
+        }
+    }
+
+    @Override
+    public void setTintMode(Mode tintMode) {
+        mDrawableContainerState.mHasTintMode = true;
+
+        if (mDrawableContainerState.mTintMode != tintMode) {
+            mDrawableContainerState.mTintMode = tintMode;
+
+            if (mCurrDrawable != null) {
+                mCurrDrawable.setTintMode(tintMode);
+            }
+        }
+    }
+
+    /**
+     * Change the global fade duration when a new drawable is entering
+     * the scene.
+     *
+     * @param ms The amount of time to fade in milliseconds.
+     */
+    public void setEnterFadeDuration(int ms) {
+        mDrawableContainerState.mEnterFadeDuration = ms;
+    }
+
+    /**
+     * Change the global fade duration when a new drawable is leaving
+     * the scene.
+     *
+     * @param ms The amount of time to fade in milliseconds.
+     */
+    public void setExitFadeDuration(int ms) {
+        mDrawableContainerState.mExitFadeDuration = ms;
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        if (mLastDrawable != null) {
+            mLastDrawable.setBounds(bounds);
+        }
+        if (mCurrDrawable != null) {
+            mCurrDrawable.setBounds(bounds);
+        }
+    }
+
+    @Override
+    public boolean isStateful() {
+        return mDrawableContainerState.isStateful();
+    }
+
+    /** @hide */
+    @Override
+    public boolean hasFocusStateSpecified() {
+        if (mCurrDrawable != null) {
+            return mCurrDrawable.hasFocusStateSpecified();
+        }
+        if (mLastDrawable != null) {
+            return mLastDrawable.hasFocusStateSpecified();
+        }
+        return false;
+    }
+
+    @Override
+    public void setAutoMirrored(boolean mirrored) {
+        if (mDrawableContainerState.mAutoMirrored != mirrored) {
+            mDrawableContainerState.mAutoMirrored = mirrored;
+            if (mCurrDrawable != null) {
+                mCurrDrawable.setAutoMirrored(mDrawableContainerState.mAutoMirrored);
+            }
+        }
+    }
+
+    @Override
+    public boolean isAutoMirrored() {
+        return mDrawableContainerState.mAutoMirrored;
+    }
+
+    @Override
+    public void jumpToCurrentState() {
+        boolean changed = false;
+        if (mLastDrawable != null) {
+            mLastDrawable.jumpToCurrentState();
+            mLastDrawable = null;
+            mLastIndex = -1;
+            changed = true;
+        }
+        if (mCurrDrawable != null) {
+            mCurrDrawable.jumpToCurrentState();
+            if (mHasAlpha) {
+                mCurrDrawable.setAlpha(mAlpha);
+            }
+        }
+        if (mExitAnimationEnd != 0) {
+            mExitAnimationEnd = 0;
+            changed = true;
+        }
+        if (mEnterAnimationEnd != 0) {
+            mEnterAnimationEnd = 0;
+            changed = true;
+        }
+        if (changed) {
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public void setHotspot(float x, float y) {
+        if (mCurrDrawable != null) {
+            mCurrDrawable.setHotspot(x, y);
+        }
+    }
+
+    @Override
+    public void setHotspotBounds(int left, int top, int right, int bottom) {
+        if (mHotspotBounds == null) {
+            mHotspotBounds = new Rect(left, top, right, bottom);
+        } else {
+            mHotspotBounds.set(left, top, right, bottom);
+        }
+
+        if (mCurrDrawable != null) {
+            mCurrDrawable.setHotspotBounds(left, top, right, bottom);
+        }
+    }
+
+    @Override
+    public void getHotspotBounds(Rect outRect) {
+        if (mHotspotBounds != null) {
+            outRect.set(mHotspotBounds);
+        } else {
+            super.getHotspotBounds(outRect);
+        }
+    }
+
+    @Override
+    protected boolean onStateChange(int[] state) {
+        if (mLastDrawable != null) {
+            return mLastDrawable.setState(state);
+        }
+        if (mCurrDrawable != null) {
+            return mCurrDrawable.setState(state);
+        }
+        return false;
+    }
+
+    @Override
+    protected boolean onLevelChange(int level) {
+        if (mLastDrawable != null) {
+            return mLastDrawable.setLevel(level);
+        }
+        if (mCurrDrawable != null) {
+            return mCurrDrawable.setLevel(level);
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onLayoutDirectionChanged(@View.ResolvedLayoutDir int layoutDirection) {
+        // Let the container handle setting its own layout direction. Otherwise,
+        // we're accessing potentially unused states.
+        return mDrawableContainerState.setLayoutDirection(layoutDirection, getCurrentIndex());
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        if (mDrawableContainerState.isConstantSize()) {
+            return mDrawableContainerState.getConstantWidth();
+        }
+        return mCurrDrawable != null ? mCurrDrawable.getIntrinsicWidth() : -1;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        if (mDrawableContainerState.isConstantSize()) {
+            return mDrawableContainerState.getConstantHeight();
+        }
+        return mCurrDrawable != null ? mCurrDrawable.getIntrinsicHeight() : -1;
+    }
+
+    @Override
+    public int getMinimumWidth() {
+        if (mDrawableContainerState.isConstantSize()) {
+            return mDrawableContainerState.getConstantMinimumWidth();
+        }
+        return mCurrDrawable != null ? mCurrDrawable.getMinimumWidth() : 0;
+    }
+
+    @Override
+    public int getMinimumHeight() {
+        if (mDrawableContainerState.isConstantSize()) {
+            return mDrawableContainerState.getConstantMinimumHeight();
+        }
+        return mCurrDrawable != null ? mCurrDrawable.getMinimumHeight() : 0;
+    }
+
+    @Override
+    public void invalidateDrawable(@NonNull Drawable who) {
+        // This may have been called as the result of a tint changing, in
+        // which case we may need to refresh the cached statefulness or
+        // opacity.
+        if (mDrawableContainerState != null) {
+            mDrawableContainerState.invalidateCache();
+        }
+
+        if (who == mCurrDrawable && getCallback() != null) {
+            getCallback().invalidateDrawable(this);
+        }
+    }
+
+    @Override
+    public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
+        if (who == mCurrDrawable && getCallback() != null) {
+            getCallback().scheduleDrawable(this, what, when);
+        }
+    }
+
+    @Override
+    public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
+        if (who == mCurrDrawable && getCallback() != null) {
+            getCallback().unscheduleDrawable(this, what);
+        }
+    }
+
+    @Override
+    public boolean setVisible(boolean visible, boolean restart) {
+        boolean changed = super.setVisible(visible, restart);
+        if (mLastDrawable != null) {
+            mLastDrawable.setVisible(visible, restart);
+        }
+        if (mCurrDrawable != null) {
+            mCurrDrawable.setVisible(visible, restart);
+        }
+        return changed;
+    }
+
+    @Override
+    public int getOpacity() {
+        return mCurrDrawable == null || !mCurrDrawable.isVisible() ? PixelFormat.TRANSPARENT :
+                mDrawableContainerState.getOpacity();
+    }
+
+    /** @hide */
+    public void setCurrentIndex(int index) {
+        selectDrawable(index);
+    }
+
+    /** @hide */
+    public int getCurrentIndex() {
+        return mCurIndex;
+    }
+
+    /**
+     * Sets the currently displayed drawable by index.
+     * <p>
+     * If an invalid index is specified, the current drawable will be set to
+     * {@code null} and the index will be set to {@code -1}.
+     *
+     * @param index the index of the drawable to display
+     * @return {@code true} if the drawable changed, {@code false} otherwise
+     */
+    public boolean selectDrawable(int index) {
+        if (index == mCurIndex) {
+            return false;
+        }
+
+        final long now = SystemClock.uptimeMillis();
+
+        if (DEBUG) android.util.Log.i(TAG, toString() + " from " + mCurIndex + " to " + index
+                + ": exit=" + mDrawableContainerState.mExitFadeDuration
+                + " enter=" + mDrawableContainerState.mEnterFadeDuration);
+
+        if (mDrawableContainerState.mExitFadeDuration > 0) {
+            if (mLastDrawable != null) {
+                mLastDrawable.setVisible(false, false);
+            }
+            if (mCurrDrawable != null) {
+                mLastDrawable = mCurrDrawable;
+                mLastIndex = mCurIndex;
+                mExitAnimationEnd = now + mDrawableContainerState.mExitFadeDuration;
+            } else {
+                mLastDrawable = null;
+                mLastIndex = -1;
+                mExitAnimationEnd = 0;
+            }
+        } else if (mCurrDrawable != null) {
+            mCurrDrawable.setVisible(false, false);
+        }
+
+        if (index >= 0 && index < mDrawableContainerState.mNumChildren) {
+            final Drawable d = mDrawableContainerState.getChild(index);
+            mCurrDrawable = d;
+            mCurIndex = index;
+            if (d != null) {
+                if (mDrawableContainerState.mEnterFadeDuration > 0) {
+                    mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration;
+                }
+                initializeDrawableForDisplay(d);
+            }
+        } else {
+            mCurrDrawable = null;
+            mCurIndex = -1;
+        }
+
+        if (mEnterAnimationEnd != 0 || mExitAnimationEnd != 0) {
+            if (mAnimationRunnable == null) {
+                mAnimationRunnable = new Runnable() {
+                    @Override public void run() {
+                        animate(true);
+                        invalidateSelf();
+                    }
+                };
+            } else {
+                unscheduleSelf(mAnimationRunnable);
+            }
+            // Compute first frame and schedule next animation.
+            animate(true);
+        }
+
+        invalidateSelf();
+
+        return true;
+    }
+
+    /**
+     * Initializes a drawable for display in this container.
+     *
+     * @param d The drawable to initialize.
+     */
+    private void initializeDrawableForDisplay(Drawable d) {
+        if (mBlockInvalidateCallback == null) {
+            mBlockInvalidateCallback = new BlockInvalidateCallback();
+        }
+
+        // Temporary fix for suspending callbacks during initialization. We
+        // don't want any of these setters causing an invalidate() since that
+        // may call back into DrawableContainer.
+        d.setCallback(mBlockInvalidateCallback.wrap(d.getCallback()));
+
+        try {
+            if (mDrawableContainerState.mEnterFadeDuration <= 0 && mHasAlpha) {
+                d.setAlpha(mAlpha);
+            }
+
+            if (mDrawableContainerState.mHasColorFilter) {
+                // Color filter always overrides tint.
+                d.setColorFilter(mDrawableContainerState.mColorFilter);
+            } else {
+                if (mDrawableContainerState.mHasTintList) {
+                    d.setTintList(mDrawableContainerState.mTintList);
+                }
+                if (mDrawableContainerState.mHasTintMode) {
+                    d.setTintMode(mDrawableContainerState.mTintMode);
+                }
+            }
+
+            d.setVisible(isVisible(), true);
+            d.setDither(mDrawableContainerState.mDither);
+            d.setState(getState());
+            d.setLevel(getLevel());
+            d.setBounds(getBounds());
+            d.setLayoutDirection(getLayoutDirection());
+            d.setAutoMirrored(mDrawableContainerState.mAutoMirrored);
+
+            final Rect hotspotBounds = mHotspotBounds;
+            if (hotspotBounds != null) {
+                d.setHotspotBounds(hotspotBounds.left, hotspotBounds.top,
+                        hotspotBounds.right, hotspotBounds.bottom);
+            }
+        } finally {
+            d.setCallback(mBlockInvalidateCallback.unwrap());
+        }
+    }
+
+    void animate(boolean schedule) {
+        mHasAlpha = true;
+
+        final long now = SystemClock.uptimeMillis();
+        boolean animating = false;
+        if (mCurrDrawable != null) {
+            if (mEnterAnimationEnd != 0) {
+                if (mEnterAnimationEnd <= now) {
+                    mCurrDrawable.setAlpha(mAlpha);
+                    mEnterAnimationEnd = 0;
+                } else {
+                    int animAlpha = (int)((mEnterAnimationEnd-now)*255)
+                            / mDrawableContainerState.mEnterFadeDuration;
+                    mCurrDrawable.setAlpha(((255-animAlpha)*mAlpha)/255);
+                    animating = true;
+                }
+            }
+        } else {
+            mEnterAnimationEnd = 0;
+        }
+        if (mLastDrawable != null) {
+            if (mExitAnimationEnd != 0) {
+                if (mExitAnimationEnd <= now) {
+                    mLastDrawable.setVisible(false, false);
+                    mLastDrawable = null;
+                    mLastIndex = -1;
+                    mExitAnimationEnd = 0;
+                } else {
+                    int animAlpha = (int)((mExitAnimationEnd-now)*255)
+                            / mDrawableContainerState.mExitFadeDuration;
+                    mLastDrawable.setAlpha((animAlpha*mAlpha)/255);
+                    animating = true;
+                }
+            }
+        } else {
+            mExitAnimationEnd = 0;
+        }
+
+        if (schedule && animating) {
+            scheduleSelf(mAnimationRunnable, now + 1000 / 60);
+        }
+    }
+
+    @Override
+    public Drawable getCurrent() {
+        return mCurrDrawable;
+    }
+
+    /**
+     * Updates the source density based on the resources used to inflate
+     * density-dependent values. Implementing classes should call this method
+     * during inflation.
+     *
+     * @param res the resources used to inflate density-dependent values
+     * @hide
+     */
+    protected final void updateDensity(Resources res) {
+        mDrawableContainerState.updateDensity(res);
+    }
+
+    @Override
+    public void applyTheme(Theme theme) {
+        mDrawableContainerState.applyTheme(theme);
+    }
+
+    @Override
+    public boolean canApplyTheme() {
+        return mDrawableContainerState.canApplyTheme();
+    }
+
+    @Override
+    public ConstantState getConstantState() {
+        if (mDrawableContainerState.canConstantState()) {
+            mDrawableContainerState.mChangingConfigurations = getChangingConfigurations();
+            return mDrawableContainerState;
+        }
+        return null;
+    }
+
+    @Override
+    public Drawable mutate() {
+        if (!mMutated && super.mutate() == this) {
+            final DrawableContainerState clone = cloneConstantState();
+            clone.mutate();
+            setConstantState(clone);
+            mMutated = true;
+        }
+        return this;
+    }
+
+    /**
+     * Returns a shallow copy of the container's constant state to be used as
+     * the base state for {@link #mutate()}.
+     *
+     * @return a shallow copy of the constant state
+     */
+    DrawableContainerState cloneConstantState() {
+        return mDrawableContainerState;
+    }
+
+    /**
+     * @hide
+     */
+    public void clearMutated() {
+        super.clearMutated();
+        mDrawableContainerState.clearMutated();
+        mMutated = false;
+    }
+
+    /**
+     * A ConstantState that can contain several {@link Drawable}s.
+     *
+     * This class was made public to enable testing, and its visibility may change in a future
+     * release.
+     */
+    public abstract static class DrawableContainerState extends ConstantState {
+        final DrawableContainer mOwner;
+
+        Resources mSourceRes;
+        int mDensity = DisplayMetrics.DENSITY_DEFAULT;
+        @Config int mChangingConfigurations;
+        @Config int mChildrenChangingConfigurations;
+
+        SparseArray<ConstantState> mDrawableFutures;
+        Drawable[] mDrawables;
+        int mNumChildren;
+
+        boolean mVariablePadding = false;
+        boolean mCheckedPadding;
+        Rect mConstantPadding;
+
+        boolean mConstantSize = false;
+        boolean mCheckedConstantSize;
+        int mConstantWidth;
+        int mConstantHeight;
+        int mConstantMinimumWidth;
+        int mConstantMinimumHeight;
+
+        boolean mCheckedOpacity;
+        int mOpacity;
+
+        boolean mCheckedStateful;
+        boolean mStateful;
+
+        boolean mCheckedConstantState;
+        boolean mCanConstantState;
+
+        boolean mDither = DEFAULT_DITHER;
+
+        boolean mMutated;
+        int mLayoutDirection;
+
+        int mEnterFadeDuration = 0;
+        int mExitFadeDuration = 0;
+
+        boolean mAutoMirrored;
+
+        ColorFilter mColorFilter;
+        boolean mHasColorFilter;
+
+        ColorStateList mTintList;
+        Mode mTintMode;
+        boolean mHasTintList;
+        boolean mHasTintMode;
+
+        /**
+         * @hide
+         */
+        protected DrawableContainerState(DrawableContainerState orig, DrawableContainer owner,
+                Resources res) {
+            mOwner = owner;
+            mSourceRes = res != null ? res : (orig != null ? orig.mSourceRes : null);
+            mDensity = Drawable.resolveDensity(res, orig != null ? orig.mDensity : 0);
+
+            if (orig != null) {
+                mChangingConfigurations = orig.mChangingConfigurations;
+                mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
+
+                mCheckedConstantState = true;
+                mCanConstantState = true;
+
+                mVariablePadding = orig.mVariablePadding;
+                mConstantSize = orig.mConstantSize;
+                mDither = orig.mDither;
+                mMutated = orig.mMutated;
+                mLayoutDirection = orig.mLayoutDirection;
+                mEnterFadeDuration = orig.mEnterFadeDuration;
+                mExitFadeDuration = orig.mExitFadeDuration;
+                mAutoMirrored = orig.mAutoMirrored;
+                mColorFilter = orig.mColorFilter;
+                mHasColorFilter = orig.mHasColorFilter;
+                mTintList = orig.mTintList;
+                mTintMode = orig.mTintMode;
+                mHasTintList = orig.mHasTintList;
+                mHasTintMode = orig.mHasTintMode;
+
+                if (orig.mDensity == mDensity) {
+                    if (orig.mCheckedPadding) {
+                        mConstantPadding = new Rect(orig.mConstantPadding);
+                        mCheckedPadding = true;
+                    }
+
+                    if (orig.mCheckedConstantSize) {
+                        mConstantWidth = orig.mConstantWidth;
+                        mConstantHeight = orig.mConstantHeight;
+                        mConstantMinimumWidth = orig.mConstantMinimumWidth;
+                        mConstantMinimumHeight = orig.mConstantMinimumHeight;
+                        mCheckedConstantSize = true;
+                    }
+                }
+
+                if (orig.mCheckedOpacity) {
+                    mOpacity = orig.mOpacity;
+                    mCheckedOpacity = true;
+                }
+
+                if (orig.mCheckedStateful) {
+                    mStateful = orig.mStateful;
+                    mCheckedStateful = true;
+                }
+
+                // Postpone cloning children and futures until we're absolutely
+                // sure that we're done computing values for the original state.
+                final Drawable[] origDr = orig.mDrawables;
+                mDrawables = new Drawable[origDr.length];
+                mNumChildren = orig.mNumChildren;
+
+                final SparseArray<ConstantState> origDf = orig.mDrawableFutures;
+                if (origDf != null) {
+                    mDrawableFutures = origDf.clone();
+                } else {
+                    mDrawableFutures = new SparseArray<>(mNumChildren);
+                }
+
+                // Create futures for drawables with constant states. If a
+                // drawable doesn't have a constant state, then we can't clone
+                // it and we'll have to reference the original.
+                final int N = mNumChildren;
+                for (int i = 0; i < N; i++) {
+                    if (origDr[i] != null) {
+                        final ConstantState cs = origDr[i].getConstantState();
+                        if (cs != null) {
+                            mDrawableFutures.put(i, cs);
+                        } else {
+                            mDrawables[i] = origDr[i];
+                        }
+                    }
+                }
+            } else {
+                mDrawables = new Drawable[10];
+                mNumChildren = 0;
+            }
+        }
+
+        @Override
+        public @Config int getChangingConfigurations() {
+            return mChangingConfigurations | mChildrenChangingConfigurations;
+        }
+
+        /**
+         * Adds the drawable to the end of the list of contained drawables.
+         *
+         * @param dr the drawable to add
+         * @return the position of the drawable within the container
+         */
+        public final int addChild(Drawable dr) {
+            final int pos = mNumChildren;
+            if (pos >= mDrawables.length) {
+                growArray(pos, pos+10);
+            }
+
+            dr.mutate();
+            dr.setVisible(false, true);
+            dr.setCallback(mOwner);
+
+            mDrawables[pos] = dr;
+            mNumChildren++;
+            mChildrenChangingConfigurations |= dr.getChangingConfigurations();
+
+            invalidateCache();
+
+            mConstantPadding = null;
+            mCheckedPadding = false;
+            mCheckedConstantSize = false;
+            mCheckedConstantState = false;
+
+            return pos;
+        }
+
+        /**
+         * Invalidates the cached opacity and statefulness.
+         */
+        void invalidateCache() {
+            mCheckedOpacity = false;
+            mCheckedStateful = false;
+        }
+
+        final int getCapacity() {
+            return mDrawables.length;
+        }
+
+        private void createAllFutures() {
+            if (mDrawableFutures != null) {
+                final int futureCount = mDrawableFutures.size();
+                for (int keyIndex = 0; keyIndex < futureCount; keyIndex++) {
+                    final int index = mDrawableFutures.keyAt(keyIndex);
+                    final ConstantState cs = mDrawableFutures.valueAt(keyIndex);
+                    mDrawables[index] = prepareDrawable(cs.newDrawable(mSourceRes));
+                }
+
+                mDrawableFutures = null;
+            }
+        }
+
+        private Drawable prepareDrawable(Drawable child) {
+            child.setLayoutDirection(mLayoutDirection);
+            child = child.mutate();
+            child.setCallback(mOwner);
+            return child;
+        }
+
+        public final int getChildCount() {
+            return mNumChildren;
+        }
+
+        /*
+         * @deprecated Use {@link #getChild} instead.
+         */
+        public final Drawable[] getChildren() {
+            // Create all futures for backwards compatibility.
+            createAllFutures();
+
+            return mDrawables;
+        }
+
+        public final Drawable getChild(int index) {
+            final Drawable result = mDrawables[index];
+            if (result != null) {
+                return result;
+            }
+
+            // Prepare future drawable if necessary.
+            if (mDrawableFutures != null) {
+                final int keyIndex = mDrawableFutures.indexOfKey(index);
+                if (keyIndex >= 0) {
+                    final ConstantState cs = mDrawableFutures.valueAt(keyIndex);
+                    final Drawable prepared = prepareDrawable(cs.newDrawable(mSourceRes));
+                    mDrawables[index] = prepared;
+                    mDrawableFutures.removeAt(keyIndex);
+                    if (mDrawableFutures.size() == 0) {
+                        mDrawableFutures = null;
+                    }
+                    return prepared;
+                }
+            }
+
+            return null;
+        }
+
+        final boolean setLayoutDirection(int layoutDirection, int currentIndex) {
+            boolean changed = false;
+
+            // No need to call createAllFutures, since future drawables will
+            // change layout direction when they are prepared.
+            final int N = mNumChildren;
+            final Drawable[] drawables = mDrawables;
+            for (int i = 0; i < N; i++) {
+                if (drawables[i] != null) {
+                    final boolean childChanged = drawables[i].setLayoutDirection(layoutDirection);
+                    if (i == currentIndex) {
+                        changed = childChanged;
+                    }
+                }
+            }
+
+            mLayoutDirection = layoutDirection;
+
+            return changed;
+        }
+
+        /**
+         * Updates the source density based on the resources used to inflate
+         * density-dependent values.
+         *
+         * @param res the resources used to inflate density-dependent values
+         */
+        final void updateDensity(Resources res) {
+            if (res != null) {
+                mSourceRes = res;
+
+                // The density may have changed since the last update (if any). Any
+                // dimension-type attributes will need their default values scaled.
+                final int targetDensity = Drawable.resolveDensity(res, mDensity);
+                final int sourceDensity = mDensity;
+                mDensity = targetDensity;
+
+                if (sourceDensity != targetDensity) {
+                    mCheckedConstantSize = false;
+                    mCheckedPadding = false;
+                }
+            }
+        }
+
+        final void applyTheme(Theme theme) {
+            if (theme != null) {
+                createAllFutures();
+
+                final int N = mNumChildren;
+                final Drawable[] drawables = mDrawables;
+                for (int i = 0; i < N; i++) {
+                    if (drawables[i] != null && drawables[i].canApplyTheme()) {
+                        drawables[i].applyTheme(theme);
+
+                        // Update cached mask of child changing configurations.
+                        mChildrenChangingConfigurations |= drawables[i].getChangingConfigurations();
+                    }
+                }
+
+                updateDensity(theme.getResources());
+            }
+        }
+
+        @Override
+        public boolean canApplyTheme() {
+            final int N = mNumChildren;
+            final Drawable[] drawables = mDrawables;
+            for (int i = 0; i < N; i++) {
+                final Drawable d = drawables[i];
+                if (d != null) {
+                    if (d.canApplyTheme()) {
+                        return true;
+                    }
+                } else {
+                    final ConstantState future = mDrawableFutures.get(i);
+                    if (future != null && future.canApplyTheme()) {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
+
+        private void mutate() {
+            // No need to call createAllFutures, since future drawables will
+            // mutate when they are prepared.
+            final int N = mNumChildren;
+            final Drawable[] drawables = mDrawables;
+            for (int i = 0; i < N; i++) {
+                if (drawables[i] != null) {
+                    drawables[i].mutate();
+                }
+            }
+
+            mMutated = true;
+        }
+
+        final void clearMutated() {
+            final int N = mNumChildren;
+            final Drawable[] drawables = mDrawables;
+            for (int i = 0; i < N; i++) {
+                if (drawables[i] != null) {
+                    drawables[i].clearMutated();
+                }
+            }
+
+            mMutated = false;
+        }
+
+        /**
+         * A boolean value indicating whether to use the maximum padding value
+         * of all frames in the set (false), or to use the padding value of the
+         * frame being shown (true). Default value is false.
+         */
+        public final void setVariablePadding(boolean variable) {
+            mVariablePadding = variable;
+        }
+
+        public final Rect getConstantPadding() {
+            if (mVariablePadding) {
+                return null;
+            }
+
+            if ((mConstantPadding != null) || mCheckedPadding) {
+                return mConstantPadding;
+            }
+
+            createAllFutures();
+
+            Rect r = null;
+            final Rect t = new Rect();
+            final int N = mNumChildren;
+            final Drawable[] drawables = mDrawables;
+            for (int i = 0; i < N; i++) {
+                if (drawables[i].getPadding(t)) {
+                    if (r == null) r = new Rect(0, 0, 0, 0);
+                    if (t.left > r.left) r.left = t.left;
+                    if (t.top > r.top) r.top = t.top;
+                    if (t.right > r.right) r.right = t.right;
+                    if (t.bottom > r.bottom) r.bottom = t.bottom;
+                }
+            }
+
+            mCheckedPadding = true;
+            return (mConstantPadding = r);
+        }
+
+        public final void setConstantSize(boolean constant) {
+            mConstantSize = constant;
+        }
+
+        public final boolean isConstantSize() {
+            return mConstantSize;
+        }
+
+        public final int getConstantWidth() {
+            if (!mCheckedConstantSize) {
+                computeConstantSize();
+            }
+
+            return mConstantWidth;
+        }
+
+        public final int getConstantHeight() {
+            if (!mCheckedConstantSize) {
+                computeConstantSize();
+            }
+
+            return mConstantHeight;
+        }
+
+        public final int getConstantMinimumWidth() {
+            if (!mCheckedConstantSize) {
+                computeConstantSize();
+            }
+
+            return mConstantMinimumWidth;
+        }
+
+        public final int getConstantMinimumHeight() {
+            if (!mCheckedConstantSize) {
+                computeConstantSize();
+            }
+
+            return mConstantMinimumHeight;
+        }
+
+        protected void computeConstantSize() {
+            mCheckedConstantSize = true;
+
+            createAllFutures();
+
+            final int N = mNumChildren;
+            final Drawable[] drawables = mDrawables;
+            mConstantWidth = mConstantHeight = -1;
+            mConstantMinimumWidth = mConstantMinimumHeight = 0;
+            for (int i = 0; i < N; i++) {
+                final Drawable dr = drawables[i];
+                int s = dr.getIntrinsicWidth();
+                if (s > mConstantWidth) mConstantWidth = s;
+                s = dr.getIntrinsicHeight();
+                if (s > mConstantHeight) mConstantHeight = s;
+                s = dr.getMinimumWidth();
+                if (s > mConstantMinimumWidth) mConstantMinimumWidth = s;
+                s = dr.getMinimumHeight();
+                if (s > mConstantMinimumHeight) mConstantMinimumHeight = s;
+            }
+        }
+
+        public final void setEnterFadeDuration(int duration) {
+            mEnterFadeDuration = duration;
+        }
+
+        public final int getEnterFadeDuration() {
+            return mEnterFadeDuration;
+        }
+
+        public final void setExitFadeDuration(int duration) {
+            mExitFadeDuration = duration;
+        }
+
+        public final int getExitFadeDuration() {
+            return mExitFadeDuration;
+        }
+
+        public final int getOpacity() {
+            if (mCheckedOpacity) {
+                return mOpacity;
+            }
+
+            createAllFutures();
+
+            final int N = mNumChildren;
+            final Drawable[] drawables = mDrawables;
+            int op = (N > 0) ? drawables[0].getOpacity() : PixelFormat.TRANSPARENT;
+            for (int i = 1; i < N; i++) {
+                op = Drawable.resolveOpacity(op, drawables[i].getOpacity());
+            }
+
+            mOpacity = op;
+            mCheckedOpacity = true;
+            return op;
+        }
+
+        public final boolean isStateful() {
+            if (mCheckedStateful) {
+                return mStateful;
+            }
+
+            createAllFutures();
+
+            final int N = mNumChildren;
+            final Drawable[] drawables = mDrawables;
+            boolean isStateful = false;
+            for (int i = 0; i < N; i++) {
+                if (drawables[i].isStateful()) {
+                    isStateful = true;
+                    break;
+                }
+            }
+
+            mStateful = isStateful;
+            mCheckedStateful = true;
+            return isStateful;
+        }
+
+        public void growArray(int oldSize, int newSize) {
+            Drawable[] newDrawables = new Drawable[newSize];
+            System.arraycopy(mDrawables, 0, newDrawables, 0, oldSize);
+            mDrawables = newDrawables;
+        }
+
+        public synchronized boolean canConstantState() {
+            if (mCheckedConstantState) {
+                return mCanConstantState;
+            }
+
+            createAllFutures();
+
+            mCheckedConstantState = true;
+
+            final int N = mNumChildren;
+            final Drawable[] drawables = mDrawables;
+            for (int i = 0; i < N; i++) {
+                if (drawables[i].getConstantState() == null) {
+                    mCanConstantState = false;
+                    return false;
+                }
+            }
+
+            mCanConstantState = true;
+            return true;
+        }
+
+    }
+
+    protected void setConstantState(DrawableContainerState state) {
+        mDrawableContainerState = state;
+
+        // The locally cached drawables may have changed.
+        if (mCurIndex >= 0) {
+            mCurrDrawable = state.getChild(mCurIndex);
+            if (mCurrDrawable != null) {
+                initializeDrawableForDisplay(mCurrDrawable);
+            }
+        }
+
+        // Clear out the last drawable. We don't have enough information to
+        // propagate local state from the past.
+        mLastIndex = -1;
+        mLastDrawable = null;
+    }
+
+    /**
+     * Callback that blocks drawable invalidation.
+     */
+    private static class BlockInvalidateCallback implements Drawable.Callback {
+        private Drawable.Callback mCallback;
+
+        public BlockInvalidateCallback wrap(Drawable.Callback callback) {
+            mCallback = callback;
+            return this;
+        }
+
+        public Drawable.Callback unwrap() {
+            final Drawable.Callback callback = mCallback;
+            mCallback = null;
+            return callback;
+        }
+
+        @Override
+        public void invalidateDrawable(@NonNull Drawable who) {
+            // Ignore invalidation.
+        }
+
+        @Override
+        public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
+            if (mCallback != null) {
+                mCallback.scheduleDrawable(who, what, when);
+            }
+        }
+
+        @Override
+        public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
+            if (mCallback != null) {
+                mCallback.unscheduleDrawable(who, what);
+            }
+        }
+    }
+}
diff --git a/android/graphics/drawable/DrawableInflater.java b/android/graphics/drawable/DrawableInflater.java
new file mode 100644
index 0000000..eea7048
--- /dev/null
+++ b/android/graphics/drawable/DrawableInflater.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2015 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.annotation.DrawableRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.util.AttributeSet;
+import android.view.InflateException;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.util.HashMap;
+
+/**
+ * Instantiates a drawable XML file into its corresponding
+ * {@link android.graphics.drawable.Drawable} objects.
+ * <p>
+ * For performance reasons, inflation relies heavily on pre-processing of
+ * XML files that is done at build time. Therefore, it is not currently possible
+ * to use this inflater with an XmlPullParser over a plain XML file at runtime;
+ * it only works with an XmlPullParser returned from a compiled resource (R.
+ * <em>something</em> file.)
+ *
+ * @hide Pending API finalization.
+ */
+public final class DrawableInflater {
+    private static final HashMap<String, Constructor<? extends Drawable>> CONSTRUCTOR_MAP =
+            new HashMap<>();
+
+    private final Resources mRes;
+    private final ClassLoader mClassLoader;
+
+    /**
+     * Loads the drawable resource with the specified identifier.
+     *
+     * @param context the context in which the drawable should be loaded
+     * @param id the identifier of the drawable resource
+     * @return a drawable, or {@code null} if the drawable failed to load
+     */
+    @Nullable
+    public static Drawable loadDrawable(@NonNull Context context, @DrawableRes int id) {
+        return loadDrawable(context.getResources(), context.getTheme(), id);
+    }
+
+    /**
+     * Loads the drawable resource with the specified identifier.
+     *
+     * @param resources the resources from which the drawable should be loaded
+     * @param theme the theme against which the drawable should be inflated
+     * @param id the identifier of the drawable resource
+     * @return a drawable, or {@code null} if the drawable failed to load
+     */
+    @Nullable
+    public static Drawable loadDrawable(
+            @NonNull Resources resources, @Nullable Theme theme, @DrawableRes int id) {
+        return resources.getDrawable(id, theme);
+    }
+
+    /**
+     * Constructs a new drawable inflater using the specified resources and
+     * class loader.
+     *
+     * @param res the resources used to resolve resource identifiers
+     * @param classLoader the class loader used to load custom drawables
+     * @hide
+     */
+    public DrawableInflater(@NonNull Resources res, @NonNull ClassLoader classLoader) {
+        mRes = res;
+        mClassLoader = classLoader;
+    }
+
+    /**
+     * Inflates a drawable from inside an XML document using an optional
+     * {@link Theme}.
+     * <p>
+     * This method should be called on a parser positioned at a tag in an XML
+     * document defining a drawable resource. It will attempt to create a
+     * Drawable from the tag at the current position.
+     *
+     * @param name the name of the tag at the current position
+     * @param parser an XML parser positioned at the drawable tag
+     * @param attrs an attribute set that wraps the parser
+     * @param theme the theme against which the drawable should be inflated, or
+     *              {@code null} to not inflate against a theme
+     * @return a drawable
+     *
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    @NonNull
+    public Drawable inflateFromXml(@NonNull String name, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
+        return inflateFromXmlForDensity(name, parser, attrs, 0, theme);
+    }
+
+    /**
+     * Version of {@link #inflateFromXml(String, XmlPullParser, AttributeSet, Theme)} that accepts
+     * an override density.
+     */
+    @NonNull
+    Drawable inflateFromXmlForDensity(@NonNull String name, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, int density, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
+        // Inner classes must be referenced as Outer$Inner, but XML tag names
+        // can't contain $, so the <drawable> tag allows developers to specify
+        // the class in an attribute. We'll still run it through inflateFromTag
+        // to stay consistent with how LayoutInflater works.
+        if (name.equals("drawable")) {
+            name = attrs.getAttributeValue(null, "class");
+            if (name == null) {
+                throw new InflateException("<drawable> tag must specify class attribute");
+            }
+        }
+
+        Drawable drawable = inflateFromTag(name);
+        if (drawable == null) {
+            drawable = inflateFromClass(name);
+        }
+        drawable.setSrcDensityOverride(density);
+        drawable.inflate(mRes, parser, attrs, theme);
+        return drawable;
+    }
+
+    @NonNull
+    @SuppressWarnings("deprecation")
+    private Drawable inflateFromTag(@NonNull String name) {
+        switch (name) {
+            case "selector":
+                return new StateListDrawable();
+            case "animated-selector":
+                return new AnimatedStateListDrawable();
+            case "level-list":
+                return new LevelListDrawable();
+            case "layer-list":
+                return new LayerDrawable();
+            case "transition":
+                return new TransitionDrawable();
+            case "ripple":
+                return new RippleDrawable();
+            case "adaptive-icon":
+                return new AdaptiveIconDrawable();
+            case "color":
+                return new ColorDrawable();
+            case "shape":
+                return new GradientDrawable();
+            case "vector":
+                return new VectorDrawable();
+            case "animated-vector":
+                return new AnimatedVectorDrawable();
+            case "scale":
+                return new ScaleDrawable();
+            case "clip":
+                return new ClipDrawable();
+            case "rotate":
+                return new RotateDrawable();
+            case "animated-rotate":
+                return new AnimatedRotateDrawable();
+            case "animation-list":
+                return new AnimationDrawable();
+            case "inset":
+                return new InsetDrawable();
+            case "bitmap":
+                return new BitmapDrawable();
+            case "nine-patch":
+                return new NinePatchDrawable();
+            default:
+                return null;
+        }
+    }
+
+    @NonNull
+    private Drawable inflateFromClass(@NonNull String className) {
+        try {
+            Constructor<? extends Drawable> constructor;
+            synchronized (CONSTRUCTOR_MAP) {
+                constructor = CONSTRUCTOR_MAP.get(className);
+                if (constructor == null) {
+                    final Class<? extends Drawable> clazz =
+                            mClassLoader.loadClass(className).asSubclass(Drawable.class);
+                    constructor = clazz.getConstructor();
+                    CONSTRUCTOR_MAP.put(className, constructor);
+                }
+            }
+            return constructor.newInstance();
+        } catch (NoSuchMethodException e) {
+            final InflateException ie = new InflateException(
+                    "Error inflating class " + className);
+            ie.initCause(e);
+            throw ie;
+        } catch (ClassCastException e) {
+            // If loaded class is not a Drawable subclass.
+            final InflateException ie = new InflateException(
+                    "Class is not a Drawable " + className);
+            ie.initCause(e);
+            throw ie;
+        } catch (ClassNotFoundException e) {
+            // If loadClass fails, we should propagate the exception.
+            final InflateException ie = new InflateException(
+                    "Class not found " + className);
+            ie.initCause(e);
+            throw ie;
+        } catch (Exception e) {
+            final InflateException ie = new InflateException(
+                    "Error inflating class " + className);
+            ie.initCause(e);
+            throw ie;
+        }
+    }
+}
diff --git a/android/graphics/drawable/DrawableWrapper.java b/android/graphics/drawable/DrawableWrapper.java
new file mode 100644
index 0000000..cf821bb
--- /dev/null
+++ b/android/graphics/drawable/DrawableWrapper.java
@@ -0,0 +1,543 @@
+/*
+ * Copyright (C) 2015 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 com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.ActivityInfo.Config;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Insets;
+import android.graphics.Outline;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.View;
+
+import java.io.IOException;
+
+/**
+ * Drawable container with only one child element.
+ */
+public abstract class DrawableWrapper extends Drawable implements Drawable.Callback {
+    private DrawableWrapperState mState;
+    private Drawable mDrawable;
+    private boolean mMutated;
+
+    DrawableWrapper(DrawableWrapperState state, Resources res) {
+        mState = state;
+
+        updateLocalState(res);
+    }
+
+    /**
+     * Creates a new wrapper around the specified drawable.
+     *
+     * @param dr the drawable to wrap
+     */
+    public DrawableWrapper(@Nullable Drawable dr) {
+        mState = null;
+        mDrawable = dr;
+    }
+
+    /**
+     * Initializes local dynamic properties from state. This should be called
+     * after significant state changes, e.g. from the One True Constructor and
+     * after inflating or applying a theme.
+     */
+    private void updateLocalState(Resources res) {
+        if (mState != null && mState.mDrawableState != null) {
+            final Drawable dr = mState.mDrawableState.newDrawable(res);
+            setDrawable(dr);
+        }
+    }
+
+    /**
+     * Sets the wrapped drawable.
+     *
+     * @param dr the wrapped drawable
+     */
+    public void setDrawable(@Nullable Drawable dr) {
+        if (mDrawable != null) {
+            mDrawable.setCallback(null);
+        }
+
+        mDrawable = dr;
+
+        if (dr != null) {
+            dr.setCallback(this);
+
+            // Only call setters for data that's stored in the base Drawable.
+            dr.setVisible(isVisible(), true);
+            dr.setState(getState());
+            dr.setLevel(getLevel());
+            dr.setBounds(getBounds());
+            dr.setLayoutDirection(getLayoutDirection());
+
+            if (mState != null) {
+                mState.mDrawableState = dr.getConstantState();
+            }
+        }
+
+        invalidateSelf();
+    }
+
+    /**
+     * @return the wrapped drawable
+     */
+    @Nullable
+    public Drawable getDrawable() {
+        return mDrawable;
+    }
+
+    @Override
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
+        super.inflate(r, parser, attrs, theme);
+
+        final DrawableWrapperState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        // The density may have changed since the last update. This will
+        // apply scaling to any existing constant state properties.
+        final int densityDpi = r.getDisplayMetrics().densityDpi;
+        final int targetDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+        state.setDensity(targetDensity);
+        state.mSrcDensityOverride = mSrcDensityOverride;
+
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.DrawableWrapper);
+        updateStateFromTypedArray(a);
+        a.recycle();
+
+        inflateChildDrawable(r, parser, attrs, theme);
+    }
+
+    @Override
+    public void applyTheme(@NonNull Theme t) {
+        super.applyTheme(t);
+
+        // If we load the drawable later as part of updating from the typed
+        // array, it will already be themed correctly. So, we can theme the
+        // local drawable first.
+        if (mDrawable != null && mDrawable.canApplyTheme()) {
+            mDrawable.applyTheme(t);
+        }
+
+        final DrawableWrapperState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        final int densityDpi = t.getResources().getDisplayMetrics().densityDpi;
+        final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+        state.setDensity(density);
+
+        if (state.mThemeAttrs != null) {
+            final TypedArray a = t.resolveAttributes(
+                    state.mThemeAttrs, R.styleable.DrawableWrapper);
+            updateStateFromTypedArray(a);
+            a.recycle();
+        }
+    }
+
+    /**
+     * Updates constant state properties from the provided typed array.
+     * <p>
+     * Implementing subclasses should call through to the super method first.
+     *
+     * @param a the typed array rom which properties should be read
+     */
+    private void updateStateFromTypedArray(@NonNull TypedArray a) {
+        final DrawableWrapperState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        // Account for any configuration changes.
+        state.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        state.mThemeAttrs = a.extractThemeAttrs();
+
+        if (a.hasValueOrEmpty(R.styleable.DrawableWrapper_drawable)) {
+            setDrawable(a.getDrawable(R.styleable.DrawableWrapper_drawable));
+        }
+    }
+
+    @Override
+    public boolean canApplyTheme() {
+        return (mState != null && mState.canApplyTheme()) || super.canApplyTheme();
+    }
+
+    @Override
+    public void invalidateDrawable(@NonNull Drawable who) {
+        final Callback callback = getCallback();
+        if (callback != null) {
+            callback.invalidateDrawable(this);
+        }
+    }
+
+    @Override
+    public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
+        final Callback callback = getCallback();
+        if (callback != null) {
+            callback.scheduleDrawable(this, what, when);
+        }
+    }
+
+    @Override
+    public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
+        final Callback callback = getCallback();
+        if (callback != null) {
+            callback.unscheduleDrawable(this, what);
+        }
+    }
+
+    @Override
+    public void draw(@NonNull Canvas canvas) {
+        if (mDrawable != null) {
+            mDrawable.draw(canvas);
+        }
+    }
+
+    @Override
+    public @Config int getChangingConfigurations() {
+        return super.getChangingConfigurations()
+                | (mState != null ? mState.getChangingConfigurations() : 0)
+                | mDrawable.getChangingConfigurations();
+    }
+
+    @Override
+    public boolean getPadding(@NonNull Rect padding) {
+        return mDrawable != null && mDrawable.getPadding(padding);
+    }
+
+    /** @hide */
+    @Override
+    public Insets getOpticalInsets() {
+        return mDrawable != null ? mDrawable.getOpticalInsets() : Insets.NONE;
+    }
+
+    @Override
+    public void setHotspot(float x, float y) {
+        if (mDrawable != null) {
+            mDrawable.setHotspot(x, y);
+        }
+    }
+
+    @Override
+    public void setHotspotBounds(int left, int top, int right, int bottom) {
+        if (mDrawable != null) {
+            mDrawable.setHotspotBounds(left, top, right, bottom);
+        }
+    }
+
+    @Override
+    public void getHotspotBounds(@NonNull Rect outRect) {
+        if (mDrawable != null) {
+            mDrawable.getHotspotBounds(outRect);
+        } else {
+            outRect.set(getBounds());
+        }
+    }
+
+    @Override
+    public boolean setVisible(boolean visible, boolean restart) {
+        final boolean superChanged = super.setVisible(visible, restart);
+        final boolean changed = mDrawable != null && mDrawable.setVisible(visible, restart);
+        return superChanged | changed;
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        if (mDrawable != null) {
+            mDrawable.setAlpha(alpha);
+        }
+    }
+
+    @Override
+    public int getAlpha() {
+        return mDrawable != null ? mDrawable.getAlpha() : 255;
+    }
+
+    @Override
+    public void setColorFilter(@Nullable ColorFilter colorFilter) {
+        if (mDrawable != null) {
+            mDrawable.setColorFilter(colorFilter);
+        }
+    }
+
+    @Override
+    public void setTintList(@Nullable ColorStateList tint) {
+        if (mDrawable != null) {
+            mDrawable.setTintList(tint);
+        }
+    }
+
+    @Override
+    public void setTintMode(@Nullable PorterDuff.Mode tintMode) {
+        if (mDrawable != null) {
+            mDrawable.setTintMode(tintMode);
+        }
+    }
+
+    @Override
+    public boolean onLayoutDirectionChanged(@View.ResolvedLayoutDir int layoutDirection) {
+        return mDrawable != null && mDrawable.setLayoutDirection(layoutDirection);
+    }
+
+    @Override
+    public int getOpacity() {
+        return mDrawable != null ? mDrawable.getOpacity() : PixelFormat.TRANSPARENT;
+    }
+
+    @Override
+    public boolean isStateful() {
+        return mDrawable != null && mDrawable.isStateful();
+    }
+
+    /** @hide */
+    @Override
+    public boolean hasFocusStateSpecified() {
+        return mDrawable != null && mDrawable.hasFocusStateSpecified();
+    }
+
+    @Override
+    protected boolean onStateChange(int[] state) {
+        if (mDrawable != null && mDrawable.isStateful()) {
+            final boolean changed = mDrawable.setState(state);
+            if (changed) {
+                onBoundsChange(getBounds());
+            }
+            return changed;
+        }
+        return false;
+    }
+
+    @Override
+    protected boolean onLevelChange(int level) {
+        return mDrawable != null && mDrawable.setLevel(level);
+    }
+
+    @Override
+    protected void onBoundsChange(@NonNull Rect bounds) {
+        if (mDrawable != null) {
+            mDrawable.setBounds(bounds);
+        }
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return mDrawable != null ? mDrawable.getIntrinsicWidth() : -1;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return mDrawable != null ? mDrawable.getIntrinsicHeight() : -1;
+    }
+
+    @Override
+    public void getOutline(@NonNull Outline outline) {
+        if (mDrawable != null) {
+            mDrawable.getOutline(outline);
+        } else {
+            super.getOutline(outline);
+        }
+    }
+
+    @Override
+    @Nullable
+    public ConstantState getConstantState() {
+        if (mState != null && mState.canConstantState()) {
+            mState.mChangingConfigurations = getChangingConfigurations();
+            return mState;
+        }
+        return null;
+    }
+
+    @Override
+    @NonNull
+    public Drawable mutate() {
+        if (!mMutated && super.mutate() == this) {
+            mState = mutateConstantState();
+            if (mDrawable != null) {
+                mDrawable.mutate();
+            }
+            if (mState != null) {
+                mState.mDrawableState = mDrawable != null ? mDrawable.getConstantState() : null;
+            }
+            mMutated = true;
+        }
+        return this;
+    }
+
+    /**
+     * Mutates the constant state and returns the new state. Responsible for
+     * updating any local copy.
+     * <p>
+     * This method should never call the super implementation; it should always
+     * mutate and return its own constant state.
+     *
+     * @return the new state
+     */
+    DrawableWrapperState mutateConstantState() {
+        return mState;
+    }
+
+    /**
+     * @hide Only used by the framework for pre-loading resources.
+     */
+    public void clearMutated() {
+        super.clearMutated();
+        if (mDrawable != null) {
+            mDrawable.clearMutated();
+        }
+        mMutated = false;
+    }
+
+    /**
+     * Called during inflation to inflate the child element. The last valid
+     * child element will take precedence over any other child elements or
+     * explicit drawable attribute.
+     */
+    private void inflateChildDrawable(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
+        // Seek to the first child element.
+        Drawable dr = null;
+        int type;
+        final int outerDepth = parser.getDepth();
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.START_TAG) {
+                dr = Drawable.createFromXmlInnerForDensity(r, parser, attrs,
+                        mState.mSrcDensityOverride, theme);
+            }
+        }
+
+        if (dr != null) {
+            setDrawable(dr);
+        }
+    }
+
+    abstract static class DrawableWrapperState extends Drawable.ConstantState {
+        private int[] mThemeAttrs;
+
+        @Config int mChangingConfigurations;
+        int mDensity = DisplayMetrics.DENSITY_DEFAULT;
+
+        /**
+         * The density to use when looking up resources from
+         * {@link Resources#getDrawableForDensity(int, int, Theme)}.
+         * A value of 0 means there is no override and the system density will be used.
+         * @hide
+         */
+        int mSrcDensityOverride = 0;
+
+        Drawable.ConstantState mDrawableState;
+
+        DrawableWrapperState(@Nullable DrawableWrapperState orig, @Nullable Resources res) {
+            if (orig != null) {
+                mThemeAttrs = orig.mThemeAttrs;
+                mChangingConfigurations = orig.mChangingConfigurations;
+                mDrawableState = orig.mDrawableState;
+                mSrcDensityOverride = orig.mSrcDensityOverride;
+            }
+
+            final int density;
+            if (res != null) {
+                density = res.getDisplayMetrics().densityDpi;
+            } else if (orig != null) {
+                density = orig.mDensity;
+            } else {
+                density = 0;
+            }
+
+            mDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density;
+        }
+
+        /**
+         * Sets the constant state density.
+         * <p>
+         * If the density has been previously set, dispatches the change to
+         * subclasses so that density-dependent properties may be scaled as
+         * necessary.
+         *
+         * @param targetDensity the new constant state density
+         */
+        public final void setDensity(int targetDensity) {
+            if (mDensity != targetDensity) {
+                final int sourceDensity = mDensity;
+                mDensity = targetDensity;
+
+                onDensityChanged(sourceDensity, targetDensity);
+            }
+        }
+
+        /**
+         * Called when the constant state density changes.
+         * <p>
+         * Subclasses with density-dependent constant state properties should
+         * override this method and scale their properties as necessary.
+         *
+         * @param sourceDensity the previous constant state density
+         * @param targetDensity the new constant state density
+         */
+        void onDensityChanged(int sourceDensity, int targetDensity) {
+            // Stub method.
+        }
+
+        @Override
+        public boolean canApplyTheme() {
+            return mThemeAttrs != null
+                    || (mDrawableState != null && mDrawableState.canApplyTheme())
+                    || super.canApplyTheme();
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return newDrawable(null);
+        }
+
+        @Override
+        public abstract Drawable newDrawable(@Nullable Resources res);
+
+        @Override
+        public @Config int getChangingConfigurations() {
+            return mChangingConfigurations
+                    | (mDrawableState != null ? mDrawableState.getChangingConfigurations() : 0);
+        }
+
+        public boolean canConstantState() {
+            return mDrawableState != null;
+        }
+    }
+}
diff --git a/android/graphics/drawable/GradientDrawable.java b/android/graphics/drawable/GradientDrawable.java
new file mode 100644
index 0000000..6c3aea2
--- /dev/null
+++ b/android/graphics/drawable/GradientDrawable.java
@@ -0,0 +1,2157 @@
+/*
+ * 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 android.annotation.ColorInt;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.ActivityInfo.Config;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.DashPathEffect;
+import android.graphics.Insets;
+import android.graphics.LinearGradient;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.RadialGradient;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.SweepGradient;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A Drawable with a color gradient for buttons, backgrounds, etc.
+ *
+ * <p>It can be defined in an XML file with the <code>&lt;shape></code> element. For more
+ * information, see the guide to <a
+ * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
+ *
+ * @attr ref android.R.styleable#GradientDrawable_visible
+ * @attr ref android.R.styleable#GradientDrawable_shape
+ * @attr ref android.R.styleable#GradientDrawable_innerRadiusRatio
+ * @attr ref android.R.styleable#GradientDrawable_innerRadius
+ * @attr ref android.R.styleable#GradientDrawable_thicknessRatio
+ * @attr ref android.R.styleable#GradientDrawable_thickness
+ * @attr ref android.R.styleable#GradientDrawable_useLevel
+ * @attr ref android.R.styleable#GradientDrawableSize_width
+ * @attr ref android.R.styleable#GradientDrawableSize_height
+ * @attr ref android.R.styleable#GradientDrawableGradient_startColor
+ * @attr ref android.R.styleable#GradientDrawableGradient_centerColor
+ * @attr ref android.R.styleable#GradientDrawableGradient_endColor
+ * @attr ref android.R.styleable#GradientDrawableGradient_useLevel
+ * @attr ref android.R.styleable#GradientDrawableGradient_angle
+ * @attr ref android.R.styleable#GradientDrawableGradient_type
+ * @attr ref android.R.styleable#GradientDrawableGradient_centerX
+ * @attr ref android.R.styleable#GradientDrawableGradient_centerY
+ * @attr ref android.R.styleable#GradientDrawableGradient_gradientRadius
+ * @attr ref android.R.styleable#GradientDrawableSolid_color
+ * @attr ref android.R.styleable#GradientDrawableStroke_width
+ * @attr ref android.R.styleable#GradientDrawableStroke_color
+ * @attr ref android.R.styleable#GradientDrawableStroke_dashWidth
+ * @attr ref android.R.styleable#GradientDrawableStroke_dashGap
+ * @attr ref android.R.styleable#GradientDrawablePadding_left
+ * @attr ref android.R.styleable#GradientDrawablePadding_top
+ * @attr ref android.R.styleable#GradientDrawablePadding_right
+ * @attr ref android.R.styleable#GradientDrawablePadding_bottom
+ */
+public class GradientDrawable extends Drawable {
+    /**
+     * Shape is a rectangle, possibly with rounded corners
+     */
+    public static final int RECTANGLE = 0;
+
+    /**
+     * Shape is an ellipse
+     */
+    public static final int OVAL = 1;
+
+    /**
+     * Shape is a line
+     */
+    public static final int LINE = 2;
+
+    /**
+     * Shape is a ring.
+     */
+    public static final int RING = 3;
+
+    /** @hide */
+    @IntDef({RECTANGLE, OVAL, LINE, RING})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Shape {}
+
+    /**
+     * Gradient is linear (default.)
+     */
+    public static final int LINEAR_GRADIENT = 0;
+
+    /**
+     * Gradient is circular.
+     */
+    public static final int RADIAL_GRADIENT = 1;
+
+    /**
+     * Gradient is a sweep.
+     */
+    public static final int SWEEP_GRADIENT  = 2;
+
+    /** @hide */
+    @IntDef({LINEAR_GRADIENT, RADIAL_GRADIENT, SWEEP_GRADIENT})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface GradientType {}
+
+    /** Radius is in pixels. */
+    private static final int RADIUS_TYPE_PIXELS = 0;
+
+    /** Radius is a fraction of the base size. */
+    private static final int RADIUS_TYPE_FRACTION = 1;
+
+    /** Radius is a fraction of the bounds size. */
+    private static final int RADIUS_TYPE_FRACTION_PARENT = 2;
+
+    /** @hide */
+    @IntDef({RADIUS_TYPE_PIXELS, RADIUS_TYPE_FRACTION, RADIUS_TYPE_FRACTION_PARENT})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RadiusType {}
+
+    private static final float DEFAULT_INNER_RADIUS_RATIO = 3.0f;
+    private static final float DEFAULT_THICKNESS_RATIO = 9.0f;
+
+    private GradientState mGradientState;
+
+    private final Paint mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+    private Rect mPadding;
+    private Paint mStrokePaint;   // optional, set by the caller
+    private ColorFilter mColorFilter;   // optional, set by the caller
+    private PorterDuffColorFilter mTintFilter;
+    private int mAlpha = 0xFF;  // modified by the caller
+
+    private final Path mPath = new Path();
+    private final RectF mRect = new RectF();
+
+    private Paint mLayerPaint;    // internal, used if we use saveLayer()
+    private boolean mGradientIsDirty;
+    private boolean mMutated;
+    private Path mRingPath;
+    private boolean mPathIsDirty = true;
+
+    /** Current gradient radius, valid when {@link #mGradientIsDirty} is false. */
+    private float mGradientRadius;
+
+    /**
+     * Controls how the gradient is oriented relative to the drawable's bounds
+     */
+    public enum Orientation {
+        /** draw the gradient from the top to the bottom */
+        TOP_BOTTOM,
+        /** draw the gradient from the top-right to the bottom-left */
+        TR_BL,
+        /** draw the gradient from the right to the left */
+        RIGHT_LEFT,
+        /** draw the gradient from the bottom-right to the top-left */
+        BR_TL,
+        /** draw the gradient from the bottom to the top */
+        BOTTOM_TOP,
+        /** draw the gradient from the bottom-left to the top-right */
+        BL_TR,
+        /** draw the gradient from the left to the right */
+        LEFT_RIGHT,
+        /** draw the gradient from the top-left to the bottom-right */
+        TL_BR,
+    }
+
+    public GradientDrawable() {
+        this(new GradientState(Orientation.TOP_BOTTOM, null), null);
+    }
+
+    /**
+     * Create a new gradient drawable given an orientation and an array
+     * of colors for the gradient.
+     */
+    public GradientDrawable(Orientation orientation, @ColorInt int[] colors) {
+        this(new GradientState(orientation, colors), null);
+    }
+
+    @Override
+    public boolean getPadding(Rect padding) {
+        if (mPadding != null) {
+            padding.set(mPadding);
+            return true;
+        } else {
+            return super.getPadding(padding);
+        }
+    }
+
+    /**
+     * Specifies radii for each of the 4 corners. For each corner, the array
+     * contains 2 values, <code>[X_radius, Y_radius]</code>. The corners are
+     * ordered top-left, top-right, bottom-right, bottom-left. This property
+     * is honored only when the shape is of type {@link #RECTANGLE}.
+     * <p>
+     * <strong>Note</strong>: changing this property will affect all instances
+     * of a drawable loaded from a resource. It is recommended to invoke
+     * {@link #mutate()} before changing this property.
+     *
+     * @param radii an array of length >= 8 containing 4 pairs of X and Y
+     *              radius for each corner, specified in pixels
+     *
+     * @see #mutate()
+     * @see #setShape(int)
+     * @see #setCornerRadius(float)
+     */
+    public void setCornerRadii(@Nullable float[] radii) {
+        mGradientState.setCornerRadii(radii);
+        mPathIsDirty = true;
+        invalidateSelf();
+    }
+
+    /**
+     * Returns the radii for each of the 4 corners. For each corner, the array
+     * contains 2 values, <code>[X_radius, Y_radius]</code>. The corners are
+     * ordered top-left, top-right, bottom-right, bottom-left.
+     * <p>
+     * If the radius was previously set with {@link #setCornerRadius(float)},
+     * or if the corners are not rounded, this method will return {@code null}.
+     *
+     * @return an array containing the radii for each of the 4 corners, or
+     *         {@code null}
+     * @see #setCornerRadii(float[])
+     */
+    @Nullable
+    public float[] getCornerRadii() {
+        return mGradientState.mRadiusArray.clone();
+    }
+
+    /**
+     * Specifies the radius for the corners of the gradient. If this is > 0,
+     * then the drawable is drawn in a round-rectangle, rather than a
+     * rectangle. This property is honored only when the shape is of type
+     * {@link #RECTANGLE}.
+     * <p>
+     * <strong>Note</strong>: changing this property will affect all instances
+     * of a drawable loaded from a resource. It is recommended to invoke
+     * {@link #mutate()} before changing this property.
+     *
+     * @param radius The radius in pixels of the corners of the rectangle shape
+     *
+     * @see #mutate()
+     * @see #setCornerRadii(float[])
+     * @see #setShape(int)
+     */
+    public void setCornerRadius(float radius) {
+        mGradientState.setCornerRadius(radius);
+        mPathIsDirty = true;
+        invalidateSelf();
+    }
+
+    /**
+     * Returns the radius for the corners of the gradient, that was previously set with
+     * {@link #setCornerRadius(float)}.
+     * <p>
+     * If the radius was previously cleared via passing {@code null}
+     * to {@link #setCornerRadii(float[])}, this method will return 0.
+     *
+     * @return the radius in pixels of the corners of the rectangle shape, or 0
+     * @see #setCornerRadius
+     */
+    public float getCornerRadius() {
+        return mGradientState.mRadius;
+    }
+
+    /**
+     * <p>Set the stroke width and color for the drawable. If width is zero,
+     * then no stroke is drawn.</p>
+     * <p><strong>Note</strong>: changing this property will affect all instances
+     * of a drawable loaded from a resource. It is recommended to invoke
+     * {@link #mutate()} before changing this property.</p>
+     *
+     * @param width The width in pixels of the stroke
+     * @param color The color of the stroke
+     *
+     * @see #mutate()
+     * @see #setStroke(int, int, float, float)
+     */
+    public void setStroke(int width, @ColorInt int color) {
+        setStroke(width, color, 0, 0);
+    }
+
+    /**
+     * <p>Set the stroke width and color state list for the drawable. If width
+     * is zero, then no stroke is drawn.</p>
+     * <p><strong>Note</strong>: changing this property will affect all instances
+     * of a drawable loaded from a resource. It is recommended to invoke
+     * {@link #mutate()} before changing this property.</p>
+     *
+     * @param width The width in pixels of the stroke
+     * @param colorStateList The color state list of the stroke
+     *
+     * @see #mutate()
+     * @see #setStroke(int, ColorStateList, float, float)
+     */
+    public void setStroke(int width, ColorStateList colorStateList) {
+        setStroke(width, colorStateList, 0, 0);
+    }
+
+    /**
+     * <p>Set the stroke width and color for the drawable. If width is zero,
+     * then no stroke is drawn. This method can also be used to dash the stroke.</p>
+     * <p><strong>Note</strong>: changing this property will affect all instances
+     * of a drawable loaded from a resource. It is recommended to invoke
+     * {@link #mutate()} before changing this property.</p>
+     *
+     * @param width The width in pixels of the stroke
+     * @param color The color of the stroke
+     * @param dashWidth The length in pixels of the dashes, set to 0 to disable dashes
+     * @param dashGap The gap in pixels between dashes
+     *
+     * @see #mutate()
+     * @see #setStroke(int, int)
+     */
+    public void setStroke(int width, @ColorInt int color, float dashWidth, float dashGap) {
+        mGradientState.setStroke(width, ColorStateList.valueOf(color), dashWidth, dashGap);
+        setStrokeInternal(width, color, dashWidth, dashGap);
+    }
+
+    /**
+     * <p>Set the stroke width and color state list for the drawable. If width
+     * is zero, then no stroke is drawn. This method can also be used to dash
+     * the stroke.</p>
+     * <p><strong>Note</strong>: changing this property will affect all instances
+     * of a drawable loaded from a resource. It is recommended to invoke
+     * {@link #mutate()} before changing this property.</p>
+     *
+     * @param width The width in pixels of the stroke
+     * @param colorStateList The color state list of the stroke
+     * @param dashWidth The length in pixels of the dashes, set to 0 to disable dashes
+     * @param dashGap The gap in pixels between dashes
+     *
+     * @see #mutate()
+     * @see #setStroke(int, ColorStateList)
+     */
+    public void setStroke(
+            int width, ColorStateList colorStateList, float dashWidth, float dashGap) {
+        mGradientState.setStroke(width, colorStateList, dashWidth, dashGap);
+        final int color;
+        if (colorStateList == null) {
+            color = Color.TRANSPARENT;
+        } else {
+            final int[] stateSet = getState();
+            color = colorStateList.getColorForState(stateSet, 0);
+        }
+        setStrokeInternal(width, color, dashWidth, dashGap);
+    }
+
+    private void setStrokeInternal(int width, int color, float dashWidth, float dashGap) {
+        if (mStrokePaint == null)  {
+            mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+            mStrokePaint.setStyle(Paint.Style.STROKE);
+        }
+        mStrokePaint.setStrokeWidth(width);
+        mStrokePaint.setColor(color);
+
+        DashPathEffect e = null;
+        if (dashWidth > 0) {
+            e = new DashPathEffect(new float[] { dashWidth, dashGap }, 0);
+        }
+        mStrokePaint.setPathEffect(e);
+        invalidateSelf();
+    }
+
+
+    /**
+     * <p>Sets the size of the shape drawn by this drawable.</p>
+     * <p><strong>Note</strong>: changing this property will affect all instances
+     * of a drawable loaded from a resource. It is recommended to invoke
+     * {@link #mutate()} before changing this property.</p>
+     *
+     * @param width The width of the shape used by this drawable
+     * @param height The height of the shape used by this drawable
+     *
+     * @see #mutate()
+     * @see #setGradientType(int)
+     */
+    public void setSize(int width, int height) {
+        mGradientState.setSize(width, height);
+        mPathIsDirty = true;
+        invalidateSelf();
+    }
+
+    /**
+     * <p>Sets the type of shape used to draw the gradient.</p>
+     * <p><strong>Note</strong>: changing this property will affect all instances
+     * of a drawable loaded from a resource. It is recommended to invoke
+     * {@link #mutate()} before changing this property.</p>
+     *
+     * @param shape The desired shape for this drawable: {@link #LINE},
+     *              {@link #OVAL}, {@link #RECTANGLE} or {@link #RING}
+     *
+     * @see #mutate()
+     */
+    public void setShape(@Shape int shape) {
+        mRingPath = null;
+        mPathIsDirty = true;
+        mGradientState.setShape(shape);
+        invalidateSelf();
+    }
+
+    /**
+     * Returns the type of shape used by this drawable, one of {@link #LINE},
+     * {@link #OVAL}, {@link #RECTANGLE} or {@link #RING}.
+     *
+     * @return the type of shape used by this drawable
+     * @see #setShape(int)
+     */
+    @Shape
+    public int getShape() {
+        return mGradientState.mShape;
+    }
+
+    /**
+     * Sets the type of gradient used by this drawable.
+     * <p>
+     * <strong>Note</strong>: changing this property will affect all instances
+     * of a drawable loaded from a resource. It is recommended to invoke
+     * {@link #mutate()} before changing this property.
+     *
+     * @param gradient The type of the gradient: {@link #LINEAR_GRADIENT},
+     *                 {@link #RADIAL_GRADIENT} or {@link #SWEEP_GRADIENT}
+     *
+     * @see #mutate()
+     * @see #getGradientType()
+     */
+    public void setGradientType(@GradientType int gradient) {
+        mGradientState.setGradientType(gradient);
+        mGradientIsDirty = true;
+        invalidateSelf();
+    }
+
+    /**
+     * Returns the type of gradient used by this drawable, one of
+     * {@link #LINEAR_GRADIENT}, {@link #RADIAL_GRADIENT}, or
+     * {@link #SWEEP_GRADIENT}.
+     *
+     * @return the type of gradient used by this drawable
+     * @see #setGradientType(int)
+     */
+    @GradientType
+    public int getGradientType() {
+        return mGradientState.mGradient;
+    }
+
+    /**
+     * Sets the position of the center of the gradient as a fraction of the
+     * width and height.
+     * <p>
+     * The default value is (0.5, 0.5).
+     * <p>
+     * <strong>Note</strong>: changing this property will affect all instances
+     * of a drawable loaded from a resource. It is recommended to invoke
+     * {@link #mutate()} before changing this property.
+     *
+     * @param x the X-position of the center of the gradient
+     * @param y the Y-position of the center of the gradient
+     *
+     * @see #mutate()
+     * @see #setGradientType(int)
+     * @see #getGradientCenterX()
+     * @see #getGradientCenterY()
+     */
+    public void setGradientCenter(float x, float y) {
+        mGradientState.setGradientCenter(x, y);
+        mGradientIsDirty = true;
+        invalidateSelf();
+    }
+
+    /**
+     * Returns the X-position of the center of the gradient as a fraction of
+     * the width.
+     *
+     * @return the X-position of the center of the gradient
+     * @see #setGradientCenter(float, float)
+     */
+    public float getGradientCenterX() {
+        return mGradientState.mCenterX;
+    }
+
+    /**
+     * Returns the Y-position of the center of this gradient as a fraction of
+     * the height.
+     *
+     * @return the Y-position of the center of the gradient
+     * @see #setGradientCenter(float, float)
+     */
+    public float getGradientCenterY() {
+        return mGradientState.mCenterY;
+    }
+
+    /**
+     * Sets the radius of the gradient. The radius is honored only when the
+     * gradient type is set to {@link #RADIAL_GRADIENT}.
+     * <p>
+     * <strong>Note</strong>: changing this property will affect all instances
+     * of a drawable loaded from a resource. It is recommended to invoke
+     * {@link #mutate()} before changing this property.
+     *
+     * @param gradientRadius the radius of the gradient in pixels
+     *
+     * @see #mutate()
+     * @see #setGradientType(int)
+     * @see #getGradientRadius()
+     */
+    public void setGradientRadius(float gradientRadius) {
+        mGradientState.setGradientRadius(gradientRadius, TypedValue.COMPLEX_UNIT_PX);
+        mGradientIsDirty = true;
+        invalidateSelf();
+    }
+
+    /**
+     * Returns the radius of the gradient in pixels. The radius is valid only
+     * when the gradient type is set to {@link #RADIAL_GRADIENT}.
+     *
+     * @return the radius of the gradient in pixels
+     * @see #setGradientRadius(float)
+     */
+    public float getGradientRadius() {
+        if (mGradientState.mGradient != RADIAL_GRADIENT) {
+            return 0;
+        }
+
+        ensureValidRect();
+        return mGradientRadius;
+    }
+
+    /**
+     * Sets whether this drawable's {@code level} property will be used to
+     * scale the gradient. If a gradient is not used, this property has no
+     * effect.
+     * <p>
+     * Scaling behavior varies based on gradient type:
+     * <ul>
+     *     <li>{@link #LINEAR_GRADIENT} adjusts the ending position along the
+     *         gradient's axis of orientation (see {@link #getOrientation()})
+     *     <li>{@link #RADIAL_GRADIENT} adjusts the outer radius
+     *     <li>{@link #SWEEP_GRADIENT} adjusts the ending angle
+     * <ul>
+     * <p>
+     * The default value for this property is {@code false}.
+     * <p>
+     * <strong>Note</strong>: This property corresponds to the
+     * {@code android:useLevel} attribute on the inner {@code &lt;gradient&gt;}
+     * tag, NOT the {@code android:useLevel} attribute on the outer
+     * {@code &lt;shape&gt;} tag. For example,
+     * <pre>{@code
+     * <shape ...>
+     *     <gradient
+     *         ...
+     *         android:useLevel="true" />
+     * </shape>
+     * }</pre><p>
+     * <strong>Note</strong>: Changing this property will affect all instances
+     * of a drawable loaded from a resource. It is recommended to invoke
+     * {@link #mutate()} before changing this property.
+     *
+     * @param useLevel {@code true} if the gradient should be scaled based on
+     *                 level, {@code false} otherwise
+     *
+     * @see #mutate()
+     * @see #setLevel(int)
+     * @see #getLevel()
+     * @see #getUseLevel()
+     * @attr ref android.R.styleable#GradientDrawableGradient_useLevel
+     */
+    public void setUseLevel(boolean useLevel) {
+        mGradientState.mUseLevel = useLevel;
+        mGradientIsDirty = true;
+        invalidateSelf();
+    }
+
+    /**
+     * Returns whether this drawable's {@code level} property will be used to
+     * scale the gradient.
+     *
+     * @return {@code true} if the gradient should be scaled based on level,
+     *         {@code false} otherwise
+     * @see #setUseLevel(boolean)
+     * @attr ref android.R.styleable#GradientDrawableGradient_useLevel
+     */
+    public boolean getUseLevel() {
+        return mGradientState.mUseLevel;
+    }
+
+    private int modulateAlpha(int alpha) {
+        int scale = mAlpha + (mAlpha >> 7);
+        return alpha * scale >> 8;
+    }
+
+    /**
+     * Returns the orientation of the gradient defined in this drawable.
+     *
+     * @return the orientation of the gradient defined in this drawable
+     * @see #setOrientation(Orientation)
+     */
+    public Orientation getOrientation() {
+        return mGradientState.mOrientation;
+    }
+
+    /**
+     * Sets the orientation of the gradient defined in this drawable.
+     * <p>
+     * <strong>Note</strong>: changing orientation will affect all instances
+     * of a drawable loaded from a resource. It is recommended to invoke
+     * {@link #mutate()} before changing the orientation.
+     *
+     * @param orientation the desired orientation (angle) of the gradient
+     *
+     * @see #mutate()
+     * @see #getOrientation()
+     */
+    public void setOrientation(Orientation orientation) {
+        mGradientState.mOrientation = orientation;
+        mGradientIsDirty = true;
+        invalidateSelf();
+    }
+
+    /**
+     * Sets the colors used to draw the gradient.
+     * <p>
+     * Each color is specified as an ARGB integer and the array must contain at
+     * least 2 colors.
+     * <p>
+     * <strong>Note</strong>: changing colors will affect all instances of a
+     * drawable loaded from a resource. It is recommended to invoke
+     * {@link #mutate()} before changing the colors.
+     *
+     * @param colors an array containing 2 or more ARGB colors
+     * @see #mutate()
+     * @see #setColor(int)
+     */
+    public void setColors(@ColorInt int[] colors) {
+        mGradientState.setGradientColors(colors);
+        mGradientIsDirty = true;
+        invalidateSelf();
+    }
+
+    /**
+     * Returns the colors used to draw the gradient, or {@code null} if the
+     * gradient is drawn using a single color or no colors.
+     *
+     * @return the colors used to draw the gradient, or {@code null}
+     * @see #setColors(int[] colors)
+     */
+    @Nullable
+    public int[] getColors() {
+        return mGradientState.mGradientColors == null ?
+                null : mGradientState.mGradientColors.clone();
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (!ensureValidRect()) {
+            // nothing to draw
+            return;
+        }
+
+        // remember the alpha values, in case we temporarily overwrite them
+        // when we modulate them with mAlpha
+        final int prevFillAlpha = mFillPaint.getAlpha();
+        final int prevStrokeAlpha = mStrokePaint != null ? mStrokePaint.getAlpha() : 0;
+        // compute the modulate alpha values
+        final int currFillAlpha = modulateAlpha(prevFillAlpha);
+        final int currStrokeAlpha = modulateAlpha(prevStrokeAlpha);
+
+        final boolean haveStroke = currStrokeAlpha > 0 && mStrokePaint != null &&
+                mStrokePaint.getStrokeWidth() > 0;
+        final boolean haveFill = currFillAlpha > 0;
+        final GradientState st = mGradientState;
+        final ColorFilter colorFilter = mColorFilter != null ? mColorFilter : mTintFilter;
+
+        /*  we need a layer iff we're drawing both a fill and stroke, and the
+            stroke is non-opaque, and our shapetype actually supports
+            fill+stroke. Otherwise we can just draw the stroke (if any) on top
+            of the fill (if any) without worrying about blending artifacts.
+         */
+        final boolean useLayer = haveStroke && haveFill && st.mShape != LINE &&
+                 currStrokeAlpha < 255 && (mAlpha < 255 || colorFilter != null);
+
+        /*  Drawing with a layer is slower than direct drawing, but it
+            allows us to apply paint effects like alpha and colorfilter to
+            the result of multiple separate draws. In our case, if the user
+            asks for a non-opaque alpha value (via setAlpha), and we're
+            stroking, then we need to apply the alpha AFTER we've drawn
+            both the fill and the stroke.
+        */
+        if (useLayer) {
+            if (mLayerPaint == null) {
+                mLayerPaint = new Paint();
+            }
+            mLayerPaint.setDither(st.mDither);
+            mLayerPaint.setAlpha(mAlpha);
+            mLayerPaint.setColorFilter(colorFilter);
+
+            float rad = mStrokePaint.getStrokeWidth();
+            canvas.saveLayer(mRect.left - rad, mRect.top - rad,
+                             mRect.right + rad, mRect.bottom + rad,
+                             mLayerPaint);
+
+            // don't perform the filter in our individual paints
+            // since the layer will do it for us
+            mFillPaint.setColorFilter(null);
+            mStrokePaint.setColorFilter(null);
+        } else {
+            /*  if we're not using a layer, apply the dither/filter to our
+                individual paints
+            */
+            mFillPaint.setAlpha(currFillAlpha);
+            mFillPaint.setDither(st.mDither);
+            mFillPaint.setColorFilter(colorFilter);
+            if (colorFilter != null && st.mSolidColors == null) {
+                mFillPaint.setColor(mAlpha << 24);
+            }
+            if (haveStroke) {
+                mStrokePaint.setAlpha(currStrokeAlpha);
+                mStrokePaint.setDither(st.mDither);
+                mStrokePaint.setColorFilter(colorFilter);
+            }
+        }
+
+        switch (st.mShape) {
+            case RECTANGLE:
+                if (st.mRadiusArray != null) {
+                    buildPathIfDirty();
+                    canvas.drawPath(mPath, mFillPaint);
+                    if (haveStroke) {
+                        canvas.drawPath(mPath, mStrokePaint);
+                    }
+                } else if (st.mRadius > 0.0f) {
+                    // since the caller is only giving us 1 value, we will force
+                    // it to be square if the rect is too small in one dimension
+                    // to show it. If we did nothing, Skia would clamp the rad
+                    // independently along each axis, giving us a thin ellipse
+                    // if the rect were very wide but not very tall
+                    float rad = Math.min(st.mRadius,
+                            Math.min(mRect.width(), mRect.height()) * 0.5f);
+                    canvas.drawRoundRect(mRect, rad, rad, mFillPaint);
+                    if (haveStroke) {
+                        canvas.drawRoundRect(mRect, rad, rad, mStrokePaint);
+                    }
+                } else {
+                    if (mFillPaint.getColor() != 0 || colorFilter != null ||
+                            mFillPaint.getShader() != null) {
+                        canvas.drawRect(mRect, mFillPaint);
+                    }
+                    if (haveStroke) {
+                        canvas.drawRect(mRect, mStrokePaint);
+                    }
+                }
+                break;
+            case OVAL:
+                canvas.drawOval(mRect, mFillPaint);
+                if (haveStroke) {
+                    canvas.drawOval(mRect, mStrokePaint);
+                }
+                break;
+            case LINE: {
+                RectF r = mRect;
+                float y = r.centerY();
+                if (haveStroke) {
+                    canvas.drawLine(r.left, y, r.right, y, mStrokePaint);
+                }
+                break;
+            }
+            case RING:
+                Path path = buildRing(st);
+                canvas.drawPath(path, mFillPaint);
+                if (haveStroke) {
+                    canvas.drawPath(path, mStrokePaint);
+                }
+                break;
+        }
+
+        if (useLayer) {
+            canvas.restore();
+        } else {
+            mFillPaint.setAlpha(prevFillAlpha);
+            if (haveStroke) {
+                mStrokePaint.setAlpha(prevStrokeAlpha);
+            }
+        }
+    }
+
+    private void buildPathIfDirty() {
+        final GradientState st = mGradientState;
+        if (mPathIsDirty) {
+            ensureValidRect();
+            mPath.reset();
+            mPath.addRoundRect(mRect, st.mRadiusArray, Path.Direction.CW);
+            mPathIsDirty = false;
+        }
+    }
+
+    private Path buildRing(GradientState st) {
+        if (mRingPath != null && (!st.mUseLevelForShape || !mPathIsDirty)) return mRingPath;
+        mPathIsDirty = false;
+
+        float sweep = st.mUseLevelForShape ? (360.0f * getLevel() / 10000.0f) : 360f;
+
+        RectF bounds = new RectF(mRect);
+
+        float x = bounds.width() / 2.0f;
+        float y = bounds.height() / 2.0f;
+
+        float thickness = st.mThickness != -1 ?
+                st.mThickness : bounds.width() / st.mThicknessRatio;
+        // inner radius
+        float radius = st.mInnerRadius != -1 ?
+                st.mInnerRadius : bounds.width() / st.mInnerRadiusRatio;
+
+        RectF innerBounds = new RectF(bounds);
+        innerBounds.inset(x - radius, y - radius);
+
+        bounds = new RectF(innerBounds);
+        bounds.inset(-thickness, -thickness);
+
+        if (mRingPath == null) {
+            mRingPath = new Path();
+        } else {
+            mRingPath.reset();
+        }
+
+        final Path ringPath = mRingPath;
+        // arcTo treats the sweep angle mod 360, so check for that, since we
+        // think 360 means draw the entire oval
+        if (sweep < 360 && sweep > -360) {
+            ringPath.setFillType(Path.FillType.EVEN_ODD);
+            // inner top
+            ringPath.moveTo(x + radius, y);
+            // outer top
+            ringPath.lineTo(x + radius + thickness, y);
+            // outer arc
+            ringPath.arcTo(bounds, 0.0f, sweep, false);
+            // inner arc
+            ringPath.arcTo(innerBounds, sweep, -sweep, false);
+            ringPath.close();
+        } else {
+            // add the entire ovals
+            ringPath.addOval(bounds, Path.Direction.CW);
+            ringPath.addOval(innerBounds, Path.Direction.CCW);
+        }
+
+        return ringPath;
+    }
+
+    /**
+     * Changes this drawable to use a single color instead of a gradient.
+     * <p>
+     * <strong>Note</strong>: changing color will affect all instances of a
+     * drawable loaded from a resource. It is recommended to invoke
+     * {@link #mutate()} before changing the color.
+     *
+     * @param argb The color used to fill the shape
+     *
+     * @see #mutate()
+     * @see #setColors(int[])
+     * @see #getColor
+     */
+    public void setColor(@ColorInt int argb) {
+        mGradientState.setSolidColors(ColorStateList.valueOf(argb));
+        mFillPaint.setColor(argb);
+        invalidateSelf();
+    }
+
+    /**
+     * Changes this drawable to use a single color state list instead of a
+     * gradient. Calling this method with a null argument will clear the color
+     * and is equivalent to calling {@link #setColor(int)} with the argument
+     * {@link Color#TRANSPARENT}.
+     * <p>
+     * <strong>Note</strong>: changing color will affect all instances of a
+     * drawable loaded from a resource. It is recommended to invoke
+     * {@link #mutate()} before changing the color.</p>
+     *
+     * @param colorStateList The color state list used to fill the shape
+     *
+     * @see #mutate()
+     * @see #getColor
+     */
+    public void setColor(@Nullable ColorStateList colorStateList) {
+        mGradientState.setSolidColors(colorStateList);
+        final int color;
+        if (colorStateList == null) {
+            color = Color.TRANSPARENT;
+        } else {
+            final int[] stateSet = getState();
+            color = colorStateList.getColorForState(stateSet, 0);
+        }
+        mFillPaint.setColor(color);
+        invalidateSelf();
+    }
+
+    /**
+     * Returns the color state list used to fill the shape, or {@code null} if
+     * the shape is filled with a gradient or has no fill color.
+     *
+     * @return the color state list used to fill this gradient, or {@code null}
+     *
+     * @see #setColor(int)
+     * @see #setColor(ColorStateList)
+     */
+    @Nullable
+    public ColorStateList getColor() {
+        return mGradientState.mSolidColors;
+    }
+
+    @Override
+    protected boolean onStateChange(int[] stateSet) {
+        boolean invalidateSelf = false;
+
+        final GradientState s = mGradientState;
+        final ColorStateList solidColors = s.mSolidColors;
+        if (solidColors != null) {
+            final int newColor = solidColors.getColorForState(stateSet, 0);
+            final int oldColor = mFillPaint.getColor();
+            if (oldColor != newColor) {
+                mFillPaint.setColor(newColor);
+                invalidateSelf = true;
+            }
+        }
+
+        final Paint strokePaint = mStrokePaint;
+        if (strokePaint != null) {
+            final ColorStateList strokeColors = s.mStrokeColors;
+            if (strokeColors != null) {
+                final int newColor = strokeColors.getColorForState(stateSet, 0);
+                final int oldColor = strokePaint.getColor();
+                if (oldColor != newColor) {
+                    strokePaint.setColor(newColor);
+                    invalidateSelf = true;
+                }
+            }
+        }
+
+        if (s.mTint != null && s.mTintMode != null) {
+            mTintFilter = updateTintFilter(mTintFilter, s.mTint, s.mTintMode);
+            invalidateSelf = true;
+        }
+
+        if (invalidateSelf) {
+            invalidateSelf();
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean isStateful() {
+        final GradientState s = mGradientState;
+        return super.isStateful()
+                || (s.mSolidColors != null && s.mSolidColors.isStateful())
+                || (s.mStrokeColors != null && s.mStrokeColors.isStateful())
+                || (s.mTint != null && s.mTint.isStateful());
+    }
+
+    /** @hide */
+    @Override
+    public boolean hasFocusStateSpecified() {
+        final GradientState s = mGradientState;
+        return (s.mSolidColors != null && s.mSolidColors.hasFocusStateSpecified())
+                || (s.mStrokeColors != null && s.mStrokeColors.hasFocusStateSpecified())
+                || (s.mTint != null && s.mTint.hasFocusStateSpecified());
+    }
+
+    @Override
+    public @Config int getChangingConfigurations() {
+        return super.getChangingConfigurations() | mGradientState.getChangingConfigurations();
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        if (alpha != mAlpha) {
+            mAlpha = alpha;
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public int getAlpha() {
+        return mAlpha;
+    }
+
+    @Override
+    public void setDither(boolean dither) {
+        if (dither != mGradientState.mDither) {
+            mGradientState.mDither = dither;
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    @Nullable
+    public ColorFilter getColorFilter() {
+        return mColorFilter;
+    }
+
+    @Override
+    public void setColorFilter(@Nullable ColorFilter colorFilter) {
+        if (colorFilter != mColorFilter) {
+            mColorFilter = colorFilter;
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public void setTintList(@Nullable ColorStateList tint) {
+        mGradientState.mTint = tint;
+        mTintFilter = updateTintFilter(mTintFilter, tint, mGradientState.mTintMode);
+        invalidateSelf();
+    }
+
+    @Override
+    public void setTintMode(@Nullable PorterDuff.Mode tintMode) {
+        mGradientState.mTintMode = tintMode;
+        mTintFilter = updateTintFilter(mTintFilter, mGradientState.mTint, tintMode);
+        invalidateSelf();
+    }
+
+    @Override
+    public int getOpacity() {
+        return (mAlpha == 255 && mGradientState.mOpaqueOverBounds && isOpaqueForState()) ?
+                PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
+    }
+
+    @Override
+    protected void onBoundsChange(Rect r) {
+        super.onBoundsChange(r);
+        mRingPath = null;
+        mPathIsDirty = true;
+        mGradientIsDirty = true;
+    }
+
+    @Override
+    protected boolean onLevelChange(int level) {
+        super.onLevelChange(level);
+        mGradientIsDirty = true;
+        mPathIsDirty = true;
+        invalidateSelf();
+        return true;
+    }
+
+    /**
+     * This checks mGradientIsDirty, and if it is true, recomputes both our drawing
+     * rectangle (mRect) and the gradient itself, since it depends on our
+     * rectangle too.
+     * @return true if the resulting rectangle is not empty, false otherwise
+     */
+    private boolean ensureValidRect() {
+        if (mGradientIsDirty) {
+            mGradientIsDirty = false;
+
+            Rect bounds = getBounds();
+            float inset = 0;
+
+            if (mStrokePaint != null) {
+                inset = mStrokePaint.getStrokeWidth() * 0.5f;
+            }
+
+            final GradientState st = mGradientState;
+
+            mRect.set(bounds.left + inset, bounds.top + inset,
+                      bounds.right - inset, bounds.bottom - inset);
+
+            final int[] gradientColors = st.mGradientColors;
+            if (gradientColors != null) {
+                final RectF r = mRect;
+                final float x0, x1, y0, y1;
+
+                if (st.mGradient == LINEAR_GRADIENT) {
+                    final float level = st.mUseLevel ? getLevel() / 10000.0f : 1.0f;
+                    switch (st.mOrientation) {
+                    case TOP_BOTTOM:
+                        x0 = r.left;            y0 = r.top;
+                        x1 = x0;                y1 = level * r.bottom;
+                        break;
+                    case TR_BL:
+                        x0 = r.right;           y0 = r.top;
+                        x1 = level * r.left;    y1 = level * r.bottom;
+                        break;
+                    case RIGHT_LEFT:
+                        x0 = r.right;           y0 = r.top;
+                        x1 = level * r.left;    y1 = y0;
+                        break;
+                    case BR_TL:
+                        x0 = r.right;           y0 = r.bottom;
+                        x1 = level * r.left;    y1 = level * r.top;
+                        break;
+                    case BOTTOM_TOP:
+                        x0 = r.left;            y0 = r.bottom;
+                        x1 = x0;                y1 = level * r.top;
+                        break;
+                    case BL_TR:
+                        x0 = r.left;            y0 = r.bottom;
+                        x1 = level * r.right;   y1 = level * r.top;
+                        break;
+                    case LEFT_RIGHT:
+                        x0 = r.left;            y0 = r.top;
+                        x1 = level * r.right;   y1 = y0;
+                        break;
+                    default:/* TL_BR */
+                        x0 = r.left;            y0 = r.top;
+                        x1 = level * r.right;   y1 = level * r.bottom;
+                        break;
+                    }
+
+                    mFillPaint.setShader(new LinearGradient(x0, y0, x1, y1,
+                            gradientColors, st.mPositions, Shader.TileMode.CLAMP));
+                } else if (st.mGradient == RADIAL_GRADIENT) {
+                    x0 = r.left + (r.right - r.left) * st.mCenterX;
+                    y0 = r.top + (r.bottom - r.top) * st.mCenterY;
+
+                    float radius = st.mGradientRadius;
+                    if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION) {
+                        // Fall back to parent width or height if intrinsic
+                        // size is not specified.
+                        final float width = st.mWidth >= 0 ? st.mWidth : r.width();
+                        final float height = st.mHeight >= 0 ? st.mHeight : r.height();
+                        radius *= Math.min(width, height);
+                    } else if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION_PARENT) {
+                        radius *= Math.min(r.width(), r.height());
+                    }
+
+                    if (st.mUseLevel) {
+                        radius *= getLevel() / 10000.0f;
+                    }
+
+                    mGradientRadius = radius;
+
+                    if (radius <= 0) {
+                        // We can't have a shader with non-positive radius, so
+                        // let's have a very, very small radius.
+                        radius = 0.001f;
+                    }
+
+                    mFillPaint.setShader(new RadialGradient(
+                            x0, y0, radius, gradientColors, null, Shader.TileMode.CLAMP));
+                } else if (st.mGradient == SWEEP_GRADIENT) {
+                    x0 = r.left + (r.right - r.left) * st.mCenterX;
+                    y0 = r.top + (r.bottom - r.top) * st.mCenterY;
+
+                    int[] tempColors = gradientColors;
+                    float[] tempPositions = null;
+
+                    if (st.mUseLevel) {
+                        tempColors = st.mTempColors;
+                        final int length = gradientColors.length;
+                        if (tempColors == null || tempColors.length != length + 1) {
+                            tempColors = st.mTempColors = new int[length + 1];
+                        }
+                        System.arraycopy(gradientColors, 0, tempColors, 0, length);
+                        tempColors[length] = gradientColors[length - 1];
+
+                        tempPositions = st.mTempPositions;
+                        final float fraction = 1.0f / (length - 1);
+                        if (tempPositions == null || tempPositions.length != length + 1) {
+                            tempPositions = st.mTempPositions = new float[length + 1];
+                        }
+
+                        final float level = getLevel() / 10000.0f;
+                        for (int i = 0; i < length; i++) {
+                            tempPositions[i] = i * fraction * level;
+                        }
+                        tempPositions[length] = 1.0f;
+
+                    }
+                    mFillPaint.setShader(new SweepGradient(x0, y0, tempColors, tempPositions));
+                }
+
+                // If we don't have a solid color, the alpha channel must be
+                // maxed out so that alpha modulation works correctly.
+                if (st.mSolidColors == null) {
+                    mFillPaint.setColor(Color.BLACK);
+                }
+            }
+        }
+        return !mRect.isEmpty();
+    }
+
+    @Override
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
+        super.inflate(r, parser, attrs, theme);
+
+        mGradientState.setDensity(Drawable.resolveDensity(r, 0));
+
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawable);
+        updateStateFromTypedArray(a);
+        a.recycle();
+
+        inflateChildElements(r, parser, attrs, theme);
+
+        updateLocalState(r);
+    }
+
+    @Override
+    public void applyTheme(@NonNull Theme t) {
+        super.applyTheme(t);
+
+        final GradientState state = mGradientState;
+        if (state == null) {
+            return;
+        }
+
+        state.setDensity(Drawable.resolveDensity(t.getResources(), 0));
+
+        if (state.mThemeAttrs != null) {
+            final TypedArray a = t.resolveAttributes(
+                    state.mThemeAttrs, R.styleable.GradientDrawable);
+            updateStateFromTypedArray(a);
+            a.recycle();
+        }
+
+        if (state.mTint != null && state.mTint.canApplyTheme()) {
+            state.mTint = state.mTint.obtainForTheme(t);
+        }
+
+        if (state.mSolidColors != null && state.mSolidColors.canApplyTheme()) {
+            state.mSolidColors = state.mSolidColors.obtainForTheme(t);
+        }
+
+        if (state.mStrokeColors != null && state.mStrokeColors.canApplyTheme()) {
+            state.mStrokeColors = state.mStrokeColors.obtainForTheme(t);
+        }
+
+        applyThemeChildElements(t);
+
+        updateLocalState(t.getResources());
+    }
+
+    /**
+     * Updates the constant state from the values in the typed array.
+     */
+    private void updateStateFromTypedArray(TypedArray a) {
+        final GradientState state = mGradientState;
+
+        // Account for any configuration changes.
+        state.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        state.mThemeAttrs = a.extractThemeAttrs();
+
+        state.mShape = a.getInt(R.styleable.GradientDrawable_shape, state.mShape);
+        state.mDither = a.getBoolean(R.styleable.GradientDrawable_dither, state.mDither);
+
+        if (state.mShape == RING) {
+            state.mInnerRadius = a.getDimensionPixelSize(
+                    R.styleable.GradientDrawable_innerRadius, state.mInnerRadius);
+
+            if (state.mInnerRadius == -1) {
+                state.mInnerRadiusRatio = a.getFloat(
+                        R.styleable.GradientDrawable_innerRadiusRatio, state.mInnerRadiusRatio);
+            }
+
+            state.mThickness = a.getDimensionPixelSize(
+                    R.styleable.GradientDrawable_thickness, state.mThickness);
+
+            if (state.mThickness == -1) {
+                state.mThicknessRatio = a.getFloat(
+                        R.styleable.GradientDrawable_thicknessRatio, state.mThicknessRatio);
+            }
+
+            state.mUseLevelForShape = a.getBoolean(
+                    R.styleable.GradientDrawable_useLevel, state.mUseLevelForShape);
+        }
+
+        final int tintMode = a.getInt(R.styleable.GradientDrawable_tintMode, -1);
+        if (tintMode != -1) {
+            state.mTintMode = Drawable.parseTintMode(tintMode, PorterDuff.Mode.SRC_IN);
+        }
+
+        final ColorStateList tint = a.getColorStateList(R.styleable.GradientDrawable_tint);
+        if (tint != null) {
+            state.mTint = tint;
+        }
+
+        final int insetLeft = a.getDimensionPixelSize(
+                R.styleable.GradientDrawable_opticalInsetLeft, state.mOpticalInsets.left);
+        final int insetTop = a.getDimensionPixelSize(
+                R.styleable.GradientDrawable_opticalInsetTop, state.mOpticalInsets.top);
+        final int insetRight = a.getDimensionPixelSize(
+                R.styleable.GradientDrawable_opticalInsetRight, state.mOpticalInsets.right);
+        final int insetBottom = a.getDimensionPixelSize(
+                R.styleable.GradientDrawable_opticalInsetBottom, state.mOpticalInsets.bottom);
+        state.mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom);
+    }
+
+    @Override
+    public boolean canApplyTheme() {
+        return (mGradientState != null && mGradientState.canApplyTheme()) || super.canApplyTheme();
+    }
+
+    private void applyThemeChildElements(Theme t) {
+        final GradientState st = mGradientState;
+
+        if (st.mAttrSize != null) {
+            final TypedArray a = t.resolveAttributes(
+                    st.mAttrSize, R.styleable.GradientDrawableSize);
+            updateGradientDrawableSize(a);
+            a.recycle();
+        }
+
+        if (st.mAttrGradient != null) {
+            final TypedArray a = t.resolveAttributes(
+                    st.mAttrGradient, R.styleable.GradientDrawableGradient);
+            try {
+                updateGradientDrawableGradient(t.getResources(), a);
+            } catch (XmlPullParserException e) {
+                rethrowAsRuntimeException(e);
+            } finally {
+                a.recycle();
+            }
+        }
+
+        if (st.mAttrSolid != null) {
+            final TypedArray a = t.resolveAttributes(
+                    st.mAttrSolid, R.styleable.GradientDrawableSolid);
+            updateGradientDrawableSolid(a);
+            a.recycle();
+        }
+
+        if (st.mAttrStroke != null) {
+            final TypedArray a = t.resolveAttributes(
+                    st.mAttrStroke, R.styleable.GradientDrawableStroke);
+            updateGradientDrawableStroke(a);
+            a.recycle();
+        }
+
+        if (st.mAttrCorners != null) {
+            final TypedArray a = t.resolveAttributes(
+                    st.mAttrCorners, R.styleable.DrawableCorners);
+            updateDrawableCorners(a);
+            a.recycle();
+        }
+
+        if (st.mAttrPadding != null) {
+            final TypedArray a = t.resolveAttributes(
+                    st.mAttrPadding, R.styleable.GradientDrawablePadding);
+            updateGradientDrawablePadding(a);
+            a.recycle();
+        }
+    }
+
+    private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
+            Theme theme) throws XmlPullParserException, IOException {
+        TypedArray a;
+        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) {
+                continue;
+            }
+
+            String name = parser.getName();
+
+            if (name.equals("size")) {
+                a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableSize);
+                updateGradientDrawableSize(a);
+                a.recycle();
+            } else if (name.equals("gradient")) {
+                a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableGradient);
+                updateGradientDrawableGradient(r, a);
+                a.recycle();
+            } else if (name.equals("solid")) {
+                a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableSolid);
+                updateGradientDrawableSolid(a);
+                a.recycle();
+            } else if (name.equals("stroke")) {
+                a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableStroke);
+                updateGradientDrawableStroke(a);
+                a.recycle();
+            } else if (name.equals("corners")) {
+                a = obtainAttributes(r, theme, attrs, R.styleable.DrawableCorners);
+                updateDrawableCorners(a);
+                a.recycle();
+            } else if (name.equals("padding")) {
+                a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawablePadding);
+                updateGradientDrawablePadding(a);
+                a.recycle();
+            } else {
+                Log.w("drawable", "Bad element under <shape>: " + name);
+            }
+        }
+    }
+
+    private void updateGradientDrawablePadding(TypedArray a) {
+        final GradientState st = mGradientState;
+
+        // Account for any configuration changes.
+        st.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        st.mAttrPadding = a.extractThemeAttrs();
+
+        if (st.mPadding == null) {
+            st.mPadding = new Rect();
+        }
+
+        final Rect pad = st.mPadding;
+        pad.set(a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_left, pad.left),
+                a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_top, pad.top),
+                a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_right, pad.right),
+                a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_bottom, pad.bottom));
+        mPadding = pad;
+    }
+
+    private void updateDrawableCorners(TypedArray a) {
+        final GradientState st = mGradientState;
+
+        // Account for any configuration changes.
+        st.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        st.mAttrCorners = a.extractThemeAttrs();
+
+        final int radius = a.getDimensionPixelSize(
+                R.styleable.DrawableCorners_radius, (int) st.mRadius);
+        setCornerRadius(radius);
+
+        // TODO: Update these to be themeable.
+        final int topLeftRadius = a.getDimensionPixelSize(
+                R.styleable.DrawableCorners_topLeftRadius, radius);
+        final int topRightRadius = a.getDimensionPixelSize(
+                R.styleable.DrawableCorners_topRightRadius, radius);
+        final int bottomLeftRadius = a.getDimensionPixelSize(
+                R.styleable.DrawableCorners_bottomLeftRadius, radius);
+        final int bottomRightRadius = a.getDimensionPixelSize(
+                R.styleable.DrawableCorners_bottomRightRadius, radius);
+        if (topLeftRadius != radius || topRightRadius != radius ||
+                bottomLeftRadius != radius || bottomRightRadius != radius) {
+            // The corner radii are specified in clockwise order (see Path.addRoundRect())
+            setCornerRadii(new float[] {
+                    topLeftRadius, topLeftRadius,
+                    topRightRadius, topRightRadius,
+                    bottomRightRadius, bottomRightRadius,
+                    bottomLeftRadius, bottomLeftRadius
+            });
+        }
+    }
+
+    private void updateGradientDrawableStroke(TypedArray a) {
+        final GradientState st = mGradientState;
+
+        // Account for any configuration changes.
+        st.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        st.mAttrStroke = a.extractThemeAttrs();
+
+        // We have an explicit stroke defined, so the default stroke width
+        // must be at least 0 or the current stroke width.
+        final int defaultStrokeWidth = Math.max(0, st.mStrokeWidth);
+        final int width = a.getDimensionPixelSize(
+                R.styleable.GradientDrawableStroke_width, defaultStrokeWidth);
+        final float dashWidth = a.getDimension(
+                R.styleable.GradientDrawableStroke_dashWidth, st.mStrokeDashWidth);
+
+        ColorStateList colorStateList = a.getColorStateList(
+                R.styleable.GradientDrawableStroke_color);
+        if (colorStateList == null) {
+            colorStateList = st.mStrokeColors;
+        }
+
+        if (dashWidth != 0.0f) {
+            final float dashGap = a.getDimension(
+                    R.styleable.GradientDrawableStroke_dashGap, st.mStrokeDashGap);
+            setStroke(width, colorStateList, dashWidth, dashGap);
+        } else {
+            setStroke(width, colorStateList);
+        }
+    }
+
+    private void updateGradientDrawableSolid(TypedArray a) {
+        final GradientState st = mGradientState;
+
+        // Account for any configuration changes.
+        st.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        st.mAttrSolid = a.extractThemeAttrs();
+
+        final ColorStateList colorStateList = a.getColorStateList(
+                R.styleable.GradientDrawableSolid_color);
+        if (colorStateList != null) {
+            setColor(colorStateList);
+        }
+    }
+
+    private void updateGradientDrawableGradient(Resources r, TypedArray a)
+            throws XmlPullParserException {
+        final GradientState st = mGradientState;
+
+        // Account for any configuration changes.
+        st.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        st.mAttrGradient = a.extractThemeAttrs();
+
+        st.mCenterX = getFloatOrFraction(
+                a, R.styleable.GradientDrawableGradient_centerX, st.mCenterX);
+        st.mCenterY = getFloatOrFraction(
+                a, R.styleable.GradientDrawableGradient_centerY, st.mCenterY);
+        st.mUseLevel = a.getBoolean(
+                R.styleable.GradientDrawableGradient_useLevel, st.mUseLevel);
+        st.mGradient = a.getInt(
+                R.styleable.GradientDrawableGradient_type, st.mGradient);
+
+        // TODO: Update these to be themeable.
+        final int startColor = a.getColor(
+                R.styleable.GradientDrawableGradient_startColor, 0);
+        final boolean hasCenterColor = a.hasValue(
+                R.styleable.GradientDrawableGradient_centerColor);
+        final int centerColor = a.getColor(
+                R.styleable.GradientDrawableGradient_centerColor, 0);
+        final int endColor = a.getColor(
+                R.styleable.GradientDrawableGradient_endColor, 0);
+
+        if (hasCenterColor) {
+            st.mGradientColors = new int[3];
+            st.mGradientColors[0] = startColor;
+            st.mGradientColors[1] = centerColor;
+            st.mGradientColors[2] = endColor;
+
+            st.mPositions = new float[3];
+            st.mPositions[0] = 0.0f;
+            // Since 0.5f is default value, try to take the one that isn't 0.5f
+            st.mPositions[1] = st.mCenterX != 0.5f ? st.mCenterX : st.mCenterY;
+            st.mPositions[2] = 1f;
+        } else {
+            st.mGradientColors = new int[2];
+            st.mGradientColors[0] = startColor;
+            st.mGradientColors[1] = endColor;
+        }
+
+        if (st.mGradient == LINEAR_GRADIENT) {
+            int angle = (int) a.getFloat(R.styleable.GradientDrawableGradient_angle, st.mAngle);
+            angle %= 360;
+
+            if (angle % 45 != 0) {
+                throw new XmlPullParserException(a.getPositionDescription()
+                        + "<gradient> tag requires 'angle' attribute to "
+                        + "be a multiple of 45");
+            }
+
+            st.mAngle = angle;
+
+            switch (angle) {
+                case 0:
+                    st.mOrientation = Orientation.LEFT_RIGHT;
+                    break;
+                case 45:
+                    st.mOrientation = Orientation.BL_TR;
+                    break;
+                case 90:
+                    st.mOrientation = Orientation.BOTTOM_TOP;
+                    break;
+                case 135:
+                    st.mOrientation = Orientation.BR_TL;
+                    break;
+                case 180:
+                    st.mOrientation = Orientation.RIGHT_LEFT;
+                    break;
+                case 225:
+                    st.mOrientation = Orientation.TR_BL;
+                    break;
+                case 270:
+                    st.mOrientation = Orientation.TOP_BOTTOM;
+                    break;
+                case 315:
+                    st.mOrientation = Orientation.TL_BR;
+                    break;
+            }
+        } else {
+            final TypedValue tv = a.peekValue(R.styleable.GradientDrawableGradient_gradientRadius);
+            if (tv != null) {
+                final float radius;
+                final @RadiusType int radiusType;
+                if (tv.type == TypedValue.TYPE_FRACTION) {
+                    radius = tv.getFraction(1.0f, 1.0f);
+
+                    final int unit = (tv.data >> TypedValue.COMPLEX_UNIT_SHIFT)
+                            & TypedValue.COMPLEX_UNIT_MASK;
+                    if (unit == TypedValue.COMPLEX_UNIT_FRACTION_PARENT) {
+                        radiusType = RADIUS_TYPE_FRACTION_PARENT;
+                    } else {
+                        radiusType = RADIUS_TYPE_FRACTION;
+                    }
+                } else if (tv.type == TypedValue.TYPE_DIMENSION) {
+                    radius = tv.getDimension(r.getDisplayMetrics());
+                    radiusType = RADIUS_TYPE_PIXELS;
+                } else {
+                    radius = tv.getFloat();
+                    radiusType = RADIUS_TYPE_PIXELS;
+                }
+
+                st.mGradientRadius = radius;
+                st.mGradientRadiusType = radiusType;
+            } else if (st.mGradient == RADIAL_GRADIENT) {
+                throw new XmlPullParserException(
+                        a.getPositionDescription()
+                        + "<gradient> tag requires 'gradientRadius' "
+                        + "attribute with radial type");
+            }
+        }
+    }
+
+    private void updateGradientDrawableSize(TypedArray a) {
+        final GradientState st = mGradientState;
+
+        // Account for any configuration changes.
+        st.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        st.mAttrSize = a.extractThemeAttrs();
+
+        st.mWidth = a.getDimensionPixelSize(R.styleable.GradientDrawableSize_width, st.mWidth);
+        st.mHeight = a.getDimensionPixelSize(R.styleable.GradientDrawableSize_height, st.mHeight);
+    }
+
+    private static float getFloatOrFraction(TypedArray a, int index, float defaultValue) {
+        TypedValue tv = a.peekValue(index);
+        float v = defaultValue;
+        if (tv != null) {
+            boolean vIsFraction = tv.type == TypedValue.TYPE_FRACTION;
+            v = vIsFraction ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
+        }
+        return v;
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return mGradientState.mWidth;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return mGradientState.mHeight;
+    }
+
+    /** @hide */
+    @Override
+    public Insets getOpticalInsets() {
+        return mGradientState.mOpticalInsets;
+    }
+
+    @Override
+    public ConstantState getConstantState() {
+        mGradientState.mChangingConfigurations = getChangingConfigurations();
+        return mGradientState;
+    }
+
+    private boolean isOpaqueForState() {
+        if (mGradientState.mStrokeWidth >= 0 && mStrokePaint != null
+                && !isOpaque(mStrokePaint.getColor())) {
+            return false;
+        }
+
+        // Don't check opacity if we're using a gradient, as we've already
+        // checked the gradient opacity in mOpaqueOverShape.
+        if (mGradientState.mGradientColors == null && !isOpaque(mFillPaint.getColor())) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public void getOutline(Outline outline) {
+        final GradientState st = mGradientState;
+        final Rect bounds = getBounds();
+        // only report non-zero alpha if shape being drawn has consistent opacity over shape. Must
+        // either not have a stroke, or have same stroke/fill opacity
+        boolean useFillOpacity = st.mOpaqueOverShape && (mGradientState.mStrokeWidth <= 0
+                || mStrokePaint == null
+                || mStrokePaint.getAlpha() == mFillPaint.getAlpha());
+        outline.setAlpha(useFillOpacity
+                ? modulateAlpha(mFillPaint.getAlpha()) / 255.0f
+                : 0.0f);
+
+        switch (st.mShape) {
+            case RECTANGLE:
+                if (st.mRadiusArray != null) {
+                    buildPathIfDirty();
+                    outline.setConvexPath(mPath);
+                    return;
+                }
+
+                float rad = 0;
+                if (st.mRadius > 0.0f) {
+                    // clamp the radius based on width & height, matching behavior in draw()
+                    rad = Math.min(st.mRadius,
+                            Math.min(bounds.width(), bounds.height()) * 0.5f);
+                }
+                outline.setRoundRect(bounds, rad);
+                return;
+            case OVAL:
+                outline.setOval(bounds);
+                return;
+            case LINE:
+                // Hairlines (0-width stroke) must have a non-empty outline for
+                // shadows to draw correctly, so we'll use a very small width.
+                final float halfStrokeWidth = mStrokePaint == null ?
+                        0.0001f : mStrokePaint.getStrokeWidth() * 0.5f;
+                final float centerY = bounds.centerY();
+                final int top = (int) Math.floor(centerY - halfStrokeWidth);
+                final int bottom = (int) Math.ceil(centerY + halfStrokeWidth);
+
+                outline.setRect(bounds.left, top, bounds.right, bottom);
+                return;
+            default:
+                // TODO: support more complex shapes
+        }
+    }
+
+    @Override
+    public Drawable mutate() {
+        if (!mMutated && super.mutate() == this) {
+            mGradientState = new GradientState(mGradientState, null);
+            updateLocalState(null);
+            mMutated = true;
+        }
+        return this;
+    }
+
+    /**
+     * @hide
+     */
+    public void clearMutated() {
+        super.clearMutated();
+        mMutated = false;
+    }
+
+    final static class GradientState extends ConstantState {
+        public @Config int mChangingConfigurations;
+        public @Shape int mShape = RECTANGLE;
+        public @GradientType int mGradient = LINEAR_GRADIENT;
+        public int mAngle = 0;
+        public Orientation mOrientation;
+        public ColorStateList mSolidColors;
+        public ColorStateList mStrokeColors;
+        public @ColorInt int[] mGradientColors;
+        public @ColorInt int[] mTempColors; // no need to copy
+        public float[] mTempPositions; // no need to copy
+        public float[] mPositions;
+        public int mStrokeWidth = -1; // if >= 0 use stroking.
+        public float mStrokeDashWidth = 0.0f;
+        public float mStrokeDashGap = 0.0f;
+        public float mRadius = 0.0f; // use this if mRadiusArray is null
+        public float[] mRadiusArray = null;
+        public Rect mPadding = null;
+        public int mWidth = -1;
+        public int mHeight = -1;
+        public float mInnerRadiusRatio = DEFAULT_INNER_RADIUS_RATIO;
+        public float mThicknessRatio = DEFAULT_THICKNESS_RATIO;
+        public int mInnerRadius = -1;
+        public int mThickness = -1;
+        public boolean mDither = false;
+        public Insets mOpticalInsets = Insets.NONE;
+
+        float mCenterX = 0.5f;
+        float mCenterY = 0.5f;
+        float mGradientRadius = 0.5f;
+        @RadiusType int mGradientRadiusType = RADIUS_TYPE_PIXELS;
+        boolean mUseLevel = false;
+        boolean mUseLevelForShape = true;
+
+        boolean mOpaqueOverBounds;
+        boolean mOpaqueOverShape;
+
+        ColorStateList mTint = null;
+        PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE;
+
+        int mDensity = DisplayMetrics.DENSITY_DEFAULT;
+
+        int[] mThemeAttrs;
+        int[] mAttrSize;
+        int[] mAttrGradient;
+        int[] mAttrSolid;
+        int[] mAttrStroke;
+        int[] mAttrCorners;
+        int[] mAttrPadding;
+
+        public GradientState(Orientation orientation, int[] gradientColors) {
+            mOrientation = orientation;
+            setGradientColors(gradientColors);
+        }
+
+        public GradientState(@NonNull GradientState orig, @Nullable Resources res) {
+            mChangingConfigurations = orig.mChangingConfigurations;
+            mShape = orig.mShape;
+            mGradient = orig.mGradient;
+            mAngle = orig.mAngle;
+            mOrientation = orig.mOrientation;
+            mSolidColors = orig.mSolidColors;
+            if (orig.mGradientColors != null) {
+                mGradientColors = orig.mGradientColors.clone();
+            }
+            if (orig.mPositions != null) {
+                mPositions = orig.mPositions.clone();
+            }
+            mStrokeColors = orig.mStrokeColors;
+            mStrokeWidth = orig.mStrokeWidth;
+            mStrokeDashWidth = orig.mStrokeDashWidth;
+            mStrokeDashGap = orig.mStrokeDashGap;
+            mRadius = orig.mRadius;
+            if (orig.mRadiusArray != null) {
+                mRadiusArray = orig.mRadiusArray.clone();
+            }
+            if (orig.mPadding != null) {
+                mPadding = new Rect(orig.mPadding);
+            }
+            mWidth = orig.mWidth;
+            mHeight = orig.mHeight;
+            mInnerRadiusRatio = orig.mInnerRadiusRatio;
+            mThicknessRatio = orig.mThicknessRatio;
+            mInnerRadius = orig.mInnerRadius;
+            mThickness = orig.mThickness;
+            mDither = orig.mDither;
+            mOpticalInsets = orig.mOpticalInsets;
+            mCenterX = orig.mCenterX;
+            mCenterY = orig.mCenterY;
+            mGradientRadius = orig.mGradientRadius;
+            mGradientRadiusType = orig.mGradientRadiusType;
+            mUseLevel = orig.mUseLevel;
+            mUseLevelForShape = orig.mUseLevelForShape;
+            mOpaqueOverBounds = orig.mOpaqueOverBounds;
+            mOpaqueOverShape = orig.mOpaqueOverShape;
+            mTint = orig.mTint;
+            mTintMode = orig.mTintMode;
+            mThemeAttrs = orig.mThemeAttrs;
+            mAttrSize = orig.mAttrSize;
+            mAttrGradient = orig.mAttrGradient;
+            mAttrSolid = orig.mAttrSolid;
+            mAttrStroke = orig.mAttrStroke;
+            mAttrCorners = orig.mAttrCorners;
+            mAttrPadding = orig.mAttrPadding;
+
+            mDensity = Drawable.resolveDensity(res, orig.mDensity);
+            if (orig.mDensity != mDensity) {
+                applyDensityScaling(orig.mDensity, mDensity);
+            }
+        }
+
+        /**
+         * Sets the constant state density.
+         * <p>
+         * If the density has been previously set, dispatches the change to
+         * subclasses so that density-dependent properties may be scaled as
+         * necessary.
+         *
+         * @param targetDensity the new constant state density
+         */
+        public final void setDensity(int targetDensity) {
+            if (mDensity != targetDensity) {
+                final int sourceDensity = mDensity;
+                mDensity = targetDensity;
+
+                applyDensityScaling(sourceDensity, targetDensity);
+            }
+        }
+
+        private void applyDensityScaling(int sourceDensity, int targetDensity) {
+            if (mInnerRadius > 0) {
+                mInnerRadius = Drawable.scaleFromDensity(
+                        mInnerRadius, sourceDensity, targetDensity, true);
+            }
+            if (mThickness > 0) {
+                mThickness = Drawable.scaleFromDensity(
+                        mThickness, sourceDensity, targetDensity, true);
+            }
+            if (mOpticalInsets != Insets.NONE) {
+                final int left = Drawable.scaleFromDensity(
+                        mOpticalInsets.left, sourceDensity, targetDensity, true);
+                final int top = Drawable.scaleFromDensity(
+                        mOpticalInsets.top, sourceDensity, targetDensity, true);
+                final int right = Drawable.scaleFromDensity(
+                        mOpticalInsets.right, sourceDensity, targetDensity, true);
+                final int bottom = Drawable.scaleFromDensity(
+                        mOpticalInsets.bottom, sourceDensity, targetDensity, true);
+                mOpticalInsets = Insets.of(left, top, right, bottom);
+            }
+            if (mPadding != null) {
+                mPadding.left = Drawable.scaleFromDensity(
+                        mPadding.left, sourceDensity, targetDensity, false);
+                mPadding.top = Drawable.scaleFromDensity(
+                        mPadding.top, sourceDensity, targetDensity, false);
+                mPadding.right = Drawable.scaleFromDensity(
+                        mPadding.right, sourceDensity, targetDensity, false);
+                mPadding.bottom = Drawable.scaleFromDensity(
+                        mPadding.bottom, sourceDensity, targetDensity, false);
+            }
+            if (mRadius > 0) {
+                mRadius = Drawable.scaleFromDensity(mRadius, sourceDensity, targetDensity);
+            }
+            if (mRadiusArray != null) {
+                mRadiusArray[0] = Drawable.scaleFromDensity(
+                        (int) mRadiusArray[0], sourceDensity, targetDensity, true);
+                mRadiusArray[1] = Drawable.scaleFromDensity(
+                        (int) mRadiusArray[1], sourceDensity, targetDensity, true);
+                mRadiusArray[2] = Drawable.scaleFromDensity(
+                        (int) mRadiusArray[2], sourceDensity, targetDensity, true);
+                mRadiusArray[3] = Drawable.scaleFromDensity(
+                        (int) mRadiusArray[3], sourceDensity, targetDensity, true);
+            }
+            if (mStrokeWidth > 0) {
+                mStrokeWidth = Drawable.scaleFromDensity(
+                        mStrokeWidth, sourceDensity, targetDensity, true);
+            }
+            if (mStrokeDashWidth > 0) {
+                mStrokeDashWidth = Drawable.scaleFromDensity(
+                        mStrokeDashGap, sourceDensity, targetDensity);
+            }
+            if (mStrokeDashGap > 0) {
+                mStrokeDashGap = Drawable.scaleFromDensity(
+                        mStrokeDashGap, sourceDensity, targetDensity);
+            }
+            if (mGradientRadiusType == RADIUS_TYPE_PIXELS) {
+                mGradientRadius = Drawable.scaleFromDensity(
+                        mGradientRadius, sourceDensity, targetDensity);
+            }
+            if (mWidth > 0) {
+                mWidth = Drawable.scaleFromDensity(mWidth, sourceDensity, targetDensity, true);
+            }
+            if (mHeight > 0) {
+                mHeight = Drawable.scaleFromDensity(mHeight, sourceDensity, targetDensity, true);
+            }
+        }
+
+        @Override
+        public boolean canApplyTheme() {
+            return mThemeAttrs != null
+                    || mAttrSize != null || mAttrGradient != null
+                    || mAttrSolid != null || mAttrStroke != null
+                    || mAttrCorners != null || mAttrPadding != null
+                    || (mTint != null && mTint.canApplyTheme())
+                    || (mStrokeColors != null && mStrokeColors.canApplyTheme())
+                    || (mSolidColors != null && mSolidColors.canApplyTheme())
+                    || super.canApplyTheme();
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return new GradientDrawable(this, null);
+        }
+
+        @Override
+        public Drawable newDrawable(@Nullable Resources res) {
+            // If this drawable is being created for a different density,
+            // just create a new constant state and call it a day.
+            final GradientState state;
+            final int density = Drawable.resolveDensity(res, mDensity);
+            if (density != mDensity) {
+                state = new GradientState(this, res);
+            } else {
+                state = this;
+            }
+
+            return new GradientDrawable(state, res);
+        }
+
+        @Override
+        public @Config int getChangingConfigurations() {
+            return mChangingConfigurations
+                    | (mStrokeColors != null ? mStrokeColors.getChangingConfigurations() : 0)
+                    | (mSolidColors != null ? mSolidColors.getChangingConfigurations() : 0)
+                    | (mTint != null ? mTint.getChangingConfigurations() : 0);
+        }
+
+        public void setShape(@Shape int shape) {
+            mShape = shape;
+            computeOpacity();
+        }
+
+        public void setGradientType(@GradientType int gradient) {
+            mGradient = gradient;
+        }
+
+        public void setGradientCenter(float x, float y) {
+            mCenterX = x;
+            mCenterY = y;
+        }
+
+        public void setGradientColors(@Nullable int[] colors) {
+            mGradientColors = colors;
+            mSolidColors = null;
+            computeOpacity();
+        }
+
+        public void setSolidColors(@Nullable ColorStateList colors) {
+            mGradientColors = null;
+            mSolidColors = colors;
+            computeOpacity();
+        }
+
+        private void computeOpacity() {
+            mOpaqueOverBounds = false;
+            mOpaqueOverShape = false;
+
+            if (mGradientColors != null) {
+                for (int i = 0; i < mGradientColors.length; i++) {
+                    if (!isOpaque(mGradientColors[i])) {
+                        return;
+                    }
+                }
+            }
+
+            // An unfilled shape is not opaque over bounds or shape
+            if (mGradientColors == null && mSolidColors == null) {
+                return;
+            }
+
+            // Colors are opaque, so opaqueOverShape=true,
+            mOpaqueOverShape = true;
+            // and opaqueOverBounds=true if shape fills bounds
+            mOpaqueOverBounds = mShape == RECTANGLE
+                    && mRadius <= 0
+                    && mRadiusArray == null;
+        }
+
+        public void setStroke(int width, @Nullable ColorStateList colors, float dashWidth,
+                float dashGap) {
+            mStrokeWidth = width;
+            mStrokeColors = colors;
+            mStrokeDashWidth = dashWidth;
+            mStrokeDashGap = dashGap;
+            computeOpacity();
+        }
+
+        public void setCornerRadius(float radius) {
+            if (radius < 0) {
+                radius = 0;
+            }
+            mRadius = radius;
+            mRadiusArray = null;
+        }
+
+        public void setCornerRadii(float[] radii) {
+            mRadiusArray = radii;
+            if (radii == null) {
+                mRadius = 0;
+            }
+        }
+
+        public void setSize(int width, int height) {
+            mWidth = width;
+            mHeight = height;
+        }
+
+        public void setGradientRadius(float gradientRadius, @RadiusType int type) {
+            mGradientRadius = gradientRadius;
+            mGradientRadiusType = type;
+        }
+    }
+
+    static boolean isOpaque(int color) {
+        return ((color >> 24) & 0xff) == 0xff;
+    }
+
+    /**
+     * Creates a new themed GradientDrawable based on the specified constant state.
+     * <p>
+     * The resulting drawable is guaranteed to have a new constant state.
+     *
+     * @param state Constant state from which the drawable inherits
+     */
+    private GradientDrawable(@NonNull GradientState state, @Nullable Resources res) {
+        mGradientState = state;
+
+        updateLocalState(res);
+    }
+
+    private void updateLocalState(Resources res) {
+        final GradientState state = mGradientState;
+
+        if (state.mSolidColors != null) {
+            final int[] currentState = getState();
+            final int stateColor = state.mSolidColors.getColorForState(currentState, 0);
+            mFillPaint.setColor(stateColor);
+        } else if (state.mGradientColors == null) {
+            // If we don't have a solid color and we don't have a gradient,
+            // the app is stroking the shape, set the color to the default
+            // value of state.mSolidColor
+            mFillPaint.setColor(0);
+        } else {
+            // Otherwise, make sure the fill alpha is maxed out.
+            mFillPaint.setColor(Color.BLACK);
+        }
+
+        mPadding = state.mPadding;
+
+        if (state.mStrokeWidth >= 0) {
+            mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+            mStrokePaint.setStyle(Paint.Style.STROKE);
+            mStrokePaint.setStrokeWidth(state.mStrokeWidth);
+
+            if (state.mStrokeColors != null) {
+                final int[] currentState = getState();
+                final int strokeStateColor = state.mStrokeColors.getColorForState(
+                        currentState, 0);
+                mStrokePaint.setColor(strokeStateColor);
+            }
+
+            if (state.mStrokeDashWidth != 0.0f) {
+                final DashPathEffect e = new DashPathEffect(
+                        new float[] { state.mStrokeDashWidth, state.mStrokeDashGap }, 0);
+                mStrokePaint.setPathEffect(e);
+            }
+        }
+
+        mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
+        mGradientIsDirty = true;
+
+        state.computeOpacity();
+    }
+}
diff --git a/android/graphics/drawable/GradientDrawable_Delegate.java b/android/graphics/drawable/GradientDrawable_Delegate.java
new file mode 100644
index 0000000..a3ad2aa
--- /dev/null
+++ b/android/graphics/drawable/GradientDrawable_Delegate.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2015 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 com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.graphics.Path;
+import android.graphics.drawable.GradientDrawable.GradientState;
+
+import java.lang.reflect.Field;
+
+/**
+ * Delegate implementing the native methods of {@link GradientDrawable}
+ *
+ * Through the layoutlib_create tool, the original native methods of GradientDrawable have been
+ * replaced by calls to methods of the same name in this delegate class.
+ */
+public class GradientDrawable_Delegate {
+
+    /**
+     * The ring can be built either by drawing full circles, or by drawing arcs in case the
+     * circle isn't complete. LayoutLib cannot handle drawing full circles (requires path
+     * subtraction). So, if we need to draw full circles, we switch to drawing 99% circle.
+     */
+    @LayoutlibDelegate
+    /*package*/ static Path buildRing(GradientDrawable thisDrawable, GradientState st) {
+        boolean useLevel = st.mUseLevelForShape;
+        int level = thisDrawable.getLevel();
+        // 10000 is the max level. See android.graphics.drawable.Drawable#getLevel()
+        float sweep = useLevel ? (360.0f * level / 10000.0f) : 360f;
+        Field mLevel = null;
+        if (sweep >= 360 || sweep <= -360) {
+            st.mUseLevelForShape = true;
+            // Use reflection to set the value of the field to prevent setting the drawable to
+            // dirty again.
+            try {
+                mLevel = Drawable.class.getDeclaredField("mLevel");
+                mLevel.setAccessible(true);
+                mLevel.setInt(thisDrawable, 9999);  // set to one less than max.
+            } catch (NoSuchFieldException e) {
+                // The field has been removed in a recent framework change. Fall back to old
+                // buggy behaviour.
+            } catch (IllegalAccessException e) {
+                // We've already set the field to be accessible.
+                assert false;
+            }
+        }
+        Path path = thisDrawable.buildRing_Original(st);
+        st.mUseLevelForShape = useLevel;
+        if (mLevel != null) {
+            try {
+                mLevel.setInt(thisDrawable, level);
+            } catch (IllegalAccessException e) {
+                assert false;
+            }
+        }
+        return path;
+    }
+}
diff --git a/android/graphics/drawable/Icon.java b/android/graphics/drawable/Icon.java
new file mode 100644
index 0000000..c329918
--- /dev/null
+++ b/android/graphics/drawable/Icon.java
@@ -0,0 +1,887 @@
+/*
+ * Copyright (C) 2015 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.ColorInt;
+import android.annotation.DrawableRes;
+import android.content.res.ColorStateList;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.PorterDuff;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * An umbrella container for several serializable graphics representations, including Bitmaps,
+ * compressed bitmap images (e.g. JPG or PNG), and drawable resources (including vectors).
+ *
+ * <a href="https://developer.android.com/training/displaying-bitmaps/index.html">Much ink</a>
+ * has been spilled on the best way to load images, and many clients may have different needs when
+ * it comes to threading and fetching. This class is therefore focused on encapsulation rather than
+ * behavior.
+ */
+
+public final class Icon implements Parcelable {
+    private static final String TAG = "Icon";
+
+    /** @hide */
+    public static final int TYPE_BITMAP   = 1;
+    /** @hide */
+    public static final int TYPE_RESOURCE = 2;
+    /** @hide */
+    public static final int TYPE_DATA     = 3;
+    /** @hide */
+    public static final int TYPE_URI      = 4;
+    /** @hide */
+    public static final int TYPE_ADAPTIVE_BITMAP = 5;
+
+    private static final int VERSION_STREAM_SERIALIZER = 1;
+
+    private final int mType;
+
+    private ColorStateList mTintList;
+    static final PorterDuff.Mode DEFAULT_TINT_MODE = Drawable.DEFAULT_TINT_MODE; // SRC_IN
+    private PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE;
+
+    // To avoid adding unnecessary overhead, we have a few basic objects that get repurposed
+    // based on the value of mType.
+
+    // TYPE_BITMAP: Bitmap
+    // TYPE_RESOURCE: Resources
+    // TYPE_DATA: DataBytes
+    private Object          mObj1;
+
+    // TYPE_RESOURCE: package name
+    // TYPE_URI: uri string
+    private String          mString1;
+
+    // TYPE_RESOURCE: resId
+    // TYPE_DATA: data length
+    private int             mInt1;
+
+    // TYPE_DATA: data offset
+    private int             mInt2;
+
+    /**
+     * @return The type of image data held in this Icon. One of
+     * {@link #TYPE_BITMAP},
+     * {@link #TYPE_RESOURCE},
+     * {@link #TYPE_DATA}, or
+     * {@link #TYPE_URI}.
+     * {@link #TYPE_ADAPTIVE_BITMAP}
+     * @hide
+     */
+    public int getType() {
+        return mType;
+    }
+
+    /**
+     * @return The {@link android.graphics.Bitmap} held by this {@link #TYPE_BITMAP} Icon.
+     * @hide
+     */
+    public Bitmap getBitmap() {
+        if (mType != TYPE_BITMAP && mType != TYPE_ADAPTIVE_BITMAP) {
+            throw new IllegalStateException("called getBitmap() on " + this);
+        }
+        return (Bitmap) mObj1;
+    }
+
+    private void setBitmap(Bitmap b) {
+        mObj1 = b;
+    }
+
+    /**
+     * @return The length of the compressed bitmap byte array held by this {@link #TYPE_DATA} Icon.
+     * @hide
+     */
+    public int getDataLength() {
+        if (mType != TYPE_DATA) {
+            throw new IllegalStateException("called getDataLength() on " + this);
+        }
+        synchronized (this) {
+            return mInt1;
+        }
+    }
+
+    /**
+     * @return The offset into the byte array held by this {@link #TYPE_DATA} Icon at which
+     * valid compressed bitmap data is found.
+     * @hide
+     */
+    public int getDataOffset() {
+        if (mType != TYPE_DATA) {
+            throw new IllegalStateException("called getDataOffset() on " + this);
+        }
+        synchronized (this) {
+            return mInt2;
+        }
+    }
+
+    /**
+     * @return The byte array held by this {@link #TYPE_DATA} Icon ctonaining compressed
+     * bitmap data.
+     * @hide
+     */
+    public byte[] getDataBytes() {
+        if (mType != TYPE_DATA) {
+            throw new IllegalStateException("called getDataBytes() on " + this);
+        }
+        synchronized (this) {
+            return (byte[]) mObj1;
+        }
+    }
+
+    /**
+     * @return The {@link android.content.res.Resources} for this {@link #TYPE_RESOURCE} Icon.
+     * @hide
+     */
+    public Resources getResources() {
+        if (mType != TYPE_RESOURCE) {
+            throw new IllegalStateException("called getResources() on " + this);
+        }
+        return (Resources) mObj1;
+    }
+
+    /**
+     * @return The package containing resources for this {@link #TYPE_RESOURCE} Icon.
+     * @hide
+     */
+    public String getResPackage() {
+        if (mType != TYPE_RESOURCE) {
+            throw new IllegalStateException("called getResPackage() on " + this);
+        }
+        return mString1;
+    }
+
+    /**
+     * @return The resource ID for this {@link #TYPE_RESOURCE} Icon.
+     * @hide
+     */
+    public int getResId() {
+        if (mType != TYPE_RESOURCE) {
+            throw new IllegalStateException("called getResId() on " + this);
+        }
+        return mInt1;
+    }
+
+    /**
+     * @return The URI (as a String) for this {@link #TYPE_URI} Icon.
+     * @hide
+     */
+    public String getUriString() {
+        if (mType != TYPE_URI) {
+            throw new IllegalStateException("called getUriString() on " + this);
+        }
+        return mString1;
+    }
+
+    /**
+     * @return The {@link android.net.Uri} for this {@link #TYPE_URI} Icon.
+     * @hide
+     */
+    public Uri getUri() {
+        return Uri.parse(getUriString());
+    }
+
+    private static final String typeToString(int x) {
+        switch (x) {
+            case TYPE_BITMAP: return "BITMAP";
+            case TYPE_ADAPTIVE_BITMAP: return "BITMAP_MASKABLE";
+            case TYPE_DATA: return "DATA";
+            case TYPE_RESOURCE: return "RESOURCE";
+            case TYPE_URI: return "URI";
+            default: return "UNKNOWN";
+        }
+    }
+
+    /**
+     * Invokes {@link #loadDrawable(Context)} on the given {@link android.os.Handler Handler}
+     * and then sends <code>andThen</code> to the same Handler when finished.
+     *
+     * @param context {@link android.content.Context Context} in which to load the drawable; see
+     *                {@link #loadDrawable(Context)}
+     * @param andThen {@link android.os.Message} to send to its target once the drawable
+     *                is available. The {@link android.os.Message#obj obj}
+     *                property is populated with the Drawable.
+     */
+    public void loadDrawableAsync(Context context, Message andThen) {
+        if (andThen.getTarget() == null) {
+            throw new IllegalArgumentException("callback message must have a target handler");
+        }
+        new LoadDrawableTask(context, andThen).runAsync();
+    }
+
+    /**
+     * Invokes {@link #loadDrawable(Context)} on a background thread and notifies the <code>
+     * {@link OnDrawableLoadedListener#onDrawableLoaded listener} </code> on the {@code handler}
+     * when finished.
+     *
+     * @param context {@link Context Context} in which to load the drawable; see
+     *                {@link #loadDrawable(Context)}
+     * @param listener to be {@link OnDrawableLoadedListener#onDrawableLoaded notified} when
+     *                 {@link #loadDrawable(Context)} finished
+     * @param handler {@link Handler} on which to notify the {@code listener}
+     */
+    public void loadDrawableAsync(Context context, final OnDrawableLoadedListener listener,
+            Handler handler) {
+        new LoadDrawableTask(context, handler, listener).runAsync();
+    }
+
+    /**
+     * Returns a Drawable that can be used to draw the image inside this Icon, constructing it
+     * if necessary. Depending on the type of image, this may not be something you want to do on
+     * the UI thread, so consider using
+     * {@link #loadDrawableAsync(Context, Message) loadDrawableAsync} instead.
+     *
+     * @param context {@link android.content.Context Context} in which to load the drawable; used
+     *                to access {@link android.content.res.Resources Resources}, for example.
+     * @return A fresh instance of a drawable for this image, yours to keep.
+     */
+    public Drawable loadDrawable(Context context) {
+        final Drawable result = loadDrawableInner(context);
+        if (result != null && (mTintList != null || mTintMode != DEFAULT_TINT_MODE)) {
+            result.mutate();
+            result.setTintList(mTintList);
+            result.setTintMode(mTintMode);
+        }
+        return result;
+    }
+
+    /**
+     * Do the heavy lifting of loading the drawable, but stop short of applying any tint.
+     */
+    private Drawable loadDrawableInner(Context context) {
+        switch (mType) {
+            case TYPE_BITMAP:
+                return new BitmapDrawable(context.getResources(), getBitmap());
+            case TYPE_ADAPTIVE_BITMAP:
+                return new AdaptiveIconDrawable(null,
+                    new BitmapDrawable(context.getResources(), getBitmap()));
+            case TYPE_RESOURCE:
+                if (getResources() == null) {
+                    // figure out where to load resources from
+                    String resPackage = getResPackage();
+                    if (TextUtils.isEmpty(resPackage)) {
+                        // if none is specified, try the given context
+                        resPackage = context.getPackageName();
+                    }
+                    if ("android".equals(resPackage)) {
+                        mObj1 = Resources.getSystem();
+                    } else {
+                        final PackageManager pm = context.getPackageManager();
+                        try {
+                            ApplicationInfo ai = pm.getApplicationInfo(
+                                    resPackage, PackageManager.MATCH_UNINSTALLED_PACKAGES);
+                            if (ai != null) {
+                                mObj1 = pm.getResourcesForApplication(ai);
+                            } else {
+                                break;
+                            }
+                        } catch (PackageManager.NameNotFoundException e) {
+                            Log.e(TAG, String.format("Unable to find pkg=%s for icon %s",
+                                    resPackage, this), e);
+                            break;
+                        }
+                    }
+                }
+                try {
+                    return getResources().getDrawable(getResId(), context.getTheme());
+                } catch (RuntimeException e) {
+                    Log.e(TAG, String.format("Unable to load resource 0x%08x from pkg=%s",
+                                    getResId(),
+                                    getResPackage()),
+                            e);
+                }
+                break;
+            case TYPE_DATA:
+                return new BitmapDrawable(context.getResources(),
+                    BitmapFactory.decodeByteArray(getDataBytes(), getDataOffset(), getDataLength())
+                );
+            case TYPE_URI:
+                final Uri uri = getUri();
+                final String scheme = uri.getScheme();
+                InputStream is = null;
+                if (ContentResolver.SCHEME_CONTENT.equals(scheme)
+                        || ContentResolver.SCHEME_FILE.equals(scheme)) {
+                    try {
+                        is = context.getContentResolver().openInputStream(uri);
+                    } catch (Exception e) {
+                        Log.w(TAG, "Unable to load image from URI: " + uri, e);
+                    }
+                } else {
+                    try {
+                        is = new FileInputStream(new File(mString1));
+                    } catch (FileNotFoundException e) {
+                        Log.w(TAG, "Unable to load image from path: " + uri, e);
+                    }
+                }
+                if (is != null) {
+                    return new BitmapDrawable(context.getResources(),
+                            BitmapFactory.decodeStream(is));
+                }
+                break;
+        }
+        return null;
+    }
+
+    /**
+     * Load the requested resources under the given userId, if the system allows it,
+     * before actually loading the drawable.
+     *
+     * @hide
+     */
+    public Drawable loadDrawableAsUser(Context context, int userId) {
+        if (mType == TYPE_RESOURCE) {
+            String resPackage = getResPackage();
+            if (TextUtils.isEmpty(resPackage)) {
+                resPackage = context.getPackageName();
+            }
+            if (getResources() == null && !(getResPackage().equals("android"))) {
+                final PackageManager pm = context.getPackageManager();
+                try {
+                    // assign getResources() as the correct user
+                    mObj1 = pm.getResourcesForApplicationAsUser(resPackage, userId);
+                } catch (PackageManager.NameNotFoundException e) {
+                    Log.e(TAG, String.format("Unable to find pkg=%s user=%d",
+                                    getResPackage(),
+                                    userId),
+                            e);
+                }
+            }
+        }
+        return loadDrawable(context);
+    }
+
+    /** @hide */
+    public static final int MIN_ASHMEM_ICON_SIZE = 128 * (1 << 10);
+
+    /**
+     * Puts the memory used by this instance into Ashmem memory, if possible.
+     * @hide
+     */
+    public void convertToAshmem() {
+        if ((mType == TYPE_BITMAP || mType == TYPE_ADAPTIVE_BITMAP) &&
+            getBitmap().isMutable() &&
+            getBitmap().getAllocationByteCount() >= MIN_ASHMEM_ICON_SIZE) {
+            setBitmap(getBitmap().createAshmemBitmap());
+        }
+    }
+
+    /**
+     * Writes a serialized version of an Icon to the specified stream.
+     *
+     * @param stream The stream on which to serialize the Icon.
+     * @hide
+     */
+    public void writeToStream(OutputStream stream) throws IOException {
+        DataOutputStream dataStream = new DataOutputStream(stream);
+
+        dataStream.writeInt(VERSION_STREAM_SERIALIZER);
+        dataStream.writeByte(mType);
+
+        switch (mType) {
+            case TYPE_BITMAP:
+            case TYPE_ADAPTIVE_BITMAP:
+                getBitmap().compress(Bitmap.CompressFormat.PNG, 100, dataStream);
+                break;
+            case TYPE_DATA:
+                dataStream.writeInt(getDataLength());
+                dataStream.write(getDataBytes(), getDataOffset(), getDataLength());
+                break;
+            case TYPE_RESOURCE:
+                dataStream.writeUTF(getResPackage());
+                dataStream.writeInt(getResId());
+                break;
+            case TYPE_URI:
+                dataStream.writeUTF(getUriString());
+                break;
+        }
+    }
+
+    private Icon(int mType) {
+        this.mType = mType;
+    }
+
+    /**
+     * Create an Icon from the specified stream.
+     *
+     * @param stream The input stream from which to reconstruct the Icon.
+     * @hide
+     */
+    public static Icon createFromStream(InputStream stream) throws IOException {
+        DataInputStream inputStream = new DataInputStream(stream);
+
+        final int version = inputStream.readInt();
+        if (version >= VERSION_STREAM_SERIALIZER) {
+            final int type = inputStream.readByte();
+            switch (type) {
+                case TYPE_BITMAP:
+                    return createWithBitmap(BitmapFactory.decodeStream(inputStream));
+                case TYPE_ADAPTIVE_BITMAP:
+                    return createWithAdaptiveBitmap(BitmapFactory.decodeStream(inputStream));
+                case TYPE_DATA:
+                    final int length = inputStream.readInt();
+                    final byte[] data = new byte[length];
+                    inputStream.read(data, 0 /* offset */, length);
+                    return createWithData(data, 0 /* offset */, length);
+                case TYPE_RESOURCE:
+                    final String packageName = inputStream.readUTF();
+                    final int resId = inputStream.readInt();
+                    return createWithResource(packageName, resId);
+                case TYPE_URI:
+                    final String uriOrPath = inputStream.readUTF();
+                    return createWithContentUri(uriOrPath);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Compares if this icon is constructed from the same resources as another icon.
+     * Note that this is an inexpensive operation and doesn't do deep Bitmap equality comparisons.
+     *
+     * @param otherIcon the other icon
+     * @return whether this icon is the same as the another one
+     * @hide
+     */
+    public boolean sameAs(Icon otherIcon) {
+        if (otherIcon == this) {
+            return true;
+        }
+        if (mType != otherIcon.getType()) {
+            return false;
+        }
+        switch (mType) {
+            case TYPE_BITMAP:
+            case TYPE_ADAPTIVE_BITMAP:
+                return getBitmap() == otherIcon.getBitmap();
+            case TYPE_DATA:
+                return getDataLength() == otherIcon.getDataLength()
+                        && getDataOffset() == otherIcon.getDataOffset()
+                        && Arrays.equals(getDataBytes(), otherIcon.getDataBytes());
+            case TYPE_RESOURCE:
+                return getResId() == otherIcon.getResId()
+                        && Objects.equals(getResPackage(), otherIcon.getResPackage());
+            case TYPE_URI:
+                return Objects.equals(getUriString(), otherIcon.getUriString());
+        }
+        return false;
+    }
+
+    /**
+     * Create an Icon pointing to a drawable resource.
+     * @param context The context for the application whose resources should be used to resolve the
+     *                given resource ID.
+     * @param resId ID of the drawable resource
+     */
+    public static Icon createWithResource(Context context, @DrawableRes int resId) {
+        if (context == null) {
+            throw new IllegalArgumentException("Context must not be null.");
+        }
+        final Icon rep = new Icon(TYPE_RESOURCE);
+        rep.mInt1 = resId;
+        rep.mString1 = context.getPackageName();
+        return rep;
+    }
+
+    /**
+     * Version of createWithResource that takes Resources. Do not use.
+     * @hide
+     */
+    public static Icon createWithResource(Resources res, @DrawableRes int resId) {
+        if (res == null) {
+            throw new IllegalArgumentException("Resource must not be null.");
+        }
+        final Icon rep = new Icon(TYPE_RESOURCE);
+        rep.mInt1 = resId;
+        rep.mString1 = res.getResourcePackageName(resId);
+        return rep;
+    }
+
+    /**
+     * Create an Icon pointing to a drawable resource.
+     * @param resPackage Name of the package containing the resource in question
+     * @param resId ID of the drawable resource
+     */
+    public static Icon createWithResource(String resPackage, @DrawableRes int resId) {
+        if (resPackage == null) {
+            throw new IllegalArgumentException("Resource package name must not be null.");
+        }
+        final Icon rep = new Icon(TYPE_RESOURCE);
+        rep.mInt1 = resId;
+        rep.mString1 = resPackage;
+        return rep;
+    }
+
+    /**
+     * Create an Icon pointing to a bitmap in memory.
+     * @param bits A valid {@link android.graphics.Bitmap} object
+     */
+    public static Icon createWithBitmap(Bitmap bits) {
+        if (bits == null) {
+            throw new IllegalArgumentException("Bitmap must not be null.");
+        }
+        final Icon rep = new Icon(TYPE_BITMAP);
+        rep.setBitmap(bits);
+        return rep;
+    }
+
+    /**
+     * Create an Icon pointing to a bitmap in memory that follows the icon design guideline defined
+     * by {@link AdaptiveIconDrawable}.
+     * @param bits A valid {@link android.graphics.Bitmap} object
+     */
+    public static Icon createWithAdaptiveBitmap(Bitmap bits) {
+        if (bits == null) {
+            throw new IllegalArgumentException("Bitmap must not be null.");
+        }
+        final Icon rep = new Icon(TYPE_ADAPTIVE_BITMAP);
+        rep.setBitmap(bits);
+        return rep;
+    }
+
+    /**
+     * Create an Icon pointing to a compressed bitmap stored in a byte array.
+     * @param data Byte array storing compressed bitmap data of a type that
+     *             {@link android.graphics.BitmapFactory}
+     *             can decode (see {@link android.graphics.Bitmap.CompressFormat}).
+     * @param offset Offset into <code>data</code> at which the bitmap data starts
+     * @param length Length of the bitmap data
+     */
+    public static Icon createWithData(byte[] data, int offset, int length) {
+        if (data == null) {
+            throw new IllegalArgumentException("Data must not be null.");
+        }
+        final Icon rep = new Icon(TYPE_DATA);
+        rep.mObj1 = data;
+        rep.mInt1 = length;
+        rep.mInt2 = offset;
+        return rep;
+    }
+
+    /**
+     * Create an Icon pointing to an image file specified by URI.
+     *
+     * @param uri A uri referring to local content:// or file:// image data.
+     */
+    public static Icon createWithContentUri(String uri) {
+        if (uri == null) {
+            throw new IllegalArgumentException("Uri must not be null.");
+        }
+        final Icon rep = new Icon(TYPE_URI);
+        rep.mString1 = uri;
+        return rep;
+    }
+
+    /**
+     * Create an Icon pointing to an image file specified by URI.
+     *
+     * @param uri A uri referring to local content:// or file:// image data.
+     */
+    public static Icon createWithContentUri(Uri uri) {
+        if (uri == null) {
+            throw new IllegalArgumentException("Uri must not be null.");
+        }
+        final Icon rep = new Icon(TYPE_URI);
+        rep.mString1 = uri.toString();
+        return rep;
+    }
+
+    /**
+     * Store a color to use whenever this Icon is drawn.
+     *
+     * @param tint a color, as in {@link Drawable#setTint(int)}
+     * @return this same object, for use in chained construction
+     */
+    public Icon setTint(@ColorInt int tint) {
+        return setTintList(ColorStateList.valueOf(tint));
+    }
+
+    /**
+     * Store a color to use whenever this Icon is drawn.
+     *
+     * @param tintList as in {@link Drawable#setTintList(ColorStateList)}, null to remove tint
+     * @return this same object, for use in chained construction
+     */
+    public Icon setTintList(ColorStateList tintList) {
+        mTintList = tintList;
+        return this;
+    }
+
+    /**
+     * Store a blending mode to use whenever this Icon is drawn.
+     *
+     * @param mode a blending mode, as in {@link Drawable#setTintMode(PorterDuff.Mode)}, may be null
+     * @return this same object, for use in chained construction
+     */
+    public Icon setTintMode(PorterDuff.Mode mode) {
+        mTintMode = mode;
+        return this;
+    }
+
+    /** @hide */
+    public boolean hasTint() {
+        return (mTintList != null) || (mTintMode != DEFAULT_TINT_MODE);
+    }
+
+    /**
+     * Create an Icon pointing to an image file specified by path.
+     *
+     * @param path A path to a file that contains compressed bitmap data of
+     *           a type that {@link android.graphics.BitmapFactory} can decode.
+     */
+    public static Icon createWithFilePath(String path) {
+        if (path == null) {
+            throw new IllegalArgumentException("Path must not be null.");
+        }
+        final Icon rep = new Icon(TYPE_URI);
+        rep.mString1 = path;
+        return rep;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("Icon(typ=").append(typeToString(mType));
+        switch (mType) {
+            case TYPE_BITMAP:
+            case TYPE_ADAPTIVE_BITMAP:
+                sb.append(" size=")
+                        .append(getBitmap().getWidth())
+                        .append("x")
+                        .append(getBitmap().getHeight());
+                break;
+            case TYPE_RESOURCE:
+                sb.append(" pkg=")
+                        .append(getResPackage())
+                        .append(" id=")
+                        .append(String.format("0x%08x", getResId()));
+                break;
+            case TYPE_DATA:
+                sb.append(" len=").append(getDataLength());
+                if (getDataOffset() != 0) {
+                    sb.append(" off=").append(getDataOffset());
+                }
+                break;
+            case TYPE_URI:
+                sb.append(" uri=").append(getUriString());
+                break;
+        }
+        if (mTintList != null) {
+            sb.append(" tint=");
+            String sep = "";
+            for (int c : mTintList.getColors()) {
+                sb.append(String.format("%s0x%08x", sep, c));
+                sep = "|";
+            }
+        }
+        if (mTintMode != DEFAULT_TINT_MODE) sb.append(" mode=").append(mTintMode);
+        sb.append(")");
+        return sb.toString();
+    }
+
+    /**
+     * Parcelable interface
+     */
+    public int describeContents() {
+        return (mType == TYPE_BITMAP || mType == TYPE_ADAPTIVE_BITMAP || mType == TYPE_DATA)
+                ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
+    }
+
+    // ===== Parcelable interface ======
+
+    private Icon(Parcel in) {
+        this(in.readInt());
+        switch (mType) {
+            case TYPE_BITMAP:
+            case TYPE_ADAPTIVE_BITMAP:
+                final Bitmap bits = Bitmap.CREATOR.createFromParcel(in);
+                mObj1 = bits;
+                break;
+            case TYPE_RESOURCE:
+                final String pkg = in.readString();
+                final int resId = in.readInt();
+                mString1 = pkg;
+                mInt1 = resId;
+                break;
+            case TYPE_DATA:
+                final int len = in.readInt();
+                final byte[] a = in.readBlob();
+                if (len != a.length) {
+                    throw new RuntimeException("internal unparceling error: blob length ("
+                            + a.length + ") != expected length (" + len + ")");
+                }
+                mInt1 = len;
+                mObj1 = a;
+                break;
+            case TYPE_URI:
+                final String uri = in.readString();
+                mString1 = uri;
+                break;
+            default:
+                throw new RuntimeException("invalid "
+                        + this.getClass().getSimpleName() + " type in parcel: " + mType);
+        }
+        if (in.readInt() == 1) {
+            mTintList = ColorStateList.CREATOR.createFromParcel(in);
+        }
+        mTintMode = PorterDuff.intToMode(in.readInt());
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mType);
+        switch (mType) {
+            case TYPE_BITMAP:
+            case TYPE_ADAPTIVE_BITMAP:
+                final Bitmap bits = getBitmap();
+                getBitmap().writeToParcel(dest, flags);
+                break;
+            case TYPE_RESOURCE:
+                dest.writeString(getResPackage());
+                dest.writeInt(getResId());
+                break;
+            case TYPE_DATA:
+                dest.writeInt(getDataLength());
+                dest.writeBlob(getDataBytes(), getDataOffset(), getDataLength());
+                break;
+            case TYPE_URI:
+                dest.writeString(getUriString());
+                break;
+        }
+        if (mTintList == null) {
+            dest.writeInt(0);
+        } else {
+            dest.writeInt(1);
+            mTintList.writeToParcel(dest, flags);
+        }
+        dest.writeInt(PorterDuff.modeToInt(mTintMode));
+    }
+
+    public static final Parcelable.Creator<Icon> CREATOR
+            = new Parcelable.Creator<Icon>() {
+        public Icon createFromParcel(Parcel in) {
+            return new Icon(in);
+        }
+
+        public Icon[] newArray(int size) {
+            return new Icon[size];
+        }
+    };
+
+    /**
+     * Scale down a bitmap to a given max width and max height. The scaling will be done in a uniform way
+     * @param bitmap the bitmap to scale down
+     * @param maxWidth the maximum width allowed
+     * @param maxHeight the maximum height allowed
+     *
+     * @return the scaled bitmap if necessary or the original bitmap if no scaling was needed
+     * @hide
+     */
+    public static Bitmap scaleDownIfNecessary(Bitmap bitmap, int maxWidth, int maxHeight) {
+        int bitmapWidth = bitmap.getWidth();
+        int bitmapHeight = bitmap.getHeight();
+        if (bitmapWidth > maxWidth || bitmapHeight > maxHeight) {
+            float scale = Math.min((float) maxWidth / bitmapWidth,
+                    (float) maxHeight / bitmapHeight);
+            bitmap = Bitmap.createScaledBitmap(bitmap, (int) (scale * bitmapWidth),
+                    (int) (scale * bitmapHeight), true /* filter */);
+        }
+        return bitmap;
+    }
+
+    /**
+     * Scale down this icon to a given max width and max height.
+     * The scaling will be done in a uniform way and currently only bitmaps are supported.
+     * @param maxWidth the maximum width allowed
+     * @param maxHeight the maximum height allowed
+     *
+     * @hide
+     */
+    public void scaleDownIfNecessary(int maxWidth, int maxHeight) {
+        if (mType != TYPE_BITMAP && mType != TYPE_ADAPTIVE_BITMAP) {
+            return;
+        }
+        Bitmap bitmap = getBitmap();
+        setBitmap(scaleDownIfNecessary(bitmap, maxWidth, maxHeight));
+    }
+
+    /**
+     * Implement this interface to receive a callback when
+     * {@link #loadDrawableAsync(Context, OnDrawableLoadedListener, Handler) loadDrawableAsync}
+     * is finished and your Drawable is ready.
+     */
+    public interface OnDrawableLoadedListener {
+        void onDrawableLoaded(Drawable d);
+    }
+
+    /**
+     * Wrapper around loadDrawable that does its work on a pooled thread and then
+     * fires back the given (targeted) Message.
+     */
+    private class LoadDrawableTask implements Runnable {
+        final Context mContext;
+        final Message mMessage;
+
+        public LoadDrawableTask(Context context, final Handler handler,
+                final OnDrawableLoadedListener listener) {
+            mContext = context;
+            mMessage = Message.obtain(handler, new Runnable() {
+                    @Override
+                    public void run() {
+                        listener.onDrawableLoaded((Drawable) mMessage.obj);
+                    }
+                });
+        }
+
+        public LoadDrawableTask(Context context, Message message) {
+            mContext = context;
+            mMessage = message;
+        }
+
+        @Override
+        public void run() {
+            mMessage.obj = loadDrawable(mContext);
+            mMessage.sendToTarget();
+        }
+
+        public void runAsync() {
+            AsyncTask.THREAD_POOL_EXECUTOR.execute(this);
+        }
+    }
+}
diff --git a/android/graphics/drawable/InsetDrawable.java b/android/graphics/drawable/InsetDrawable.java
new file mode 100644
index 0000000..443aa49
--- /dev/null
+++ b/android/graphics/drawable/InsetDrawable.java
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) 2008 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 com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Insets;
+import android.graphics.Outline;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+
+import java.io.IOException;
+
+/**
+ * A Drawable that insets another Drawable by a specified distance or fraction of the content bounds.
+ * This is used when a View needs a background that is smaller than
+ * the View's actual bounds.
+ *
+ * <p>It can be defined in an XML file with the <code>&lt;inset></code> element. For more
+ * information, see the guide to <a
+ * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
+ *
+ * @attr ref android.R.styleable#InsetDrawable_visible
+ * @attr ref android.R.styleable#InsetDrawable_drawable
+ * @attr ref android.R.styleable#InsetDrawable_insetLeft
+ * @attr ref android.R.styleable#InsetDrawable_insetRight
+ * @attr ref android.R.styleable#InsetDrawable_insetTop
+ * @attr ref android.R.styleable#InsetDrawable_insetBottom
+ */
+public class InsetDrawable extends DrawableWrapper {
+    private final Rect mTmpRect = new Rect();
+    private final Rect mTmpInsetRect = new Rect();
+
+    private InsetState mState;
+
+    /**
+     * No-arg constructor used by drawable inflation.
+     */
+    InsetDrawable() {
+        this(new InsetState(null, null), null);
+    }
+
+    /**
+     * Creates a new inset drawable with the specified inset.
+     *
+     * @param drawable The drawable to inset.
+     * @param inset Inset in pixels around the drawable.
+     */
+    public InsetDrawable(@Nullable Drawable drawable, int inset) {
+        this(drawable, inset, inset, inset, inset);
+    }
+
+    /**
+     * Creates a new inset drawable with the specified inset.
+     *
+     * @param drawable The drawable to inset.
+     * @param inset Inset in fraction (range: [0, 1)) of the inset content bounds.
+     */
+    public InsetDrawable(@Nullable Drawable drawable, float inset) {
+        this(drawable, inset, inset, inset, inset);
+    }
+
+    /**
+     * Creates a new inset drawable with the specified insets in pixels.
+     *
+     * @param drawable The drawable to inset.
+     * @param insetLeft Left inset in pixels.
+     * @param insetTop Top inset in pixels.
+     * @param insetRight Right inset in pixels.
+     * @param insetBottom Bottom inset in pixels.
+     */
+    public InsetDrawable(@Nullable Drawable drawable, int insetLeft, int insetTop,
+            int insetRight, int insetBottom) {
+        this(new InsetState(null, null), null);
+
+        mState.mInsetLeft = new InsetValue(0f, insetLeft);
+        mState.mInsetTop = new InsetValue(0f, insetTop);
+        mState.mInsetRight = new InsetValue(0f, insetRight);
+        mState.mInsetBottom = new InsetValue(0f, insetBottom);
+
+        setDrawable(drawable);
+    }
+
+    /**
+     * Creates a new inset drawable with the specified insets in fraction of the view bounds.
+     *
+     * @param drawable The drawable to inset.
+     * @param insetLeftFraction Left inset in fraction (range: [0, 1)) of the inset content bounds.
+     * @param insetTopFraction Top inset in fraction (range: [0, 1)) of the inset content bounds.
+     * @param insetRightFraction Right inset in fraction (range: [0, 1)) of the inset content bounds.
+     * @param insetBottomFraction Bottom inset in fraction (range: [0, 1)) of the inset content bounds.
+     */
+    public InsetDrawable(@Nullable Drawable drawable, float insetLeftFraction,
+        float insetTopFraction, float insetRightFraction, float insetBottomFraction) {
+        this(new InsetState(null, null), null);
+
+        mState.mInsetLeft = new InsetValue(insetLeftFraction, 0);
+        mState.mInsetTop = new InsetValue(insetTopFraction, 0);
+        mState.mInsetRight = new InsetValue(insetRightFraction, 0);
+        mState.mInsetBottom = new InsetValue(insetBottomFraction, 0);
+
+        setDrawable(drawable);
+    }
+
+    @Override
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.InsetDrawable);
+
+        // Inflation will advance the XmlPullParser and AttributeSet.
+        super.inflate(r, parser, attrs, theme);
+
+        updateStateFromTypedArray(a);
+        verifyRequiredAttributes(a);
+        a.recycle();
+    }
+
+    @Override
+    public void applyTheme(@NonNull Theme t) {
+        super.applyTheme(t);
+
+        final InsetState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        if (state.mThemeAttrs != null) {
+            final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.InsetDrawable);
+            try {
+                updateStateFromTypedArray(a);
+                verifyRequiredAttributes(a);
+            } catch (XmlPullParserException e) {
+                rethrowAsRuntimeException(e);
+            } finally {
+                a.recycle();
+            }
+        }
+    }
+
+    private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
+        // If we're not waiting on a theme, verify required attributes.
+        if (getDrawable() == null && (mState.mThemeAttrs == null
+                || mState.mThemeAttrs[R.styleable.InsetDrawable_drawable] == 0)) {
+            throw new XmlPullParserException(a.getPositionDescription()
+                    + ": <inset> tag requires a 'drawable' attribute or "
+                    + "child tag defining a drawable");
+        }
+    }
+
+    private void updateStateFromTypedArray(@NonNull TypedArray a) {
+        final InsetState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        // Account for any configuration changes.
+        state.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        state.mThemeAttrs = a.extractThemeAttrs();
+
+        // Inset attribute may be overridden by more specific attributes.
+        if (a.hasValue(R.styleable.InsetDrawable_inset)) {
+            final InsetValue inset = getInset(a, R.styleable.InsetDrawable_inset, new InsetValue());
+            state.mInsetLeft = inset;
+            state.mInsetTop = inset;
+            state.mInsetRight = inset;
+            state.mInsetBottom = inset;
+        }
+        state.mInsetLeft = getInset(a, R.styleable.InsetDrawable_insetLeft, state.mInsetLeft);
+        state.mInsetTop = getInset(a, R.styleable.InsetDrawable_insetTop, state.mInsetTop);
+        state.mInsetRight = getInset(a, R.styleable.InsetDrawable_insetRight, state.mInsetRight);
+        state.mInsetBottom = getInset(a, R.styleable.InsetDrawable_insetBottom, state.mInsetBottom);
+    }
+
+    private InsetValue getInset(@NonNull TypedArray a, int index, InsetValue defaultValue) {
+        if (a.hasValue(index)) {
+            TypedValue tv = a.peekValue(index);
+            if (tv.type == TypedValue.TYPE_FRACTION) {
+                float f = tv.getFraction(1.0f, 1.0f);
+                if (f >= 1f) {
+                    throw new IllegalStateException("Fraction cannot be larger than 1");
+                }
+                return new InsetValue(f, 0);
+            } else {
+                int dimension = a.getDimensionPixelOffset(index, 0);
+                if (dimension != 0) {
+                    return new InsetValue(0, dimension);
+                }
+            }
+        }
+        return defaultValue;
+    }
+
+    private void getInsets(Rect out) {
+        final Rect b = getBounds();
+        out.left = mState.mInsetLeft.getDimension(b.width());
+        out.right = mState.mInsetRight.getDimension(b.width());
+        out.top = mState.mInsetTop.getDimension(b.height());
+        out.bottom = mState.mInsetBottom.getDimension(b.height());
+    }
+
+    @Override
+    public boolean getPadding(Rect padding) {
+        final boolean pad = super.getPadding(padding);
+        getInsets(mTmpInsetRect);
+        padding.left += mTmpInsetRect.left;
+        padding.right += mTmpInsetRect.right;
+        padding.top += mTmpInsetRect.top;
+        padding.bottom += mTmpInsetRect.bottom;
+
+        return pad || (mTmpInsetRect.left | mTmpInsetRect.right
+                | mTmpInsetRect.top | mTmpInsetRect.bottom) != 0;
+    }
+
+    /** @hide */
+    @Override
+    public Insets getOpticalInsets() {
+        final Insets contentInsets = super.getOpticalInsets();
+        getInsets(mTmpInsetRect);
+        return Insets.of(
+                contentInsets.left + mTmpInsetRect.left,
+                contentInsets.top + mTmpInsetRect.top,
+                contentInsets.right + mTmpInsetRect.right,
+                contentInsets.bottom + mTmpInsetRect.bottom);
+    }
+
+    @Override
+    public int getOpacity() {
+        final InsetState state = mState;
+        final int opacity = getDrawable().getOpacity();
+        getInsets(mTmpInsetRect);
+        if (opacity == PixelFormat.OPAQUE &&
+            (mTmpInsetRect.left > 0 || mTmpInsetRect.top > 0 || mTmpInsetRect.right > 0
+                || mTmpInsetRect.bottom > 0)) {
+            return PixelFormat.TRANSLUCENT;
+        }
+        return opacity;
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        final Rect r = mTmpRect;
+        r.set(bounds);
+
+        r.left += mState.mInsetLeft.getDimension(bounds.width());
+        r.top += mState.mInsetTop.getDimension(bounds.height());
+        r.right -= mState.mInsetRight.getDimension(bounds.width());
+        r.bottom -= mState.mInsetBottom.getDimension(bounds.height());
+
+        // Apply inset bounds to the wrapped drawable.
+        super.onBoundsChange(r);
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        final int childWidth = getDrawable().getIntrinsicWidth();
+        final float fraction = mState.mInsetLeft.mFraction + mState.mInsetRight.mFraction;
+        if (childWidth < 0 || fraction >= 1) {
+            return -1;
+        }
+        return (int) (childWidth / (1 - fraction)) + mState.mInsetLeft.mDimension
+            + mState.mInsetRight.mDimension;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        final int childHeight = getDrawable().getIntrinsicHeight();
+        final float fraction = mState.mInsetTop.mFraction + mState.mInsetBottom.mFraction;
+        if (childHeight < 0 || fraction >= 1) {
+            return -1;
+        }
+        return (int) (childHeight / (1 - fraction)) + mState.mInsetTop.mDimension
+            + mState.mInsetBottom.mDimension;
+    }
+
+    @Override
+    public void getOutline(@NonNull Outline outline) {
+        getDrawable().getOutline(outline);
+    }
+
+    @Override
+    DrawableWrapperState mutateConstantState() {
+        mState = new InsetState(mState, null);
+        return mState;
+    }
+
+    static final class InsetState extends DrawableWrapper.DrawableWrapperState {
+        private int[] mThemeAttrs;
+
+        InsetValue mInsetLeft;
+        InsetValue mInsetTop;
+        InsetValue mInsetRight;
+        InsetValue mInsetBottom;
+
+        InsetState(@Nullable InsetState orig, @Nullable Resources res) {
+            super(orig, res);
+
+            if (orig != null) {
+                mInsetLeft = orig.mInsetLeft.clone();
+                mInsetTop = orig.mInsetTop.clone();
+                mInsetRight = orig.mInsetRight.clone();
+                mInsetBottom = orig.mInsetBottom.clone();
+
+                if (orig.mDensity != mDensity) {
+                    applyDensityScaling(orig.mDensity, mDensity);
+                }
+            } else {
+                mInsetLeft = new InsetValue();
+                mInsetTop = new InsetValue();
+                mInsetRight = new InsetValue();
+                mInsetBottom = new InsetValue();
+            }
+        }
+
+        @Override
+        void onDensityChanged(int sourceDensity, int targetDensity) {
+            super.onDensityChanged(sourceDensity, targetDensity);
+
+            applyDensityScaling(sourceDensity, targetDensity);
+        }
+
+        /**
+         * Called when the constant state density changes to scale
+         * density-dependent properties specific to insets.
+         *
+         * @param sourceDensity the previous constant state density
+         * @param targetDensity the new constant state density
+         */
+        private void applyDensityScaling(int sourceDensity, int targetDensity) {
+            mInsetLeft.scaleFromDensity(sourceDensity, targetDensity);
+            mInsetTop.scaleFromDensity(sourceDensity, targetDensity);
+            mInsetRight.scaleFromDensity(sourceDensity, targetDensity);
+            mInsetBottom.scaleFromDensity(sourceDensity, targetDensity);
+        }
+
+        @Override
+        public Drawable newDrawable(@Nullable Resources res) {
+            // If this drawable is being created for a different density,
+            // just create a new constant state and call it a day.
+            final InsetState state;
+            if (res != null) {
+                final int densityDpi = res.getDisplayMetrics().densityDpi;
+                final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+                if (density != mDensity) {
+                    state = new InsetState(this, res);
+                } else {
+                    state = this;
+                }
+            } else {
+                state = this;
+            }
+
+            return new InsetDrawable(state, res);
+        }
+    }
+
+    static final class InsetValue implements Cloneable {
+        final float mFraction;
+        int mDimension;
+
+        public InsetValue() {
+            this(0f, 0);
+        }
+
+        public InsetValue(float fraction, int dimension) {
+            mFraction = fraction;
+            mDimension = dimension;
+        }
+        int getDimension(int boundSize) {
+            return (int) (boundSize * mFraction) + mDimension;
+        }
+
+        void scaleFromDensity(int sourceDensity, int targetDensity) {
+            if (mDimension != 0) {
+                mDimension = Bitmap.scaleFromDensity(mDimension, sourceDensity, targetDensity);
+            }
+        }
+
+        @Override
+        public InsetValue clone() {
+            return new InsetValue(mFraction, mDimension);
+        }
+    }
+
+    /**
+     * The one constructor to rule them all. This is called by all public
+     * constructors to set the state and initialize local properties.
+     */
+    private InsetDrawable(@NonNull InsetState state, @Nullable Resources res) {
+        super(state, res);
+
+        mState = state;
+    }
+}
+
diff --git a/android/graphics/drawable/LayerDrawable.java b/android/graphics/drawable/LayerDrawable.java
new file mode 100644
index 0000000..4725c2c
--- /dev/null
+++ b/android/graphics/drawable/LayerDrawable.java
@@ -0,0 +1,2162 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.ActivityInfo.Config;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Outline;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.LayoutDirection;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * A Drawable that manages an array of other Drawables. These are drawn in array
+ * order, so the element with the largest index will be drawn on top.
+ * <p>
+ * It can be defined in an XML file with the <code>&lt;layer-list></code> element.
+ * Each Drawable in the layer is defined in a nested <code>&lt;item></code>.
+ * <p>
+ * For more information, see the guide to
+ * <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.
+ *
+ * @attr ref android.R.styleable#LayerDrawable_paddingMode
+ * @attr ref android.R.styleable#LayerDrawableItem_left
+ * @attr ref android.R.styleable#LayerDrawableItem_top
+ * @attr ref android.R.styleable#LayerDrawableItem_right
+ * @attr ref android.R.styleable#LayerDrawableItem_bottom
+ * @attr ref android.R.styleable#LayerDrawableItem_start
+ * @attr ref android.R.styleable#LayerDrawableItem_end
+ * @attr ref android.R.styleable#LayerDrawableItem_width
+ * @attr ref android.R.styleable#LayerDrawableItem_height
+ * @attr ref android.R.styleable#LayerDrawableItem_gravity
+ * @attr ref android.R.styleable#LayerDrawableItem_drawable
+ * @attr ref android.R.styleable#LayerDrawableItem_id
+*/
+public class LayerDrawable extends Drawable implements Drawable.Callback {
+    private static final String LOG_TAG = "LayerDrawable";
+
+    /**
+     * Padding mode used to nest each layer inside the padding of the previous
+     * layer.
+     *
+     * @see #setPaddingMode(int)
+     */
+    public static final int PADDING_MODE_NEST = 0;
+
+    /**
+     * Padding mode used to stack each layer directly atop the previous layer.
+     *
+     * @see #setPaddingMode(int)
+     */
+    public static final int PADDING_MODE_STACK = 1;
+
+    /**
+     * Value used for undefined start and end insets.
+     *
+     * @see #getLayerInsetStart(int)
+     * @see #getLayerInsetEnd(int)
+     */
+    public static final int INSET_UNDEFINED = Integer.MIN_VALUE;
+
+    @NonNull
+    LayerState mLayerState;
+
+    private int[] mPaddingL;
+    private int[] mPaddingT;
+    private int[] mPaddingR;
+    private int[] mPaddingB;
+
+    private final Rect mTmpRect = new Rect();
+    private final Rect mTmpOutRect = new Rect();
+    private final Rect mTmpContainer = new Rect();
+    private Rect mHotspotBounds;
+    private boolean mMutated;
+
+    private boolean mSuspendChildInvalidation;
+    private boolean mChildRequestedInvalidation;
+
+    /**
+     * Creates a new layer drawable with the list of specified layers.
+     *
+     * @param layers a list of drawables to use as layers in this new drawable,
+     *               must be non-null
+     */
+    public LayerDrawable(@NonNull Drawable[] layers) {
+        this(layers, null);
+    }
+
+    /**
+     * Creates a new layer drawable with the specified list of layers and the
+     * specified constant state.
+     *
+     * @param layers The list of layers to add to this drawable.
+     * @param state The constant drawable state.
+     */
+    LayerDrawable(@NonNull Drawable[] layers, @Nullable LayerState state) {
+        this(state, null);
+
+        if (layers == null) {
+            throw new IllegalArgumentException("layers must be non-null");
+        }
+
+        final int length = layers.length;
+        final ChildDrawable[] r = new ChildDrawable[length];
+        for (int i = 0; i < length; i++) {
+            r[i] = new ChildDrawable(mLayerState.mDensity);
+            r[i].mDrawable = layers[i];
+            layers[i].setCallback(this);
+            mLayerState.mChildrenChangingConfigurations |= layers[i].getChangingConfigurations();
+        }
+        mLayerState.mNumChildren = length;
+        mLayerState.mChildren = r;
+
+        ensurePadding();
+        refreshPadding();
+    }
+
+    LayerDrawable() {
+        this((LayerState) null, null);
+    }
+
+    /**
+     * The one constructor to rule them all. This is called by all public
+     * constructors to set the state and initialize local properties.
+     */
+    LayerDrawable(@Nullable LayerState state, @Nullable Resources res) {
+        mLayerState = createConstantState(state, res);
+        if (mLayerState.mNumChildren > 0) {
+            ensurePadding();
+            refreshPadding();
+        }
+    }
+
+    LayerState createConstantState(@Nullable LayerState state, @Nullable Resources res) {
+        return new LayerState(state, this, res);
+    }
+
+    @Override
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
+        super.inflate(r, parser, attrs, theme);
+
+        // The density may have changed since the last update. This will
+        // apply scaling to any existing constant state properties.
+        final LayerState state = mLayerState;
+        final int density = Drawable.resolveDensity(r, 0);
+        state.setDensity(density);
+
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawable);
+        updateStateFromTypedArray(a);
+        a.recycle();
+
+        final ChildDrawable[] array = state.mChildren;
+        final int N = state.mNumChildren;
+        for (int i = 0; i < N; i++) {
+            final ChildDrawable layer = array[i];
+            layer.setDensity(density);
+        }
+
+        inflateLayers(r, parser, attrs, theme);
+
+        ensurePadding();
+        refreshPadding();
+    }
+
+    @Override
+    public void applyTheme(@NonNull Theme t) {
+        super.applyTheme(t);
+
+        final LayerState state = mLayerState;
+        final int density = Drawable.resolveDensity(t.getResources(), 0);
+        state.setDensity(density);
+
+        if (state.mThemeAttrs != null) {
+            final TypedArray a = t.resolveAttributes(
+                    state.mThemeAttrs, R.styleable.LayerDrawable);
+            updateStateFromTypedArray(a);
+            a.recycle();
+        }
+
+        final ChildDrawable[] array = state.mChildren;
+        final int N = state.mNumChildren;
+        for (int i = 0; i < N; i++) {
+            final ChildDrawable layer = array[i];
+            layer.setDensity(density);
+
+            if (layer.mThemeAttrs != null) {
+                final TypedArray a = t.resolveAttributes(
+                        layer.mThemeAttrs, R.styleable.LayerDrawableItem);
+                updateLayerFromTypedArray(layer, a);
+                a.recycle();
+            }
+
+            final Drawable d = layer.mDrawable;
+            if (d != null && d.canApplyTheme()) {
+                d.applyTheme(t);
+
+                // Update cached mask of child changing configurations.
+                state.mChildrenChangingConfigurations |= d.getChangingConfigurations();
+            }
+        }
+    }
+
+    /**
+     * Inflates child layers using the specified parser.
+     */
+    private void inflateLayers(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
+        final LayerState state = mLayerState;
+
+        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;
+            }
+
+            final ChildDrawable layer = new ChildDrawable(state.mDensity);
+            final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawableItem);
+            updateLayerFromTypedArray(layer, a);
+            a.recycle();
+
+            // If the layer doesn't have a drawable or unresolved theme
+            // attribute for a drawable, attempt to parse one from the child
+            // element. If multiple child elements exist, we'll only use the
+            // first one.
+            if (layer.mDrawable == null && (layer.mThemeAttrs == null ||
+                    layer.mThemeAttrs[R.styleable.LayerDrawableItem_drawable] == 0)) {
+                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");
+                }
+
+                // We found a child drawable. Take ownership.
+                layer.mDrawable = Drawable.createFromXmlInner(r, parser, attrs, theme);
+                layer.mDrawable.setCallback(this);
+                state.mChildrenChangingConfigurations |=
+                        layer.mDrawable.getChangingConfigurations();
+            }
+
+            addLayer(layer);
+        }
+    }
+
+    /**
+     * Initializes the constant state from the values in the typed array.
+     */
+    private void updateStateFromTypedArray(@NonNull TypedArray a) {
+        final LayerState state = mLayerState;
+
+        // Account for any configuration changes.
+        state.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        state.mThemeAttrs = a.extractThemeAttrs();
+
+        final int N = a.getIndexCount();
+        for (int i = 0; i < N; i++) {
+            final int attr = a.getIndex(i);
+            switch (attr) {
+                case R.styleable.LayerDrawable_opacity:
+                    state.mOpacityOverride = a.getInt(attr, state.mOpacityOverride);
+                    break;
+                case R.styleable.LayerDrawable_paddingTop:
+                    state.mPaddingTop = a.getDimensionPixelOffset(attr, state.mPaddingTop);
+                    break;
+                case R.styleable.LayerDrawable_paddingBottom:
+                    state.mPaddingBottom = a.getDimensionPixelOffset(attr, state.mPaddingBottom);
+                    break;
+                case R.styleable.LayerDrawable_paddingLeft:
+                    state.mPaddingLeft = a.getDimensionPixelOffset(attr, state.mPaddingLeft);
+                    break;
+                case R.styleable.LayerDrawable_paddingRight:
+                    state.mPaddingRight = a.getDimensionPixelOffset(attr, state.mPaddingRight);
+                    break;
+                case R.styleable.LayerDrawable_paddingStart:
+                    state.mPaddingStart = a.getDimensionPixelOffset(attr, state.mPaddingStart);
+                    break;
+                case R.styleable.LayerDrawable_paddingEnd:
+                    state.mPaddingEnd = a.getDimensionPixelOffset(attr, state.mPaddingEnd);
+                    break;
+                case R.styleable.LayerDrawable_autoMirrored:
+                    state.mAutoMirrored = a.getBoolean(attr, state.mAutoMirrored);
+                    break;
+                case R.styleable.LayerDrawable_paddingMode:
+                    state.mPaddingMode = a.getInteger(attr, state.mPaddingMode);
+                    break;
+            }
+        }
+    }
+
+    private void updateLayerFromTypedArray(@NonNull ChildDrawable layer, @NonNull TypedArray a) {
+        final LayerState state = mLayerState;
+
+        // Account for any configuration changes.
+        state.mChildrenChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        layer.mThemeAttrs = a.extractThemeAttrs();
+
+        final int N = a.getIndexCount();
+        for (int i = 0; i < N; i++) {
+            final int attr = a.getIndex(i);
+            switch (attr) {
+                case R.styleable.LayerDrawableItem_left:
+                    layer.mInsetL = a.getDimensionPixelOffset(attr, layer.mInsetL);
+                    break;
+                case R.styleable.LayerDrawableItem_top:
+                    layer.mInsetT = a.getDimensionPixelOffset(attr, layer.mInsetT);
+                    break;
+                case R.styleable.LayerDrawableItem_right:
+                    layer.mInsetR = a.getDimensionPixelOffset(attr, layer.mInsetR);
+                    break;
+                case R.styleable.LayerDrawableItem_bottom:
+                    layer.mInsetB = a.getDimensionPixelOffset(attr, layer.mInsetB);
+                    break;
+                case R.styleable.LayerDrawableItem_start:
+                    layer.mInsetS = a.getDimensionPixelOffset(attr, layer.mInsetS);
+                    break;
+                case R.styleable.LayerDrawableItem_end:
+                    layer.mInsetE = a.getDimensionPixelOffset(attr, layer.mInsetE);
+                    break;
+                case R.styleable.LayerDrawableItem_width:
+                    layer.mWidth = a.getDimensionPixelSize(attr, layer.mWidth);
+                    break;
+                case R.styleable.LayerDrawableItem_height:
+                    layer.mHeight = a.getDimensionPixelSize(attr, layer.mHeight);
+                    break;
+                case R.styleable.LayerDrawableItem_gravity:
+                    layer.mGravity = a.getInteger(attr, layer.mGravity);
+                    break;
+                case R.styleable.LayerDrawableItem_id:
+                    layer.mId = a.getResourceId(attr, layer.mId);
+                    break;
+            }
+        }
+
+        final Drawable dr = a.getDrawable(R.styleable.LayerDrawableItem_drawable);
+        if (dr != null) {
+            if (layer.mDrawable != null) {
+                // It's possible that a drawable was already set, in which case
+                // we should clear the callback. We may have also integrated the
+                // drawable's changing configurations, but we don't have enough
+                // information to revert that change.
+                layer.mDrawable.setCallback(null);
+            }
+
+            // Take ownership of the new drawable.
+            layer.mDrawable = dr;
+            layer.mDrawable.setCallback(this);
+            state.mChildrenChangingConfigurations |=
+                    layer.mDrawable.getChangingConfigurations();
+        }
+    }
+
+    @Override
+    public boolean canApplyTheme() {
+        return mLayerState.canApplyTheme() || super.canApplyTheme();
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public boolean isProjected() {
+        if (super.isProjected()) {
+            return true;
+        }
+
+        final ChildDrawable[] layers = mLayerState.mChildren;
+        final int N = mLayerState.mNumChildren;
+        for (int i = 0; i < N; i++) {
+            if (layers[i].mDrawable.isProjected()) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Adds a new layer at the end of list of layers and returns its index.
+     *
+     * @param layer The layer to add.
+     * @return The index of the layer.
+     */
+    int addLayer(@NonNull ChildDrawable layer) {
+        final LayerState st = mLayerState;
+        final int N = st.mChildren != null ? st.mChildren.length : 0;
+        final int i = st.mNumChildren;
+        if (i >= N) {
+            final ChildDrawable[] nu = new ChildDrawable[N + 10];
+            if (i > 0) {
+                System.arraycopy(st.mChildren, 0, nu, 0, i);
+            }
+
+            st.mChildren = nu;
+        }
+
+        st.mChildren[i] = layer;
+        st.mNumChildren++;
+        st.invalidateCache();
+        return i;
+    }
+
+    /**
+     * Add a new layer to this drawable. The new layer is identified by an id.
+     *
+     * @param dr The drawable to add as a layer.
+     * @param themeAttrs Theme attributes extracted from the layer.
+     * @param id The id of the new layer.
+     * @param left The left padding of the new layer.
+     * @param top The top padding of the new layer.
+     * @param right The right padding of the new layer.
+     * @param bottom The bottom padding of the new layer.
+     */
+    ChildDrawable addLayer(Drawable dr, int[] themeAttrs, int id,
+            int left, int top, int right, int bottom) {
+        final ChildDrawable childDrawable = createLayer(dr);
+        childDrawable.mId = id;
+        childDrawable.mThemeAttrs = themeAttrs;
+        childDrawable.mDrawable.setAutoMirrored(isAutoMirrored());
+        childDrawable.mInsetL = left;
+        childDrawable.mInsetT = top;
+        childDrawable.mInsetR = right;
+        childDrawable.mInsetB = bottom;
+
+        addLayer(childDrawable);
+
+        mLayerState.mChildrenChangingConfigurations |= dr.getChangingConfigurations();
+        dr.setCallback(this);
+
+        return childDrawable;
+    }
+
+    private ChildDrawable createLayer(Drawable dr) {
+        final ChildDrawable layer = new ChildDrawable(mLayerState.mDensity);
+        layer.mDrawable = dr;
+        return layer;
+    }
+
+    /**
+     * Adds a new layer containing the specified {@code drawable} to the end of
+     * the layer list and returns its index.
+     *
+     * @param dr The drawable to add as a new layer.
+     * @return The index of the new layer.
+     */
+    public int addLayer(Drawable dr) {
+        final ChildDrawable layer = createLayer(dr);
+        final int index = addLayer(layer);
+        ensurePadding();
+        refreshChildPadding(index, layer);
+        return index;
+    }
+
+    /**
+     * Looks for a layer with the given ID and returns its {@link Drawable}.
+     * <p>
+     * If multiple layers are found for the given ID, returns the
+     * {@link Drawable} for the matching layer at the highest index.
+     *
+     * @param id The layer ID to search for.
+     * @return The {@link Drawable} for the highest-indexed layer that has the
+     *         given ID, or null if not found.
+     */
+    public Drawable findDrawableByLayerId(int id) {
+        final ChildDrawable[] layers = mLayerState.mChildren;
+        for (int i = mLayerState.mNumChildren - 1; i >= 0; i--) {
+            if (layers[i].mId == id) {
+                return layers[i].mDrawable;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Sets the ID of a layer.
+     *
+     * @param index The index of the layer to modify, must be in the range
+     *              {@code 0...getNumberOfLayers()-1}.
+     * @param id The id to assign to the layer.
+     *
+     * @see #getId(int)
+     * @attr ref android.R.styleable#LayerDrawableItem_id
+     */
+    public void setId(int index, int id) {
+        mLayerState.mChildren[index].mId = id;
+    }
+
+    /**
+     * Returns the ID of the specified layer.
+     *
+     * @param index The index of the layer, must be in the range
+     *              {@code 0...getNumberOfLayers()-1}.
+     * @return The id of the layer or {@link android.view.View#NO_ID} if the
+     *         layer has no id.
+     *
+     * @see #setId(int, int)
+     * @attr ref android.R.styleable#LayerDrawableItem_id
+     */
+    public int getId(int index) {
+        if (index >= mLayerState.mNumChildren) {
+            throw new IndexOutOfBoundsException();
+        }
+        return mLayerState.mChildren[index].mId;
+    }
+
+    /**
+     * Returns the number of layers contained within this layer drawable.
+     *
+     * @return The number of layers.
+     */
+    public int getNumberOfLayers() {
+        return mLayerState.mNumChildren;
+    }
+
+    /**
+     * Replaces the {@link Drawable} for the layer with the given id.
+     *
+     * @param id The layer ID to search for.
+     * @param drawable The replacement {@link Drawable}.
+     * @return Whether the {@link Drawable} was replaced (could return false if
+     *         the id was not found).
+     */
+    public boolean setDrawableByLayerId(int id, Drawable drawable) {
+        final int index = findIndexByLayerId(id);
+        if (index < 0) {
+            return false;
+        }
+
+        setDrawable(index, drawable);
+        return true;
+    }
+
+    /**
+     * Returns the layer with the specified {@code id}.
+     * <p>
+     * If multiple layers have the same ID, returns the layer with the lowest
+     * index.
+     *
+     * @param id The ID of the layer to return.
+     * @return The index of the layer with the specified ID.
+     */
+    public int findIndexByLayerId(int id) {
+        final ChildDrawable[] layers = mLayerState.mChildren;
+        final int N = mLayerState.mNumChildren;
+        for (int i = 0; i < N; i++) {
+            final ChildDrawable childDrawable = layers[i];
+            if (childDrawable.mId == id) {
+                return i;
+            }
+        }
+
+        return -1;
+    }
+
+    /**
+     * Sets the drawable for the layer at the specified index.
+     *
+     * @param index The index of the layer to modify, must be in the range
+     *              {@code 0...getNumberOfLayers()-1}.
+     * @param drawable The drawable to set for the layer.
+     *
+     * @see #getDrawable(int)
+     * @attr ref android.R.styleable#LayerDrawableItem_drawable
+     */
+    public void setDrawable(int index, Drawable drawable) {
+        if (index >= mLayerState.mNumChildren) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        final ChildDrawable[] layers = mLayerState.mChildren;
+        final ChildDrawable childDrawable = layers[index];
+        if (childDrawable.mDrawable != null) {
+            if (drawable != null) {
+                final Rect bounds = childDrawable.mDrawable.getBounds();
+                drawable.setBounds(bounds);
+            }
+
+            childDrawable.mDrawable.setCallback(null);
+        }
+
+        if (drawable != null) {
+            drawable.setCallback(this);
+        }
+
+        childDrawable.mDrawable = drawable;
+        mLayerState.invalidateCache();
+
+        refreshChildPadding(index, childDrawable);
+    }
+
+    /**
+     * Returns the drawable for the layer at the specified index.
+     *
+     * @param index The index of the layer, must be in the range
+     *              {@code 0...getNumberOfLayers()-1}.
+     * @return The {@link Drawable} at the specified layer index.
+     *
+     * @see #setDrawable(int, Drawable)
+     * @attr ref android.R.styleable#LayerDrawableItem_drawable
+     */
+    public Drawable getDrawable(int index) {
+        if (index >= mLayerState.mNumChildren) {
+            throw new IndexOutOfBoundsException();
+        }
+        return mLayerState.mChildren[index].mDrawable;
+    }
+
+    /**
+     * Sets an explicit size for the specified layer.
+     * <p>
+     * <strong>Note:</strong> Setting an explicit layer size changes the
+     * default layer gravity behavior. See {@link #setLayerGravity(int, int)}
+     * for more information.
+     *
+     * @param index the index of the layer to adjust
+     * @param w width in pixels, or -1 to use the intrinsic width
+     * @param h height in pixels, or -1 to use the intrinsic height
+     * @see #getLayerWidth(int)
+     * @see #getLayerHeight(int)
+     * @attr ref android.R.styleable#LayerDrawableItem_width
+     * @attr ref android.R.styleable#LayerDrawableItem_height
+     */
+    public void setLayerSize(int index, int w, int h) {
+        final ChildDrawable childDrawable = mLayerState.mChildren[index];
+        childDrawable.mWidth = w;
+        childDrawable.mHeight = h;
+    }
+
+    /**
+     * @param index the index of the layer to adjust
+     * @param w width in pixels, or -1 to use the intrinsic width
+     * @attr ref android.R.styleable#LayerDrawableItem_width
+     */
+    public void setLayerWidth(int index, int w) {
+        final ChildDrawable childDrawable = mLayerState.mChildren[index];
+        childDrawable.mWidth = w;
+    }
+
+    /**
+     * @param index the index of the drawable to adjust
+     * @return the explicit width of the layer, or -1 if not specified
+     * @see #setLayerSize(int, int, int)
+     * @attr ref android.R.styleable#LayerDrawableItem_width
+     */
+    public int getLayerWidth(int index) {
+        final ChildDrawable childDrawable = mLayerState.mChildren[index];
+        return childDrawable.mWidth;
+    }
+
+    /**
+     * @param index the index of the layer to adjust
+     * @param h height in pixels, or -1 to use the intrinsic height
+     * @attr ref android.R.styleable#LayerDrawableItem_height
+     */
+    public void setLayerHeight(int index, int h) {
+        final ChildDrawable childDrawable = mLayerState.mChildren[index];
+        childDrawable.mHeight = h;
+    }
+
+    /**
+     * @param index the index of the drawable to adjust
+     * @return the explicit height of the layer, or -1 if not specified
+     * @see #setLayerSize(int, int, int)
+     * @attr ref android.R.styleable#LayerDrawableItem_height
+     */
+    public int getLayerHeight(int index) {
+        final ChildDrawable childDrawable = mLayerState.mChildren[index];
+        return childDrawable.mHeight;
+    }
+
+    /**
+     * Sets the gravity used to position or stretch the specified layer within
+     * its container. Gravity is applied after any layer insets (see
+     * {@link #setLayerInset(int, int, int, int, int)}) or padding (see
+     * {@link #setPaddingMode(int)}).
+     * <p>
+     * If gravity is specified as {@link Gravity#NO_GRAVITY}, the default
+     * behavior depends on whether an explicit width or height has been set
+     * (see {@link #setLayerSize(int, int, int)}), If a dimension is not set,
+     * gravity in that direction defaults to {@link Gravity#FILL_HORIZONTAL} or
+     * {@link Gravity#FILL_VERTICAL}; otherwise, gravity in that direction
+     * defaults to {@link Gravity#LEFT} or {@link Gravity#TOP}.
+     *
+     * @param index the index of the drawable to adjust
+     * @param gravity the gravity to set for the layer
+     *
+     * @see #getLayerGravity(int)
+     * @attr ref android.R.styleable#LayerDrawableItem_gravity
+     */
+    public void setLayerGravity(int index, int gravity) {
+        final ChildDrawable childDrawable = mLayerState.mChildren[index];
+        childDrawable.mGravity = gravity;
+    }
+
+    /**
+     * @param index the index of the layer
+     * @return the gravity used to position or stretch the specified layer
+     *         within its container
+     *
+     * @see #setLayerGravity(int, int)
+     * @attr ref android.R.styleable#LayerDrawableItem_gravity
+     */
+    public int getLayerGravity(int index) {
+        final ChildDrawable childDrawable = mLayerState.mChildren[index];
+        return childDrawable.mGravity;
+    }
+
+    /**
+     * Specifies the insets in pixels for the drawable at the specified index.
+     *
+     * @param index the index of the drawable to adjust
+     * @param l number of pixels to add to the left bound
+     * @param t number of pixels to add to the top bound
+     * @param r number of pixels to subtract from the right bound
+     * @param b number of pixels to subtract from the bottom bound
+     *
+     * @attr ref android.R.styleable#LayerDrawableItem_left
+     * @attr ref android.R.styleable#LayerDrawableItem_top
+     * @attr ref android.R.styleable#LayerDrawableItem_right
+     * @attr ref android.R.styleable#LayerDrawableItem_bottom
+     */
+    public void setLayerInset(int index, int l, int t, int r, int b) {
+        setLayerInsetInternal(index, l, t, r, b, INSET_UNDEFINED, INSET_UNDEFINED);
+    }
+
+    /**
+     * Specifies the relative insets in pixels for the drawable at the
+     * specified index.
+     *
+     * @param index the index of the layer to adjust
+     * @param s number of pixels to inset from the start bound
+     * @param t number of pixels to inset from the top bound
+     * @param e number of pixels to inset from the end bound
+     * @param b number of pixels to inset from the bottom bound
+     *
+     * @attr ref android.R.styleable#LayerDrawableItem_start
+     * @attr ref android.R.styleable#LayerDrawableItem_top
+     * @attr ref android.R.styleable#LayerDrawableItem_end
+     * @attr ref android.R.styleable#LayerDrawableItem_bottom
+     */
+    public void setLayerInsetRelative(int index, int s, int t, int e, int b) {
+        setLayerInsetInternal(index, 0, t, 0, b, s, e);
+    }
+
+    /**
+     * @param index the index of the layer to adjust
+     * @param l number of pixels to inset from the left bound
+     * @attr ref android.R.styleable#LayerDrawableItem_left
+     */
+    public void setLayerInsetLeft(int index, int l) {
+        final ChildDrawable childDrawable = mLayerState.mChildren[index];
+        childDrawable.mInsetL = l;
+    }
+
+    /**
+     * @param index the index of the layer
+     * @return number of pixels to inset from the left bound
+     * @attr ref android.R.styleable#LayerDrawableItem_left
+     */
+    public int getLayerInsetLeft(int index) {
+        final ChildDrawable childDrawable = mLayerState.mChildren[index];
+        return childDrawable.mInsetL;
+    }
+
+    /**
+     * @param index the index of the layer to adjust
+     * @param r number of pixels to inset from the right bound
+     * @attr ref android.R.styleable#LayerDrawableItem_right
+     */
+    public void setLayerInsetRight(int index, int r) {
+        final ChildDrawable childDrawable = mLayerState.mChildren[index];
+        childDrawable.mInsetR = r;
+    }
+
+    /**
+     * @param index the index of the layer
+     * @return number of pixels to inset from the right bound
+     * @attr ref android.R.styleable#LayerDrawableItem_right
+     */
+    public int getLayerInsetRight(int index) {
+        final ChildDrawable childDrawable = mLayerState.mChildren[index];
+        return childDrawable.mInsetR;
+    }
+
+    /**
+     * @param index the index of the layer to adjust
+     * @param t number of pixels to inset from the top bound
+     * @attr ref android.R.styleable#LayerDrawableItem_top
+     */
+    public void setLayerInsetTop(int index, int t) {
+        final ChildDrawable childDrawable = mLayerState.mChildren[index];
+        childDrawable.mInsetT = t;
+    }
+
+    /**
+     * @param index the index of the layer
+     * @return number of pixels to inset from the top bound
+     * @attr ref android.R.styleable#LayerDrawableItem_top
+     */
+    public int getLayerInsetTop(int index) {
+        final ChildDrawable childDrawable = mLayerState.mChildren[index];
+        return childDrawable.mInsetT;
+    }
+
+    /**
+     * @param index the index of the layer to adjust
+     * @param b number of pixels to inset from the bottom bound
+     * @attr ref android.R.styleable#LayerDrawableItem_bottom
+     */
+    public void setLayerInsetBottom(int index, int b) {
+        final ChildDrawable childDrawable = mLayerState.mChildren[index];
+        childDrawable.mInsetB = b;
+    }
+
+    /**
+     * @param index the index of the layer
+     * @return number of pixels to inset from the bottom bound
+     * @attr ref android.R.styleable#LayerDrawableItem_bottom
+     */
+    public int getLayerInsetBottom(int index) {
+        final ChildDrawable childDrawable = mLayerState.mChildren[index];
+        return childDrawable.mInsetB;
+    }
+
+    /**
+     * @param index the index of the layer to adjust
+     * @param s number of pixels to inset from the start bound
+     * @attr ref android.R.styleable#LayerDrawableItem_start
+     */
+    public void setLayerInsetStart(int index, int s) {
+        final ChildDrawable childDrawable = mLayerState.mChildren[index];
+        childDrawable.mInsetS = s;
+    }
+
+    /**
+     * @param index the index of the layer
+     * @return the number of pixels to inset from the start bound, or
+     *         {@link #INSET_UNDEFINED} if not specified
+     * @attr ref android.R.styleable#LayerDrawableItem_start
+     */
+    public int getLayerInsetStart(int index) {
+        final ChildDrawable childDrawable = mLayerState.mChildren[index];
+        return childDrawable.mInsetS;
+    }
+
+    /**
+     * @param index the index of the layer to adjust
+     * @param e number of pixels to inset from the end bound, or
+     *         {@link #INSET_UNDEFINED} if not specified
+     * @attr ref android.R.styleable#LayerDrawableItem_end
+     */
+    public void setLayerInsetEnd(int index, int e) {
+        final ChildDrawable childDrawable = mLayerState.mChildren[index];
+        childDrawable.mInsetE = e;
+    }
+
+    /**
+     * @param index the index of the layer
+     * @return number of pixels to inset from the end bound
+     * @attr ref android.R.styleable#LayerDrawableItem_end
+     */
+    public int getLayerInsetEnd(int index) {
+        final ChildDrawable childDrawable = mLayerState.mChildren[index];
+        return childDrawable.mInsetE;
+    }
+
+    private void setLayerInsetInternal(int index, int l, int t, int r, int b, int s, int e) {
+        final ChildDrawable childDrawable = mLayerState.mChildren[index];
+        childDrawable.mInsetL = l;
+        childDrawable.mInsetT = t;
+        childDrawable.mInsetR = r;
+        childDrawable.mInsetB = b;
+        childDrawable.mInsetS = s;
+        childDrawable.mInsetE = e;
+    }
+
+    /**
+     * Specifies how layer padding should affect the bounds of subsequent
+     * layers. The default value is {@link #PADDING_MODE_NEST}.
+     *
+     * @param mode padding mode, one of:
+     *            <ul>
+     *            <li>{@link #PADDING_MODE_NEST} to nest each layer inside the
+     *            padding of the previous layer
+     *            <li>{@link #PADDING_MODE_STACK} to stack each layer directly
+     *            atop the previous layer
+     *            </ul>
+     *
+     * @see #getPaddingMode()
+     * @attr ref android.R.styleable#LayerDrawable_paddingMode
+     */
+    public void setPaddingMode(int mode) {
+        if (mLayerState.mPaddingMode != mode) {
+            mLayerState.mPaddingMode = mode;
+        }
+    }
+
+    /**
+     * @return the current padding mode
+     *
+     * @see #setPaddingMode(int)
+     * @attr ref android.R.styleable#LayerDrawable_paddingMode
+     */
+    public int getPaddingMode() {
+      return mLayerState.mPaddingMode;
+    }
+
+    /**
+     * 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 {
+            // This may have been called as the result of a tint changing, in
+            // which case we may need to refresh the cached statefulness or
+            // opacity.
+            mLayerState.invalidateCache();
+
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
+        scheduleSelf(what, when);
+    }
+
+    @Override
+    public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
+        unscheduleSelf(what);
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int N = mLayerState.mNumChildren;
+        for (int i = 0; i < N; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.draw(canvas);
+            }
+        }
+    }
+
+    @Override
+    public @Config int getChangingConfigurations() {
+        return super.getChangingConfigurations() | mLayerState.getChangingConfigurations();
+    }
+
+    @Override
+    public boolean getPadding(Rect padding) {
+        final LayerState layerState = mLayerState;
+        if (layerState.mPaddingMode == PADDING_MODE_NEST) {
+            computeNestedPadding(padding);
+        } else {
+            computeStackedPadding(padding);
+        }
+
+        final int paddingT = layerState.mPaddingTop;
+        final int paddingB = layerState.mPaddingBottom;
+
+        // Resolve padding for RTL. Relative padding overrides absolute
+        // padding.
+        final boolean isLayoutRtl = getLayoutDirection() == LayoutDirection.RTL;
+        final int paddingRtlL = isLayoutRtl ? layerState.mPaddingEnd : layerState.mPaddingStart;
+        final int paddingRtlR = isLayoutRtl ? layerState.mPaddingStart : layerState.mPaddingEnd;
+        final int paddingL = paddingRtlL >= 0 ? paddingRtlL : layerState.mPaddingLeft;
+        final int paddingR = paddingRtlR >= 0 ? paddingRtlR : layerState.mPaddingRight;
+
+        // If padding was explicitly specified (e.g. not -1) then override the
+        // computed padding in that dimension.
+        if (paddingL >= 0) {
+            padding.left = paddingL;
+        }
+
+        if (paddingT >= 0) {
+            padding.top = paddingT;
+        }
+
+        if (paddingR >= 0) {
+            padding.right = paddingR;
+        }
+
+        if (paddingB >= 0) {
+            padding.bottom = paddingB;
+        }
+
+        return padding.left != 0 || padding.top != 0 || padding.right != 0 || padding.bottom != 0;
+    }
+
+    /**
+     * Sets the absolute padding.
+     * <p>
+     * If padding in a dimension is specified as {@code -1}, the resolved
+     * padding will use the value computed according to the padding mode (see
+     * {@link #setPaddingMode(int)}).
+     * <p>
+     * Calling this method clears any relative padding values previously set
+     * using {@link #setPaddingRelative(int, int, int, int)}.
+     *
+     * @param left the left padding in pixels, or -1 to use computed padding
+     * @param top the top padding in pixels, or -1 to use computed padding
+     * @param right the right padding in pixels, or -1 to use computed padding
+     * @param bottom the bottom padding in pixels, or -1 to use computed
+     *               padding
+     * @attr ref android.R.styleable#LayerDrawable_paddingLeft
+     * @attr ref android.R.styleable#LayerDrawable_paddingTop
+     * @attr ref android.R.styleable#LayerDrawable_paddingRight
+     * @attr ref android.R.styleable#LayerDrawable_paddingBottom
+     * @see #setPaddingRelative(int, int, int, int)
+     */
+    public void setPadding(int left, int top, int right, int bottom) {
+        final LayerState layerState = mLayerState;
+        layerState.mPaddingLeft = left;
+        layerState.mPaddingTop = top;
+        layerState.mPaddingRight = right;
+        layerState.mPaddingBottom = bottom;
+
+        // Clear relative padding values.
+        layerState.mPaddingStart = -1;
+        layerState.mPaddingEnd = -1;
+    }
+
+    /**
+     * Sets the relative padding.
+     * <p>
+     * If padding in a dimension is specified as {@code -1}, the resolved
+     * padding will use the value computed according to the padding mode (see
+     * {@link #setPaddingMode(int)}).
+     * <p>
+     * Calling this method clears any absolute padding values previously set
+     * using {@link #setPadding(int, int, int, int)}.
+     *
+     * @param start the start padding in pixels, or -1 to use computed padding
+     * @param top the top padding in pixels, or -1 to use computed padding
+     * @param end the end padding in pixels, or -1 to use computed padding
+     * @param bottom the bottom padding in pixels, or -1 to use computed
+     *               padding
+     * @attr ref android.R.styleable#LayerDrawable_paddingStart
+     * @attr ref android.R.styleable#LayerDrawable_paddingTop
+     * @attr ref android.R.styleable#LayerDrawable_paddingEnd
+     * @attr ref android.R.styleable#LayerDrawable_paddingBottom
+     * @see #setPadding(int, int, int, int)
+     */
+    public void setPaddingRelative(int start, int top, int end, int bottom) {
+        final LayerState layerState = mLayerState;
+        layerState.mPaddingStart = start;
+        layerState.mPaddingTop = top;
+        layerState.mPaddingEnd = end;
+        layerState.mPaddingBottom = bottom;
+
+        // Clear absolute padding values.
+        layerState.mPaddingLeft = -1;
+        layerState.mPaddingRight = -1;
+    }
+
+    /**
+     * Returns the left padding in pixels.
+     * <p>
+     * A return value of {@code -1} means there is no explicit padding set for
+     * this dimension. As a result, the value for this dimension returned by
+     * {@link #getPadding(Rect)} will be computed from the child layers
+     * according to the padding mode (see {@link #getPaddingMode()}.
+     *
+     * @return the left padding in pixels, or -1 if not explicitly specified
+     * @see #setPadding(int, int, int, int)
+     * @see #getPadding(Rect)
+     */
+    public int getLeftPadding() {
+        return mLayerState.mPaddingLeft;
+    }
+
+    /**
+     * Returns the right padding in pixels.
+     * <p>
+     * A return value of {@code -1} means there is no explicit padding set for
+     * this dimension. As a result, the value for this dimension returned by
+     * {@link #getPadding(Rect)} will be computed from the child layers
+     * according to the padding mode (see {@link #getPaddingMode()}.
+     *
+     * @return the right padding in pixels, or -1 if not explicitly specified
+     * @see #setPadding(int, int, int, int)
+     * @see #getPadding(Rect)
+     */
+    public int getRightPadding() {
+        return mLayerState.mPaddingRight;
+    }
+
+    /**
+     * Returns the start padding in pixels.
+     * <p>
+     * A return value of {@code -1} means there is no explicit padding set for
+     * this dimension. As a result, the value for this dimension returned by
+     * {@link #getPadding(Rect)} will be computed from the child layers
+     * according to the padding mode (see {@link #getPaddingMode()}.
+     *
+     * @return the start padding in pixels, or -1 if not explicitly specified
+     * @see #setPaddingRelative(int, int, int, int)
+     * @see #getPadding(Rect)
+     */
+    public int getStartPadding() {
+        return mLayerState.mPaddingStart;
+    }
+
+    /**
+     * Returns the end padding in pixels.
+     * <p>
+     * A return value of {@code -1} means there is no explicit padding set for
+     * this dimension. As a result, the value for this dimension returned by
+     * {@link #getPadding(Rect)} will be computed from the child layers
+     * according to the padding mode (see {@link #getPaddingMode()}.
+     *
+     * @return the end padding in pixels, or -1 if not explicitly specified
+     * @see #setPaddingRelative(int, int, int, int)
+     * @see #getPadding(Rect)
+     */
+    public int getEndPadding() {
+        return mLayerState.mPaddingEnd;
+    }
+
+    /**
+     * Returns the top padding in pixels.
+     * <p>
+     * A return value of {@code -1} means there is no explicit padding set for
+     * this dimension. As a result, the value for this dimension returned by
+     * {@link #getPadding(Rect)} will be computed from the child layers
+     * according to the padding mode (see {@link #getPaddingMode()}.
+     *
+     * @return the top padding in pixels, or -1 if not explicitly specified
+     * @see #setPadding(int, int, int, int)
+     * @see #setPaddingRelative(int, int, int, int)
+     * @see #getPadding(Rect)
+     */
+    public int getTopPadding() {
+        return mLayerState.mPaddingTop;
+    }
+
+    /**
+     * Returns the bottom padding in pixels.
+     * <p>
+     * A return value of {@code -1} means there is no explicit padding set for
+     * this dimension. As a result, the value for this dimension returned by
+     * {@link #getPadding(Rect)} will be computed from the child layers
+     * according to the padding mode (see {@link #getPaddingMode()}.
+     *
+     * @return the bottom padding in pixels, or -1 if not explicitly specified
+     * @see #setPadding(int, int, int, int)
+     * @see #setPaddingRelative(int, int, int, int)
+     * @see #getPadding(Rect)
+     */
+    public int getBottomPadding() {
+        return mLayerState.mPaddingBottom;
+    }
+
+    private void computeNestedPadding(Rect padding) {
+        padding.left = 0;
+        padding.top = 0;
+        padding.right = 0;
+        padding.bottom = 0;
+
+        // Add all the padding.
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int N = mLayerState.mNumChildren;
+        for (int i = 0; i < N; i++) {
+            refreshChildPadding(i, array[i]);
+
+            padding.left += mPaddingL[i];
+            padding.top += mPaddingT[i];
+            padding.right += mPaddingR[i];
+            padding.bottom += mPaddingB[i];
+        }
+    }
+
+    private void computeStackedPadding(Rect padding) {
+        padding.left = 0;
+        padding.top = 0;
+        padding.right = 0;
+        padding.bottom = 0;
+
+        // Take the max padding.
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int N = mLayerState.mNumChildren;
+        for (int i = 0; i < N; i++) {
+            refreshChildPadding(i, array[i]);
+
+            padding.left = Math.max(padding.left, mPaddingL[i]);
+            padding.top = Math.max(padding.top, mPaddingT[i]);
+            padding.right = Math.max(padding.right, mPaddingR[i]);
+            padding.bottom = Math.max(padding.bottom, mPaddingB[i]);
+        }
+    }
+
+    /**
+     * Populates <code>outline</code> with the first available (non-empty) layer outline.
+     *
+     * @param outline Outline in which to place the first available layer outline
+     */
+    @Override
+    public void getOutline(@NonNull Outline outline) {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int N = mLayerState.mNumChildren;
+        for (int i = 0; i < N; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.getOutline(outline);
+                if (!outline.isEmpty()) {
+                    return;
+                }
+            }
+        }
+    }
+
+    @Override
+    public void setHotspot(float x, float y) {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int N = mLayerState.mNumChildren;
+        for (int i = 0; i < N; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.setHotspot(x, y);
+            }
+        }
+    }
+
+    @Override
+    public void setHotspotBounds(int left, int top, int right, int bottom) {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int N = mLayerState.mNumChildren;
+        for (int i = 0; i < N; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.setHotspotBounds(left, top, right, bottom);
+            }
+        }
+
+        if (mHotspotBounds == null) {
+            mHotspotBounds = new Rect(left, top, right, bottom);
+        } else {
+            mHotspotBounds.set(left, top, right, bottom);
+        }
+    }
+
+    @Override
+    public void getHotspotBounds(Rect outRect) {
+        if (mHotspotBounds != null) {
+            outRect.set(mHotspotBounds);
+        } else {
+            super.getHotspotBounds(outRect);
+        }
+    }
+
+    @Override
+    public boolean setVisible(boolean visible, boolean restart) {
+        final boolean changed = super.setVisible(visible, restart);
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int N = mLayerState.mNumChildren;
+        for (int i = 0; i < N; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.setVisible(visible, restart);
+            }
+        }
+
+        return changed;
+    }
+
+    @Override
+    public void setDither(boolean dither) {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int N = mLayerState.mNumChildren;
+        for (int i = 0; i < N; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.setDither(dither);
+            }
+        }
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int N = mLayerState.mNumChildren;
+        for (int i = 0; i < N; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.setAlpha(alpha);
+            }
+        }
+    }
+
+    @Override
+    public int getAlpha() {
+        final Drawable dr = getFirstNonNullDrawable();
+        if (dr != null) {
+            return dr.getAlpha();
+        } else {
+            return super.getAlpha();
+        }
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int N = mLayerState.mNumChildren;
+        for (int i = 0; i < N; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.setColorFilter(colorFilter);
+            }
+        }
+    }
+
+    @Override
+    public void setTintList(ColorStateList tint) {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int N = mLayerState.mNumChildren;
+        for (int i = 0; i < N; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.setTintList(tint);
+            }
+        }
+    }
+
+    @Override
+    public void setTintMode(Mode tintMode) {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int N = mLayerState.mNumChildren;
+        for (int i = 0; i < N; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.setTintMode(tintMode);
+            }
+        }
+    }
+
+    private Drawable getFirstNonNullDrawable() {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int N = mLayerState.mNumChildren;
+        for (int i = 0; i < N; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                return dr;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Sets the opacity of this drawable directly instead of collecting the
+     * states from the layers.
+     *
+     * @param opacity The opacity to use, or {@link PixelFormat#UNKNOWN
+     *            PixelFormat.UNKNOWN} for the default behavior
+     * @see PixelFormat#UNKNOWN
+     * @see PixelFormat#TRANSLUCENT
+     * @see PixelFormat#TRANSPARENT
+     * @see PixelFormat#OPAQUE
+     */
+    public void setOpacity(int opacity) {
+        mLayerState.mOpacityOverride = opacity;
+    }
+
+    @Override
+    public int getOpacity() {
+        if (mLayerState.mOpacityOverride != PixelFormat.UNKNOWN) {
+            return mLayerState.mOpacityOverride;
+        }
+        return mLayerState.getOpacity();
+    }
+
+    @Override
+    public void setAutoMirrored(boolean mirrored) {
+        mLayerState.mAutoMirrored = mirrored;
+
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int N = mLayerState.mNumChildren;
+        for (int i = 0; i < N; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.setAutoMirrored(mirrored);
+            }
+        }
+    }
+
+    @Override
+    public boolean isAutoMirrored() {
+        return mLayerState.mAutoMirrored;
+    }
+
+    @Override
+    public void jumpToCurrentState() {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int N = mLayerState.mNumChildren;
+        for (int i = 0; i < N; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.jumpToCurrentState();
+            }
+        }
+    }
+
+    @Override
+    public boolean isStateful() {
+        return mLayerState.isStateful();
+    }
+
+    /** @hide */
+    @Override
+    public boolean hasFocusStateSpecified() {
+        return mLayerState.hasFocusStateSpecified();
+    }
+
+    @Override
+    protected boolean onStateChange(int[] state) {
+        boolean changed = false;
+
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int N = mLayerState.mNumChildren;
+        for (int i = 0; i < N; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null && dr.isStateful() && dr.setState(state)) {
+                refreshChildPadding(i, array[i]);
+                changed = true;
+            }
+        }
+
+        if (changed) {
+            updateLayerBounds(getBounds());
+        }
+
+        return changed;
+    }
+
+    @Override
+    protected boolean onLevelChange(int level) {
+        boolean changed = false;
+
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int N = mLayerState.mNumChildren;
+        for (int i = 0; i < N; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null && dr.setLevel(level)) {
+                refreshChildPadding(i, array[i]);
+                changed = true;
+            }
+        }
+
+        if (changed) {
+            updateLayerBounds(getBounds());
+        }
+
+        return changed;
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        updateLayerBounds(bounds);
+    }
+
+    private void updateLayerBounds(Rect bounds) {
+        try {
+            suspendChildInvalidation();
+            updateLayerBoundsInternal(bounds);
+        } finally {
+            resumeChildInvalidation();
+        }
+    }
+
+    private void updateLayerBoundsInternal(Rect bounds) {
+        int paddingL = 0;
+        int paddingT = 0;
+        int paddingR = 0;
+        int paddingB = 0;
+
+        final Rect outRect = mTmpOutRect;
+        final int layoutDirection = getLayoutDirection();
+        final boolean isLayoutRtl = layoutDirection == LayoutDirection.RTL;
+        final boolean isPaddingNested = mLayerState.mPaddingMode == PADDING_MODE_NEST;
+        final ChildDrawable[] array = mLayerState.mChildren;
+
+        for (int i = 0, count = mLayerState.mNumChildren; i < count; i++) {
+            final ChildDrawable r = array[i];
+            final Drawable d = r.mDrawable;
+            if (d == null) {
+                continue;
+            }
+
+            final int insetT = r.mInsetT;
+            final int insetB = r.mInsetB;
+
+            // Resolve insets for RTL. Relative insets override absolute
+            // insets.
+            final int insetRtlL = isLayoutRtl ? r.mInsetE : r.mInsetS;
+            final int insetRtlR = isLayoutRtl ? r.mInsetS : r.mInsetE;
+            final int insetL = insetRtlL == INSET_UNDEFINED ? r.mInsetL : insetRtlL;
+            final int insetR = insetRtlR == INSET_UNDEFINED ? r.mInsetR : insetRtlR;
+
+            // Establish containing region based on aggregate padding and
+            // requested insets for the current layer.
+            final Rect container = mTmpContainer;
+            container.set(bounds.left + insetL + paddingL, bounds.top + insetT + paddingT,
+                    bounds.right - insetR - paddingR, bounds.bottom - insetB - paddingB);
+
+            // Compute a reasonable default gravity based on the intrinsic and
+            // explicit dimensions, if specified.
+            final int intrinsicW = d.getIntrinsicWidth();
+            final int intrinsicH = d.getIntrinsicHeight();
+            final int layerW = r.mWidth;
+            final int layerH = r.mHeight;
+            final int gravity = resolveGravity(r.mGravity, layerW, layerH, intrinsicW, intrinsicH);
+
+            // Explicit dimensions override intrinsic dimensions.
+            final int resolvedW = layerW < 0 ? intrinsicW : layerW;
+            final int resolvedH = layerH < 0 ? intrinsicH : layerH;
+            Gravity.apply(gravity, resolvedW, resolvedH, container, outRect, layoutDirection);
+            d.setBounds(outRect);
+
+            if (isPaddingNested) {
+                paddingL += mPaddingL[i];
+                paddingR += mPaddingR[i];
+                paddingT += mPaddingT[i];
+                paddingB += mPaddingB[i];
+            }
+        }
+    }
+
+    /**
+     * Resolves layer gravity given explicit gravity and dimensions.
+     * <p>
+     * If the client hasn't specified a gravity but has specified an explicit
+     * dimension, defaults to START or TOP. Otherwise, defaults to FILL to
+     * preserve legacy behavior.
+     *
+     * @param gravity layer gravity
+     * @param width width of the layer if set, -1 otherwise
+     * @param height height of the layer if set, -1 otherwise
+     * @return the default gravity for the layer
+     */
+    private static int resolveGravity(int gravity, int width, int height,
+            int intrinsicWidth, int intrinsicHeight) {
+        if (!Gravity.isHorizontal(gravity)) {
+            if (width < 0) {
+                gravity |= Gravity.FILL_HORIZONTAL;
+            } else {
+                gravity |= Gravity.START;
+            }
+        }
+
+        if (!Gravity.isVertical(gravity)) {
+            if (height < 0) {
+                gravity |= Gravity.FILL_VERTICAL;
+            } else {
+                gravity |= Gravity.TOP;
+            }
+        }
+
+        // If a dimension if not specified, either implicitly or explicitly,
+        // force FILL for that dimension's gravity. This ensures that colors
+        // are handled correctly and ensures backward compatibility.
+        if (width < 0 && intrinsicWidth < 0) {
+            gravity |= Gravity.FILL_HORIZONTAL;
+        }
+
+        if (height < 0 && intrinsicHeight < 0) {
+            gravity |= Gravity.FILL_VERTICAL;
+        }
+
+        return gravity;
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        int width = -1;
+        int padL = 0;
+        int padR = 0;
+
+        final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST;
+        final boolean isLayoutRtl = getLayoutDirection() == LayoutDirection.RTL;
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int N = mLayerState.mNumChildren;
+        for (int i = 0; i < N; i++) {
+            final ChildDrawable r = array[i];
+            if (r.mDrawable == null) {
+                continue;
+            }
+
+            // Take the resolved layout direction into account. If start / end
+            // padding are defined, they will be resolved (hence overriding) to
+            // left / right or right / left depending on the resolved layout
+            // direction. If start / end padding are not defined, use the
+            // left / right ones.
+            final int insetRtlL = isLayoutRtl ? r.mInsetE : r.mInsetS;
+            final int insetRtlR = isLayoutRtl ? r.mInsetS : r.mInsetE;
+            final int insetL = insetRtlL == INSET_UNDEFINED ? r.mInsetL : insetRtlL;
+            final int insetR = insetRtlR == INSET_UNDEFINED ? r.mInsetR : insetRtlR;
+
+            // Don't apply padding and insets for children that don't have
+            // an intrinsic dimension.
+            final int minWidth = r.mWidth < 0 ? r.mDrawable.getIntrinsicWidth() : r.mWidth;
+            final int w = minWidth < 0 ? -1 : minWidth + insetL + insetR + padL + padR;
+            if (w > width) {
+                width = w;
+            }
+
+            if (nest) {
+                padL += mPaddingL[i];
+                padR += mPaddingR[i];
+            }
+        }
+
+        return width;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        int height = -1;
+        int padT = 0;
+        int padB = 0;
+
+        final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST;
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int N = mLayerState.mNumChildren;
+        for (int i = 0; i < N; i++) {
+            final ChildDrawable r = array[i];
+            if (r.mDrawable == null) {
+                continue;
+            }
+
+            // Don't apply padding and insets for children that don't have
+            // an intrinsic dimension.
+            final int minHeight = r.mHeight < 0 ? r.mDrawable.getIntrinsicHeight() : r.mHeight;
+            final int h = minHeight < 0 ? -1 : minHeight + r.mInsetT + r.mInsetB + padT + padB;
+            if (h > height) {
+                height = h;
+            }
+
+            if (nest) {
+                padT += mPaddingT[i];
+                padB += mPaddingB[i];
+            }
+        }
+
+        return height;
+    }
+
+    /**
+     * Refreshes the cached padding values for the specified child.
+     *
+     * @return true if the child's padding has changed
+     */
+    private boolean refreshChildPadding(int i, ChildDrawable r) {
+        if (r.mDrawable != null) {
+            final Rect rect = mTmpRect;
+            r.mDrawable.getPadding(rect);
+            if (rect.left != mPaddingL[i] || rect.top != mPaddingT[i]
+                    || rect.right != mPaddingR[i] || rect.bottom != mPaddingB[i]) {
+                mPaddingL[i] = rect.left;
+                mPaddingT[i] = rect.top;
+                mPaddingR[i] = rect.right;
+                mPaddingB[i] = rect.bottom;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Ensures the child padding caches are large enough.
+     */
+    void ensurePadding() {
+        final int N = mLayerState.mNumChildren;
+        if (mPaddingL != null && mPaddingL.length >= N) {
+            return;
+        }
+
+        mPaddingL = new int[N];
+        mPaddingT = new int[N];
+        mPaddingR = new int[N];
+        mPaddingB = new int[N];
+    }
+
+    void refreshPadding() {
+        final int N = mLayerState.mNumChildren;
+        final ChildDrawable[] array = mLayerState.mChildren;
+        for (int i = 0; i < N; i++) {
+            refreshChildPadding(i, array[i]);
+        }
+    }
+
+    @Override
+    public ConstantState getConstantState() {
+        if (mLayerState.canConstantState()) {
+            mLayerState.mChangingConfigurations = getChangingConfigurations();
+            return mLayerState;
+        }
+        return null;
+    }
+
+    @Override
+    public Drawable mutate() {
+        if (!mMutated && super.mutate() == this) {
+            mLayerState = createConstantState(mLayerState, null);
+            final ChildDrawable[] array = mLayerState.mChildren;
+            final int N = mLayerState.mNumChildren;
+            for (int i = 0; i < N; i++) {
+                final Drawable dr = array[i].mDrawable;
+                if (dr != null) {
+                    dr.mutate();
+                }
+            }
+            mMutated = true;
+        }
+        return this;
+    }
+
+    /**
+     * @hide
+     */
+    public void clearMutated() {
+        super.clearMutated();
+
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int N = mLayerState.mNumChildren;
+        for (int i = 0; i < N; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.clearMutated();
+            }
+        }
+        mMutated = false;
+    }
+
+    @Override
+    public boolean onLayoutDirectionChanged(@View.ResolvedLayoutDir int layoutDirection) {
+        boolean changed = false;
+
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int N = mLayerState.mNumChildren;
+        for (int i = 0; i < N; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                changed |= dr.setLayoutDirection(layoutDirection);
+            }
+        }
+
+        updateLayerBounds(getBounds());
+        return changed;
+    }
+
+    static class ChildDrawable {
+        public Drawable mDrawable;
+        public int[] mThemeAttrs;
+        public int mDensity = DisplayMetrics.DENSITY_DEFAULT;
+        public int mInsetL, mInsetT, mInsetR, mInsetB;
+        public int mInsetS = INSET_UNDEFINED;
+        public int mInsetE = INSET_UNDEFINED;
+        public int mWidth = -1;
+        public int mHeight = -1;
+        public int mGravity = Gravity.NO_GRAVITY;
+        public int mId = View.NO_ID;
+
+        ChildDrawable(int density) {
+            mDensity = density;
+        }
+
+        ChildDrawable(@NonNull ChildDrawable orig, @NonNull LayerDrawable owner,
+                @Nullable Resources res) {
+            final Drawable dr = orig.mDrawable;
+            final Drawable clone;
+            if (dr != null) {
+                final ConstantState cs = dr.getConstantState();
+                if (cs == null) {
+                    clone = dr;
+                    if (dr.getCallback() != null) {
+                        // This drawable already has an owner.
+                        Log.w(LOG_TAG, "Invalid drawable added to LayerDrawable! Drawable already "
+                                + "belongs to another owner but does not expose a constant state.",
+                                new RuntimeException());
+                    }
+                } else if (res != null) {
+                    clone = cs.newDrawable(res);
+                } else {
+                    clone = cs.newDrawable();
+                }
+                clone.setLayoutDirection(dr.getLayoutDirection());
+                clone.setBounds(dr.getBounds());
+                clone.setLevel(dr.getLevel());
+
+                // Set the callback last to prevent invalidation from
+                // propagating before the constant state has been set.
+                clone.setCallback(owner);
+            } else {
+                clone = null;
+            }
+
+            mDrawable = clone;
+            mThemeAttrs = orig.mThemeAttrs;
+            mInsetL = orig.mInsetL;
+            mInsetT = orig.mInsetT;
+            mInsetR = orig.mInsetR;
+            mInsetB = orig.mInsetB;
+            mInsetS = orig.mInsetS;
+            mInsetE = orig.mInsetE;
+            mWidth = orig.mWidth;
+            mHeight = orig.mHeight;
+            mGravity = orig.mGravity;
+            mId = orig.mId;
+
+            mDensity = Drawable.resolveDensity(res, orig.mDensity);
+            if (orig.mDensity != mDensity) {
+                applyDensityScaling(orig.mDensity, mDensity);
+            }
+        }
+
+        public boolean canApplyTheme() {
+            return mThemeAttrs != null
+                    || (mDrawable != null && mDrawable.canApplyTheme());
+        }
+
+        public final void setDensity(int targetDensity) {
+            if (mDensity != targetDensity) {
+                final int sourceDensity = mDensity;
+                mDensity = targetDensity;
+
+                applyDensityScaling(sourceDensity, targetDensity);
+            }
+        }
+
+        private void applyDensityScaling(int sourceDensity, int targetDensity) {
+            mInsetL = Drawable.scaleFromDensity(mInsetL, sourceDensity, targetDensity, false);
+            mInsetT = Drawable.scaleFromDensity(mInsetT, sourceDensity, targetDensity, false);
+            mInsetR = Drawable.scaleFromDensity(mInsetR, sourceDensity, targetDensity, false);
+            mInsetB = Drawable.scaleFromDensity(mInsetB, sourceDensity, targetDensity, false);
+            if (mInsetS != INSET_UNDEFINED) {
+                mInsetS = Drawable.scaleFromDensity(mInsetS, sourceDensity, targetDensity, false);
+            }
+            if (mInsetE != INSET_UNDEFINED) {
+                mInsetE = Drawable.scaleFromDensity(mInsetE, sourceDensity, targetDensity, false);
+            }
+            if (mWidth > 0) {
+                mWidth = Drawable.scaleFromDensity(mWidth, sourceDensity, targetDensity, true);
+            }
+            if (mHeight > 0) {
+                mHeight = Drawable.scaleFromDensity(mHeight, sourceDensity, targetDensity, true);
+            }
+        }
+    }
+
+    static class LayerState extends ConstantState {
+        private int[] mThemeAttrs;
+
+        int mNumChildren;
+        ChildDrawable[] mChildren;
+
+        int mDensity;
+
+        // These values all correspond to mDensity.
+        int mPaddingTop = -1;
+        int mPaddingBottom = -1;
+        int mPaddingLeft = -1;
+        int mPaddingRight = -1;
+        int mPaddingStart = -1;
+        int mPaddingEnd = -1;
+        int mOpacityOverride = PixelFormat.UNKNOWN;
+
+        @Config int mChangingConfigurations;
+        @Config int mChildrenChangingConfigurations;
+
+        private boolean mCheckedOpacity;
+        private int mOpacity;
+
+        private boolean mCheckedStateful;
+        private boolean mIsStateful;
+
+        private boolean mAutoMirrored = false;
+
+        private int mPaddingMode = PADDING_MODE_NEST;
+
+        LayerState(@Nullable LayerState orig, @NonNull LayerDrawable owner,
+                @Nullable Resources res) {
+            mDensity = Drawable.resolveDensity(res, orig != null ? orig.mDensity : 0);
+
+            if (orig != null) {
+                final ChildDrawable[] origChildDrawable = orig.mChildren;
+                final int N = orig.mNumChildren;
+
+                mNumChildren = N;
+                mChildren = new ChildDrawable[N];
+
+                mChangingConfigurations = orig.mChangingConfigurations;
+                mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
+
+                for (int i = 0; i < N; i++) {
+                    final ChildDrawable or = origChildDrawable[i];
+                    mChildren[i] = new ChildDrawable(or, owner, res);
+                }
+
+                mCheckedOpacity = orig.mCheckedOpacity;
+                mOpacity = orig.mOpacity;
+                mCheckedStateful = orig.mCheckedStateful;
+                mIsStateful = orig.mIsStateful;
+                mAutoMirrored = orig.mAutoMirrored;
+                mPaddingMode = orig.mPaddingMode;
+                mThemeAttrs = orig.mThemeAttrs;
+                mPaddingTop = orig.mPaddingTop;
+                mPaddingBottom = orig.mPaddingBottom;
+                mPaddingLeft = orig.mPaddingLeft;
+                mPaddingRight = orig.mPaddingRight;
+                mPaddingStart = orig.mPaddingStart;
+                mPaddingEnd = orig.mPaddingEnd;
+                mOpacityOverride = orig.mOpacityOverride;
+
+                if (orig.mDensity != mDensity) {
+                    applyDensityScaling(orig.mDensity, mDensity);
+                }
+            } else {
+                mNumChildren = 0;
+                mChildren = null;
+            }
+        }
+
+        public final void setDensity(int targetDensity) {
+            if (mDensity != targetDensity) {
+                final int sourceDensity = mDensity;
+                mDensity = targetDensity;
+
+                onDensityChanged(sourceDensity, targetDensity);
+            }
+        }
+
+        protected void onDensityChanged(int sourceDensity, int targetDensity) {
+            applyDensityScaling(sourceDensity, targetDensity);
+        }
+
+        private void applyDensityScaling(int sourceDensity, int targetDensity) {
+            if (mPaddingLeft > 0) {
+                mPaddingLeft = Drawable.scaleFromDensity(
+                        mPaddingLeft, sourceDensity, targetDensity, false);
+            }
+            if (mPaddingTop > 0) {
+                mPaddingTop = Drawable.scaleFromDensity(
+                        mPaddingTop, sourceDensity, targetDensity, false);
+            }
+            if (mPaddingRight > 0) {
+                mPaddingRight = Drawable.scaleFromDensity(
+                        mPaddingRight, sourceDensity, targetDensity, false);
+            }
+            if (mPaddingBottom > 0) {
+                mPaddingBottom = Drawable.scaleFromDensity(
+                        mPaddingBottom, sourceDensity, targetDensity, false);
+            }
+            if (mPaddingStart > 0) {
+                mPaddingStart = Drawable.scaleFromDensity(
+                        mPaddingStart, sourceDensity, targetDensity, false);
+            }
+            if (mPaddingEnd > 0) {
+                mPaddingEnd = Drawable.scaleFromDensity(
+                        mPaddingEnd, sourceDensity, targetDensity, false);
+            }
+        }
+
+        @Override
+        public boolean canApplyTheme() {
+            if (mThemeAttrs != null || super.canApplyTheme()) {
+                return true;
+            }
+
+            final ChildDrawable[] array = mChildren;
+            final int N = mNumChildren;
+            for (int i = 0; i < N; i++) {
+                final ChildDrawable layer = array[i];
+                if (layer.canApplyTheme()) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return new LayerDrawable(this, null);
+        }
+
+        @Override
+        public Drawable newDrawable(@Nullable Resources res) {
+            return new LayerDrawable(this, res);
+        }
+
+        @Override
+        public @Config int getChangingConfigurations() {
+            return mChangingConfigurations
+                    | mChildrenChangingConfigurations;
+        }
+
+        public final int getOpacity() {
+            if (mCheckedOpacity) {
+                return mOpacity;
+            }
+
+            final int N = mNumChildren;
+            final ChildDrawable[] array = mChildren;
+
+            // Seek to the first non-null drawable.
+            int firstIndex = -1;
+            for (int i = 0; i < N; i++) {
+                if (array[i].mDrawable != null) {
+                    firstIndex = i;
+                    break;
+                }
+            }
+
+            int op;
+            if (firstIndex >= 0) {
+                op = array[firstIndex].mDrawable.getOpacity();
+            } else {
+                op = PixelFormat.TRANSPARENT;
+            }
+
+            // Merge all remaining non-null drawables.
+            for (int i = firstIndex + 1; i < N; i++) {
+                final Drawable dr = array[i].mDrawable;
+                if (dr != null) {
+                    op = Drawable.resolveOpacity(op, dr.getOpacity());
+                }
+            }
+
+            mOpacity = op;
+            mCheckedOpacity = true;
+            return op;
+        }
+
+        public final boolean isStateful() {
+            if (mCheckedStateful) {
+                return mIsStateful;
+            }
+
+            final int N = mNumChildren;
+            final ChildDrawable[] array = mChildren;
+            boolean isStateful = false;
+            for (int i = 0; i < N; i++) {
+                final Drawable dr = array[i].mDrawable;
+                if (dr != null && dr.isStateful()) {
+                    isStateful = true;
+                    break;
+                }
+            }
+
+            mIsStateful = isStateful;
+            mCheckedStateful = true;
+            return isStateful;
+        }
+
+        public final boolean hasFocusStateSpecified() {
+            final int N = mNumChildren;
+            final ChildDrawable[] array = mChildren;
+            for (int i = 0; i < N; i++) {
+                final Drawable dr = array[i].mDrawable;
+                if (dr != null && dr.hasFocusStateSpecified()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public final boolean canConstantState() {
+            final ChildDrawable[] array = mChildren;
+            final int N = mNumChildren;
+            for (int i = 0; i < N; i++) {
+                final Drawable dr = array[i].mDrawable;
+                if (dr != null && dr.getConstantState() == null) {
+                    return false;
+                }
+            }
+
+            // Don't cache the result, this method is not called very often.
+            return true;
+        }
+
+        /**
+         * Invalidates the cached opacity and statefulness.
+         */
+        void invalidateCache() {
+            mCheckedOpacity = false;
+            mCheckedStateful = false;
+        }
+
+    }
+}
+
diff --git a/android/graphics/drawable/LevelListDrawable.java b/android/graphics/drawable/LevelListDrawable.java
new file mode 100644
index 0000000..4ce52d1
--- /dev/null
+++ b/android/graphics/drawable/LevelListDrawable.java
@@ -0,0 +1,252 @@
+/*
+ * 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 org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.annotation.NonNull;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.Resources.Theme;
+import android.util.AttributeSet;
+
+/**
+ * A resource that manages a number of alternate Drawables, each assigned a maximum numerical value.
+ * Setting the level value of the object with {@link #setLevel(int)} will load the image with the next
+ * greater or equal value assigned to its max attribute.
+ * A good example use of
+ * a LevelListDrawable would be a battery level indicator icon, with different images to indicate the current
+ * battery level.
+ * <p>
+ * It can be defined in an XML file with the <code>&lt;level-list></code> element.
+ * Each Drawable level is defined in a nested <code>&lt;item></code>. For example:
+ * </p>
+ * <pre>
+ * &lt;level-list xmlns:android="http://schemas.android.com/apk/res/android">
+ *  &lt;item android:maxLevel="0" android:drawable="@drawable/ic_wifi_signal_1" />
+ *  &lt;item android:maxLevel="1" android:drawable="@drawable/ic_wifi_signal_2" />
+ *  &lt;item android:maxLevel="2" android:drawable="@drawable/ic_wifi_signal_3" />
+ *  &lt;item android:maxLevel="3" android:drawable="@drawable/ic_wifi_signal_4" />
+ * &lt;/level-list>
+ *</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}. The default image is the first in the list.
+ * It can then be changed to one of the other levels with
+ * {@link android.widget.ImageView#setImageLevel(int)}. For more
+ * information, see the guide to <a
+ * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
+ *
+ * @attr ref android.R.styleable#LevelListDrawableItem_minLevel
+ * @attr ref android.R.styleable#LevelListDrawableItem_maxLevel
+ * @attr ref android.R.styleable#LevelListDrawableItem_drawable
+ */
+public class LevelListDrawable extends DrawableContainer {
+    private LevelListState mLevelListState;
+    private boolean mMutated;
+
+    public LevelListDrawable() {
+        this(null, null);
+    }
+
+    public void addLevel(int low, int high, Drawable drawable) {
+        if (drawable != null) {
+            mLevelListState.addLevel(low, high, drawable);
+            // in case the new state matches our current state...
+            onLevelChange(getLevel());
+        }
+    }
+
+    // overrides from Drawable
+
+    @Override
+    protected boolean onLevelChange(int level) {
+        int idx = mLevelListState.indexOfLevel(level);
+        if (selectDrawable(idx)) {
+            return true;
+        }
+        return super.onLevelChange(level);
+    }
+
+    @Override
+    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+            throws XmlPullParserException, IOException {
+        super.inflate(r, parser, attrs, theme);
+        updateDensity(r);
+
+        inflateChildElements(r, parser, attrs, theme);
+    }
+
+    private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
+            Theme theme) throws XmlPullParserException, IOException {
+        int type;
+
+        int low = 0;
+
+        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 = obtainAttributes(r, theme, attrs,
+                    com.android.internal.R.styleable.LevelListDrawableItem);
+
+            low = a.getInt(
+                    com.android.internal.R.styleable.LevelListDrawableItem_minLevel, 0);
+            int high = a.getInt(
+                    com.android.internal.R.styleable.LevelListDrawableItem_maxLevel, 0);
+            int drawableRes = a.getResourceId(
+                    com.android.internal.R.styleable.LevelListDrawableItem_drawable, 0);
+
+            a.recycle();
+
+            if (high < 0) {
+                throw new XmlPullParserException(parser.getPositionDescription()
+                        + ": <item> tag requires a 'maxLevel' attribute");
+            }
+
+            Drawable dr;
+            if (drawableRes != 0) {
+                dr = r.getDrawable(drawableRes, theme);
+            } 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, theme);
+            }
+
+            mLevelListState.addLevel(low, high, dr);
+        }
+
+        onLevelChange(getLevel());
+    }
+
+    @Override
+    public Drawable mutate() {
+        if (!mMutated && super.mutate() == this) {
+            mLevelListState.mutate();
+            mMutated = true;
+        }
+        return this;
+    }
+
+    @Override
+    LevelListState cloneConstantState() {
+        return new LevelListState(mLevelListState, this, null);
+    }
+
+    /**
+     * @hide
+     */
+    public void clearMutated() {
+        super.clearMutated();
+        mMutated = false;
+    }
+
+    private final static class LevelListState extends DrawableContainerState {
+        private int[] mLows;
+        private int[] mHighs;
+
+        LevelListState(LevelListState orig, LevelListDrawable owner, Resources res) {
+            super(orig, owner, res);
+
+            if (orig != null) {
+                // Perform a shallow copy and rely on mutate() to deep-copy.
+                mLows = orig.mLows;
+                mHighs = orig.mHighs;
+            } else {
+                mLows = new int[getCapacity()];
+                mHighs = new int[getCapacity()];
+            }
+        }
+
+        private void mutate() {
+            mLows = mLows.clone();
+            mHighs = mHighs.clone();
+        }
+
+        public void addLevel(int low, int high, Drawable drawable) {
+            int pos = addChild(drawable);
+            mLows[pos] = low;
+            mHighs[pos] = high;
+        }
+
+        public int indexOfLevel(int level) {
+            final int[] lows = mLows;
+            final int[] highs = mHighs;
+            final int N = getChildCount();
+            for (int i = 0; i < N; i++) {
+                if (level >= lows[i] && level <= highs[i]) {
+                    return i;
+                }
+            }
+            return -1;
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return new LevelListDrawable(this, null);
+        }
+
+        @Override
+        public Drawable newDrawable(Resources res) {
+            return new LevelListDrawable(this, res);
+        }
+
+        @Override
+        public void growArray(int oldSize, int newSize) {
+            super.growArray(oldSize, newSize);
+            int[] newInts = new int[newSize];
+            System.arraycopy(mLows, 0, newInts, 0, oldSize);
+            mLows = newInts;
+            newInts = new int[newSize];
+            System.arraycopy(mHighs, 0, newInts, 0, oldSize);
+            mHighs = newInts;
+        }
+    }
+
+    @Override
+    protected void setConstantState(@NonNull DrawableContainerState state) {
+        super.setConstantState(state);
+
+        if (state instanceof LevelListState) {
+            mLevelListState = (LevelListState) state;
+        }
+    }
+
+    private LevelListDrawable(LevelListState state, Resources res) {
+        final LevelListState as = new LevelListState(state, this, res);
+        setConstantState(as);
+        onLevelChange(getLevel());
+    }
+}
+
diff --git a/android/graphics/drawable/NinePatchDrawable.java b/android/graphics/drawable/NinePatchDrawable.java
new file mode 100644
index 0000000..1790020
--- /dev/null
+++ b/android/graphics/drawable/NinePatchDrawable.java
@@ -0,0 +1,749 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.ActivityInfo.Config;
+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.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Insets;
+import android.graphics.NinePatch;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.LayoutDirection;
+import android.util.TypedValue;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ *
+ * A resizeable bitmap, with stretchable areas that you define. This type of image
+ * is defined in a .png file with a special format.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about how to use a NinePatchDrawable, read the
+ * <a href="{@docRoot}guide/topics/graphics/2d-graphics.html#nine-patch">
+ * Canvas and Drawables</a> developer guide. For information about creating a NinePatch image
+ * file using the draw9patch tool, see the
+ * <a href="{@docRoot}guide/developing/tools/draw9patch.html">Draw 9-patch</a> tool guide.</p></div>
+ */
+public class NinePatchDrawable extends Drawable {
+    // dithering helps a lot, and is pretty cheap, so default is true
+    private static final boolean DEFAULT_DITHER = false;
+
+    /** Temporary rect used for density scaling. */
+    private Rect mTempRect;
+
+    private NinePatchState mNinePatchState;
+    private PorterDuffColorFilter mTintFilter;
+    private Rect mPadding;
+    private Insets mOpticalInsets = Insets.NONE;
+    private Rect mOutlineInsets;
+    private float mOutlineRadius;
+    private Paint mPaint;
+    private boolean mMutated;
+
+    private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
+
+    // These are scaled to match the target density.
+    private int mBitmapWidth = -1;
+    private int mBitmapHeight = -1;
+
+    NinePatchDrawable() {
+        mNinePatchState = new NinePatchState();
+    }
+
+    /**
+     * Create drawable from raw nine-patch data, not dealing with density.
+     *
+     * @deprecated Use {@link #NinePatchDrawable(Resources, Bitmap, byte[], Rect, String)}
+     *             to ensure that the drawable has correctly set its target density.
+     */
+    @Deprecated
+    public NinePatchDrawable(Bitmap bitmap, byte[] chunk, Rect padding, String srcName) {
+        this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), null);
+    }
+
+    /**
+     * Create drawable from raw nine-patch data, setting initial target density
+     * based on the display metrics of the resources.
+     */
+    public NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk,
+            Rect padding, String srcName) {
+        this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), res);
+    }
+
+    /**
+     * Create drawable from raw nine-patch data, setting initial target density
+     * based on the display metrics of the resources.
+     *
+     * @hide
+     */
+    public NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk,
+            Rect padding, Rect opticalInsets, String srcName) {
+        this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding, opticalInsets),
+                res);
+    }
+
+    /**
+     * Create drawable from existing nine-patch, not dealing with density.
+     *
+     * @deprecated Use {@link #NinePatchDrawable(Resources, NinePatch)}
+     *             to ensure that the drawable has correctly set its target
+     *             density.
+     */
+    @Deprecated
+    public NinePatchDrawable(@NonNull NinePatch patch) {
+        this(new NinePatchState(patch, new Rect()), null);
+    }
+
+    /**
+     * Create drawable from existing nine-patch, setting initial target density
+     * based on the display metrics of the resources.
+     */
+    public NinePatchDrawable(@Nullable Resources res, @NonNull NinePatch patch) {
+        this(new NinePatchState(patch, new Rect()), res);
+    }
+
+    /**
+     * Set the density scale at which this drawable will be rendered. This
+     * method assumes the drawable will be rendered at the same density as the
+     * specified canvas.
+     *
+     * @param canvas The Canvas from which the density scale must be obtained.
+     *
+     * @see android.graphics.Bitmap#setDensity(int)
+     * @see android.graphics.Bitmap#getDensity()
+     */
+    public void setTargetDensity(@NonNull Canvas canvas) {
+        setTargetDensity(canvas.getDensity());
+    }
+
+    /**
+     * Set the density scale at which this drawable will be rendered.
+     *
+     * @param metrics The DisplayMetrics indicating the density scale for this drawable.
+     *
+     * @see android.graphics.Bitmap#setDensity(int)
+     * @see android.graphics.Bitmap#getDensity()
+     */
+    public void setTargetDensity(@NonNull DisplayMetrics metrics) {
+        setTargetDensity(metrics.densityDpi);
+    }
+
+    /**
+     * Set the density at which this drawable will be rendered.
+     *
+     * @param density The density scale for this drawable.
+     *
+     * @see android.graphics.Bitmap#setDensity(int)
+     * @see android.graphics.Bitmap#getDensity()
+     */
+    public void setTargetDensity(int density) {
+        if (density == 0) {
+            density = DisplayMetrics.DENSITY_DEFAULT;
+        }
+
+        if (mTargetDensity != density) {
+            mTargetDensity = density;
+
+            computeBitmapSize();
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        final NinePatchState state = mNinePatchState;
+
+        Rect bounds = getBounds();
+        int restoreToCount = -1;
+
+        final boolean clearColorFilter;
+        if (mTintFilter != null && getPaint().getColorFilter() == null) {
+            mPaint.setColorFilter(mTintFilter);
+            clearColorFilter = true;
+        } else {
+            clearColorFilter = false;
+        }
+
+        final int restoreAlpha;
+        if (state.mBaseAlpha != 1.0f) {
+            restoreAlpha = getPaint().getAlpha();
+            mPaint.setAlpha((int) (restoreAlpha * state.mBaseAlpha + 0.5f));
+        } else {
+            restoreAlpha = -1;
+        }
+
+        final boolean needsDensityScaling = canvas.getDensity() == 0;
+        if (needsDensityScaling) {
+            restoreToCount = restoreToCount >= 0 ? restoreToCount : canvas.save();
+
+            // Apply density scaling.
+            final float scale = mTargetDensity / (float) state.mNinePatch.getDensity();
+            final float px = bounds.left;
+            final float py = bounds.top;
+            canvas.scale(scale, scale, px, py);
+
+            if (mTempRect == null) {
+                mTempRect = new Rect();
+            }
+
+            // Scale the bounds to match.
+            final Rect scaledBounds = mTempRect;
+            scaledBounds.left = bounds.left;
+            scaledBounds.top = bounds.top;
+            scaledBounds.right = bounds.left + Math.round(bounds.width() / scale);
+            scaledBounds.bottom = bounds.top + Math.round(bounds.height() / scale);
+            bounds = scaledBounds;
+        }
+
+        final boolean needsMirroring = needsMirroring();
+        if (needsMirroring) {
+            restoreToCount = restoreToCount >= 0 ? restoreToCount : canvas.save();
+
+            // Mirror the 9patch.
+            final float cx = (bounds.left + bounds.right) / 2.0f;
+            final float cy = (bounds.top + bounds.bottom) / 2.0f;
+            canvas.scale(-1.0f, 1.0f, cx, cy);
+        }
+
+        state.mNinePatch.draw(canvas, bounds, mPaint);
+
+        if (restoreToCount >= 0) {
+            canvas.restoreToCount(restoreToCount);
+        }
+
+        if (clearColorFilter) {
+            mPaint.setColorFilter(null);
+        }
+
+        if (restoreAlpha >= 0) {
+            mPaint.setAlpha(restoreAlpha);
+        }
+    }
+
+    @Override
+    public @Config int getChangingConfigurations() {
+        return super.getChangingConfigurations() | mNinePatchState.getChangingConfigurations();
+    }
+
+    @Override
+    public boolean getPadding(@NonNull Rect padding) {
+        if (mPadding != null) {
+            padding.set(mPadding);
+            return (padding.left | padding.top | padding.right | padding.bottom) != 0;
+        } else {
+            return super.getPadding(padding);
+        }
+    }
+
+    @Override
+    public void getOutline(@NonNull Outline outline) {
+        final Rect bounds = getBounds();
+        if (bounds.isEmpty()) {
+            return;
+        }
+
+        if (mNinePatchState != null && mOutlineInsets != null) {
+            final NinePatch.InsetStruct insets =
+                    mNinePatchState.mNinePatch.getBitmap().getNinePatchInsets();
+            if (insets != null) {
+                outline.setRoundRect(bounds.left + mOutlineInsets.left,
+                        bounds.top + mOutlineInsets.top,
+                        bounds.right - mOutlineInsets.right,
+                        bounds.bottom - mOutlineInsets.bottom,
+                        mOutlineRadius);
+                outline.setAlpha(insets.outlineAlpha * (getAlpha() / 255.0f));
+                return;
+            }
+        }
+
+        super.getOutline(outline);
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public Insets getOpticalInsets() {
+        final Insets opticalInsets = mOpticalInsets;
+        if (needsMirroring()) {
+            return Insets.of(opticalInsets.right, opticalInsets.top,
+                    opticalInsets.left, opticalInsets.bottom);
+        } else {
+            return opticalInsets;
+        }
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        if (mPaint == null && alpha == 0xFF) {
+            // Fast common case -- leave at normal alpha.
+            return;
+        }
+        getPaint().setAlpha(alpha);
+        invalidateSelf();
+    }
+
+    @Override
+    public int getAlpha() {
+        if (mPaint == null) {
+            // Fast common case -- normal alpha.
+            return 0xFF;
+        }
+        return getPaint().getAlpha();
+    }
+
+    @Override
+    public void setColorFilter(@Nullable ColorFilter colorFilter) {
+        if (mPaint == null && colorFilter == null) {
+            // Fast common case -- leave at no color filter.
+            return;
+        }
+        getPaint().setColorFilter(colorFilter);
+        invalidateSelf();
+    }
+
+    @Override
+    public void setTintList(@Nullable ColorStateList tint) {
+        mNinePatchState.mTint = tint;
+        mTintFilter = updateTintFilter(mTintFilter, tint, mNinePatchState.mTintMode);
+        invalidateSelf();
+    }
+
+    @Override
+    public void setTintMode(@Nullable PorterDuff.Mode tintMode) {
+        mNinePatchState.mTintMode = tintMode;
+        mTintFilter = updateTintFilter(mTintFilter, mNinePatchState.mTint, tintMode);
+        invalidateSelf();
+    }
+
+    @Override
+    public void setDither(boolean dither) {
+        //noinspection PointlessBooleanExpression
+        if (mPaint == null && dither == DEFAULT_DITHER) {
+            // Fast common case -- leave at default dither.
+            return;
+        }
+
+        getPaint().setDither(dither);
+        invalidateSelf();
+    }
+
+    @Override
+    public void setAutoMirrored(boolean mirrored) {
+        mNinePatchState.mAutoMirrored = mirrored;
+    }
+
+    private boolean needsMirroring() {
+        return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
+    }
+
+    @Override
+    public boolean isAutoMirrored() {
+        return mNinePatchState.mAutoMirrored;
+    }
+
+    @Override
+    public void setFilterBitmap(boolean filter) {
+        getPaint().setFilterBitmap(filter);
+        invalidateSelf();
+    }
+
+    @Override
+    public boolean isFilterBitmap() {
+        return mPaint != null && getPaint().isFilterBitmap();
+    }
+
+    @Override
+    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+            throws XmlPullParserException, IOException {
+        super.inflate(r, parser, attrs, theme);
+
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.NinePatchDrawable);
+        updateStateFromTypedArray(a);
+        a.recycle();
+
+        updateLocalState(r);
+    }
+
+    /**
+     * Updates the constant state from the values in the typed array.
+     */
+    private void updateStateFromTypedArray(@NonNull TypedArray a) throws XmlPullParserException {
+        final Resources r = a.getResources();
+        final NinePatchState state = mNinePatchState;
+
+        // Account for any configuration changes.
+        state.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        state.mThemeAttrs = a.extractThemeAttrs();
+
+        state.mDither = a.getBoolean(R.styleable.NinePatchDrawable_dither, state.mDither);
+
+        final int srcResId = a.getResourceId(R.styleable.NinePatchDrawable_src, 0);
+        if (srcResId != 0) {
+            final BitmapFactory.Options options = new BitmapFactory.Options();
+            options.inDither = !state.mDither;
+            options.inScreenDensity = r.getDisplayMetrics().noncompatDensityDpi;
+
+            final Rect padding = new Rect();
+            final Rect opticalInsets = new Rect();
+            Bitmap bitmap = null;
+
+            try {
+                final TypedValue value = new TypedValue();
+                final InputStream is = r.openRawResource(srcResId, value);
+
+                bitmap = BitmapFactory.decodeResourceStream(r, value, is, padding, options);
+
+                is.close();
+            } catch (IOException e) {
+                // Ignore
+            }
+
+            if (bitmap == null) {
+                throw new XmlPullParserException(a.getPositionDescription() +
+                        ": <nine-patch> requires a valid src attribute");
+            } else if (bitmap.getNinePatchChunk() == null) {
+                throw new XmlPullParserException(a.getPositionDescription() +
+                        ": <nine-patch> requires a valid 9-patch source image");
+            }
+
+            bitmap.getOpticalInsets(opticalInsets);
+
+            state.mNinePatch = new NinePatch(bitmap, bitmap.getNinePatchChunk());
+            state.mPadding = padding;
+            state.mOpticalInsets = Insets.of(opticalInsets);
+        }
+
+        state.mAutoMirrored = a.getBoolean(
+                R.styleable.NinePatchDrawable_autoMirrored, state.mAutoMirrored);
+        state.mBaseAlpha = a.getFloat(R.styleable.NinePatchDrawable_alpha, state.mBaseAlpha);
+
+        final int tintMode = a.getInt(R.styleable.NinePatchDrawable_tintMode, -1);
+        if (tintMode != -1) {
+            state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
+        }
+
+        final ColorStateList tint = a.getColorStateList(R.styleable.NinePatchDrawable_tint);
+        if (tint != null) {
+            state.mTint = tint;
+        }
+    }
+
+    @Override
+    public void applyTheme(@NonNull Theme t) {
+        super.applyTheme(t);
+
+        final NinePatchState state = mNinePatchState;
+        if (state == null) {
+            return;
+        }
+
+        if (state.mThemeAttrs != null) {
+            final TypedArray a = t.resolveAttributes(
+                    state.mThemeAttrs, R.styleable.NinePatchDrawable);
+            try {
+                updateStateFromTypedArray(a);
+            } catch (XmlPullParserException e) {
+                rethrowAsRuntimeException(e);
+            } finally {
+                a.recycle();
+            }
+        }
+
+        if (state.mTint != null && state.mTint.canApplyTheme()) {
+            state.mTint = state.mTint.obtainForTheme(t);
+        }
+
+        updateLocalState(t.getResources());
+    }
+
+    @Override
+    public boolean canApplyTheme() {
+        return mNinePatchState != null && mNinePatchState.canApplyTheme();
+    }
+
+    @NonNull
+    public Paint getPaint() {
+        if (mPaint == null) {
+            mPaint = new Paint();
+            mPaint.setDither(DEFAULT_DITHER);
+        }
+        return mPaint;
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return mBitmapWidth;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return mBitmapHeight;
+    }
+
+    @Override
+    public int getOpacity() {
+        return mNinePatchState.mNinePatch.hasAlpha()
+                || (mPaint != null && mPaint.getAlpha() < 255) ?
+                        PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
+    }
+
+    @Override
+    public Region getTransparentRegion() {
+        return mNinePatchState.mNinePatch.getTransparentRegion(getBounds());
+    }
+
+    @Override
+    public ConstantState getConstantState() {
+        mNinePatchState.mChangingConfigurations = getChangingConfigurations();
+        return mNinePatchState;
+    }
+
+    @Override
+    public Drawable mutate() {
+        if (!mMutated && super.mutate() == this) {
+            mNinePatchState = new NinePatchState(mNinePatchState);
+            mMutated = true;
+        }
+        return this;
+    }
+
+    /**
+     * @hide
+     */
+    public void clearMutated() {
+        super.clearMutated();
+        mMutated = false;
+    }
+
+    @Override
+    protected boolean onStateChange(int[] stateSet) {
+        final NinePatchState state = mNinePatchState;
+        if (state.mTint != null && state.mTintMode != null) {
+            mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean isStateful() {
+        final NinePatchState s = mNinePatchState;
+        return super.isStateful() || (s.mTint != null && s.mTint.isStateful());
+    }
+
+    /** @hide */
+    @Override
+    public boolean hasFocusStateSpecified() {
+        return mNinePatchState.mTint != null && mNinePatchState.mTint.hasFocusStateSpecified();
+    }
+
+    final static class NinePatchState extends ConstantState {
+        @Config int mChangingConfigurations;
+
+        // Values loaded during inflation.
+        NinePatch mNinePatch = null;
+        ColorStateList mTint = null;
+        Mode mTintMode = DEFAULT_TINT_MODE;
+        Rect mPadding = null;
+        Insets mOpticalInsets = Insets.NONE;
+        float mBaseAlpha = 1.0f;
+        boolean mDither = DEFAULT_DITHER;
+        boolean mAutoMirrored = false;
+
+        int[] mThemeAttrs;
+
+        NinePatchState() {
+            // Empty constructor.
+        }
+
+        NinePatchState(@NonNull NinePatch ninePatch, @Nullable Rect padding) {
+            this(ninePatch, padding, null, DEFAULT_DITHER, false);
+        }
+
+        NinePatchState(@NonNull NinePatch ninePatch, @Nullable Rect padding,
+                @Nullable Rect opticalInsets) {
+            this(ninePatch, padding, opticalInsets, DEFAULT_DITHER, false);
+        }
+
+        NinePatchState(@NonNull NinePatch ninePatch, @Nullable Rect padding,
+                @Nullable Rect opticalInsets, boolean dither, boolean autoMirror) {
+            mNinePatch = ninePatch;
+            mPadding = padding;
+            mOpticalInsets = Insets.of(opticalInsets);
+            mDither = dither;
+            mAutoMirrored = autoMirror;
+        }
+
+        NinePatchState(@NonNull NinePatchState orig) {
+            mChangingConfigurations = orig.mChangingConfigurations;
+            mNinePatch = orig.mNinePatch;
+            mTint = orig.mTint;
+            mTintMode = orig.mTintMode;
+            mPadding = orig.mPadding;
+            mOpticalInsets = orig.mOpticalInsets;
+            mBaseAlpha = orig.mBaseAlpha;
+            mDither = orig.mDither;
+            mAutoMirrored = orig.mAutoMirrored;
+            mThemeAttrs = orig.mThemeAttrs;
+        }
+
+        @Override
+        public boolean canApplyTheme() {
+            return mThemeAttrs != null
+                    || (mTint != null && mTint.canApplyTheme())
+                    || super.canApplyTheme();
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return new NinePatchDrawable(this, null);
+        }
+
+        @Override
+        public Drawable newDrawable(Resources res) {
+            return new NinePatchDrawable(this, res);
+        }
+
+        @Override
+        public @Config int getChangingConfigurations() {
+            return mChangingConfigurations
+                    | (mTint != null ? mTint.getChangingConfigurations() : 0);
+        }
+    }
+
+    private void computeBitmapSize() {
+        final NinePatch ninePatch = mNinePatchState.mNinePatch;
+        if (ninePatch == null) {
+            return;
+        }
+
+        final int sourceDensity = ninePatch.getDensity();
+        final int targetDensity = mTargetDensity;
+
+        final Insets sourceOpticalInsets = mNinePatchState.mOpticalInsets;
+        if (sourceOpticalInsets != Insets.NONE) {
+            final int left = Drawable.scaleFromDensity(
+                    sourceOpticalInsets.left, sourceDensity, targetDensity, true);
+            final int top = Drawable.scaleFromDensity(
+                    sourceOpticalInsets.top, sourceDensity, targetDensity, true);
+            final int right = Drawable.scaleFromDensity(
+                    sourceOpticalInsets.right, sourceDensity, targetDensity, true);
+            final int bottom = Drawable.scaleFromDensity(
+                    sourceOpticalInsets.bottom, sourceDensity, targetDensity, true);
+            mOpticalInsets = Insets.of(left, top, right, bottom);
+        } else {
+            mOpticalInsets = Insets.NONE;
+        }
+
+        final Rect sourcePadding = mNinePatchState.mPadding;
+        if (sourcePadding != null) {
+            if (mPadding == null) {
+                mPadding = new Rect();
+            }
+            mPadding.left = Drawable.scaleFromDensity(
+                    sourcePadding.left, sourceDensity, targetDensity, false);
+            mPadding.top = Drawable.scaleFromDensity(
+                    sourcePadding.top, sourceDensity, targetDensity, false);
+            mPadding.right = Drawable.scaleFromDensity(
+                    sourcePadding.right, sourceDensity, targetDensity, false);
+            mPadding.bottom = Drawable.scaleFromDensity(
+                    sourcePadding.bottom, sourceDensity, targetDensity, false);
+        } else {
+            mPadding = null;
+        }
+
+        mBitmapHeight = Drawable.scaleFromDensity(
+                ninePatch.getHeight(), sourceDensity, targetDensity, true);
+        mBitmapWidth = Drawable.scaleFromDensity(
+                ninePatch.getWidth(), sourceDensity, targetDensity, true);
+
+        final NinePatch.InsetStruct insets = ninePatch.getBitmap().getNinePatchInsets();
+        if (insets != null) {
+            Rect outlineRect = insets.outlineRect;
+            mOutlineInsets = NinePatch.InsetStruct.scaleInsets(outlineRect.left, outlineRect.top,
+                    outlineRect.right, outlineRect.bottom, targetDensity / (float) sourceDensity);
+            mOutlineRadius = Drawable.scaleFromDensity(
+                    insets.outlineRadius, sourceDensity, targetDensity);
+        } else {
+            mOutlineInsets = null;
+        }
+    }
+
+    /**
+     * The one constructor to rule them all. This is called by all public
+     * constructors to set the state and initialize local properties.
+     *
+     * @param state constant state to assign to the new drawable
+     */
+    private NinePatchDrawable(@NonNull NinePatchState state, @Nullable Resources res) {
+        mNinePatchState = state;
+
+        updateLocalState(res);
+    }
+
+    /**
+     * Initializes local dynamic properties from state.
+     */
+    private void updateLocalState(@Nullable Resources res) {
+        final NinePatchState state = mNinePatchState;
+
+        // If we can, avoid calling any methods that initialize Paint.
+        if (state.mDither != DEFAULT_DITHER) {
+            setDither(state.mDither);
+        }
+
+        // The nine-patch may have been created without a Resources object, in
+        // which case we should try to match the density of the nine patch (if
+        // available).
+        if (res == null && state.mNinePatch != null) {
+            mTargetDensity = state.mNinePatch.getDensity();
+        } else {
+            mTargetDensity = Drawable.resolveDensity(res, mTargetDensity);
+        }
+        mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
+        computeBitmapSize();
+    }
+}
diff --git a/android/graphics/drawable/PaintDrawable.java b/android/graphics/drawable/PaintDrawable.java
new file mode 100644
index 0000000..a82e7b9
--- /dev/null
+++ b/android/graphics/drawable/PaintDrawable.java
@@ -0,0 +1,108 @@
+/*
+ * 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 android.graphics.drawable.shapes.RoundRectShape;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * Drawable that draws its bounds in the given paint, with optional
+ * rounded corners.
+*/
+public class PaintDrawable extends ShapeDrawable {
+
+    public PaintDrawable() {
+    }
+
+    public PaintDrawable(int color) {
+        getPaint().setColor(color);
+    }
+
+    /**
+     * Specify radius for the corners of the rectangle. If this is > 0, then the
+     * drawable is drawn in a round-rectangle, rather than a rectangle.
+     * @param radius the radius for the corners of the rectangle
+     */
+    public void setCornerRadius(float radius) {
+        float[] radii = null;
+        if (radius > 0) {
+            radii = new float[8];
+            for (int i = 0; i < 8; i++) {
+                radii[i] = radius;
+            }
+        }
+        setCornerRadii(radii);
+    }
+
+    /**
+     * Specify radii for each of the 4 corners. For each corner, the array
+     * contains 2 values, [X_radius, Y_radius]. The corners are ordered
+     * top-left, top-right, bottom-right, bottom-left
+     * @param radii the x and y radii of the corners
+     */
+    public void setCornerRadii(float[] radii) {
+        if (radii == null) {
+            if (getShape() != null) {
+                setShape(null);
+            }
+        } else {
+            setShape(new RoundRectShape(radii, null, null));
+        }
+        invalidateSelf();
+    }
+
+    @Override
+    protected boolean inflateTag(String name, Resources r, XmlPullParser parser,
+                                 AttributeSet attrs) {
+        if (name.equals("corners")) {
+            TypedArray a = r.obtainAttributes(attrs,
+                                        com.android.internal.R.styleable.DrawableCorners);
+            int radius = a.getDimensionPixelSize(
+                                com.android.internal.R.styleable.DrawableCorners_radius, 0);
+            setCornerRadius(radius);
+
+            // now check of they have any per-corner radii
+
+            int topLeftRadius = a.getDimensionPixelSize(
+                    com.android.internal.R.styleable.DrawableCorners_topLeftRadius, radius);
+            int topRightRadius = a.getDimensionPixelSize(
+                    com.android.internal.R.styleable.DrawableCorners_topRightRadius, radius);
+            int bottomLeftRadius = a.getDimensionPixelSize(
+                com.android.internal.R.styleable.DrawableCorners_bottomLeftRadius, radius);
+            int bottomRightRadius = a.getDimensionPixelSize(
+                com.android.internal.R.styleable.DrawableCorners_bottomRightRadius, radius);
+
+            if (topLeftRadius != radius || topRightRadius != radius ||
+                    bottomLeftRadius != radius || bottomRightRadius != radius) {
+                setCornerRadii(new float[] {
+                               topLeftRadius, topLeftRadius,
+                               topRightRadius, topRightRadius,
+                               bottomLeftRadius, bottomLeftRadius,
+                               bottomRightRadius, bottomRightRadius
+                               });
+            }
+            a.recycle();
+            return true;
+        }
+        return super.inflateTag(name, r, parser, attrs);
+    }
+}
+
diff --git a/android/graphics/drawable/PictureDrawable.java b/android/graphics/drawable/PictureDrawable.java
new file mode 100644
index 0000000..583cffb
--- /dev/null
+++ b/android/graphics/drawable/PictureDrawable.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2008 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.drawable.Drawable;
+import android.graphics.Picture;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+
+/**
+ * Drawable subclass that wraps a Picture, allowing the picture to be used
+ * wherever a Drawable is supported.
+ */
+public class PictureDrawable extends Drawable {
+
+    private Picture mPicture;
+
+    /**
+     * Construct a new drawable referencing the specified picture. The picture
+     * may be null.
+     *
+     * @param picture The picture to associate with the drawable. May be null.
+     */
+    public PictureDrawable(Picture picture) {
+        mPicture = picture;
+    }
+
+    /**
+     * Return the picture associated with the drawable. May be null.
+     *
+     * @return the picture associated with the drawable, or null.
+     */
+    public Picture getPicture() {
+        return mPicture;
+    }
+
+    /**
+     * Associate a picture with this drawable. The picture may be null.
+     *
+     * @param picture The picture to associate with the drawable. May be null.
+     */
+    public void setPicture(Picture picture) {
+        mPicture = picture;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (mPicture != null) {
+            Rect bounds = getBounds();
+            canvas.save();
+            canvas.clipRect(bounds);
+            canvas.translate(bounds.left, bounds.top);
+            canvas.drawPicture(mPicture);
+            canvas.restore();
+        }
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return mPicture != null ? mPicture.getWidth() : -1;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return mPicture != null ? mPicture.getHeight() : -1;
+    }
+
+    @Override
+    public int getOpacity() {
+        // not sure, so be safe
+        return PixelFormat.TRANSLUCENT;
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {}
+
+    @Override
+    public void setAlpha(int alpha) {}
+}
+
diff --git a/android/graphics/drawable/RippleBackground.java b/android/graphics/drawable/RippleBackground.java
new file mode 100644
index 0000000..6bd2646
--- /dev/null
+++ b/android/graphics/drawable/RippleBackground.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2013 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.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.graphics.Canvas;
+import android.graphics.CanvasProperty;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.FloatProperty;
+import android.view.DisplayListCanvas;
+import android.view.RenderNodeAnimator;
+import android.view.animation.LinearInterpolator;
+
+/**
+ * Draws a ripple background.
+ */
+class RippleBackground extends RippleComponent {
+
+    private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
+
+    private static final int OPACITY_ENTER_DURATION = 600;
+    private static final int OPACITY_ENTER_DURATION_FAST = 120;
+    private static final int OPACITY_EXIT_DURATION = 480;
+
+    // Hardware rendering properties.
+    private CanvasProperty<Paint> mPropPaint;
+    private CanvasProperty<Float> mPropRadius;
+    private CanvasProperty<Float> mPropX;
+    private CanvasProperty<Float> mPropY;
+
+    // Software rendering properties.
+    private float mOpacity = 0;
+
+    /** Whether this ripple is bounded. */
+    private boolean mIsBounded;
+
+    public RippleBackground(RippleDrawable owner, Rect bounds, boolean isBounded,
+            boolean forceSoftware) {
+        super(owner, bounds, forceSoftware);
+
+        mIsBounded = isBounded;
+    }
+
+    public boolean isVisible() {
+        return mOpacity > 0 || isHardwareAnimating();
+    }
+
+    @Override
+    protected boolean drawSoftware(Canvas c, Paint p) {
+        boolean hasContent = false;
+
+        final int origAlpha = p.getAlpha();
+        final int alpha = (int) (origAlpha * mOpacity + 0.5f);
+        if (alpha > 0) {
+            p.setAlpha(alpha);
+            c.drawCircle(0, 0, mTargetRadius, p);
+            p.setAlpha(origAlpha);
+            hasContent = true;
+        }
+
+        return hasContent;
+    }
+
+    @Override
+    protected boolean drawHardware(DisplayListCanvas c) {
+        c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
+        return true;
+    }
+
+    @Override
+    protected Animator createSoftwareEnter(boolean fast) {
+        // Linear enter based on current opacity.
+        final int maxDuration = fast ? OPACITY_ENTER_DURATION_FAST : OPACITY_ENTER_DURATION;
+        final int duration = (int) ((1 - mOpacity) * maxDuration);
+
+        final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 1);
+        opacity.setAutoCancel(true);
+        opacity.setDuration(duration);
+        opacity.setInterpolator(LINEAR_INTERPOLATOR);
+
+        return opacity;
+    }
+
+    @Override
+    protected Animator createSoftwareExit() {
+        final AnimatorSet set = new AnimatorSet();
+
+        // Linear exit after enter is completed.
+        final ObjectAnimator exit = ObjectAnimator.ofFloat(this, RippleBackground.OPACITY, 0);
+        exit.setInterpolator(LINEAR_INTERPOLATOR);
+        exit.setDuration(OPACITY_EXIT_DURATION);
+        exit.setAutoCancel(true);
+
+        final AnimatorSet.Builder builder = set.play(exit);
+
+        // Linear "fast" enter based on current opacity.
+        final int fastEnterDuration = mIsBounded ?
+                (int) ((1 - mOpacity) * OPACITY_ENTER_DURATION_FAST) : 0;
+        if (fastEnterDuration > 0) {
+            final ObjectAnimator enter = ObjectAnimator.ofFloat(this, RippleBackground.OPACITY, 1);
+            enter.setInterpolator(LINEAR_INTERPOLATOR);
+            enter.setDuration(fastEnterDuration);
+            enter.setAutoCancel(true);
+
+            builder.after(enter);
+        }
+
+        return set;
+    }
+
+    @Override
+    protected RenderNodeAnimatorSet createHardwareExit(Paint p) {
+        final RenderNodeAnimatorSet set = new RenderNodeAnimatorSet();
+
+        final int targetAlpha = p.getAlpha();
+        final int currentAlpha = (int) (mOpacity * targetAlpha + 0.5f);
+        p.setAlpha(currentAlpha);
+
+        mPropPaint = CanvasProperty.createPaint(p);
+        mPropRadius = CanvasProperty.createFloat(mTargetRadius);
+        mPropX = CanvasProperty.createFloat(0);
+        mPropY = CanvasProperty.createFloat(0);
+
+        final int fastEnterDuration = mIsBounded ?
+                (int) ((1 - mOpacity) * OPACITY_ENTER_DURATION_FAST) : 0;
+
+        // Linear exit after enter is completed.
+        final RenderNodeAnimator exit = new RenderNodeAnimator(
+                mPropPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
+        exit.setInterpolator(LINEAR_INTERPOLATOR);
+        exit.setDuration(OPACITY_EXIT_DURATION);
+        if (fastEnterDuration > 0) {
+            exit.setStartDelay(fastEnterDuration);
+            exit.setStartValue(targetAlpha);
+        }
+        set.add(exit);
+
+        // Linear "fast" enter based on current opacity.
+        if (fastEnterDuration > 0) {
+            final RenderNodeAnimator enter = new RenderNodeAnimator(
+                    mPropPaint, RenderNodeAnimator.PAINT_ALPHA, targetAlpha);
+            enter.setInterpolator(LINEAR_INTERPOLATOR);
+            enter.setDuration(fastEnterDuration);
+            set.add(enter);
+        }
+
+        return set;
+    }
+
+    @Override
+    protected void jumpValuesToExit() {
+        mOpacity = 0;
+    }
+
+    private static abstract class BackgroundProperty extends FloatProperty<RippleBackground> {
+        public BackgroundProperty(String name) {
+            super(name);
+        }
+    }
+
+    private static final BackgroundProperty OPACITY = new BackgroundProperty("opacity") {
+        @Override
+        public void setValue(RippleBackground object, float value) {
+            object.mOpacity = value;
+            object.invalidateSelf();
+        }
+
+        @Override
+        public Float get(RippleBackground object) {
+            return object.mOpacity;
+        }
+    };
+}
diff --git a/android/graphics/drawable/RippleComponent.java b/android/graphics/drawable/RippleComponent.java
new file mode 100644
index 0000000..e83513c
--- /dev/null
+++ b/android/graphics/drawable/RippleComponent.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2015 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.animation.Animator;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.DisplayMetrics;
+import android.view.DisplayListCanvas;
+import android.view.RenderNodeAnimator;
+
+import java.util.ArrayList;
+
+/**
+ * Abstract class that handles hardware/software hand-off and lifecycle for
+ * animated ripple foreground and background components.
+ */
+abstract class RippleComponent {
+    private final RippleDrawable mOwner;
+
+    /** Bounds used for computing max radius. May be modified by the owner. */
+    protected final Rect mBounds;
+
+    /** Whether we can use hardware acceleration for the exit animation. */
+    private boolean mHasDisplayListCanvas;
+
+    private boolean mHasPendingHardwareAnimator;
+    private RenderNodeAnimatorSet mHardwareAnimator;
+
+    private Animator mSoftwareAnimator;
+
+    /** Whether we have an explicit maximum radius. */
+    private boolean mHasMaxRadius;
+
+    /** How big this ripple should be when fully entered. */
+    protected float mTargetRadius;
+
+    /** Screen density used to adjust pixel-based constants. */
+    protected float mDensityScale;
+
+    /**
+     * If set, force all ripple animations to not run on RenderThread, even if it would be
+     * available.
+     */
+    private final boolean mForceSoftware;
+
+    public RippleComponent(RippleDrawable owner, Rect bounds, boolean forceSoftware) {
+        mOwner = owner;
+        mBounds = bounds;
+        mForceSoftware = forceSoftware;
+    }
+
+    public void onBoundsChange() {
+        if (!mHasMaxRadius) {
+            mTargetRadius = getTargetRadius(mBounds);
+            onTargetRadiusChanged(mTargetRadius);
+        }
+    }
+
+    public final void setup(float maxRadius, int densityDpi) {
+        if (maxRadius >= 0) {
+            mHasMaxRadius = true;
+            mTargetRadius = maxRadius;
+        } else {
+            mTargetRadius = getTargetRadius(mBounds);
+        }
+
+        mDensityScale = densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+
+        onTargetRadiusChanged(mTargetRadius);
+    }
+
+    private static float getTargetRadius(Rect bounds) {
+        final float halfWidth = bounds.width() / 2.0f;
+        final float halfHeight = bounds.height() / 2.0f;
+        return (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
+    }
+
+    /**
+     * Starts a ripple enter animation.
+     *
+     * @param fast whether the ripple should enter quickly
+     */
+    public final void enter(boolean fast) {
+        cancel();
+
+        mSoftwareAnimator = createSoftwareEnter(fast);
+
+        if (mSoftwareAnimator != null) {
+            mSoftwareAnimator.start();
+        }
+    }
+
+    /**
+     * Starts a ripple exit animation.
+     */
+    public final void exit() {
+        cancel();
+
+        if (mHasDisplayListCanvas) {
+            // We don't have access to a canvas here, but we expect one on the
+            // next frame. We'll start the render thread animation then.
+            mHasPendingHardwareAnimator = true;
+
+            // Request another frame.
+            invalidateSelf();
+        } else {
+            mSoftwareAnimator = createSoftwareExit();
+            mSoftwareAnimator.start();
+        }
+    }
+
+    /**
+     * Cancels all animations. Software animation values are left in the
+     * current state, while hardware animation values jump to the end state.
+     */
+    public void cancel() {
+        cancelSoftwareAnimations();
+        endHardwareAnimations();
+    }
+
+    /**
+     * Ends all animations, jumping values to the end state.
+     */
+    public void end() {
+        endSoftwareAnimations();
+        endHardwareAnimations();
+    }
+
+    /**
+     * Draws the ripple to the canvas, inheriting the paint's color and alpha
+     * properties.
+     *
+     * @param c the canvas to which the ripple should be drawn
+     * @param p the paint used to draw the ripple
+     * @return {@code true} if something was drawn, {@code false} otherwise
+     */
+    public boolean draw(Canvas c, Paint p) {
+        final boolean hasDisplayListCanvas = !mForceSoftware && c.isHardwareAccelerated()
+                && c instanceof DisplayListCanvas;
+        if (mHasDisplayListCanvas != hasDisplayListCanvas) {
+            mHasDisplayListCanvas = hasDisplayListCanvas;
+
+            if (!hasDisplayListCanvas) {
+                // We've switched from hardware to non-hardware mode. Panic.
+                endHardwareAnimations();
+            }
+        }
+
+        if (hasDisplayListCanvas) {
+            final DisplayListCanvas hw = (DisplayListCanvas) c;
+            startPendingAnimation(hw, p);
+
+            if (mHardwareAnimator != null) {
+                return drawHardware(hw);
+            }
+        }
+
+        return drawSoftware(c, p);
+    }
+
+    /**
+     * Populates {@code bounds} with the maximum drawing bounds of the ripple
+     * relative to its center. The resulting bounds should be translated into
+     * parent drawable coordinates before use.
+     *
+     * @param bounds the rect to populate with drawing bounds
+     */
+    public void getBounds(Rect bounds) {
+        final int r = (int) Math.ceil(mTargetRadius);
+        bounds.set(-r, -r, r, r);
+    }
+
+    /**
+     * Starts the pending hardware animation, if available.
+     *
+     * @param hw hardware canvas on which the animation should draw
+     * @param p paint whose properties the hardware canvas should use
+     */
+    private void startPendingAnimation(DisplayListCanvas hw, Paint p) {
+        if (mHasPendingHardwareAnimator) {
+            mHasPendingHardwareAnimator = false;
+
+            mHardwareAnimator = createHardwareExit(new Paint(p));
+            mHardwareAnimator.start(hw);
+
+            // Preemptively jump the software values to the end state now that
+            // the hardware exit has read whatever values it needs.
+            jumpValuesToExit();
+        }
+    }
+
+    /**
+     * Cancels any current software animations, leaving the values in their
+     * current state.
+     */
+    private void cancelSoftwareAnimations() {
+        if (mSoftwareAnimator != null) {
+            mSoftwareAnimator.cancel();
+            mSoftwareAnimator = null;
+        }
+    }
+
+    /**
+     * Ends any current software animations, jumping the values to their end
+     * state.
+     */
+    private void endSoftwareAnimations() {
+        if (mSoftwareAnimator != null) {
+            mSoftwareAnimator.end();
+            mSoftwareAnimator = null;
+        }
+    }
+
+    /**
+     * Ends any pending or current hardware animations.
+     * <p>
+     * Hardware animations can't synchronize values back to the software
+     * thread, so there is no "cancel" equivalent.
+     */
+    private void endHardwareAnimations() {
+        if (mHardwareAnimator != null) {
+            mHardwareAnimator.end();
+            mHardwareAnimator = null;
+        }
+
+        if (mHasPendingHardwareAnimator) {
+            mHasPendingHardwareAnimator = false;
+
+            // Manually jump values to their exited state. Normally we'd do that
+            // later when starting the hardware exit, but we're aborting early.
+            jumpValuesToExit();
+        }
+    }
+
+    protected final void invalidateSelf() {
+        mOwner.invalidateSelf(false);
+    }
+
+    protected final boolean isHardwareAnimating() {
+        return mHardwareAnimator != null && mHardwareAnimator.isRunning()
+                || mHasPendingHardwareAnimator;
+    }
+
+    protected final void onHotspotBoundsChanged() {
+        if (!mHasMaxRadius) {
+            final float halfWidth = mBounds.width() / 2.0f;
+            final float halfHeight = mBounds.height() / 2.0f;
+            final float targetRadius = (float) Math.sqrt(halfWidth * halfWidth
+                    + halfHeight * halfHeight);
+
+            onTargetRadiusChanged(targetRadius);
+        }
+    }
+
+    /**
+     * Called when the target radius changes.
+     *
+     * @param targetRadius the new target radius
+     */
+    protected void onTargetRadiusChanged(float targetRadius) {
+        // Stub.
+    }
+
+    protected abstract Animator createSoftwareEnter(boolean fast);
+
+    protected abstract Animator createSoftwareExit();
+
+    protected abstract RenderNodeAnimatorSet createHardwareExit(Paint p);
+
+    protected abstract boolean drawHardware(DisplayListCanvas c);
+
+    protected abstract boolean drawSoftware(Canvas c, Paint p);
+
+    /**
+     * Called when the hardware exit is cancelled. Jumps software values to end
+     * state to ensure that software and hardware values are synchronized.
+     */
+    protected abstract void jumpValuesToExit();
+
+    public static class RenderNodeAnimatorSet {
+        private final ArrayList<RenderNodeAnimator> mAnimators = new ArrayList<>();
+
+        public void add(RenderNodeAnimator anim) {
+            mAnimators.add(anim);
+        }
+
+        public void clear() {
+            mAnimators.clear();
+        }
+
+        public void start(DisplayListCanvas target) {
+            if (target == null) {
+                throw new IllegalArgumentException("Hardware canvas must be non-null");
+            }
+
+            final ArrayList<RenderNodeAnimator> animators = mAnimators;
+            final int N = animators.size();
+            for (int i = 0; i < N; i++) {
+                final RenderNodeAnimator anim = animators.get(i);
+                anim.setTarget(target);
+                anim.start();
+            }
+        }
+
+        public void cancel() {
+            final ArrayList<RenderNodeAnimator> animators = mAnimators;
+            final int N = animators.size();
+            for (int i = 0; i < N; i++) {
+                final RenderNodeAnimator anim = animators.get(i);
+                anim.cancel();
+            }
+        }
+
+        public void end() {
+            final ArrayList<RenderNodeAnimator> animators = mAnimators;
+            final int N = animators.size();
+            for (int i = 0; i < N; i++) {
+                final RenderNodeAnimator anim = animators.get(i);
+                anim.end();
+            }
+        }
+
+        public boolean isRunning() {
+            final ArrayList<RenderNodeAnimator> animators = mAnimators;
+            final int N = animators.size();
+            for (int i = 0; i < N; i++) {
+                final RenderNodeAnimator anim = animators.get(i);
+                if (anim.isRunning()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+}
diff --git a/android/graphics/drawable/RippleDrawable.java b/android/graphics/drawable/RippleDrawable.java
new file mode 100644
index 0000000..8f314c9
--- /dev/null
+++ b/android/graphics/drawable/RippleDrawable.java
@@ -0,0 +1,1066 @@
+/*
+ * Copyright (C) 2014 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 com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.ActivityInfo.Config;
+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.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.util.AttributeSet;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Drawable that shows a ripple effect in response to state changes. The
+ * anchoring position of the ripple for a given state may be specified by
+ * calling {@link #setHotspot(float, float)} with the corresponding state
+ * attribute identifier.
+ * <p>
+ * A touch feedback drawable may contain multiple child layers, including a
+ * special mask layer that is not drawn to the screen. A single layer may be
+ * set as the mask from XML by specifying its {@code android:id} value as
+ * {@link android.R.id#mask}. At run time, a single layer may be set as the
+ * mask using {@code setId(..., android.R.id.mask)} or an existing mask layer
+ * may be replaced using {@code setDrawableByLayerId(android.R.id.mask, ...)}.
+ * <pre>
+ * <code>&lt;!-- A red ripple masked against an opaque rectangle. --/>
+ * &lt;ripple android:color="#ffff0000">
+ *   &lt;item android:id="@android:id/mask"
+ *         android:drawable="@android:color/white" />
+ * &lt;/ripple></code>
+ * </pre>
+ * <p>
+ * If a mask layer is set, the ripple effect will be masked against that layer
+ * before it is drawn over the composite of the remaining child layers.
+ * <p>
+ * If no mask layer is set, the ripple effect is masked against the composite
+ * of the child layers.
+ * <pre>
+ * <code>&lt;!-- A green ripple drawn atop a black rectangle. --/>
+ * &lt;ripple android:color="#ff00ff00">
+ *   &lt;item android:drawable="@android:color/black" />
+ * &lt;/ripple>
+ *
+ * &lt;!-- A blue ripple drawn atop a drawable resource. --/>
+ * &lt;ripple android:color="#ff0000ff">
+ *   &lt;item android:drawable="@drawable/my_drawable" />
+ * &lt;/ripple></code>
+ * </pre>
+ * <p>
+ * If no child layers or mask is specified and the ripple is set as a View
+ * background, the ripple will be drawn atop the first available parent
+ * background within the View's hierarchy. In this case, the drawing region
+ * may extend outside of the Drawable bounds.
+ * <pre>
+ * <code>&lt;!-- An unbounded red ripple. --/>
+ * &lt;ripple android:color="#ffff0000" /></code>
+ * </pre>
+ *
+ * @attr ref android.R.styleable#RippleDrawable_color
+ */
+public class RippleDrawable extends LayerDrawable {
+    /**
+     * Radius value that specifies the ripple radius should be computed based
+     * on the size of the ripple's container.
+     */
+    public static final int RADIUS_AUTO = -1;
+
+    private static final int MASK_UNKNOWN = -1;
+    private static final int MASK_NONE = 0;
+    private static final int MASK_CONTENT = 1;
+    private static final int MASK_EXPLICIT = 2;
+
+    /** The maximum number of ripples supported. */
+    private static final int MAX_RIPPLES = 10;
+
+    private final Rect mTempRect = new Rect();
+
+    /** Current ripple effect bounds, used to constrain ripple effects. */
+    private final Rect mHotspotBounds = new Rect();
+
+    /** Current drawing bounds, used to compute dirty region. */
+    private final Rect mDrawingBounds = new Rect();
+
+    /** Current dirty bounds, union of current and previous drawing bounds. */
+    private final Rect mDirtyBounds = new Rect();
+
+    /** Mirrors mLayerState with some extra information. */
+    private RippleState mState;
+
+    /** The masking layer, e.g. the layer with id R.id.mask. */
+    private Drawable mMask;
+
+    /** The current background. May be actively animating or pending entry. */
+    private RippleBackground mBackground;
+
+    private Bitmap mMaskBuffer;
+    private BitmapShader mMaskShader;
+    private Canvas mMaskCanvas;
+    private Matrix mMaskMatrix;
+    private PorterDuffColorFilter mMaskColorFilter;
+    private boolean mHasValidMask;
+
+    /** Whether we expect to draw a background when visible. */
+    private boolean mBackgroundActive;
+
+    /** The current ripple. May be actively animating or pending entry. */
+    private RippleForeground mRipple;
+
+    /** Whether we expect to draw a ripple when visible. */
+    private boolean mRippleActive;
+
+    // Hotspot coordinates that are awaiting activation.
+    private float mPendingX;
+    private float mPendingY;
+    private boolean mHasPending;
+
+    /**
+     * Lazily-created array of actively animating ripples. Inactive ripples are
+     * pruned during draw(). The locations of these will not change.
+     */
+    private RippleForeground[] mExitingRipples;
+    private int mExitingRipplesCount = 0;
+
+    /** Paint used to control appearance of ripples. */
+    private Paint mRipplePaint;
+
+    /** Target density of the display into which ripples are drawn. */
+    private int mDensity;
+
+    /** Whether bounds are being overridden. */
+    private boolean mOverrideBounds;
+
+    /**
+     * If set, force all ripple animations to not run on RenderThread, even if it would be
+     * available.
+     */
+    private boolean mForceSoftware;
+
+    /**
+     * Constructor used for drawable inflation.
+     */
+    RippleDrawable() {
+        this(new RippleState(null, null, null), null);
+    }
+
+    /**
+     * Creates a new ripple drawable with the specified ripple color and
+     * optional content and mask drawables.
+     *
+     * @param color The ripple color
+     * @param content The content drawable, may be {@code null}
+     * @param mask The mask drawable, may be {@code null}
+     */
+    public RippleDrawable(@NonNull ColorStateList color, @Nullable Drawable content,
+            @Nullable Drawable mask) {
+        this(new RippleState(null, null, null), null);
+
+        if (color == null) {
+            throw new IllegalArgumentException("RippleDrawable requires a non-null color");
+        }
+
+        if (content != null) {
+            addLayer(content, null, 0, 0, 0, 0, 0);
+        }
+
+        if (mask != null) {
+            addLayer(mask, null, android.R.id.mask, 0, 0, 0, 0);
+        }
+
+        setColor(color);
+        ensurePadding();
+        refreshPadding();
+        updateLocalState();
+    }
+
+    @Override
+    public void jumpToCurrentState() {
+        super.jumpToCurrentState();
+
+        if (mRipple != null) {
+            mRipple.end();
+        }
+
+        if (mBackground != null) {
+            mBackground.end();
+        }
+
+        cancelExitingRipples();
+    }
+
+    private void cancelExitingRipples() {
+        final int count = mExitingRipplesCount;
+        final RippleForeground[] ripples = mExitingRipples;
+        for (int i = 0; i < count; i++) {
+            ripples[i].end();
+        }
+
+        if (ripples != null) {
+            Arrays.fill(ripples, 0, count, null);
+        }
+        mExitingRipplesCount = 0;
+
+        // Always draw an additional "clean" frame after canceling animations.
+        invalidateSelf(false);
+    }
+
+    @Override
+    public int getOpacity() {
+        // Worst-case scenario.
+        return PixelFormat.TRANSLUCENT;
+    }
+
+    @Override
+    protected boolean onStateChange(int[] stateSet) {
+        final boolean changed = super.onStateChange(stateSet);
+
+        boolean enabled = false;
+        boolean pressed = false;
+        boolean focused = false;
+        boolean hovered = false;
+
+        for (int state : stateSet) {
+            if (state == R.attr.state_enabled) {
+                enabled = true;
+            } else if (state == R.attr.state_focused) {
+                focused = true;
+            } else if (state == R.attr.state_pressed) {
+                pressed = true;
+            } else if (state == R.attr.state_hovered) {
+                hovered = true;
+            }
+        }
+
+        setRippleActive(enabled && pressed);
+        setBackgroundActive(hovered || focused || (enabled && pressed), focused || hovered);
+
+        return changed;
+    }
+
+    private void setRippleActive(boolean active) {
+        if (mRippleActive != active) {
+            mRippleActive = active;
+            if (active) {
+                tryRippleEnter();
+            } else {
+                tryRippleExit();
+            }
+        }
+    }
+
+    private void setBackgroundActive(boolean active, boolean focused) {
+        if (mBackgroundActive != active) {
+            mBackgroundActive = active;
+            if (active) {
+                tryBackgroundEnter(focused);
+            } else {
+                tryBackgroundExit();
+            }
+        }
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        super.onBoundsChange(bounds);
+
+        if (!mOverrideBounds) {
+            mHotspotBounds.set(bounds);
+            onHotspotBoundsChanged();
+        }
+
+        if (mBackground != null) {
+            mBackground.onBoundsChange();
+        }
+
+        if (mRipple != null) {
+            mRipple.onBoundsChange();
+        }
+
+        invalidateSelf();
+    }
+
+    @Override
+    public boolean setVisible(boolean visible, boolean restart) {
+        final boolean changed = super.setVisible(visible, restart);
+
+        if (!visible) {
+            clearHotspots();
+        } else if (changed) {
+            // If we just became visible, ensure the background and ripple
+            // visibilities are consistent with their internal states.
+            if (mRippleActive) {
+                tryRippleEnter();
+            }
+
+            if (mBackgroundActive) {
+                tryBackgroundEnter(false);
+            }
+
+            // Skip animations, just show the correct final states.
+            jumpToCurrentState();
+        }
+
+        return changed;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public boolean isProjected() {
+        // If the layer is bounded, then we don't need to project.
+        if (isBounded()) {
+            return false;
+        }
+
+        // Otherwise, if the maximum radius is contained entirely within the
+        // bounds then we don't need to project. This is sort of a hack to
+        // prevent check box ripples from being projected across the edges of
+        // scroll views. It does not impact rendering performance, and it can
+        // be removed once we have better handling of projection in scrollable
+        // views.
+        final int radius = mState.mMaxRadius;
+        final Rect drawableBounds = getBounds();
+        final Rect hotspotBounds = mHotspotBounds;
+        if (radius != RADIUS_AUTO
+                && radius <= hotspotBounds.width() / 2
+                && radius <= hotspotBounds.height() / 2
+                && (drawableBounds.equals(hotspotBounds)
+                        || drawableBounds.contains(hotspotBounds))) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private boolean isBounded() {
+        return getNumberOfLayers() > 0;
+    }
+
+    @Override
+    public boolean isStateful() {
+        return true;
+    }
+
+    /** @hide */
+    @Override
+    public boolean hasFocusStateSpecified() {
+        return true;
+    }
+
+    /**
+     * Sets the ripple color.
+     *
+     * @param color Ripple color as a color state list.
+     *
+     * @attr ref android.R.styleable#RippleDrawable_color
+     */
+    public void setColor(ColorStateList color) {
+        mState.mColor = color;
+        invalidateSelf(false);
+    }
+
+    /**
+     * Sets the radius in pixels of the fully expanded ripple.
+     *
+     * @param radius ripple radius in pixels, or {@link #RADIUS_AUTO} to
+     *               compute the radius based on the container size
+     * @attr ref android.R.styleable#RippleDrawable_radius
+     */
+    public void setRadius(int radius) {
+        mState.mMaxRadius = radius;
+        invalidateSelf(false);
+    }
+
+    /**
+     * @return the radius in pixels of the fully expanded ripple if an explicit
+     *         radius has been set, or {@link #RADIUS_AUTO} if the radius is
+     *         computed based on the container size
+     * @attr ref android.R.styleable#RippleDrawable_radius
+     */
+    public int getRadius() {
+        return mState.mMaxRadius;
+    }
+
+    @Override
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.RippleDrawable);
+
+        // Force padding default to STACK before inflating.
+        setPaddingMode(PADDING_MODE_STACK);
+
+        // Inflation will advance the XmlPullParser and AttributeSet.
+        super.inflate(r, parser, attrs, theme);
+
+        updateStateFromTypedArray(a);
+        verifyRequiredAttributes(a);
+        a.recycle();
+
+        updateLocalState();
+    }
+
+    @Override
+    public boolean setDrawableByLayerId(int id, Drawable drawable) {
+        if (super.setDrawableByLayerId(id, drawable)) {
+            if (id == R.id.mask) {
+                mMask = drawable;
+                mHasValidMask = false;
+            }
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Specifies how layer padding should affect the bounds of subsequent
+     * layers. The default and recommended value for RippleDrawable is
+     * {@link #PADDING_MODE_STACK}.
+     *
+     * @param mode padding mode, one of:
+     *            <ul>
+     *            <li>{@link #PADDING_MODE_NEST} to nest each layer inside the
+     *            padding of the previous layer
+     *            <li>{@link #PADDING_MODE_STACK} to stack each layer directly
+     *            atop the previous layer
+     *            </ul>
+     * @see #getPaddingMode()
+     */
+    @Override
+    public void setPaddingMode(int mode) {
+        super.setPaddingMode(mode);
+    }
+
+    /**
+     * Initializes the constant state from the values in the typed array.
+     */
+    private void updateStateFromTypedArray(@NonNull TypedArray a) throws XmlPullParserException {
+        final RippleState state = mState;
+
+        // Account for any configuration changes.
+        state.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        state.mTouchThemeAttrs = a.extractThemeAttrs();
+
+        final ColorStateList color = a.getColorStateList(R.styleable.RippleDrawable_color);
+        if (color != null) {
+            mState.mColor = color;
+        }
+
+        mState.mMaxRadius = a.getDimensionPixelSize(
+                R.styleable.RippleDrawable_radius, mState.mMaxRadius);
+    }
+
+    private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
+        if (mState.mColor == null && (mState.mTouchThemeAttrs == null
+                || mState.mTouchThemeAttrs[R.styleable.RippleDrawable_color] == 0)) {
+            throw new XmlPullParserException(a.getPositionDescription() +
+                    ": <ripple> requires a valid color attribute");
+        }
+    }
+
+    @Override
+    public void applyTheme(@NonNull Theme t) {
+        super.applyTheme(t);
+
+        final RippleState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        if (state.mTouchThemeAttrs != null) {
+            final TypedArray a = t.resolveAttributes(state.mTouchThemeAttrs,
+                    R.styleable.RippleDrawable);
+            try {
+                updateStateFromTypedArray(a);
+                verifyRequiredAttributes(a);
+            } catch (XmlPullParserException e) {
+                rethrowAsRuntimeException(e);
+            } finally {
+                a.recycle();
+            }
+        }
+
+        if (state.mColor != null && state.mColor.canApplyTheme()) {
+            state.mColor = state.mColor.obtainForTheme(t);
+        }
+
+        updateLocalState();
+    }
+
+    @Override
+    public boolean canApplyTheme() {
+        return (mState != null && mState.canApplyTheme()) || super.canApplyTheme();
+    }
+
+    @Override
+    public void setHotspot(float x, float y) {
+        if (mRipple == null || mBackground == null) {
+            mPendingX = x;
+            mPendingY = y;
+            mHasPending = true;
+        }
+
+        if (mRipple != null) {
+            mRipple.move(x, y);
+        }
+    }
+
+    /**
+     * Creates an active hotspot at the specified location.
+     */
+    private void tryBackgroundEnter(boolean focused) {
+        if (mBackground == null) {
+            final boolean isBounded = isBounded();
+            mBackground = new RippleBackground(this, mHotspotBounds, isBounded, mForceSoftware);
+        }
+
+        mBackground.setup(mState.mMaxRadius, mDensity);
+        mBackground.enter(focused);
+    }
+
+    private void tryBackgroundExit() {
+        if (mBackground != null) {
+            // Don't null out the background, we need it to draw!
+            mBackground.exit();
+        }
+    }
+
+    /**
+     * Attempts to start an enter animation for the active hotspot. Fails if
+     * there are too many animating ripples.
+     */
+    private void tryRippleEnter() {
+        if (mExitingRipplesCount >= MAX_RIPPLES) {
+            // This should never happen unless the user is tapping like a maniac
+            // or there is a bug that's preventing ripples from being removed.
+            return;
+        }
+
+        if (mRipple == null) {
+            final float x;
+            final float y;
+            if (mHasPending) {
+                mHasPending = false;
+                x = mPendingX;
+                y = mPendingY;
+            } else {
+                x = mHotspotBounds.exactCenterX();
+                y = mHotspotBounds.exactCenterY();
+            }
+
+            final boolean isBounded = isBounded();
+            mRipple = new RippleForeground(this, mHotspotBounds, x, y, isBounded, mForceSoftware);
+        }
+
+        mRipple.setup(mState.mMaxRadius, mDensity);
+        mRipple.enter(false);
+    }
+
+    /**
+     * Attempts to start an exit animation for the active hotspot. Fails if
+     * there is no active hotspot.
+     */
+    private void tryRippleExit() {
+        if (mRipple != null) {
+            if (mExitingRipples == null) {
+                mExitingRipples = new RippleForeground[MAX_RIPPLES];
+            }
+            mExitingRipples[mExitingRipplesCount++] = mRipple;
+            mRipple.exit();
+            mRipple = null;
+        }
+    }
+
+    /**
+     * Cancels and removes the active ripple, all exiting ripples, and the
+     * background. Nothing will be drawn after this method is called.
+     */
+    private void clearHotspots() {
+        if (mRipple != null) {
+            mRipple.end();
+            mRipple = null;
+            mRippleActive = false;
+        }
+
+        if (mBackground != null) {
+            mBackground.end();
+            mBackground = null;
+            mBackgroundActive = false;
+        }
+
+        cancelExitingRipples();
+    }
+
+    @Override
+    public void setHotspotBounds(int left, int top, int right, int bottom) {
+        mOverrideBounds = true;
+        mHotspotBounds.set(left, top, right, bottom);
+
+        onHotspotBoundsChanged();
+    }
+
+    @Override
+    public void getHotspotBounds(Rect outRect) {
+        outRect.set(mHotspotBounds);
+    }
+
+    /**
+     * Notifies all the animating ripples that the hotspot bounds have changed.
+     */
+    private void onHotspotBoundsChanged() {
+        final int count = mExitingRipplesCount;
+        final RippleForeground[] ripples = mExitingRipples;
+        for (int i = 0; i < count; i++) {
+            ripples[i].onHotspotBoundsChanged();
+        }
+
+        if (mRipple != null) {
+            mRipple.onHotspotBoundsChanged();
+        }
+
+        if (mBackground != null) {
+            mBackground.onHotspotBoundsChanged();
+        }
+    }
+
+    /**
+     * Populates <code>outline</code> with the first available layer outline,
+     * excluding the mask layer.
+     *
+     * @param outline Outline in which to place the first available layer outline
+     */
+    @Override
+    public void getOutline(@NonNull Outline outline) {
+        final LayerState state = mLayerState;
+        final ChildDrawable[] children = state.mChildren;
+        final int N = state.mNumChildren;
+        for (int i = 0; i < N; i++) {
+            if (children[i].mId != R.id.mask) {
+                children[i].mDrawable.getOutline(outline);
+                if (!outline.isEmpty()) return;
+            }
+        }
+    }
+
+    /**
+     * Optimized for drawing ripples with a mask layer and optional content.
+     */
+    @Override
+    public void draw(@NonNull Canvas canvas) {
+        pruneRipples();
+
+        // Clip to the dirty bounds, which will be the drawable bounds if we
+        // have a mask or content and the ripple bounds if we're projecting.
+        final Rect bounds = getDirtyBounds();
+        final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
+        canvas.clipRect(bounds);
+
+        drawContent(canvas);
+        drawBackgroundAndRipples(canvas);
+
+        canvas.restoreToCount(saveCount);
+    }
+
+    @Override
+    public void invalidateSelf() {
+        invalidateSelf(true);
+    }
+
+    void invalidateSelf(boolean invalidateMask) {
+        super.invalidateSelf();
+
+        if (invalidateMask) {
+            // Force the mask to update on the next draw().
+            mHasValidMask = false;
+        }
+
+    }
+
+    private void pruneRipples() {
+        int remaining = 0;
+
+        // Move remaining entries into pruned spaces.
+        final RippleForeground[] ripples = mExitingRipples;
+        final int count = mExitingRipplesCount;
+        for (int i = 0; i < count; i++) {
+            if (!ripples[i].hasFinishedExit()) {
+                ripples[remaining++] = ripples[i];
+            }
+        }
+
+        // Null out the remaining entries.
+        for (int i = remaining; i < count; i++) {
+            ripples[i] = null;
+        }
+
+        mExitingRipplesCount = remaining;
+    }
+
+    /**
+     * @return whether we need to use a mask
+     */
+    private void updateMaskShaderIfNeeded() {
+        if (mHasValidMask) {
+            return;
+        }
+
+        final int maskType = getMaskType();
+        if (maskType == MASK_UNKNOWN) {
+            return;
+        }
+
+        mHasValidMask = true;
+
+        final Rect bounds = getBounds();
+        if (maskType == MASK_NONE || bounds.isEmpty()) {
+            if (mMaskBuffer != null) {
+                mMaskBuffer.recycle();
+                mMaskBuffer = null;
+                mMaskShader = null;
+                mMaskCanvas = null;
+            }
+            mMaskMatrix = null;
+            mMaskColorFilter = null;
+            return;
+        }
+
+        // Ensure we have a correctly-sized buffer.
+        if (mMaskBuffer == null
+                || mMaskBuffer.getWidth() != bounds.width()
+                || mMaskBuffer.getHeight() != bounds.height()) {
+            if (mMaskBuffer != null) {
+                mMaskBuffer.recycle();
+            }
+
+            mMaskBuffer = Bitmap.createBitmap(
+                    bounds.width(), bounds.height(), Bitmap.Config.ALPHA_8);
+            mMaskShader = new BitmapShader(mMaskBuffer,
+                    Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+            mMaskCanvas = new Canvas(mMaskBuffer);
+        } else {
+            mMaskBuffer.eraseColor(Color.TRANSPARENT);
+        }
+
+        if (mMaskMatrix == null) {
+            mMaskMatrix = new Matrix();
+        } else {
+            mMaskMatrix.reset();
+        }
+
+        if (mMaskColorFilter == null) {
+            mMaskColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_IN);
+        }
+
+        // Draw the appropriate mask anchored to (0,0).
+        final int left = bounds.left;
+        final int top = bounds.top;
+        mMaskCanvas.translate(-left, -top);
+        if (maskType == MASK_EXPLICIT) {
+            drawMask(mMaskCanvas);
+        } else if (maskType == MASK_CONTENT) {
+            drawContent(mMaskCanvas);
+        }
+        mMaskCanvas.translate(left, top);
+    }
+
+    private int getMaskType() {
+        if (mRipple == null && mExitingRipplesCount <= 0
+                && (mBackground == null || !mBackground.isVisible())) {
+            // We might need a mask later.
+            return MASK_UNKNOWN;
+        }
+
+        if (mMask != null) {
+            if (mMask.getOpacity() == PixelFormat.OPAQUE) {
+                // Clipping handles opaque explicit masks.
+                return MASK_NONE;
+            } else {
+                return MASK_EXPLICIT;
+            }
+        }
+
+        // Check for non-opaque, non-mask content.
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int count = mLayerState.mNumChildren;
+        for (int i = 0; i < count; i++) {
+            if (array[i].mDrawable.getOpacity() != PixelFormat.OPAQUE) {
+                return MASK_CONTENT;
+            }
+        }
+
+        // Clipping handles opaque content.
+        return MASK_NONE;
+    }
+
+    private void drawContent(Canvas canvas) {
+        // Draw everything except the mask.
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int count = mLayerState.mNumChildren;
+        for (int i = 0; i < count; i++) {
+            if (array[i].mId != R.id.mask) {
+                array[i].mDrawable.draw(canvas);
+            }
+        }
+    }
+
+    private void drawBackgroundAndRipples(Canvas canvas) {
+        final RippleForeground active = mRipple;
+        final RippleBackground background = mBackground;
+        final int count = mExitingRipplesCount;
+        if (active == null && count <= 0 && (background == null || !background.isVisible())) {
+            // Move along, nothing to draw here.
+            return;
+        }
+
+        final float x = mHotspotBounds.exactCenterX();
+        final float y = mHotspotBounds.exactCenterY();
+        canvas.translate(x, y);
+
+        updateMaskShaderIfNeeded();
+
+        // Position the shader to account for canvas translation.
+        if (mMaskShader != null) {
+            final Rect bounds = getBounds();
+            mMaskMatrix.setTranslate(bounds.left - x, bounds.top - y);
+            mMaskShader.setLocalMatrix(mMaskMatrix);
+        }
+
+        // Grab the color for the current state and cut the alpha channel in
+        // half so that the ripple and background together yield full alpha.
+        final int color = mState.mColor.getColorForState(getState(), Color.BLACK);
+        final int halfAlpha = (Color.alpha(color) / 2) << 24;
+        final Paint p = getRipplePaint();
+
+        if (mMaskColorFilter != null) {
+            // The ripple timing depends on the paint's alpha value, so we need
+            // to push just the alpha channel into the paint and let the filter
+            // handle the full-alpha color.
+            final int fullAlphaColor = color | (0xFF << 24);
+            mMaskColorFilter.setColor(fullAlphaColor);
+
+            p.setColor(halfAlpha);
+            p.setColorFilter(mMaskColorFilter);
+            p.setShader(mMaskShader);
+        } else {
+            final int halfAlphaColor = (color & 0xFFFFFF) | halfAlpha;
+            p.setColor(halfAlphaColor);
+            p.setColorFilter(null);
+            p.setShader(null);
+        }
+
+        if (background != null && background.isVisible()) {
+            background.draw(canvas, p);
+        }
+
+        if (count > 0) {
+            final RippleForeground[] ripples = mExitingRipples;
+            for (int i = 0; i < count; i++) {
+                ripples[i].draw(canvas, p);
+            }
+        }
+
+        if (active != null) {
+            active.draw(canvas, p);
+        }
+
+        canvas.translate(-x, -y);
+    }
+
+    private void drawMask(Canvas canvas) {
+        mMask.draw(canvas);
+    }
+
+    private Paint getRipplePaint() {
+        if (mRipplePaint == null) {
+            mRipplePaint = new Paint();
+            mRipplePaint.setAntiAlias(true);
+            mRipplePaint.setStyle(Paint.Style.FILL);
+        }
+        return mRipplePaint;
+    }
+
+    @Override
+    public Rect getDirtyBounds() {
+        if (!isBounded()) {
+            final Rect drawingBounds = mDrawingBounds;
+            final Rect dirtyBounds = mDirtyBounds;
+            dirtyBounds.set(drawingBounds);
+            drawingBounds.setEmpty();
+
+            final int cX = (int) mHotspotBounds.exactCenterX();
+            final int cY = (int) mHotspotBounds.exactCenterY();
+            final Rect rippleBounds = mTempRect;
+
+            final RippleForeground[] activeRipples = mExitingRipples;
+            final int N = mExitingRipplesCount;
+            for (int i = 0; i < N; i++) {
+                activeRipples[i].getBounds(rippleBounds);
+                rippleBounds.offset(cX, cY);
+                drawingBounds.union(rippleBounds);
+            }
+
+            final RippleBackground background = mBackground;
+            if (background != null) {
+                background.getBounds(rippleBounds);
+                rippleBounds.offset(cX, cY);
+                drawingBounds.union(rippleBounds);
+            }
+
+            dirtyBounds.union(drawingBounds);
+            dirtyBounds.union(super.getDirtyBounds());
+            return dirtyBounds;
+        } else {
+            return getBounds();
+        }
+    }
+
+    /**
+     * Sets whether to disable RenderThread animations for this ripple.
+     *
+     * @param forceSoftware true if RenderThread animations should be disabled, false otherwise
+     * @hide
+     */
+    public void setForceSoftware(boolean forceSoftware) {
+        mForceSoftware = forceSoftware;
+    }
+
+    @Override
+    public ConstantState getConstantState() {
+        return mState;
+    }
+
+    @Override
+    public Drawable mutate() {
+        super.mutate();
+
+        // LayerDrawable creates a new state using createConstantState, so
+        // this should always be a safe cast.
+        mState = (RippleState) mLayerState;
+
+        // The locally cached drawable may have changed.
+        mMask = findDrawableByLayerId(R.id.mask);
+
+        return this;
+    }
+
+    @Override
+    RippleState createConstantState(LayerState state, Resources res) {
+        return new RippleState(state, this, res);
+    }
+
+    static class RippleState extends LayerState {
+        int[] mTouchThemeAttrs;
+        ColorStateList mColor = ColorStateList.valueOf(Color.MAGENTA);
+        int mMaxRadius = RADIUS_AUTO;
+
+        public RippleState(LayerState orig, RippleDrawable owner, Resources res) {
+            super(orig, owner, res);
+
+            if (orig != null && orig instanceof RippleState) {
+                final RippleState origs = (RippleState) orig;
+                mTouchThemeAttrs = origs.mTouchThemeAttrs;
+                mColor = origs.mColor;
+                mMaxRadius = origs.mMaxRadius;
+
+                if (origs.mDensity != mDensity) {
+                    applyDensityScaling(orig.mDensity, mDensity);
+                }
+            }
+        }
+
+        @Override
+        protected void onDensityChanged(int sourceDensity, int targetDensity) {
+            super.onDensityChanged(sourceDensity, targetDensity);
+
+            applyDensityScaling(sourceDensity, targetDensity);
+        }
+
+        private void applyDensityScaling(int sourceDensity, int targetDensity) {
+            if (mMaxRadius != RADIUS_AUTO) {
+                mMaxRadius = Drawable.scaleFromDensity(
+                        mMaxRadius, sourceDensity, targetDensity, true);
+            }
+        }
+
+        @Override
+        public boolean canApplyTheme() {
+            return mTouchThemeAttrs != null
+                    || (mColor != null && mColor.canApplyTheme())
+                    || super.canApplyTheme();
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return new RippleDrawable(this, null);
+        }
+
+        @Override
+        public Drawable newDrawable(Resources res) {
+            return new RippleDrawable(this, res);
+        }
+
+        @Override
+        public @Config int getChangingConfigurations() {
+            return super.getChangingConfigurations()
+                    | (mColor != null ? mColor.getChangingConfigurations() : 0);
+        }
+    }
+
+    private RippleDrawable(RippleState state, Resources res) {
+        mState = new RippleState(state, this, res);
+        mLayerState = mState;
+        mDensity = Drawable.resolveDensity(res, mState.mDensity);
+
+        if (mState.mNumChildren > 0) {
+            ensurePadding();
+            refreshPadding();
+        }
+
+        updateLocalState();
+    }
+
+    private void updateLocalState() {
+        // Initialize from constant state.
+        mMask = findDrawableByLayerId(R.id.mask);
+    }
+}
diff --git a/android/graphics/drawable/RippleForeground.java b/android/graphics/drawable/RippleForeground.java
new file mode 100644
index 0000000..829733e
--- /dev/null
+++ b/android/graphics/drawable/RippleForeground.java
@@ -0,0 +1,434 @@
+/*
+ * Copyright (C) 2015 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.graphics.Canvas;
+import android.graphics.CanvasProperty;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.FloatProperty;
+import android.util.MathUtils;
+import android.view.DisplayListCanvas;
+import android.view.RenderNodeAnimator;
+import android.view.animation.LinearInterpolator;
+
+/**
+ * Draws a ripple foreground.
+ */
+class RippleForeground extends RippleComponent {
+    private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
+    private static final TimeInterpolator DECELERATE_INTERPOLATOR = new LogDecelerateInterpolator(
+            400f, 1.4f, 0);
+
+    // Pixel-based accelerations and velocities.
+    private static final float WAVE_TOUCH_DOWN_ACCELERATION = 1024;
+    private static final float WAVE_TOUCH_UP_ACCELERATION = 3400;
+    private static final float WAVE_OPACITY_DECAY_VELOCITY = 3;
+
+    // Bounded ripple animation properties.
+    private static final int BOUNDED_ORIGIN_EXIT_DURATION = 300;
+    private static final int BOUNDED_RADIUS_EXIT_DURATION = 800;
+    private static final int BOUNDED_OPACITY_EXIT_DURATION = 400;
+    private static final float MAX_BOUNDED_RADIUS = 350;
+
+    private static final int RIPPLE_ENTER_DELAY = 80;
+    private static final int OPACITY_ENTER_DURATION_FAST = 120;
+
+    // Parent-relative values for starting position.
+    private float mStartingX;
+    private float mStartingY;
+    private float mClampedStartingX;
+    private float mClampedStartingY;
+
+    // Hardware rendering properties.
+    private CanvasProperty<Paint> mPropPaint;
+    private CanvasProperty<Float> mPropRadius;
+    private CanvasProperty<Float> mPropX;
+    private CanvasProperty<Float> mPropY;
+
+    // Target values for tween animations.
+    private float mTargetX = 0;
+    private float mTargetY = 0;
+
+    /** Ripple target radius used when bounded. Not used for clamping. */
+    private float mBoundedRadius = 0;
+
+    // Software rendering properties.
+    private float mOpacity = 1;
+
+    // Values used to tween between the start and end positions.
+    private float mTweenRadius = 0;
+    private float mTweenX = 0;
+    private float mTweenY = 0;
+
+    /** Whether this ripple is bounded. */
+    private boolean mIsBounded;
+
+    /** Whether this ripple has finished its exit animation. */
+    private boolean mHasFinishedExit;
+
+    public RippleForeground(RippleDrawable owner, Rect bounds, float startingX, float startingY,
+            boolean isBounded, boolean forceSoftware) {
+        super(owner, bounds, forceSoftware);
+
+        mIsBounded = isBounded;
+        mStartingX = startingX;
+        mStartingY = startingY;
+
+        if (isBounded) {
+            mBoundedRadius = MAX_BOUNDED_RADIUS * 0.9f
+                    + (float) (MAX_BOUNDED_RADIUS * Math.random() * 0.1);
+        } else {
+            mBoundedRadius = 0;
+        }
+    }
+
+    @Override
+    protected void onTargetRadiusChanged(float targetRadius) {
+        clampStartingPosition();
+    }
+
+    @Override
+    protected boolean drawSoftware(Canvas c, Paint p) {
+        boolean hasContent = false;
+
+        final int origAlpha = p.getAlpha();
+        final int alpha = (int) (origAlpha * mOpacity + 0.5f);
+        final float radius = getCurrentRadius();
+        if (alpha > 0 && radius > 0) {
+            final float x = getCurrentX();
+            final float y = getCurrentY();
+            p.setAlpha(alpha);
+            c.drawCircle(x, y, radius, p);
+            p.setAlpha(origAlpha);
+            hasContent = true;
+        }
+
+        return hasContent;
+    }
+
+    @Override
+    protected boolean drawHardware(DisplayListCanvas c) {
+        c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
+        return true;
+    }
+
+    /**
+     * Returns the maximum bounds of the ripple relative to the ripple center.
+     */
+    public void getBounds(Rect bounds) {
+        final int outerX = (int) mTargetX;
+        final int outerY = (int) mTargetY;
+        final int r = (int) mTargetRadius + 1;
+        bounds.set(outerX - r, outerY - r, outerX + r, outerY + r);
+    }
+
+    /**
+     * Specifies the starting position relative to the drawable bounds. No-op if
+     * the ripple has already entered.
+     */
+    public void move(float x, float y) {
+        mStartingX = x;
+        mStartingY = y;
+
+        clampStartingPosition();
+    }
+
+    /**
+     * @return {@code true} if this ripple has finished its exit animation
+     */
+    public boolean hasFinishedExit() {
+        return mHasFinishedExit;
+    }
+
+    @Override
+    protected Animator createSoftwareEnter(boolean fast) {
+        // Bounded ripples don't have enter animations.
+        if (mIsBounded) {
+            return null;
+        }
+
+        final int duration = (int)
+                (1000 * Math.sqrt(mTargetRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensityScale) + 0.5);
+
+        final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1);
+        tweenRadius.setAutoCancel(true);
+        tweenRadius.setDuration(duration);
+        tweenRadius.setInterpolator(LINEAR_INTERPOLATOR);
+        tweenRadius.setStartDelay(RIPPLE_ENTER_DELAY);
+
+        final ObjectAnimator tweenOrigin = ObjectAnimator.ofFloat(this, TWEEN_ORIGIN, 1);
+        tweenOrigin.setAutoCancel(true);
+        tweenOrigin.setDuration(duration);
+        tweenOrigin.setInterpolator(LINEAR_INTERPOLATOR);
+        tweenOrigin.setStartDelay(RIPPLE_ENTER_DELAY);
+
+        final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 1);
+        opacity.setAutoCancel(true);
+        opacity.setDuration(OPACITY_ENTER_DURATION_FAST);
+        opacity.setInterpolator(LINEAR_INTERPOLATOR);
+
+        final AnimatorSet set = new AnimatorSet();
+        set.play(tweenOrigin).with(tweenRadius).with(opacity);
+
+        return set;
+    }
+
+    private float getCurrentX() {
+        return MathUtils.lerp(mClampedStartingX - mBounds.exactCenterX(), mTargetX, mTweenX);
+    }
+
+    private float getCurrentY() {
+        return MathUtils.lerp(mClampedStartingY - mBounds.exactCenterY(), mTargetY, mTweenY);
+    }
+
+    private int getRadiusExitDuration() {
+        final float remainingRadius = mTargetRadius - getCurrentRadius();
+        return (int) (1000 * Math.sqrt(remainingRadius / (WAVE_TOUCH_UP_ACCELERATION
+                + WAVE_TOUCH_DOWN_ACCELERATION) * mDensityScale) + 0.5);
+    }
+
+    private float getCurrentRadius() {
+        return MathUtils.lerp(0, mTargetRadius, mTweenRadius);
+    }
+
+    private int getOpacityExitDuration() {
+        return (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
+    }
+
+    /**
+     * Compute target values that are dependent on bounding.
+     */
+    private void computeBoundedTargetValues() {
+        mTargetX = (mClampedStartingX - mBounds.exactCenterX()) * .7f;
+        mTargetY = (mClampedStartingY - mBounds.exactCenterY()) * .7f;
+        mTargetRadius = mBoundedRadius;
+    }
+
+    @Override
+    protected Animator createSoftwareExit() {
+        final int radiusDuration;
+        final int originDuration;
+        final int opacityDuration;
+        if (mIsBounded) {
+            computeBoundedTargetValues();
+
+            radiusDuration = BOUNDED_RADIUS_EXIT_DURATION;
+            originDuration = BOUNDED_ORIGIN_EXIT_DURATION;
+            opacityDuration = BOUNDED_OPACITY_EXIT_DURATION;
+        } else {
+            radiusDuration = getRadiusExitDuration();
+            originDuration = radiusDuration;
+            opacityDuration = getOpacityExitDuration();
+        }
+
+        final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1);
+        tweenRadius.setAutoCancel(true);
+        tweenRadius.setDuration(radiusDuration);
+        tweenRadius.setInterpolator(DECELERATE_INTERPOLATOR);
+
+        final ObjectAnimator tweenOrigin = ObjectAnimator.ofFloat(this, TWEEN_ORIGIN, 1);
+        tweenOrigin.setAutoCancel(true);
+        tweenOrigin.setDuration(originDuration);
+        tweenOrigin.setInterpolator(DECELERATE_INTERPOLATOR);
+
+        final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 0);
+        opacity.setAutoCancel(true);
+        opacity.setDuration(opacityDuration);
+        opacity.setInterpolator(LINEAR_INTERPOLATOR);
+
+        final AnimatorSet set = new AnimatorSet();
+        set.play(tweenOrigin).with(tweenRadius).with(opacity);
+        set.addListener(mAnimationListener);
+
+        return set;
+    }
+
+    @Override
+    protected RenderNodeAnimatorSet createHardwareExit(Paint p) {
+        final int radiusDuration;
+        final int originDuration;
+        final int opacityDuration;
+        if (mIsBounded) {
+            computeBoundedTargetValues();
+
+            radiusDuration = BOUNDED_RADIUS_EXIT_DURATION;
+            originDuration = BOUNDED_ORIGIN_EXIT_DURATION;
+            opacityDuration = BOUNDED_OPACITY_EXIT_DURATION;
+        } else {
+            radiusDuration = getRadiusExitDuration();
+            originDuration = radiusDuration;
+            opacityDuration = getOpacityExitDuration();
+        }
+
+        final float startX = getCurrentX();
+        final float startY = getCurrentY();
+        final float startRadius = getCurrentRadius();
+
+        p.setAlpha((int) (p.getAlpha() * mOpacity + 0.5f));
+
+        mPropPaint = CanvasProperty.createPaint(p);
+        mPropRadius = CanvasProperty.createFloat(startRadius);
+        mPropX = CanvasProperty.createFloat(startX);
+        mPropY = CanvasProperty.createFloat(startY);
+
+        final RenderNodeAnimator radius = new RenderNodeAnimator(mPropRadius, mTargetRadius);
+        radius.setDuration(radiusDuration);
+        radius.setInterpolator(DECELERATE_INTERPOLATOR);
+
+        final RenderNodeAnimator x = new RenderNodeAnimator(mPropX, mTargetX);
+        x.setDuration(originDuration);
+        x.setInterpolator(DECELERATE_INTERPOLATOR);
+
+        final RenderNodeAnimator y = new RenderNodeAnimator(mPropY, mTargetY);
+        y.setDuration(originDuration);
+        y.setInterpolator(DECELERATE_INTERPOLATOR);
+
+        final RenderNodeAnimator opacity = new RenderNodeAnimator(mPropPaint,
+                RenderNodeAnimator.PAINT_ALPHA, 0);
+        opacity.setDuration(opacityDuration);
+        opacity.setInterpolator(LINEAR_INTERPOLATOR);
+        opacity.addListener(mAnimationListener);
+
+        final RenderNodeAnimatorSet set = new RenderNodeAnimatorSet();
+        set.add(radius);
+        set.add(opacity);
+        set.add(x);
+        set.add(y);
+
+        return set;
+    }
+
+    @Override
+    protected void jumpValuesToExit() {
+        mOpacity = 0;
+        mTweenX = 1;
+        mTweenY = 1;
+        mTweenRadius = 1;
+    }
+
+    /**
+     * Clamps the starting position to fit within the ripple bounds.
+     */
+    private void clampStartingPosition() {
+        final float cX = mBounds.exactCenterX();
+        final float cY = mBounds.exactCenterY();
+        final float dX = mStartingX - cX;
+        final float dY = mStartingY - cY;
+        final float r = mTargetRadius;
+        if (dX * dX + dY * dY > r * r) {
+            // Point is outside the circle, clamp to the perimeter.
+            final double angle = Math.atan2(dY, dX);
+            mClampedStartingX = cX + (float) (Math.cos(angle) * r);
+            mClampedStartingY = cY + (float) (Math.sin(angle) * r);
+        } else {
+            mClampedStartingX = mStartingX;
+            mClampedStartingY = mStartingY;
+        }
+    }
+
+    private final AnimatorListenerAdapter mAnimationListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animator) {
+            mHasFinishedExit = true;
+        }
+    };
+
+    /**
+    * Interpolator with a smooth log deceleration.
+    */
+    private static final class LogDecelerateInterpolator implements TimeInterpolator {
+        private final float mBase;
+        private final float mDrift;
+        private final float mTimeScale;
+        private final float mOutputScale;
+
+        public LogDecelerateInterpolator(float base, float timeScale, float drift) {
+            mBase = base;
+            mDrift = drift;
+            mTimeScale = 1f / timeScale;
+
+            mOutputScale = 1f / computeLog(1f);
+        }
+
+        private float computeLog(float t) {
+            return 1f - (float) Math.pow(mBase, -t * mTimeScale) + (mDrift * t);
+        }
+
+        @Override
+        public float getInterpolation(float t) {
+            return computeLog(t) * mOutputScale;
+        }
+    }
+
+    /**
+     * Property for animating radius between its initial and target values.
+     */
+    private static final FloatProperty<RippleForeground> TWEEN_RADIUS =
+            new FloatProperty<RippleForeground>("tweenRadius") {
+        @Override
+        public void setValue(RippleForeground object, float value) {
+            object.mTweenRadius = value;
+            object.invalidateSelf();
+        }
+
+        @Override
+        public Float get(RippleForeground object) {
+            return object.mTweenRadius;
+        }
+    };
+
+    /**
+     * Property for animating origin between its initial and target values.
+     */
+    private static final FloatProperty<RippleForeground> TWEEN_ORIGIN =
+            new FloatProperty<RippleForeground>("tweenOrigin") {
+                @Override
+                public void setValue(RippleForeground object, float value) {
+                    object.mTweenX = value;
+                    object.mTweenY = value;
+                    object.invalidateSelf();
+                }
+
+                @Override
+                public Float get(RippleForeground object) {
+                    return object.mTweenX;
+                }
+            };
+
+    /**
+     * Property for animating opacity between 0 and its target value.
+     */
+    private static final FloatProperty<RippleForeground> OPACITY =
+            new FloatProperty<RippleForeground>("opacity") {
+        @Override
+        public void setValue(RippleForeground object, float value) {
+            object.mOpacity = value;
+            object.invalidateSelf();
+        }
+
+        @Override
+        public Float get(RippleForeground object) {
+            return object.mOpacity;
+        }
+    };
+}
diff --git a/android/graphics/drawable/RotateDrawable.java b/android/graphics/drawable/RotateDrawable.java
new file mode 100644
index 0000000..c0dfe77
--- /dev/null
+++ b/android/graphics/drawable/RotateDrawable.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2007 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 com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.Resources.Theme;
+import android.util.MathUtils;
+import android.util.TypedValue;
+import android.util.AttributeSet;
+
+import java.io.IOException;
+
+/**
+ * <p>
+ * A Drawable that can rotate another Drawable based on the current level value.
+ * The start and end angles of rotation can be controlled to map any circular
+ * arc to the level values range.
+ * <p>
+ * It can be defined in an XML file with the <code>&lt;rotate&gt;</code> element.
+ * For more information, see the guide to
+ * <a href="{@docRoot}guide/topics/resources/animation-resource.html">Animation Resources</a>.
+ *
+ * @attr ref android.R.styleable#RotateDrawable_visible
+ * @attr ref android.R.styleable#RotateDrawable_fromDegrees
+ * @attr ref android.R.styleable#RotateDrawable_toDegrees
+ * @attr ref android.R.styleable#RotateDrawable_pivotX
+ * @attr ref android.R.styleable#RotateDrawable_pivotY
+ * @attr ref android.R.styleable#RotateDrawable_drawable
+ */
+public class RotateDrawable extends DrawableWrapper {
+    private static final int MAX_LEVEL = 10000;
+
+    private RotateState mState;
+
+    /**
+     * Creates a new rotating drawable with no wrapped drawable.
+     */
+    public RotateDrawable() {
+        this(new RotateState(null, null), null);
+    }
+
+    @Override
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.RotateDrawable);
+
+        // Inflation will advance the XmlPullParser and AttributeSet.
+        super.inflate(r, parser, attrs, theme);
+
+        updateStateFromTypedArray(a);
+        verifyRequiredAttributes(a);
+        a.recycle();
+    }
+
+    @Override
+    public void applyTheme(@NonNull Theme t) {
+        super.applyTheme(t);
+
+        final RotateState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        if (state.mThemeAttrs != null) {
+            final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.RotateDrawable);
+            try {
+                updateStateFromTypedArray(a);
+                verifyRequiredAttributes(a);
+            } catch (XmlPullParserException e) {
+                rethrowAsRuntimeException(e);
+            } finally {
+                a.recycle();
+            }
+        }
+    }
+
+    private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
+        // If we're not waiting on a theme, verify required attributes.
+        if (getDrawable() == null && (mState.mThemeAttrs == null
+                || mState.mThemeAttrs[R.styleable.RotateDrawable_drawable] == 0)) {
+            throw new XmlPullParserException(a.getPositionDescription()
+                    + ": <rotate> tag requires a 'drawable' attribute or "
+                    + "child tag defining a drawable");
+        }
+    }
+
+    private void updateStateFromTypedArray(@NonNull TypedArray a) {
+        final RotateState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        // Account for any configuration changes.
+        state.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        state.mThemeAttrs = a.extractThemeAttrs();
+
+        if (a.hasValue(R.styleable.RotateDrawable_pivotX)) {
+            final TypedValue tv = a.peekValue(R.styleable.RotateDrawable_pivotX);
+            state.mPivotXRel = tv.type == TypedValue.TYPE_FRACTION;
+            state.mPivotX = state.mPivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
+        }
+
+        if (a.hasValue(R.styleable.RotateDrawable_pivotY)) {
+            final TypedValue tv = a.peekValue(R.styleable.RotateDrawable_pivotY);
+            state.mPivotYRel = tv.type == TypedValue.TYPE_FRACTION;
+            state.mPivotY = state.mPivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
+        }
+
+        state.mFromDegrees = a.getFloat(
+                R.styleable.RotateDrawable_fromDegrees, state.mFromDegrees);
+        state.mToDegrees = a.getFloat(
+                R.styleable.RotateDrawable_toDegrees, state.mToDegrees);
+        state.mCurrentDegrees = state.mFromDegrees;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        final Drawable d = getDrawable();
+        final Rect bounds = d.getBounds();
+        final int w = bounds.right - bounds.left;
+        final int h = bounds.bottom - bounds.top;
+        final RotateState st = mState;
+        final float px = st.mPivotXRel ? (w * st.mPivotX) : st.mPivotX;
+        final float py = st.mPivotYRel ? (h * st.mPivotY) : st.mPivotY;
+
+        final int saveCount = canvas.save();
+        canvas.rotate(st.mCurrentDegrees, px + bounds.left, py + bounds.top);
+        d.draw(canvas);
+        canvas.restoreToCount(saveCount);
+    }
+
+    /**
+     * Sets the start angle for rotation.
+     *
+     * @param fromDegrees starting angle in degrees
+     * @see #getFromDegrees()
+     * @attr ref android.R.styleable#RotateDrawable_fromDegrees
+     */
+    public void setFromDegrees(float fromDegrees) {
+        if (mState.mFromDegrees != fromDegrees) {
+            mState.mFromDegrees = fromDegrees;
+            invalidateSelf();
+        }
+    }
+
+    /**
+     * @return starting angle for rotation in degrees
+     * @see #setFromDegrees(float)
+     * @attr ref android.R.styleable#RotateDrawable_fromDegrees
+     */
+    public float getFromDegrees() {
+        return mState.mFromDegrees;
+    }
+
+    /**
+     * Sets the end angle for rotation.
+     *
+     * @param toDegrees ending angle in degrees
+     * @see #getToDegrees()
+     * @attr ref android.R.styleable#RotateDrawable_toDegrees
+     */
+    public void setToDegrees(float toDegrees) {
+        if (mState.mToDegrees != toDegrees) {
+            mState.mToDegrees = toDegrees;
+            invalidateSelf();
+        }
+    }
+
+    /**
+     * @return ending angle for rotation in degrees
+     * @see #setToDegrees(float)
+     * @attr ref android.R.styleable#RotateDrawable_toDegrees
+     */
+    public float getToDegrees() {
+        return mState.mToDegrees;
+    }
+
+    /**
+     * Sets the X position around which the drawable is rotated.
+     * <p>
+     * If the X pivot is relative (as specified by
+     * {@link #setPivotXRelative(boolean)}), then the position represents a
+     * fraction of the drawable width. Otherwise, the position represents an
+     * absolute value in pixels.
+     *
+     * @param pivotX X position around which to rotate
+     * @see #setPivotXRelative(boolean)
+     * @attr ref android.R.styleable#RotateDrawable_pivotX
+     */
+    public void setPivotX(float pivotX) {
+        if (mState.mPivotX != pivotX) {
+            mState.mPivotX = pivotX;
+            invalidateSelf();
+        }
+    }
+
+    /**
+     * @return X position around which to rotate
+     * @see #setPivotX(float)
+     * @attr ref android.R.styleable#RotateDrawable_pivotX
+     */
+    public float getPivotX() {
+        return mState.mPivotX;
+    }
+
+    /**
+     * Sets whether the X pivot value represents a fraction of the drawable
+     * width or an absolute value in pixels.
+     *
+     * @param relative true if the X pivot represents a fraction of the drawable
+     *            width, or false if it represents an absolute value in pixels
+     * @see #isPivotXRelative()
+     */
+    public void setPivotXRelative(boolean relative) {
+        if (mState.mPivotXRel != relative) {
+            mState.mPivotXRel = relative;
+            invalidateSelf();
+        }
+    }
+
+    /**
+     * @return true if the X pivot represents a fraction of the drawable width,
+     *         or false if it represents an absolute value in pixels
+     * @see #setPivotXRelative(boolean)
+     */
+    public boolean isPivotXRelative() {
+        return mState.mPivotXRel;
+    }
+
+    /**
+     * Sets the Y position around which the drawable is rotated.
+     * <p>
+     * If the Y pivot is relative (as specified by
+     * {@link #setPivotYRelative(boolean)}), then the position represents a
+     * fraction of the drawable height. Otherwise, the position represents an
+     * absolute value in pixels.
+     *
+     * @param pivotY Y position around which to rotate
+     * @see #getPivotY()
+     * @attr ref android.R.styleable#RotateDrawable_pivotY
+     */
+    public void setPivotY(float pivotY) {
+        if (mState.mPivotY != pivotY) {
+            mState.mPivotY = pivotY;
+            invalidateSelf();
+        }
+    }
+
+    /**
+     * @return Y position around which to rotate
+     * @see #setPivotY(float)
+     * @attr ref android.R.styleable#RotateDrawable_pivotY
+     */
+    public float getPivotY() {
+        return mState.mPivotY;
+    }
+
+    /**
+     * Sets whether the Y pivot value represents a fraction of the drawable
+     * height or an absolute value in pixels.
+     *
+     * @param relative True if the Y pivot represents a fraction of the drawable
+     *            height, or false if it represents an absolute value in pixels
+     * @see #isPivotYRelative()
+     */
+    public void setPivotYRelative(boolean relative) {
+        if (mState.mPivotYRel != relative) {
+            mState.mPivotYRel = relative;
+            invalidateSelf();
+        }
+    }
+
+    /**
+     * @return true if the Y pivot represents a fraction of the drawable height,
+     *         or false if it represents an absolute value in pixels
+     * @see #setPivotYRelative(boolean)
+     */
+    public boolean isPivotYRelative() {
+        return mState.mPivotYRel;
+    }
+
+    @Override
+    protected boolean onLevelChange(int level) {
+        super.onLevelChange(level);
+
+        final float value = level / (float) MAX_LEVEL;
+        final float degrees = MathUtils.lerp(mState.mFromDegrees, mState.mToDegrees, value);
+        mState.mCurrentDegrees = degrees;
+
+        invalidateSelf();
+        return true;
+    }
+
+    @Override
+    DrawableWrapperState mutateConstantState() {
+        mState = new RotateState(mState, null);
+        return mState;
+    }
+
+    static final class RotateState extends DrawableWrapper.DrawableWrapperState {
+        private int[] mThemeAttrs;
+
+        boolean mPivotXRel = true;
+        float mPivotX = 0.5f;
+        boolean mPivotYRel = true;
+        float mPivotY = 0.5f;
+        float mFromDegrees = 0.0f;
+        float mToDegrees = 360.0f;
+        float mCurrentDegrees = 0.0f;
+
+        RotateState(RotateState orig, Resources res) {
+            super(orig, res);
+
+            if (orig != null) {
+                mPivotXRel = orig.mPivotXRel;
+                mPivotX = orig.mPivotX;
+                mPivotYRel = orig.mPivotYRel;
+                mPivotY = orig.mPivotY;
+                mFromDegrees = orig.mFromDegrees;
+                mToDegrees = orig.mToDegrees;
+                mCurrentDegrees = orig.mCurrentDegrees;
+            }
+        }
+
+        @Override
+        public Drawable newDrawable(Resources res) {
+            return new RotateDrawable(this, res);
+        }
+    }
+
+    private RotateDrawable(RotateState state, Resources res) {
+        super(state, res);
+
+        mState = state;
+    }
+}
diff --git a/android/graphics/drawable/ScaleDrawable.java b/android/graphics/drawable/ScaleDrawable.java
new file mode 100644
index 0000000..51e143b
--- /dev/null
+++ b/android/graphics/drawable/ScaleDrawable.java
@@ -0,0 +1,302 @@
+/*
+ * 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 com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Gravity;
+
+import java.io.IOException;
+
+/**
+ * A Drawable that changes the size of another Drawable based on its current
+ * level value. You can control how much the child Drawable changes in width
+ * and height based on the level, as well as a gravity to control where it is
+ * placed in its overall container. Most often used to implement things like
+ * progress bars.
+ * <p>
+ * The default level may be specified from XML using the
+ * {@link android.R.styleable#ScaleDrawable_level android:level} property. When
+ * this property is not specified, the default level is 0, which corresponds to
+ * zero height and/or width depending on the values specified for
+ * {@code android.R.styleable#ScaleDrawable_scaleWidth scaleWidth} and
+ * {@code android.R.styleable#ScaleDrawable_scaleHeight scaleHeight}. At run
+ * time, the level may be set via {@link #setLevel(int)}.
+ * <p>
+ * A scale drawable may be defined in an XML file with the {@code <scale>}
+ * element. For more information, see the guide to
+ * <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable
+ * Resources</a>.
+ *
+ * @attr ref android.R.styleable#ScaleDrawable_scaleWidth
+ * @attr ref android.R.styleable#ScaleDrawable_scaleHeight
+ * @attr ref android.R.styleable#ScaleDrawable_scaleGravity
+ * @attr ref android.R.styleable#ScaleDrawable_drawable
+ * @attr ref android.R.styleable#ScaleDrawable_level
+ */
+public class ScaleDrawable extends DrawableWrapper {
+    private static final int MAX_LEVEL = 10000;
+
+    private final Rect mTmpRect = new Rect();
+
+    private ScaleState mState;
+
+    ScaleDrawable() {
+        this(new ScaleState(null, null), null);
+    }
+
+    /**
+     * Creates a new scale drawable with the specified gravity and scale
+     * properties.
+     *
+     * @param drawable the drawable to scale
+     * @param gravity gravity constant (see {@link Gravity} used to position
+     *                the scaled drawable within the parent container
+     * @param scaleWidth width scaling factor [0...1] to use then the level is
+     *                   at the maximum value, or -1 to not scale width
+     * @param scaleHeight height scaling factor [0...1] to use then the level
+     *                    is at the maximum value, or -1 to not scale height
+     */
+    public ScaleDrawable(Drawable drawable, int gravity, float scaleWidth, float scaleHeight) {
+        this(new ScaleState(null, null), null);
+
+        mState.mGravity = gravity;
+        mState.mScaleWidth = scaleWidth;
+        mState.mScaleHeight = scaleHeight;
+
+        setDrawable(drawable);
+    }
+
+    @Override
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ScaleDrawable);
+
+        // Inflation will advance the XmlPullParser and AttributeSet.
+        super.inflate(r, parser, attrs, theme);
+
+        updateStateFromTypedArray(a);
+        verifyRequiredAttributes(a);
+        a.recycle();
+
+        updateLocalState();
+    }
+
+    @Override
+    public void applyTheme(@NonNull Theme t) {
+        super.applyTheme(t);
+
+        final ScaleState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        if (state.mThemeAttrs != null) {
+            final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ScaleDrawable);
+            try {
+                updateStateFromTypedArray(a);
+                verifyRequiredAttributes(a);
+            } catch (XmlPullParserException e) {
+                rethrowAsRuntimeException(e);
+            } finally {
+                a.recycle();
+            }
+        }
+
+        updateLocalState();
+    }
+
+    private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
+        // If we're not waiting on a theme, verify required attributes.
+        if (getDrawable() == null && (mState.mThemeAttrs == null
+                || mState.mThemeAttrs[R.styleable.ScaleDrawable_drawable] == 0)) {
+            throw new XmlPullParserException(a.getPositionDescription()
+                    + ": <scale> tag requires a 'drawable' attribute or "
+                    + "child tag defining a drawable");
+        }
+    }
+
+    private void updateStateFromTypedArray(@NonNull TypedArray a) {
+        final ScaleState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        // Account for any configuration changes.
+        state.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        state.mThemeAttrs = a.extractThemeAttrs();
+
+        state.mScaleWidth = getPercent(a,
+                R.styleable.ScaleDrawable_scaleWidth, state.mScaleWidth);
+        state.mScaleHeight = getPercent(a,
+                R.styleable.ScaleDrawable_scaleHeight, state.mScaleHeight);
+        state.mGravity = a.getInt(
+                R.styleable.ScaleDrawable_scaleGravity, state.mGravity);
+        state.mUseIntrinsicSizeAsMin = a.getBoolean(
+                R.styleable.ScaleDrawable_useIntrinsicSizeAsMinimum, state.mUseIntrinsicSizeAsMin);
+        state.mInitialLevel = a.getInt(
+                R.styleable.ScaleDrawable_level, state.mInitialLevel);
+    }
+
+    private static float getPercent(TypedArray a, int index, float defaultValue) {
+        final int type = a.getType(index);
+        if (type == TypedValue.TYPE_FRACTION || type == TypedValue.TYPE_NULL) {
+            return a.getFraction(index, 1, 1, defaultValue);
+        }
+
+        // Coerce to float.
+        final String s = a.getString(index);
+        if (s != null) {
+            if (s.endsWith("%")) {
+                final String f = s.substring(0, s.length() - 1);
+                return Float.parseFloat(f) / 100.0f;
+            }
+        }
+
+        return defaultValue;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        final Drawable d = getDrawable();
+        if (d != null && d.getLevel() != 0) {
+            d.draw(canvas);
+        }
+    }
+
+    @Override
+    public int getOpacity() {
+        final Drawable d = getDrawable();
+        if (d.getLevel() == 0) {
+            return PixelFormat.TRANSPARENT;
+        }
+
+        final int opacity = d.getOpacity();
+        if (opacity == PixelFormat.OPAQUE && d.getLevel() < MAX_LEVEL) {
+            return PixelFormat.TRANSLUCENT;
+        }
+
+        return opacity;
+    }
+
+    @Override
+    protected boolean onLevelChange(int level) {
+        super.onLevelChange(level);
+        onBoundsChange(getBounds());
+        invalidateSelf();
+        return true;
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        final Drawable d = getDrawable();
+        final Rect r = mTmpRect;
+        final boolean min = mState.mUseIntrinsicSizeAsMin;
+        final int level = getLevel();
+
+        int w = bounds.width();
+        if (mState.mScaleWidth > 0) {
+            final int iw = min ? d.getIntrinsicWidth() : 0;
+            w -= (int) ((w - iw) * (MAX_LEVEL - level) * mState.mScaleWidth / MAX_LEVEL);
+        }
+
+        int h = bounds.height();
+        if (mState.mScaleHeight > 0) {
+            final int ih = min ? d.getIntrinsicHeight() : 0;
+            h -= (int) ((h - ih) * (MAX_LEVEL - level) * mState.mScaleHeight / MAX_LEVEL);
+        }
+
+        final int layoutDirection = getLayoutDirection();
+        Gravity.apply(mState.mGravity, w, h, bounds, r, layoutDirection);
+
+        if (w > 0 && h > 0) {
+            d.setBounds(r.left, r.top, r.right, r.bottom);
+        }
+    }
+
+    @Override
+    DrawableWrapperState mutateConstantState() {
+        mState = new ScaleState(mState, null);
+        return mState;
+    }
+
+    static final class ScaleState extends DrawableWrapper.DrawableWrapperState {
+        /** Constant used to disable scaling for a particular dimension. */
+        private static final float DO_NOT_SCALE = -1.0f;
+
+        private int[] mThemeAttrs;
+
+        float mScaleWidth = DO_NOT_SCALE;
+        float mScaleHeight = DO_NOT_SCALE;
+        int mGravity = Gravity.LEFT;
+        boolean mUseIntrinsicSizeAsMin = false;
+        int mInitialLevel = 0;
+
+        ScaleState(ScaleState orig, Resources res) {
+            super(orig, res);
+
+            if (orig != null) {
+                mScaleWidth = orig.mScaleWidth;
+                mScaleHeight = orig.mScaleHeight;
+                mGravity = orig.mGravity;
+                mUseIntrinsicSizeAsMin = orig.mUseIntrinsicSizeAsMin;
+                mInitialLevel = orig.mInitialLevel;
+            }
+        }
+
+        @Override
+        public Drawable newDrawable(Resources res) {
+            return new ScaleDrawable(this, res);
+        }
+    }
+
+    /**
+     * Creates a new ScaleDrawable based on the specified constant state.
+     * <p>
+     * The resulting drawable is guaranteed to have a new constant state.
+     *
+     * @param state constant state from which the drawable inherits
+     */
+    private ScaleDrawable(ScaleState state, Resources res) {
+        super(state, res);
+
+        mState = state;
+
+        updateLocalState();
+    }
+
+    private void updateLocalState() {
+        setLevel(mState.mInitialLevel);
+    }
+}
+
diff --git a/android/graphics/drawable/ShapeDrawable.java b/android/graphics/drawable/ShapeDrawable.java
new file mode 100644
index 0000000..34da928
--- /dev/null
+++ b/android/graphics/drawable/ShapeDrawable.java
@@ -0,0 +1,651 @@
+/*
+ * Copyright (C) 2007 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.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.content.pm.ActivityInfo.Config;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.graphics.Xfermode;
+import android.graphics.drawable.shapes.Shape;
+import android.util.AttributeSet;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * A Drawable object that draws primitive shapes. A ShapeDrawable takes a
+ * {@link android.graphics.drawable.shapes.Shape} object and manages its
+ * presence on the screen. If no Shape is given, then the ShapeDrawable will
+ * default to a {@link android.graphics.drawable.shapes.RectShape}.
+ * <p>
+ * This object can be defined in an XML file with the <code>&lt;shape></code>
+ * element.
+ * </p>
+ * <div class="special reference"> <h3>Developer Guides</h3>
+ * <p>
+ * For more information about how to use ShapeDrawable, read the <a
+ * href="{@docRoot}guide/topics/graphics/2d-graphics.html#shape-drawable">
+ * Canvas and Drawables</a> document. For more information about defining a
+ * ShapeDrawable in XML, read the
+ * <a href="{@docRoot}guide/topics/resources/drawable-resource.html#Shape">
+ * Drawable Resources</a> document.
+ * </p>
+ * </div>
+ *
+ * @attr ref android.R.styleable#ShapeDrawablePadding_left
+ * @attr ref android.R.styleable#ShapeDrawablePadding_top
+ * @attr ref android.R.styleable#ShapeDrawablePadding_right
+ * @attr ref android.R.styleable#ShapeDrawablePadding_bottom
+ * @attr ref android.R.styleable#ShapeDrawable_color
+ * @attr ref android.R.styleable#ShapeDrawable_width
+ * @attr ref android.R.styleable#ShapeDrawable_height
+ */
+public class ShapeDrawable extends Drawable {
+    private @NonNull ShapeState mShapeState;
+    private PorterDuffColorFilter mTintFilter;
+    private boolean mMutated;
+
+    /**
+     * ShapeDrawable constructor.
+     */
+    public ShapeDrawable() {
+        this(new ShapeState(), null);
+    }
+
+    /**
+     * Creates a ShapeDrawable with a specified Shape.
+     *
+     * @param s the Shape that this ShapeDrawable should be
+     */
+    public ShapeDrawable(Shape s) {
+        this(new ShapeState(), null);
+
+        mShapeState.mShape = s;
+    }
+
+    /**
+     * Returns the Shape of this ShapeDrawable.
+     */
+    public Shape getShape() {
+        return mShapeState.mShape;
+    }
+
+    /**
+     * Sets the Shape of this ShapeDrawable.
+     */
+    public void setShape(Shape s) {
+        mShapeState.mShape = s;
+        updateShape();
+    }
+
+    /**
+     * Sets a ShaderFactory to which requests for a
+     * {@link android.graphics.Shader} object will be made.
+     *
+     * @param fact an instance of your ShaderFactory implementation
+     */
+    public void setShaderFactory(ShaderFactory fact) {
+        mShapeState.mShaderFactory = fact;
+    }
+
+    /**
+     * Returns the ShaderFactory used by this ShapeDrawable for requesting a
+     * {@link android.graphics.Shader}.
+     */
+    public ShaderFactory getShaderFactory() {
+        return mShapeState.mShaderFactory;
+    }
+
+    /**
+     * Returns the Paint used to draw the shape.
+     */
+    public Paint getPaint() {
+        return mShapeState.mPaint;
+    }
+
+    /**
+     * Sets padding for the shape.
+     *
+     * @param left padding for the left side (in pixels)
+     * @param top padding for the top (in pixels)
+     * @param right padding for the right side (in pixels)
+     * @param bottom padding for the bottom (in pixels)
+     */
+    public void setPadding(int left, int top, int right, int bottom) {
+        if ((left | top | right | bottom) == 0) {
+            mShapeState.mPadding = null;
+        } else {
+            if (mShapeState.mPadding == null) {
+                mShapeState.mPadding = new Rect();
+            }
+            mShapeState.mPadding.set(left, top, right, bottom);
+        }
+        invalidateSelf();
+    }
+
+    /**
+     * Sets padding for this shape, defined by a Rect object. Define the padding
+     * in the Rect object as: left, top, right, bottom.
+     */
+    public void setPadding(Rect padding) {
+        if (padding == null) {
+            mShapeState.mPadding = null;
+        } else {
+            if (mShapeState.mPadding == null) {
+                mShapeState.mPadding = new Rect();
+            }
+            mShapeState.mPadding.set(padding);
+        }
+        invalidateSelf();
+    }
+
+    /**
+     * Sets the intrinsic (default) width for this shape.
+     *
+     * @param width the intrinsic width (in pixels)
+     */
+    public void setIntrinsicWidth(int width) {
+        mShapeState.mIntrinsicWidth = width;
+        invalidateSelf();
+    }
+
+    /**
+     * Sets the intrinsic (default) height for this shape.
+     *
+     * @param height the intrinsic height (in pixels)
+     */
+    public void setIntrinsicHeight(int height) {
+        mShapeState.mIntrinsicHeight = height;
+        invalidateSelf();
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return mShapeState.mIntrinsicWidth;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return mShapeState.mIntrinsicHeight;
+    }
+
+    @Override
+    public boolean getPadding(Rect padding) {
+        if (mShapeState.mPadding != null) {
+            padding.set(mShapeState.mPadding);
+            return true;
+        } else {
+            return super.getPadding(padding);
+        }
+    }
+
+    private static int modulateAlpha(int paintAlpha, int alpha) {
+        int scale = alpha + (alpha >>> 7); // convert to 0..256
+        return paintAlpha * scale >>> 8;
+    }
+
+    /**
+     * Called from the drawable's draw() method after the canvas has been set to
+     * draw the shape at (0,0). Subclasses can override for special effects such
+     * as multiple layers, stroking, etc.
+     */
+    protected void onDraw(Shape shape, Canvas canvas, Paint paint) {
+        shape.draw(canvas, paint);
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        final Rect r = getBounds();
+        final ShapeState state = mShapeState;
+        final Paint paint = state.mPaint;
+
+        final int prevAlpha = paint.getAlpha();
+        paint.setAlpha(modulateAlpha(prevAlpha, state.mAlpha));
+
+        // only draw shape if it may affect output
+        if (paint.getAlpha() != 0 || paint.getXfermode() != null || paint.hasShadowLayer()) {
+            final boolean clearColorFilter;
+            if (mTintFilter != null && paint.getColorFilter() == null) {
+                paint.setColorFilter(mTintFilter);
+                clearColorFilter = true;
+            } else {
+                clearColorFilter = false;
+            }
+
+            if (state.mShape != null) {
+                // need the save both for the translate, and for the (unknown)
+                // Shape
+                final int count = canvas.save();
+                canvas.translate(r.left, r.top);
+                onDraw(state.mShape, canvas, paint);
+                canvas.restoreToCount(count);
+            } else {
+                canvas.drawRect(r, paint);
+            }
+
+            if (clearColorFilter) {
+                paint.setColorFilter(null);
+            }
+        }
+
+        // restore
+        paint.setAlpha(prevAlpha);
+    }
+
+    @Override
+    public @Config int getChangingConfigurations() {
+        return super.getChangingConfigurations() | mShapeState.getChangingConfigurations();
+    }
+
+    /**
+     * Set the alpha level for this drawable [0..255]. Note that this drawable
+     * also has a color in its paint, which has an alpha as well. These two
+     * values are automatically combined during drawing. Thus if the color's
+     * alpha is 75% (i.e. 192) and the drawable's alpha is 50% (i.e. 128), then
+     * the combined alpha that will be used during drawing will be 37.5% (i.e.
+     * 96).
+     */
+    @Override
+    public void setAlpha(int alpha) {
+        mShapeState.mAlpha = alpha;
+        invalidateSelf();
+    }
+
+    @Override
+    public int getAlpha() {
+        return mShapeState.mAlpha;
+    }
+
+    @Override
+    public void setTintList(ColorStateList tint) {
+        mShapeState.mTint = tint;
+        mTintFilter = updateTintFilter(mTintFilter, tint, mShapeState.mTintMode);
+        invalidateSelf();
+    }
+
+    @Override
+    public void setTintMode(PorterDuff.Mode tintMode) {
+        mShapeState.mTintMode = tintMode;
+        mTintFilter = updateTintFilter(mTintFilter, mShapeState.mTint, tintMode);
+        invalidateSelf();
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        mShapeState.mPaint.setColorFilter(colorFilter);
+        invalidateSelf();
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    @TestApi
+    public void setXfermode(@Nullable Xfermode mode) {
+        mShapeState.mPaint.setXfermode(mode);
+        invalidateSelf();
+    }
+
+    @Override
+    public int getOpacity() {
+        if (mShapeState.mShape == null) {
+            final Paint p = mShapeState.mPaint;
+            if (p.getXfermode() == null) {
+                final int alpha = p.getAlpha();
+                if (alpha == 0) {
+                    return PixelFormat.TRANSPARENT;
+                }
+                if (alpha == 255) {
+                    return PixelFormat.OPAQUE;
+                }
+            }
+        }
+        // not sure, so be safe
+        return PixelFormat.TRANSLUCENT;
+    }
+
+    @Override
+    public void setDither(boolean dither) {
+        mShapeState.mPaint.setDither(dither);
+        invalidateSelf();
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        super.onBoundsChange(bounds);
+        updateShape();
+    }
+
+    @Override
+    protected boolean onStateChange(int[] stateSet) {
+        final ShapeState state = mShapeState;
+        if (state.mTint != null && state.mTintMode != null) {
+            mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isStateful() {
+        final ShapeState s = mShapeState;
+        return super.isStateful() || (s.mTint != null && s.mTint.isStateful());
+    }
+
+    /** @hide */
+    @Override
+    public boolean hasFocusStateSpecified() {
+        return mShapeState.mTint != null && mShapeState.mTint.hasFocusStateSpecified();
+    }
+
+    /**
+     * Subclasses override this to parse custom subelements. If you handle it,
+     * return true, else return <em>super.inflateTag(...)</em>.
+     */
+    protected boolean inflateTag(String name, Resources r, XmlPullParser parser,
+            AttributeSet attrs) {
+
+        if ("padding".equals(name)) {
+            TypedArray a = r.obtainAttributes(attrs,
+                    com.android.internal.R.styleable.ShapeDrawablePadding);
+            setPadding(
+                    a.getDimensionPixelOffset(
+                            com.android.internal.R.styleable.ShapeDrawablePadding_left, 0),
+                    a.getDimensionPixelOffset(
+                            com.android.internal.R.styleable.ShapeDrawablePadding_top, 0),
+                    a.getDimensionPixelOffset(
+                            com.android.internal.R.styleable.ShapeDrawablePadding_right, 0),
+                    a.getDimensionPixelOffset(
+                            com.android.internal.R.styleable.ShapeDrawablePadding_bottom, 0));
+            a.recycle();
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+            throws XmlPullParserException, IOException {
+        super.inflate(r, parser, attrs, theme);
+
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ShapeDrawable);
+        updateStateFromTypedArray(a);
+        a.recycle();
+
+        int type;
+        final int outerDepth = parser.getDepth();
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            final String name = parser.getName();
+            // call our subclass
+            if (!inflateTag(name, r, parser, attrs)) {
+                android.util.Log.w("drawable", "Unknown element: " + name +
+                        " for ShapeDrawable " + this);
+            }
+        }
+
+        // Update local properties.
+        updateLocalState();
+    }
+
+    @Override
+    public void applyTheme(Theme t) {
+        super.applyTheme(t);
+
+        final ShapeState state = mShapeState;
+        if (state == null) {
+            return;
+        }
+
+        if (state.mThemeAttrs != null) {
+            final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ShapeDrawable);
+            updateStateFromTypedArray(a);
+            a.recycle();
+        }
+
+        // Apply theme to contained color state list.
+        if (state.mTint != null && state.mTint.canApplyTheme()) {
+            state.mTint = state.mTint.obtainForTheme(t);
+        }
+
+        // Update local properties.
+        updateLocalState();
+    }
+
+    private void updateStateFromTypedArray(TypedArray a) {
+        final ShapeState state = mShapeState;
+        final Paint paint = state.mPaint;
+
+        // Account for any configuration changes.
+        state.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        state.mThemeAttrs = a.extractThemeAttrs();
+
+        int color = paint.getColor();
+        color = a.getColor(R.styleable.ShapeDrawable_color, color);
+        paint.setColor(color);
+
+        boolean dither = paint.isDither();
+        dither = a.getBoolean(R.styleable.ShapeDrawable_dither, dither);
+        paint.setDither(dither);
+
+        state.mIntrinsicWidth = (int) a.getDimension(
+                R.styleable.ShapeDrawable_width, state.mIntrinsicWidth);
+        state.mIntrinsicHeight = (int) a.getDimension(
+                R.styleable.ShapeDrawable_height, state.mIntrinsicHeight);
+
+        final int tintMode = a.getInt(R.styleable.ShapeDrawable_tintMode, -1);
+        if (tintMode != -1) {
+            state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
+        }
+
+        final ColorStateList tint = a.getColorStateList(R.styleable.ShapeDrawable_tint);
+        if (tint != null) {
+            state.mTint = tint;
+        }
+    }
+
+    private void updateShape() {
+        if (mShapeState.mShape != null) {
+            final Rect r = getBounds();
+            final int w = r.width();
+            final int h = r.height();
+
+            mShapeState.mShape.resize(w, h);
+            if (mShapeState.mShaderFactory != null) {
+                mShapeState.mPaint.setShader(mShapeState.mShaderFactory.resize(w, h));
+            }
+        }
+        invalidateSelf();
+    }
+
+    @Override
+    public void getOutline(Outline outline) {
+        if (mShapeState.mShape != null) {
+            mShapeState.mShape.getOutline(outline);
+            outline.setAlpha(getAlpha() / 255.0f);
+        }
+    }
+
+    @Override
+    public ConstantState getConstantState() {
+        mShapeState.mChangingConfigurations = getChangingConfigurations();
+        return mShapeState;
+    }
+
+    @Override
+    public Drawable mutate() {
+        if (!mMutated && super.mutate() == this) {
+            mShapeState = new ShapeState(mShapeState);
+            updateLocalState();
+            mMutated = true;
+        }
+        return this;
+    }
+
+    /**
+     * @hide
+     */
+    public void clearMutated() {
+        super.clearMutated();
+        mMutated = false;
+    }
+
+    /**
+     * Defines the intrinsic properties of this ShapeDrawable's Shape.
+     */
+    static final class ShapeState extends ConstantState {
+        final @NonNull Paint mPaint;
+
+        @Config int mChangingConfigurations;
+        int[] mThemeAttrs;
+        Shape mShape;
+        ColorStateList mTint;
+        Mode mTintMode = DEFAULT_TINT_MODE;
+        Rect mPadding;
+        int mIntrinsicWidth;
+        int mIntrinsicHeight;
+        int mAlpha = 255;
+        ShaderFactory mShaderFactory;
+
+        /**
+         * Constructs a new ShapeState.
+         */
+        ShapeState() {
+            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        }
+
+        /**
+         * Constructs a new ShapeState that contains a deep copy of the
+         * specified ShapeState.
+         *
+         * @param orig the state to create a deep copy of
+         */
+        ShapeState(@NonNull ShapeState orig) {
+            mChangingConfigurations = orig.mChangingConfigurations;
+            mPaint = new Paint(orig.mPaint);
+            mThemeAttrs = orig.mThemeAttrs;
+            if (orig.mShape != null) {
+                try {
+                    mShape = orig.mShape.clone();
+                } catch (CloneNotSupportedException e) {
+                    // Well, at least we tried.
+                    mShape = orig.mShape;
+                }
+            }
+            mTint = orig.mTint;
+            mTintMode = orig.mTintMode;
+            if (orig.mPadding != null) {
+                mPadding = new Rect(orig.mPadding);
+            }
+            mIntrinsicWidth = orig.mIntrinsicWidth;
+            mIntrinsicHeight = orig.mIntrinsicHeight;
+            mAlpha = orig.mAlpha;
+
+            // We don't have any way to clone a shader factory, so hopefully
+            // this class doesn't contain any local state.
+            mShaderFactory = orig.mShaderFactory;
+        }
+
+        @Override
+        public boolean canApplyTheme() {
+            return mThemeAttrs != null
+                    || (mTint != null && mTint.canApplyTheme());
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return new ShapeDrawable(this, null);
+        }
+
+        @Override
+        public Drawable newDrawable(Resources res) {
+            return new ShapeDrawable(this, res);
+        }
+
+        @Override
+        public @Config int getChangingConfigurations() {
+            return mChangingConfigurations
+                    | (mTint != null ? mTint.getChangingConfigurations() : 0);
+        }
+    }
+
+    /**
+     * The one constructor to rule them all. This is called by all public
+     * constructors to set the state and initialize local properties.
+     */
+    private ShapeDrawable(ShapeState state, Resources res) {
+        mShapeState = state;
+
+        updateLocalState();
+    }
+
+    /**
+     * Initializes local dynamic properties from state. This should be called
+     * after significant state changes, e.g. from the One True Constructor and
+     * after inflating or applying a theme.
+     */
+    private void updateLocalState() {
+        mTintFilter = updateTintFilter(mTintFilter, mShapeState.mTint, mShapeState.mTintMode);
+    }
+
+    /**
+     * Base class defines a factory object that is called each time the drawable
+     * is resized (has a new width or height). Its resize() method returns a
+     * corresponding shader, or null. Implement this class if you'd like your
+     * ShapeDrawable to use a special {@link android.graphics.Shader}, such as a
+     * {@link android.graphics.LinearGradient}.
+     */
+    public static abstract class ShaderFactory {
+        /**
+         * Returns the Shader to be drawn when a Drawable is drawn. The
+         * dimensions of the Drawable are passed because they may be needed to
+         * adjust how the Shader is configured for drawing. This is called by
+         * ShapeDrawable.setShape().
+         *
+         * @param width the width of the Drawable being drawn
+         * @param height the heigh of the Drawable being drawn
+         * @return the Shader to be drawn
+         */
+        public abstract Shader resize(int width, int height);
+    }
+}
diff --git a/android/graphics/drawable/StateListDrawable.java b/android/graphics/drawable/StateListDrawable.java
new file mode 100644
index 0000000..c98f160
--- /dev/null
+++ b/android/graphics/drawable/StateListDrawable.java
@@ -0,0 +1,411 @@
+/*
+ * 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 com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.Resources.Theme;
+import android.util.AttributeSet;
+import android.util.StateSet;
+
+/**
+ * Lets you assign a number of graphic images to a single Drawable and swap out the visible item by a string
+ * ID value.
+ * <p/>
+ * <p>It can be defined in an XML file with the <code>&lt;selector></code> element.
+ * Each state Drawable is defined in a nested <code>&lt;item></code> element. For more
+ * information, see the guide to <a
+ * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
+ *
+ * @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";
+
+    private static final boolean DEBUG = false;
+
+    private StateListState mStateListState;
+    private boolean mMutated;
+
+    public StateListDrawable() {
+        this(null, null);
+    }
+
+    /**
+     * 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.
+     */
+    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());
+        }
+    }
+
+    @Override
+    public boolean isStateful() {
+        return true;
+    }
+
+    /** @hide */
+    @Override
+    public boolean hasFocusStateSpecified() {
+        return mStateListState.hasFocusStateSpecified();
+    }
+
+    @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;
+    }
+
+    @Override
+    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+            throws XmlPullParserException, IOException {
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.StateListDrawable);
+        super.inflateWithAttributes(r, parser, a, R.styleable.StateListDrawable_visible);
+        updateStateFromTypedArray(a);
+        updateDensity(r);
+        a.recycle();
+
+        inflateChildElements(r, parser, attrs, theme);
+
+        onStateChange(getState());
+    }
+
+    /**
+     * Updates the constant state from the values in the typed array.
+     */
+    private void updateStateFromTypedArray(TypedArray a) {
+        final StateListState state = mStateListState;
+
+        // Account for any configuration changes.
+        state.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        state.mThemeAttrs = a.extractThemeAttrs();
+
+        state.mVariablePadding = a.getBoolean(
+                R.styleable.StateListDrawable_variablePadding, state.mVariablePadding);
+        state.mConstantSize = a.getBoolean(
+                R.styleable.StateListDrawable_constantSize, state.mConstantSize);
+        state.mEnterFadeDuration = a.getInt(
+                R.styleable.StateListDrawable_enterFadeDuration, state.mEnterFadeDuration);
+        state.mExitFadeDuration = a.getInt(
+                R.styleable.StateListDrawable_exitFadeDuration, state.mExitFadeDuration);
+        state.mDither = a.getBoolean(
+                R.styleable.StateListDrawable_dither, state.mDither);
+        state.mAutoMirrored = a.getBoolean(
+                R.styleable.StateListDrawable_autoMirrored, state.mAutoMirrored);
+    }
+
+    /**
+     * 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 = obtainAttributes(r, theme, 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()
+                                    + ": <item> 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 number of states contained in this drawable.
+     *
+     * @return The number of states contained in this drawable.
+     * @hide pending API council
+     * @see #getStateSet(int)
+     * @see #getStateDrawable(int)
+     */
+    public int getStateCount() {
+        return mStateListState.getChildCount();
+    }
+
+    /**
+     * Gets the state set at an index.
+     *
+     * @param index The index of the state set.
+     * @return The state set at the index.
+     * @hide pending API council
+     * @see #getStateCount()
+     * @see #getStateDrawable(int)
+     */
+    public int[] getStateSet(int index) {
+        return mStateListState.mStateSets[index];
+    }
+
+    /**
+     * Gets the drawable at an index.
+     *
+     * @param index The index of the drawable.
+     * @return The drawable at the index.
+     * @hide pending API council
+     * @see #getStateCount()
+     * @see #getStateSet(int)
+     */
+    public Drawable getStateDrawable(int index) {
+        return mStateListState.getChild(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
+     * @hide pending API council
+     * @see #getStateDrawable(int)
+     * @see #getStateSet(int)
+     */
+    public int getStateDrawableIndex(int[] stateSet) {
+        return mStateListState.indexOfStateSet(stateSet);
+    }
+
+    @Override
+    public Drawable mutate() {
+        if (!mMutated && super.mutate() == this) {
+            mStateListState.mutate();
+            mMutated = true;
+        }
+        return this;
+    }
+
+    @Override
+    StateListState cloneConstantState() {
+        return new StateListState(mStateListState, this, null);
+    }
+
+    /**
+     * @hide
+     */
+    public void clearMutated() {
+        super.clearMutated();
+        mMutated = false;
+    }
+
+    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;
+        }
+
+        boolean hasFocusStateSpecified() {
+            return StateSet.containsAttribute(mStateSets, R.attr.state_focused);
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return new StateListDrawable(this, null);
+        }
+
+        @Override
+        public Drawable newDrawable(Resources res) {
+            return new StateListDrawable(this, res);
+        }
+
+        @Override
+        public boolean canApplyTheme() {
+            return mThemeAttrs != null || super.canApplyTheme();
+        }
+
+        @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;
+        }
+    }
+
+    @Override
+    public void applyTheme(Theme theme) {
+        super.applyTheme(theme);
+
+        onStateChange(getState());
+    }
+
+    protected void setConstantState(@NonNull 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(@Nullable StateListState state) {
+        if (state != null) {
+            setConstantState(state);
+        }
+    }
+}
+
diff --git a/android/graphics/drawable/TransitionDrawable.java b/android/graphics/drawable/TransitionDrawable.java
new file mode 100644
index 0000000..3dfd680
--- /dev/null
+++ b/android/graphics/drawable/TransitionDrawable.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2008 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.content.pm.ActivityInfo.Config;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.os.SystemClock;
+
+/**
+ * An extension of LayerDrawables that is intended to cross-fade between
+ * the first and second layer. To start the transition, call {@link #startTransition(int)}. To
+ * display just the first layer, call {@link #resetTransition()}.
+ * <p>
+ * It can be defined in an XML file with the <code>&lt;transition></code> element.
+ * Each Drawable in the transition is defined in a nested <code>&lt;item></code>. For more
+ * information, see the guide to <a
+ * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
+ *
+ * @attr ref android.R.styleable#LayerDrawableItem_left
+ * @attr ref android.R.styleable#LayerDrawableItem_top
+ * @attr ref android.R.styleable#LayerDrawableItem_right
+ * @attr ref android.R.styleable#LayerDrawableItem_bottom
+ * @attr ref android.R.styleable#LayerDrawableItem_drawable
+ * @attr ref android.R.styleable#LayerDrawableItem_id
+ *
+ */
+public class TransitionDrawable extends LayerDrawable implements Drawable.Callback {
+
+    /**
+     * A transition is about to start.
+     */
+    private static final int TRANSITION_STARTING = 0;
+
+    /**
+     * The transition has started and the animation is in progress
+     */
+    private static final int TRANSITION_RUNNING = 1;
+
+    /**
+     * No transition will be applied
+     */
+    private static final int TRANSITION_NONE = 2;
+
+    /**
+     * The current state of the transition. One of {@link #TRANSITION_STARTING},
+     * {@link #TRANSITION_RUNNING} and {@link #TRANSITION_NONE}
+     */
+    private int mTransitionState = TRANSITION_NONE;
+
+    private boolean mReverse;
+    private long mStartTimeMillis;
+    private int mFrom;
+    private int mTo;
+    private int mDuration;
+    private int mOriginalDuration;
+    private int mAlpha = 0;
+    private boolean mCrossFade;
+
+    /**
+     * Create a new transition drawable with the specified list of layers. At least
+     * 2 layers are required for this drawable to work properly.
+     */
+    public TransitionDrawable(Drawable[] layers) {
+        this(new TransitionState(null, null, null), layers);
+    }
+
+    /**
+     * Create a new transition drawable with no layer. To work correctly, at least 2
+     * layers must be added to this drawable.
+     *
+     * @see #TransitionDrawable(Drawable[])
+     */
+    TransitionDrawable() {
+        this(new TransitionState(null, null, null), (Resources) null);
+    }
+
+    private TransitionDrawable(TransitionState state, Resources res) {
+        super(state, res);
+    }
+
+    private TransitionDrawable(TransitionState state, Drawable[] layers) {
+        super(layers, state);
+    }
+
+    @Override
+    LayerState createConstantState(LayerState state, Resources res) {
+        return new TransitionState((TransitionState) state, this, res);
+    }
+
+    /**
+     * Begin the second layer on top of the first layer.
+     *
+     * @param durationMillis The length of the transition in milliseconds
+     */
+    public void startTransition(int durationMillis) {
+        mFrom = 0;
+        mTo = 255;
+        mAlpha = 0;
+        mDuration = mOriginalDuration = durationMillis;
+        mReverse = false;
+        mTransitionState = TRANSITION_STARTING;
+        invalidateSelf();
+    }
+
+    /**
+     * Show the second layer on top of the first layer immediately
+     *
+     * @hide
+     */
+    public void showSecondLayer() {
+        mAlpha = 255;
+        mReverse = false;
+        mTransitionState = TRANSITION_NONE;
+        invalidateSelf();
+    }
+
+    /**
+     * Show only the first layer.
+     */
+    public void resetTransition() {
+        mAlpha = 0;
+        mTransitionState = TRANSITION_NONE;
+        invalidateSelf();
+    }
+
+    /**
+     * Reverses the transition, picking up where the transition currently is.
+     * If the transition is not currently running, this will start the transition
+     * with the specified duration. If the transition is already running, the last
+     * known duration will be used.
+     *
+     * @param duration The duration to use if no transition is running.
+     */
+    public void reverseTransition(int duration) {
+        final long time = SystemClock.uptimeMillis();
+        // Animation is over
+        if (time - mStartTimeMillis > mDuration) {
+            if (mTo == 0) {
+                mFrom = 0;
+                mTo = 255;
+                mAlpha = 0;
+                mReverse = false;
+            } else {
+                mFrom = 255;
+                mTo = 0;
+                mAlpha = 255;
+                mReverse = true;
+            }
+            mDuration = mOriginalDuration = duration;
+            mTransitionState = TRANSITION_STARTING;
+            invalidateSelf();
+            return;
+        }
+
+        mReverse = !mReverse;
+        mFrom = mAlpha;
+        mTo = mReverse ? 0 : 255;
+        mDuration = (int) (mReverse ? time - mStartTimeMillis :
+                mOriginalDuration - (time - mStartTimeMillis));
+        mTransitionState = TRANSITION_STARTING;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        boolean done = true;
+
+        switch (mTransitionState) {
+            case TRANSITION_STARTING:
+                mStartTimeMillis = SystemClock.uptimeMillis();
+                done = false;
+                mTransitionState = TRANSITION_RUNNING;
+                break;
+
+            case TRANSITION_RUNNING:
+                if (mStartTimeMillis >= 0) {
+                    float normalized = (float)
+                            (SystemClock.uptimeMillis() - mStartTimeMillis) / mDuration;
+                    done = normalized >= 1.0f;
+                    normalized = Math.min(normalized, 1.0f);
+                    mAlpha = (int) (mFrom  + (mTo - mFrom) * normalized);
+                }
+                break;
+        }
+
+        final int alpha = mAlpha;
+        final boolean crossFade = mCrossFade;
+        final ChildDrawable[] array = mLayerState.mChildren;
+
+        if (done) {
+            // the setAlpha() calls below trigger invalidation and redraw. If we're done, just draw
+            // the appropriate drawable[s] and return
+            if (!crossFade || alpha == 0) {
+                array[0].mDrawable.draw(canvas);
+            }
+            if (alpha == 0xFF) {
+                array[1].mDrawable.draw(canvas);
+            }
+            return;
+        }
+
+        Drawable d;
+        d = array[0].mDrawable;
+        if (crossFade) {
+            d.setAlpha(255 - alpha);
+        }
+        d.draw(canvas);
+        if (crossFade) {
+            d.setAlpha(0xFF);
+        }
+
+        if (alpha > 0) {
+            d = array[1].mDrawable;
+            d.setAlpha(alpha);
+            d.draw(canvas);
+            d.setAlpha(0xFF);
+        }
+
+        if (!done) {
+            invalidateSelf();
+        }
+    }
+
+    /**
+     * Enables or disables the cross fade of the drawables. When cross fade
+     * is disabled, the first drawable is always drawn opaque. With cross
+     * fade enabled, the first drawable is drawn with the opposite alpha of
+     * the second drawable. Cross fade is disabled by default.
+     *
+     * @param enabled True to enable cross fading, false otherwise.
+     */
+    public void setCrossFadeEnabled(boolean enabled) {
+        mCrossFade = enabled;
+    }
+
+    /**
+     * Indicates whether the cross fade is enabled for this transition.
+     *
+     * @return True if cross fading is enabled, false otherwise.
+     */
+    public boolean isCrossFadeEnabled() {
+        return mCrossFade;
+    }
+
+    static class TransitionState extends LayerState {
+        TransitionState(TransitionState orig, TransitionDrawable owner, Resources res) {
+            super(orig, owner, res);
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return new TransitionDrawable(this, (Resources) null);
+        }
+
+        @Override
+        public Drawable newDrawable(Resources res) {
+            return new TransitionDrawable(this, res);
+        }
+
+        @Override
+        public @Config int getChangingConfigurations() {
+            return mChangingConfigurations;
+        }
+    }
+}
diff --git a/android/graphics/drawable/VectorDrawable.java b/android/graphics/drawable/VectorDrawable.java
new file mode 100644
index 0000000..c3ef450
--- /dev/null
+++ b/android/graphics/drawable/VectorDrawable.java
@@ -0,0 +1,2291 @@
+/*
+ * Copyright (C) 2014 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.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.ActivityInfo.Config;
+import android.content.res.ColorStateList;
+import android.content.res.ComplexColor;
+import android.content.res.GradientColor;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Insets;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.util.ArrayMap;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.FloatProperty;
+import android.util.IntProperty;
+import android.util.LayoutDirection;
+import android.util.Log;
+import android.util.PathParser;
+import android.util.Property;
+import android.util.Xml;
+
+import com.android.internal.R;
+import com.android.internal.util.VirtualRefBasePtr;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Stack;
+
+import dalvik.annotation.optimization.FastNative;
+import dalvik.system.VMRuntime;
+
+/**
+ * This lets you create a drawable based on an XML vector graphic.
+ * <p/>
+ * <strong>Note:</strong> To optimize for the re-drawing performance, one bitmap cache is created
+ * for each VectorDrawable. Therefore, referring to the same VectorDrawable means sharing the same
+ * bitmap cache. If these references don't agree upon on the same size, the bitmap will be recreated
+ * and redrawn every time size is changed. In other words, if a VectorDrawable is used for
+ * different sizes, it is more efficient to create multiple VectorDrawables, one for each size.
+ * <p/>
+ * VectorDrawable can be defined in an XML file with the <code>&lt;vector></code> element.
+ * <p/>
+ * The vector drawable has the following elements:
+ * <p/>
+ * <dt><code>&lt;vector></code></dt>
+ * <dl>
+ * <dd>Used to define a vector drawable
+ * <dl>
+ * <dt><code>android:name</code></dt>
+ * <dd>Defines the name of this vector drawable.</dd>
+ * <dt><code>android:width</code></dt>
+ * <dd>Used to define the intrinsic width of the drawable.
+ * This support all the dimension units, normally specified with dp.</dd>
+ * <dt><code>android:height</code></dt>
+ * <dd>Used to define the intrinsic height the drawable.
+ * This support all the dimension units, normally specified with dp.</dd>
+ * <dt><code>android:viewportWidth</code></dt>
+ * <dd>Used to define the width of the viewport space. Viewport is basically
+ * the virtual canvas where the paths are drawn on.</dd>
+ * <dt><code>android:viewportHeight</code></dt>
+ * <dd>Used to define the height of the viewport space. Viewport is basically
+ * the virtual canvas where the paths are drawn on.</dd>
+ * <dt><code>android:tint</code></dt>
+ * <dd>The color to apply to the drawable as a tint. By default, no tint is applied.</dd>
+ * <dt><code>android:tintMode</code></dt>
+ * <dd>The Porter-Duff blending mode for the tint color. Default is src_in.</dd>
+ * <dt><code>android:autoMirrored</code></dt>
+ * <dd>Indicates if the drawable needs to be mirrored when its layout direction is
+ * RTL (right-to-left). Default is false.</dd>
+ * <dt><code>android:alpha</code></dt>
+ * <dd>The opacity of this drawable. Default is 1.0.</dd>
+ * </dl></dd>
+ * </dl>
+ *
+ * <dl>
+ * <dt><code>&lt;group></code></dt>
+ * <dd>Defines a group of paths or subgroups, plus transformation information.
+ * The transformations are defined in the same coordinates as the viewport.
+ * And the transformations are applied in the order of scale, rotate then translate.
+ * <dl>
+ * <dt><code>android:name</code></dt>
+ * <dd>Defines the name of the group.</dd>
+ * <dt><code>android:rotation</code></dt>
+ * <dd>The degrees of rotation of the group. Default is 0.</dd>
+ * <dt><code>android:pivotX</code></dt>
+ * <dd>The X coordinate of the pivot for the scale and rotation of the group.
+ * This is defined in the viewport space. Default is 0.</dd>
+ * <dt><code>android:pivotY</code></dt>
+ * <dd>The Y coordinate of the pivot for the scale and rotation of the group.
+ * This is defined in the viewport space. Default is 0.</dd>
+ * <dt><code>android:scaleX</code></dt>
+ * <dd>The amount of scale on the X Coordinate. Default is 1.</dd>
+ * <dt><code>android:scaleY</code></dt>
+ * <dd>The amount of scale on the Y coordinate. Default is 1.</dd>
+ * <dt><code>android:translateX</code></dt>
+ * <dd>The amount of translation on the X coordinate.
+ * This is defined in the viewport space. Default is 0.</dd>
+ * <dt><code>android:translateY</code></dt>
+ * <dd>The amount of translation on the Y coordinate.
+ * This is defined in the viewport space. Default is 0.</dd>
+ * </dl></dd>
+ * </dl>
+ *
+ * <dl>
+ * <dt><code>&lt;path></code></dt>
+ * <dd>Defines paths to be drawn.
+ * <dl>
+ * <dt><code>android:name</code></dt>
+ * <dd>Defines the name of the path.</dd>
+ * <dt><code>android:pathData</code></dt>
+ * <dd>Defines path data using exactly same format as "d" attribute
+ * in the SVG's path data. This is defined in the viewport space.</dd>
+ * <dt><code>android:fillColor</code></dt>
+ * <dd>Specifies the color used to fill the path. May be a color or, for SDK 24+, a color state list
+ * or a gradient color (See {@link android.R.styleable#GradientColor}
+ * and {@link android.R.styleable#GradientColorItem}).
+ * If this property is animated, any value set by the animation will override the original value.
+ * No path fill is drawn if this property is not specified.</dd>
+ * <dt><code>android:strokeColor</code></dt>
+ * <dd>Specifies the color used to draw the path outline. May be a color or, for SDK 24+, a color
+ * state list or a gradient color (See {@link android.R.styleable#GradientColor}
+ * and {@link android.R.styleable#GradientColorItem}).
+ * If this property is animated, any value set by the animation will override the original value.
+ * No path outline is drawn if this property is not specified.</dd>
+ * <dt><code>android:strokeWidth</code></dt>
+ * <dd>The width a path stroke. Default is 0.</dd>
+ * <dt><code>android:strokeAlpha</code></dt>
+ * <dd>The opacity of a path stroke. Default is 1.</dd>
+ * <dt><code>android:fillAlpha</code></dt>
+ * <dd>The opacity to fill the path with. Default is 1.</dd>
+ * <dt><code>android:trimPathStart</code></dt>
+ * <dd>The fraction of the path to trim from the start, in the range from 0 to 1. Default is 0.</dd>
+ * <dt><code>android:trimPathEnd</code></dt>
+ * <dd>The fraction of the path to trim from the end, in the range from 0 to 1. Default is 1.</dd>
+ * <dt><code>android:trimPathOffset</code></dt>
+ * <dd>Shift trim region (allows showed region to include the start and end), in the range
+ * from 0 to 1. Default is 0.</dd>
+ * <dt><code>android:strokeLineCap</code></dt>
+ * <dd>Sets the linecap for a stroked path: butt, round, square. Default is butt.</dd>
+ * <dt><code>android:strokeLineJoin</code></dt>
+ * <dd>Sets the lineJoin for a stroked path: miter,round,bevel. Default is miter.</dd>
+ * <dt><code>android:strokeMiterLimit</code></dt>
+ * <dd>Sets the Miter limit for a stroked path. Default is 4.</dd>
+ * <dt><code>android:fillType</code></dt>
+ * <dd>For SDK 24+, sets the fillType for a path. The types can be either "evenOdd" or "nonZero". They behave the
+ * same as SVG's "fill-rule" properties. Default is nonZero. For more details, see
+ * <a href="https://www.w3.org/TR/SVG/painting.html#FillRuleProperty">FillRuleProperty</a></dd>
+ * </dl></dd>
+ *
+ * </dl>
+ *
+ * <dl>
+ * <dt><code>&lt;clip-path></code></dt>
+ * <dd>Defines path to be the current clip. Note that the clip path only apply to
+ * the current group and its children.
+ * <dl>
+ * <dt><code>android:name</code></dt>
+ * <dd>Defines the name of the clip path.</dd>
+ * <dd>Animatable : No.</dd>
+ * <dt><code>android:pathData</code></dt>
+ * <dd>Defines clip path using the same format as "d" attribute
+ * in the SVG's path data.</dd>
+ * <dd>Animatable : Yes.</dd>
+ * </dl></dd>
+ * </dl>
+ * <li>Here is a simple VectorDrawable in this vectordrawable.xml file.
+ * <pre>
+ * &lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
+ *     android:height=&quot;64dp&quot;
+ *     android:width=&quot;64dp&quot;
+ *     android:viewportHeight=&quot;600&quot;
+ *     android:viewportWidth=&quot;600&quot; &gt;
+ *     &lt;group
+ *         android:name=&quot;rotationGroup&quot;
+ *         android:pivotX=&quot;300.0&quot;
+ *         android:pivotY=&quot;300.0&quot;
+ *         android:rotation=&quot;45.0&quot; &gt;
+ *         &lt;path
+ *             android:name=&quot;v&quot;
+ *             android:fillColor=&quot;#000000&quot;
+ *             android:pathData=&quot;M300,70 l 0,-70 70,70 0,0 -70,70z&quot; /&gt;
+ *     &lt;/group&gt;
+ * &lt;/vector&gt;
+ * </pre>
+ * </li>
+ * <li>And here is an example of linear gradient color, which is supported in SDK 24+.
+ * See more details in {@link android.R.styleable#GradientColor} and
+ * {@link android.R.styleable#GradientColorItem}.
+ * <pre>
+ * &lt;gradient xmlns:android="http://schemas.android.com/apk/res/android"
+ *     android:angle="90"
+ *     android:startColor="?android:attr/colorPrimary"
+ *     android:endColor="?android:attr/colorControlActivated"
+ *     android:centerColor="#f00"
+ *     android:startX="0"
+ *     android:startY="0"
+ *     android:endX="100"
+ *     android:endY="100"
+ *     android:type="linear"&gt;
+ * &lt;/gradient&gt;
+ * </pre>
+ * </li>
+ *
+ */
+
+public class VectorDrawable extends Drawable {
+    private static final String LOGTAG = VectorDrawable.class.getSimpleName();
+
+    private static final String SHAPE_CLIP_PATH = "clip-path";
+    private static final String SHAPE_GROUP = "group";
+    private static final String SHAPE_PATH = "path";
+    private static final String SHAPE_VECTOR = "vector";
+
+    private VectorDrawableState mVectorState;
+
+    private PorterDuffColorFilter mTintFilter;
+    private ColorFilter mColorFilter;
+
+    private boolean mMutated;
+
+    /** The density of the display on which this drawable will be rendered. */
+    private int mTargetDensity;
+
+    // Given the virtual display setup, the dpi can be different than the inflation's dpi.
+    // Therefore, we need to scale the values we got from the getDimension*().
+    private int mDpiScaledWidth = 0;
+    private int mDpiScaledHeight = 0;
+    private Insets mDpiScaledInsets = Insets.NONE;
+
+    /** Whether DPI-scaled width, height, and insets need to be updated. */
+    private boolean mDpiScaledDirty = true;
+
+    // Temp variable, only for saving "new" operation at the draw() time.
+    private final Rect mTmpBounds = new Rect();
+
+    public VectorDrawable() {
+        this(new VectorDrawableState(null), null);
+    }
+
+    /**
+     * The one constructor to rule them all. This is called by all public
+     * constructors to set the state and initialize local properties.
+     */
+    private VectorDrawable(@NonNull VectorDrawableState state, @Nullable Resources res) {
+        mVectorState = state;
+        updateLocalState(res);
+    }
+
+    /**
+     * Initializes local dynamic properties from state. This should be called
+     * after significant state changes, e.g. from the One True Constructor and
+     * after inflating or applying a theme.
+     *
+     * @param res resources of the context in which the drawable will be
+     *            displayed, or {@code null} to use the constant state defaults
+     */
+    private void updateLocalState(Resources res) {
+        final int density = Drawable.resolveDensity(res, mVectorState.mDensity);
+        if (mTargetDensity != density) {
+            mTargetDensity = density;
+            mDpiScaledDirty = true;
+        }
+
+        mTintFilter = updateTintFilter(mTintFilter, mVectorState.mTint, mVectorState.mTintMode);
+    }
+
+    @Override
+    public Drawable mutate() {
+        if (!mMutated && super.mutate() == this) {
+            mVectorState = new VectorDrawableState(mVectorState);
+            mMutated = true;
+        }
+        return this;
+    }
+
+    /**
+     * @hide
+     */
+    public void clearMutated() {
+        super.clearMutated();
+        mMutated = false;
+    }
+
+    Object getTargetByName(String name) {
+        return mVectorState.mVGTargetsMap.get(name);
+    }
+
+    @Override
+    public ConstantState getConstantState() {
+        mVectorState.mChangingConfigurations = getChangingConfigurations();
+        return mVectorState;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        // We will offset the bounds for drawBitmap, so copyBounds() here instead
+        // of getBounds().
+        copyBounds(mTmpBounds);
+        if (mTmpBounds.width() <= 0 || mTmpBounds.height() <= 0) {
+            // Nothing to draw
+            return;
+        }
+
+        // Color filters always override tint filters.
+        final ColorFilter colorFilter = (mColorFilter == null ? mTintFilter : mColorFilter);
+        final long colorFilterNativeInstance = colorFilter == null ? 0 :
+                colorFilter.getNativeInstance();
+        boolean canReuseCache = mVectorState.canReuseCache();
+        int pixelCount = nDraw(mVectorState.getNativeRenderer(), canvas.getNativeCanvasWrapper(),
+                colorFilterNativeInstance, mTmpBounds, needMirroring(),
+                canReuseCache);
+        if (pixelCount == 0) {
+            // Invalid canvas matrix or drawable bounds. This would not affect existing bitmap
+            // cache, if any.
+            return;
+        }
+
+        int deltaInBytes;
+        // Track different bitmap cache based whether the canvas is hw accelerated. By doing so,
+        // we don't over count bitmap cache allocation: if the input canvas is always of the same
+        // type, only one bitmap cache is allocated.
+        if (canvas.isHardwareAccelerated()) {
+            // Each pixel takes 4 bytes.
+            deltaInBytes = (pixelCount - mVectorState.mLastHWCachePixelCount) * 4;
+            mVectorState.mLastHWCachePixelCount = pixelCount;
+        } else {
+            // Each pixel takes 4 bytes.
+            deltaInBytes = (pixelCount - mVectorState.mLastSWCachePixelCount) * 4;
+            mVectorState.mLastSWCachePixelCount = pixelCount;
+        }
+        if (deltaInBytes > 0) {
+            VMRuntime.getRuntime().registerNativeAllocation(deltaInBytes);
+        } else if (deltaInBytes < 0) {
+            VMRuntime.getRuntime().registerNativeFree(-deltaInBytes);
+        }
+    }
+
+
+    @Override
+    public int getAlpha() {
+        return (int) (mVectorState.getAlpha() * 255);
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        if (mVectorState.setAlpha(alpha / 255f)) {
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        mColorFilter = colorFilter;
+        invalidateSelf();
+    }
+
+    @Override
+    public ColorFilter getColorFilter() {
+        return mColorFilter;
+    }
+
+    @Override
+    public void setTintList(ColorStateList tint) {
+        final VectorDrawableState state = mVectorState;
+        if (state.mTint != tint) {
+            state.mTint = tint;
+            mTintFilter = updateTintFilter(mTintFilter, tint, state.mTintMode);
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public void setTintMode(Mode tintMode) {
+        final VectorDrawableState state = mVectorState;
+        if (state.mTintMode != tintMode) {
+            state.mTintMode = tintMode;
+            mTintFilter = updateTintFilter(mTintFilter, state.mTint, tintMode);
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public boolean isStateful() {
+        return super.isStateful() || (mVectorState != null && mVectorState.isStateful());
+    }
+
+    /** @hide */
+    @Override
+    public boolean hasFocusStateSpecified() {
+        return mVectorState != null && mVectorState.hasFocusStateSpecified();
+    }
+
+    @Override
+    protected boolean onStateChange(int[] stateSet) {
+        boolean changed = false;
+
+        // When the VD is stateful, we need to mutate the drawable such that we don't share the
+        // cache bitmap with others. Such that the state change only affect this new cached bitmap.
+        if (isStateful()) {
+            mutate();
+        }
+        final VectorDrawableState state = mVectorState;
+        if (state.onStateChange(stateSet)) {
+            changed = true;
+            state.mCacheDirty = true;
+        }
+        if (state.mTint != null && state.mTintMode != null) {
+            mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
+            changed = true;
+        }
+
+        return changed;
+    }
+
+    @Override
+    public int getOpacity() {
+        // We can't tell whether the drawable is fully opaque unless we examine all the pixels,
+        // but we could tell it is transparent if the root alpha is 0.
+        return getAlpha() == 0 ? PixelFormat.TRANSPARENT : PixelFormat.TRANSLUCENT;
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        if (mDpiScaledDirty) {
+            computeVectorSize();
+        }
+        return mDpiScaledWidth;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        if (mDpiScaledDirty) {
+            computeVectorSize();
+        }
+        return mDpiScaledHeight;
+    }
+
+    /** @hide */
+    @Override
+    public Insets getOpticalInsets() {
+        if (mDpiScaledDirty) {
+            computeVectorSize();
+        }
+        return mDpiScaledInsets;
+    }
+
+    /*
+     * Update local dimensions to adjust for a target density that may differ
+     * from the source density against which the constant state was loaded.
+     */
+    void computeVectorSize() {
+        final Insets opticalInsets = mVectorState.mOpticalInsets;
+
+        final int sourceDensity = mVectorState.mDensity;
+        final int targetDensity = mTargetDensity;
+        if (targetDensity != sourceDensity) {
+            mDpiScaledWidth = Drawable.scaleFromDensity(mVectorState.mBaseWidth, sourceDensity,
+                    targetDensity, true);
+            mDpiScaledHeight = Drawable.scaleFromDensity(mVectorState.mBaseHeight,sourceDensity,
+                    targetDensity, true);
+            final int left = Drawable.scaleFromDensity(
+                    opticalInsets.left, sourceDensity, targetDensity, false);
+            final int right = Drawable.scaleFromDensity(
+                    opticalInsets.right, sourceDensity, targetDensity, false);
+            final int top = Drawable.scaleFromDensity(
+                    opticalInsets.top, sourceDensity, targetDensity, false);
+            final int bottom = Drawable.scaleFromDensity(
+                    opticalInsets.bottom, sourceDensity, targetDensity, false);
+            mDpiScaledInsets = Insets.of(left, top, right, bottom);
+        } else {
+            mDpiScaledWidth = mVectorState.mBaseWidth;
+            mDpiScaledHeight = mVectorState.mBaseHeight;
+            mDpiScaledInsets = opticalInsets;
+        }
+
+        mDpiScaledDirty = false;
+    }
+
+    @Override
+    public boolean canApplyTheme() {
+        return (mVectorState != null && mVectorState.canApplyTheme()) || super.canApplyTheme();
+    }
+
+    @Override
+    public void applyTheme(Theme t) {
+        super.applyTheme(t);
+
+        final VectorDrawableState state = mVectorState;
+        if (state == null) {
+            return;
+        }
+
+        final boolean changedDensity = mVectorState.setDensity(
+                Drawable.resolveDensity(t.getResources(), 0));
+        mDpiScaledDirty |= changedDensity;
+
+        if (state.mThemeAttrs != null) {
+            final TypedArray a = t.resolveAttributes(
+                    state.mThemeAttrs, R.styleable.VectorDrawable);
+            try {
+                state.mCacheDirty = true;
+                updateStateFromTypedArray(a);
+            } catch (XmlPullParserException e) {
+                throw new RuntimeException(e);
+            } finally {
+                a.recycle();
+            }
+
+            // May have changed size.
+            mDpiScaledDirty = true;
+        }
+
+        // Apply theme to contained color state list.
+        if (state.mTint != null && state.mTint.canApplyTheme()) {
+            state.mTint = state.mTint.obtainForTheme(t);
+        }
+
+        if (mVectorState != null && mVectorState.canApplyTheme()) {
+            mVectorState.applyTheme(t);
+        }
+
+        // Update local properties.
+        updateLocalState(t.getResources());
+    }
+
+    /**
+     * The size of a pixel when scaled from the intrinsic dimension to the viewport dimension.
+     * This is used to calculate the path animation accuracy.
+     *
+     * @hide
+     */
+    public float getPixelSize() {
+        if (mVectorState == null ||
+                mVectorState.mBaseWidth == 0 ||
+                mVectorState.mBaseHeight == 0 ||
+                mVectorState.mViewportHeight == 0 ||
+                mVectorState.mViewportWidth == 0) {
+            return 1; // fall back to 1:1 pixel mapping.
+        }
+        float intrinsicWidth = mVectorState.mBaseWidth;
+        float intrinsicHeight = mVectorState.mBaseHeight;
+        float viewportWidth = mVectorState.mViewportWidth;
+        float viewportHeight = mVectorState.mViewportHeight;
+        float scaleX = viewportWidth / intrinsicWidth;
+        float scaleY = viewportHeight / intrinsicHeight;
+        return Math.min(scaleX, scaleY);
+    }
+
+    /** @hide */
+    public static VectorDrawable create(Resources resources, int rid) {
+        try {
+            final XmlPullParser parser = resources.getXml(rid);
+            final AttributeSet attrs = Xml.asAttributeSet(parser);
+            int type;
+            while ((type=parser.next()) != XmlPullParser.START_TAG &&
+                    type != XmlPullParser.END_DOCUMENT) {
+                // Empty loop
+            }
+            if (type != XmlPullParser.START_TAG) {
+                throw new XmlPullParserException("No start tag found");
+            }
+
+            final VectorDrawable drawable = new VectorDrawable();
+            drawable.inflate(resources, parser, attrs);
+
+            return drawable;
+        } catch (XmlPullParserException e) {
+            Log.e(LOGTAG, "parser error", e);
+        } catch (IOException e) {
+            Log.e(LOGTAG, "parser error", e);
+        }
+        return null;
+    }
+
+    @Override
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
+        if (mVectorState.mRootGroup != null || mVectorState.mNativeTree != null) {
+            // This VD has been used to display other VD resource content, clean up.
+            if (mVectorState.mRootGroup != null) {
+                // Subtract the native allocation for all the nodes.
+                VMRuntime.getRuntime().registerNativeFree(mVectorState.mRootGroup.getNativeSize());
+                // Remove child nodes' reference to tree
+                mVectorState.mRootGroup.setTree(null);
+            }
+            mVectorState.mRootGroup = new VGroup();
+            if (mVectorState.mNativeTree != null) {
+                // Subtract the native allocation for the tree wrapper, which contains root node
+                // as well as rendering related data.
+                VMRuntime.getRuntime().registerNativeFree(mVectorState.NATIVE_ALLOCATION_SIZE);
+                mVectorState.mNativeTree.release();
+            }
+            mVectorState.createNativeTree(mVectorState.mRootGroup);
+        }
+        final VectorDrawableState state = mVectorState;
+        state.setDensity(Drawable.resolveDensity(r, 0));
+
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.VectorDrawable);
+        updateStateFromTypedArray(a);
+        a.recycle();
+
+        mDpiScaledDirty = true;
+
+        state.mCacheDirty = true;
+        inflateChildElements(r, parser, attrs, theme);
+
+        state.onTreeConstructionFinished();
+        // Update local properties.
+        updateLocalState(r);
+    }
+
+    private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
+        final VectorDrawableState state = mVectorState;
+
+        // Account for any configuration changes.
+        state.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        state.mThemeAttrs = a.extractThemeAttrs();
+
+        final int tintMode = a.getInt(R.styleable.VectorDrawable_tintMode, -1);
+        if (tintMode != -1) {
+            state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
+        }
+
+        final ColorStateList tint = a.getColorStateList(R.styleable.VectorDrawable_tint);
+        if (tint != null) {
+            state.mTint = tint;
+        }
+
+        state.mAutoMirrored = a.getBoolean(
+                R.styleable.VectorDrawable_autoMirrored, state.mAutoMirrored);
+
+        float viewportWidth = a.getFloat(
+                R.styleable.VectorDrawable_viewportWidth, state.mViewportWidth);
+        float viewportHeight = a.getFloat(
+                R.styleable.VectorDrawable_viewportHeight, state.mViewportHeight);
+        state.setViewportSize(viewportWidth, viewportHeight);
+
+        if (state.mViewportWidth <= 0) {
+            throw new XmlPullParserException(a.getPositionDescription() +
+                    "<vector> tag requires viewportWidth > 0");
+        } else if (state.mViewportHeight <= 0) {
+            throw new XmlPullParserException(a.getPositionDescription() +
+                    "<vector> tag requires viewportHeight > 0");
+        }
+
+        state.mBaseWidth = a.getDimensionPixelSize(
+                R.styleable.VectorDrawable_width, state.mBaseWidth);
+        state.mBaseHeight = a.getDimensionPixelSize(
+                R.styleable.VectorDrawable_height, state.mBaseHeight);
+
+        if (state.mBaseWidth <= 0) {
+            throw new XmlPullParserException(a.getPositionDescription() +
+                    "<vector> tag requires width > 0");
+        } else if (state.mBaseHeight <= 0) {
+            throw new XmlPullParserException(a.getPositionDescription() +
+                    "<vector> tag requires height > 0");
+        }
+
+        final int insetLeft = a.getDimensionPixelOffset(
+                R.styleable.VectorDrawable_opticalInsetLeft, state.mOpticalInsets.left);
+        final int insetTop = a.getDimensionPixelOffset(
+                R.styleable.VectorDrawable_opticalInsetTop, state.mOpticalInsets.top);
+        final int insetRight = a.getDimensionPixelOffset(
+                R.styleable.VectorDrawable_opticalInsetRight, state.mOpticalInsets.right);
+        final int insetBottom = a.getDimensionPixelOffset(
+                R.styleable.VectorDrawable_opticalInsetBottom, state.mOpticalInsets.bottom);
+        state.mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom);
+
+        final float alphaInFloat = a.getFloat(
+                R.styleable.VectorDrawable_alpha, state.getAlpha());
+        state.setAlpha(alphaInFloat);
+
+        final String name = a.getString(R.styleable.VectorDrawable_name);
+        if (name != null) {
+            state.mRootName = name;
+            state.mVGTargetsMap.put(name, state);
+        }
+    }
+
+    private void inflateChildElements(Resources res, XmlPullParser parser, AttributeSet attrs,
+            Theme theme) throws XmlPullParserException, IOException {
+        final VectorDrawableState state = mVectorState;
+        boolean noPathTag = true;
+
+        // Use a stack to help to build the group tree.
+        // The top of the stack is always the current group.
+        final Stack<VGroup> groupStack = new Stack<VGroup>();
+        groupStack.push(state.mRootGroup);
+
+        int eventType = parser.getEventType();
+        final int innerDepth = parser.getDepth() + 1;
+
+        // Parse everything until the end of the vector element.
+        while (eventType != XmlPullParser.END_DOCUMENT
+                && (parser.getDepth() >= innerDepth || eventType != XmlPullParser.END_TAG)) {
+            if (eventType == XmlPullParser.START_TAG) {
+                final String tagName = parser.getName();
+                final VGroup currentGroup = groupStack.peek();
+
+                if (SHAPE_PATH.equals(tagName)) {
+                    final VFullPath path = new VFullPath();
+                    path.inflate(res, attrs, theme);
+                    currentGroup.addChild(path);
+                    if (path.getPathName() != null) {
+                        state.mVGTargetsMap.put(path.getPathName(), path);
+                    }
+                    noPathTag = false;
+                    state.mChangingConfigurations |= path.mChangingConfigurations;
+                } else if (SHAPE_CLIP_PATH.equals(tagName)) {
+                    final VClipPath path = new VClipPath();
+                    path.inflate(res, attrs, theme);
+                    currentGroup.addChild(path);
+                    if (path.getPathName() != null) {
+                        state.mVGTargetsMap.put(path.getPathName(), path);
+                    }
+                    state.mChangingConfigurations |= path.mChangingConfigurations;
+                } else if (SHAPE_GROUP.equals(tagName)) {
+                    VGroup newChildGroup = new VGroup();
+                    newChildGroup.inflate(res, attrs, theme);
+                    currentGroup.addChild(newChildGroup);
+                    groupStack.push(newChildGroup);
+                    if (newChildGroup.getGroupName() != null) {
+                        state.mVGTargetsMap.put(newChildGroup.getGroupName(),
+                                newChildGroup);
+                    }
+                    state.mChangingConfigurations |= newChildGroup.mChangingConfigurations;
+                }
+            } else if (eventType == XmlPullParser.END_TAG) {
+                final String tagName = parser.getName();
+                if (SHAPE_GROUP.equals(tagName)) {
+                    groupStack.pop();
+                }
+            }
+            eventType = parser.next();
+        }
+
+        if (noPathTag) {
+            final StringBuffer tag = new StringBuffer();
+
+            if (tag.length() > 0) {
+                tag.append(" or ");
+            }
+            tag.append(SHAPE_PATH);
+
+            throw new XmlPullParserException("no " + tag + " defined");
+        }
+    }
+
+    @Override
+    public @Config int getChangingConfigurations() {
+        return super.getChangingConfigurations() | mVectorState.getChangingConfigurations();
+    }
+
+    void setAllowCaching(boolean allowCaching) {
+        nSetAllowCaching(mVectorState.getNativeRenderer(), allowCaching);
+    }
+
+    private boolean needMirroring() {
+        return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
+    }
+
+    @Override
+    public void setAutoMirrored(boolean mirrored) {
+        if (mVectorState.mAutoMirrored != mirrored) {
+            mVectorState.mAutoMirrored = mirrored;
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public boolean isAutoMirrored() {
+        return mVectorState.mAutoMirrored;
+    }
+
+    /**
+     * @hide
+     */
+    public long getNativeTree() {
+        return mVectorState.getNativeRenderer();
+    }
+
+    static class VectorDrawableState extends ConstantState {
+        // Variables below need to be copied (deep copy if applicable) for mutation.
+        int[] mThemeAttrs;
+        @Config int mChangingConfigurations;
+        ColorStateList mTint = null;
+        Mode mTintMode = DEFAULT_TINT_MODE;
+        boolean mAutoMirrored;
+
+        int mBaseWidth = 0;
+        int mBaseHeight = 0;
+        float mViewportWidth = 0;
+        float mViewportHeight = 0;
+        Insets mOpticalInsets = Insets.NONE;
+        String mRootName = null;
+        VGroup mRootGroup;
+        VirtualRefBasePtr mNativeTree = null;
+
+        int mDensity = DisplayMetrics.DENSITY_DEFAULT;
+        final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<>();
+
+        // Fields for cache
+        int[] mCachedThemeAttrs;
+        ColorStateList mCachedTint;
+        Mode mCachedTintMode;
+        boolean mCachedAutoMirrored;
+        boolean mCacheDirty;
+
+        // Since sw canvas and hw canvas uses different bitmap caches, we track the allocation of
+        // these bitmaps separately.
+        int mLastSWCachePixelCount = 0;
+        int mLastHWCachePixelCount = 0;
+
+        final static Property<VectorDrawableState, Float> ALPHA =
+                new FloatProperty<VectorDrawableState>("alpha") {
+                    @Override
+                    public void setValue(VectorDrawableState state, float value) {
+                        state.setAlpha(value);
+                    }
+
+                    @Override
+                    public Float get(VectorDrawableState state) {
+                        return state.getAlpha();
+                    }
+                };
+
+        Property getProperty(String propertyName) {
+            if (ALPHA.getName().equals(propertyName)) {
+                return ALPHA;
+            }
+            return null;
+        }
+
+        // This tracks the total native allocation for all the nodes.
+        private int mAllocationOfAllNodes = 0;
+
+        private static final int NATIVE_ALLOCATION_SIZE = 316;
+
+        // If copy is not null, deep copy the given VectorDrawableState. Otherwise, create a
+        // native vector drawable tree with an empty root group.
+        public VectorDrawableState(VectorDrawableState copy) {
+            if (copy != null) {
+                mThemeAttrs = copy.mThemeAttrs;
+                mChangingConfigurations = copy.mChangingConfigurations;
+                mTint = copy.mTint;
+                mTintMode = copy.mTintMode;
+                mAutoMirrored = copy.mAutoMirrored;
+                mRootGroup = new VGroup(copy.mRootGroup, mVGTargetsMap);
+                createNativeTreeFromCopy(copy, mRootGroup);
+
+                mBaseWidth = copy.mBaseWidth;
+                mBaseHeight = copy.mBaseHeight;
+                setViewportSize(copy.mViewportWidth, copy.mViewportHeight);
+                mOpticalInsets = copy.mOpticalInsets;
+
+                mRootName = copy.mRootName;
+                mDensity = copy.mDensity;
+                if (copy.mRootName != null) {
+                    mVGTargetsMap.put(copy.mRootName, this);
+                }
+            } else {
+                mRootGroup = new VGroup();
+                createNativeTree(mRootGroup);
+            }
+            onTreeConstructionFinished();
+        }
+
+        private void createNativeTree(VGroup rootGroup) {
+            mNativeTree = new VirtualRefBasePtr(nCreateTree(rootGroup.mNativePtr));
+            // Register tree size
+            VMRuntime.getRuntime().registerNativeAllocation(NATIVE_ALLOCATION_SIZE);
+        }
+
+        // Create a new native tree with the given root group, and copy the properties from the
+        // given VectorDrawableState's native tree.
+        private void createNativeTreeFromCopy(VectorDrawableState copy, VGroup rootGroup) {
+            mNativeTree = new VirtualRefBasePtr(nCreateTreeFromCopy(
+                    copy.mNativeTree.get(), rootGroup.mNativePtr));
+            // Register tree size
+            VMRuntime.getRuntime().registerNativeAllocation(NATIVE_ALLOCATION_SIZE);
+        }
+
+        // This should be called every time after a new RootGroup and all its subtrees are created
+        // (i.e. in constructors of VectorDrawableState and in inflate).
+        void onTreeConstructionFinished() {
+            mRootGroup.setTree(mNativeTree);
+            mAllocationOfAllNodes = mRootGroup.getNativeSize();
+            VMRuntime.getRuntime().registerNativeAllocation(mAllocationOfAllNodes);
+        }
+
+        long getNativeRenderer() {
+            if (mNativeTree == null) {
+                return 0;
+            }
+            return mNativeTree.get();
+        }
+
+        public boolean canReuseCache() {
+            if (!mCacheDirty
+                    && mCachedThemeAttrs == mThemeAttrs
+                    && mCachedTint == mTint
+                    && mCachedTintMode == mTintMode
+                    && mCachedAutoMirrored == mAutoMirrored) {
+                return true;
+            }
+            updateCacheStates();
+            return false;
+        }
+
+        public void updateCacheStates() {
+            // Use shallow copy here and shallow comparison in canReuseCache(),
+            // likely hit cache miss more, but practically not much difference.
+            mCachedThemeAttrs = mThemeAttrs;
+            mCachedTint = mTint;
+            mCachedTintMode = mTintMode;
+            mCachedAutoMirrored = mAutoMirrored;
+            mCacheDirty = false;
+        }
+
+        public void applyTheme(Theme t) {
+            mRootGroup.applyTheme(t);
+        }
+
+        @Override
+        public boolean canApplyTheme() {
+            return mThemeAttrs != null
+                    || (mRootGroup != null && mRootGroup.canApplyTheme())
+                    || (mTint != null && mTint.canApplyTheme())
+                    || super.canApplyTheme();
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return new VectorDrawable(this, null);
+        }
+
+        @Override
+        public Drawable newDrawable(Resources res) {
+            return new VectorDrawable(this, res);
+        }
+
+        @Override
+        public @Config int getChangingConfigurations() {
+            return mChangingConfigurations
+                    | (mTint != null ? mTint.getChangingConfigurations() : 0);
+        }
+
+        public boolean isStateful() {
+            return (mTint != null && mTint.isStateful())
+                    || (mRootGroup != null && mRootGroup.isStateful());
+        }
+
+        public boolean hasFocusStateSpecified() {
+            return mTint != null && mTint.hasFocusStateSpecified()
+                    || (mRootGroup != null && mRootGroup.hasFocusStateSpecified());
+        }
+
+        void setViewportSize(float viewportWidth, float viewportHeight) {
+            mViewportWidth = viewportWidth;
+            mViewportHeight = viewportHeight;
+            nSetRendererViewportSize(getNativeRenderer(), viewportWidth, viewportHeight);
+        }
+
+        public final boolean setDensity(int targetDensity) {
+            if (mDensity != targetDensity) {
+                final int sourceDensity = mDensity;
+                mDensity = targetDensity;
+                applyDensityScaling(sourceDensity, targetDensity);
+                return true;
+            }
+            return false;
+        }
+
+        private void applyDensityScaling(int sourceDensity, int targetDensity) {
+            mBaseWidth = Drawable.scaleFromDensity(mBaseWidth, sourceDensity, targetDensity, true);
+            mBaseHeight = Drawable.scaleFromDensity(mBaseHeight, sourceDensity, targetDensity,
+                    true);
+
+            final int insetLeft = Drawable.scaleFromDensity(
+                    mOpticalInsets.left, sourceDensity, targetDensity, false);
+            final int insetTop = Drawable.scaleFromDensity(
+                    mOpticalInsets.top, sourceDensity, targetDensity, false);
+            final int insetRight = Drawable.scaleFromDensity(
+                    mOpticalInsets.right, sourceDensity, targetDensity, false);
+            final int insetBottom = Drawable.scaleFromDensity(
+                    mOpticalInsets.bottom, sourceDensity, targetDensity, false);
+            mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom);
+        }
+
+        public boolean onStateChange(int[] stateSet) {
+            return mRootGroup.onStateChange(stateSet);
+        }
+
+        @Override
+        public void finalize() throws Throwable {
+            super.finalize();
+            int bitmapCacheSize = mLastHWCachePixelCount * 4 + mLastSWCachePixelCount * 4;
+            VMRuntime.getRuntime().registerNativeFree(NATIVE_ALLOCATION_SIZE
+                    + mAllocationOfAllNodes + bitmapCacheSize);
+        }
+
+        /**
+         * setAlpha() and getAlpha() are used mostly for animation purpose. Return true if alpha
+         * has changed.
+         */
+        public boolean setAlpha(float alpha) {
+            return nSetRootAlpha(mNativeTree.get(), alpha);
+        }
+
+        @SuppressWarnings("unused")
+        public float getAlpha() {
+            return nGetRootAlpha(mNativeTree.get());
+        }
+    }
+
+    static class VGroup extends VObject {
+        private static final int ROTATION_INDEX = 0;
+        private static final int PIVOT_X_INDEX = 1;
+        private static final int PIVOT_Y_INDEX = 2;
+        private static final int SCALE_X_INDEX = 3;
+        private static final int SCALE_Y_INDEX = 4;
+        private static final int TRANSLATE_X_INDEX = 5;
+        private static final int TRANSLATE_Y_INDEX = 6;
+        private static final int TRANSFORM_PROPERTY_COUNT = 7;
+
+        private static final int NATIVE_ALLOCATION_SIZE = 100;
+
+        private static final HashMap<String, Integer> sPropertyIndexMap =
+                new HashMap<String, Integer>() {
+                    {
+                        put("translateX", TRANSLATE_X_INDEX);
+                        put("translateY", TRANSLATE_Y_INDEX);
+                        put("scaleX", SCALE_X_INDEX);
+                        put("scaleY", SCALE_Y_INDEX);
+                        put("pivotX", PIVOT_X_INDEX);
+                        put("pivotY", PIVOT_Y_INDEX);
+                        put("rotation", ROTATION_INDEX);
+                    }
+                };
+
+        static int getPropertyIndex(String propertyName) {
+            if (sPropertyIndexMap.containsKey(propertyName)) {
+                return sPropertyIndexMap.get(propertyName);
+            } else {
+                // property not found
+                return -1;
+            }
+        }
+
+        // Below are the Properties that wrap the setters to avoid reflection overhead in animations
+        private static final Property<VGroup, Float> TRANSLATE_X =
+                new FloatProperty<VGroup> ("translateX") {
+                    @Override
+                    public void setValue(VGroup object, float value) {
+                        object.setTranslateX(value);
+                    }
+
+                    @Override
+                    public Float get(VGroup object) {
+                        return object.getTranslateX();
+                    }
+                };
+
+        private static final Property<VGroup, Float> TRANSLATE_Y =
+                new FloatProperty<VGroup> ("translateY") {
+                    @Override
+                    public void setValue(VGroup object, float value) {
+                        object.setTranslateY(value);
+                    }
+
+                    @Override
+                    public Float get(VGroup object) {
+                        return object.getTranslateY();
+                    }
+        };
+
+        private static final Property<VGroup, Float> SCALE_X =
+                new FloatProperty<VGroup> ("scaleX") {
+                    @Override
+                    public void setValue(VGroup object, float value) {
+                        object.setScaleX(value);
+                    }
+
+                    @Override
+                    public Float get(VGroup object) {
+                        return object.getScaleX();
+                    }
+                };
+
+        private static final Property<VGroup, Float> SCALE_Y =
+                new FloatProperty<VGroup> ("scaleY") {
+                    @Override
+                    public void setValue(VGroup object, float value) {
+                        object.setScaleY(value);
+                    }
+
+                    @Override
+                    public Float get(VGroup object) {
+                        return object.getScaleY();
+                    }
+                };
+
+        private static final Property<VGroup, Float> PIVOT_X =
+                new FloatProperty<VGroup> ("pivotX") {
+                    @Override
+                    public void setValue(VGroup object, float value) {
+                        object.setPivotX(value);
+                    }
+
+                    @Override
+                    public Float get(VGroup object) {
+                        return object.getPivotX();
+                    }
+                };
+
+        private static final Property<VGroup, Float> PIVOT_Y =
+                new FloatProperty<VGroup> ("pivotY") {
+                    @Override
+                    public void setValue(VGroup object, float value) {
+                        object.setPivotY(value);
+                    }
+
+                    @Override
+                    public Float get(VGroup object) {
+                        return object.getPivotY();
+                    }
+                };
+
+        private static final Property<VGroup, Float> ROTATION =
+                new FloatProperty<VGroup> ("rotation") {
+                    @Override
+                    public void setValue(VGroup object, float value) {
+                        object.setRotation(value);
+                    }
+
+                    @Override
+                    public Float get(VGroup object) {
+                        return object.getRotation();
+                    }
+                };
+
+        private static final HashMap<String, Property> sPropertyMap =
+                new HashMap<String, Property>() {
+                    {
+                        put("translateX", TRANSLATE_X);
+                        put("translateY", TRANSLATE_Y);
+                        put("scaleX", SCALE_X);
+                        put("scaleY", SCALE_Y);
+                        put("pivotX", PIVOT_X);
+                        put("pivotY", PIVOT_Y);
+                        put("rotation", ROTATION);
+                    }
+                };
+        // Temp array to store transform values obtained from native.
+        private float[] mTransform;
+        /////////////////////////////////////////////////////
+        // Variables below need to be copied (deep copy if applicable) for mutation.
+        private final ArrayList<VObject> mChildren = new ArrayList<>();
+        private boolean mIsStateful;
+
+        // mLocalMatrix is updated based on the update of transformation information,
+        // either parsed from the XML or by animation.
+        private @Config int mChangingConfigurations;
+        private int[] mThemeAttrs;
+        private String mGroupName = null;
+
+        // The native object will be created in the constructor and will be destroyed in native
+        // when the neither java nor native has ref to the tree. This pointer should be valid
+        // throughout this VGroup Java object's life.
+        private final long mNativePtr;
+        public VGroup(VGroup copy, ArrayMap<String, Object> targetsMap) {
+
+            mIsStateful = copy.mIsStateful;
+            mThemeAttrs = copy.mThemeAttrs;
+            mGroupName = copy.mGroupName;
+            mChangingConfigurations = copy.mChangingConfigurations;
+            if (mGroupName != null) {
+                targetsMap.put(mGroupName, this);
+            }
+            mNativePtr = nCreateGroup(copy.mNativePtr);
+
+            final ArrayList<VObject> children = copy.mChildren;
+            for (int i = 0; i < children.size(); i++) {
+                final VObject copyChild = children.get(i);
+                if (copyChild instanceof VGroup) {
+                    final VGroup copyGroup = (VGroup) copyChild;
+                    addChild(new VGroup(copyGroup, targetsMap));
+                } else {
+                    final VPath newPath;
+                    if (copyChild instanceof VFullPath) {
+                        newPath = new VFullPath((VFullPath) copyChild);
+                    } else if (copyChild instanceof VClipPath) {
+                        newPath = new VClipPath((VClipPath) copyChild);
+                    } else {
+                        throw new IllegalStateException("Unknown object in the tree!");
+                    }
+                    addChild(newPath);
+                    if (newPath.mPathName != null) {
+                        targetsMap.put(newPath.mPathName, newPath);
+                    }
+                }
+            }
+        }
+
+        public VGroup() {
+            mNativePtr = nCreateGroup();
+        }
+
+        Property getProperty(String propertyName) {
+            if (sPropertyMap.containsKey(propertyName)) {
+                return sPropertyMap.get(propertyName);
+            } else {
+                // property not found
+                return null;
+            }
+        }
+
+        public String getGroupName() {
+            return mGroupName;
+        }
+
+        public void addChild(VObject child) {
+            nAddChild(mNativePtr, child.getNativePtr());
+            mChildren.add(child);
+            mIsStateful |= child.isStateful();
+        }
+
+        @Override
+        public void setTree(VirtualRefBasePtr treeRoot) {
+            super.setTree(treeRoot);
+            for (int i = 0; i < mChildren.size(); i++) {
+                mChildren.get(i).setTree(treeRoot);
+            }
+        }
+
+        @Override
+        public long getNativePtr() {
+            return mNativePtr;
+        }
+
+        @Override
+        public void inflate(Resources res, AttributeSet attrs, Theme theme) {
+            final TypedArray a = obtainAttributes(res, theme, attrs,
+                    R.styleable.VectorDrawableGroup);
+            updateStateFromTypedArray(a);
+            a.recycle();
+        }
+
+        void updateStateFromTypedArray(TypedArray a) {
+            // Account for any configuration changes.
+            mChangingConfigurations |= a.getChangingConfigurations();
+
+            // Extract the theme attributes, if any.
+            mThemeAttrs = a.extractThemeAttrs();
+            if (mTransform == null) {
+                // Lazy initialization: If the group is created through copy constructor, this may
+                // never get called.
+                mTransform = new float[TRANSFORM_PROPERTY_COUNT];
+            }
+            boolean success = nGetGroupProperties(mNativePtr, mTransform, TRANSFORM_PROPERTY_COUNT);
+            if (!success) {
+                throw new RuntimeException("Error: inconsistent property count");
+            }
+            float rotate = a.getFloat(R.styleable.VectorDrawableGroup_rotation,
+                    mTransform[ROTATION_INDEX]);
+            float pivotX = a.getFloat(R.styleable.VectorDrawableGroup_pivotX,
+                    mTransform[PIVOT_X_INDEX]);
+            float pivotY = a.getFloat(R.styleable.VectorDrawableGroup_pivotY,
+                    mTransform[PIVOT_Y_INDEX]);
+            float scaleX = a.getFloat(R.styleable.VectorDrawableGroup_scaleX,
+                    mTransform[SCALE_X_INDEX]);
+            float scaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY,
+                    mTransform[SCALE_Y_INDEX]);
+            float translateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX,
+                    mTransform[TRANSLATE_X_INDEX]);
+            float translateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY,
+                    mTransform[TRANSLATE_Y_INDEX]);
+
+            final String groupName = a.getString(R.styleable.VectorDrawableGroup_name);
+            if (groupName != null) {
+                mGroupName = groupName;
+                nSetName(mNativePtr, mGroupName);
+            }
+             nUpdateGroupProperties(mNativePtr, rotate, pivotX, pivotY, scaleX, scaleY,
+                     translateX, translateY);
+        }
+
+        @Override
+        public boolean onStateChange(int[] stateSet) {
+            boolean changed = false;
+
+            final ArrayList<VObject> children = mChildren;
+            for (int i = 0, count = children.size(); i < count; i++) {
+                final VObject child = children.get(i);
+                if (child.isStateful()) {
+                    changed |= child.onStateChange(stateSet);
+                }
+            }
+
+            return changed;
+        }
+
+        @Override
+        public boolean isStateful() {
+            return mIsStateful;
+        }
+
+        @Override
+        public boolean hasFocusStateSpecified() {
+            boolean result = false;
+
+            final ArrayList<VObject> children = mChildren;
+            for (int i = 0, count = children.size(); i < count; i++) {
+                final VObject child = children.get(i);
+                if (child.isStateful()) {
+                    result |= child.hasFocusStateSpecified();
+                }
+            }
+
+            return result;
+        }
+
+        @Override
+        int getNativeSize() {
+            // Return the native allocation needed for the subtree.
+            int size = NATIVE_ALLOCATION_SIZE;
+            for (int i = 0; i < mChildren.size(); i++) {
+                size += mChildren.get(i).getNativeSize();
+            }
+            return size;
+        }
+
+        @Override
+        public boolean canApplyTheme() {
+            if (mThemeAttrs != null) {
+                return true;
+            }
+
+            final ArrayList<VObject> children = mChildren;
+            for (int i = 0, count = children.size(); i < count; i++) {
+                final VObject child = children.get(i);
+                if (child.canApplyTheme()) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        @Override
+        public void applyTheme(Theme t) {
+            if (mThemeAttrs != null) {
+                final TypedArray a = t.resolveAttributes(mThemeAttrs,
+                        R.styleable.VectorDrawableGroup);
+                updateStateFromTypedArray(a);
+                a.recycle();
+            }
+
+            final ArrayList<VObject> children = mChildren;
+            for (int i = 0, count = children.size(); i < count; i++) {
+                final VObject child = children.get(i);
+                if (child.canApplyTheme()) {
+                    child.applyTheme(t);
+
+                    // Applying a theme may have made the child stateful.
+                    mIsStateful |= child.isStateful();
+                }
+            }
+        }
+
+        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
+        @SuppressWarnings("unused")
+        public float getRotation() {
+            return isTreeValid() ? nGetRotation(mNativePtr) : 0;
+        }
+
+        @SuppressWarnings("unused")
+        public void setRotation(float rotation) {
+            if (isTreeValid()) {
+                nSetRotation(mNativePtr, rotation);
+            }
+        }
+
+        @SuppressWarnings("unused")
+        public float getPivotX() {
+            return isTreeValid() ? nGetPivotX(mNativePtr) : 0;
+        }
+
+        @SuppressWarnings("unused")
+        public void setPivotX(float pivotX) {
+            if (isTreeValid()) {
+                nSetPivotX(mNativePtr, pivotX);
+            }
+        }
+
+        @SuppressWarnings("unused")
+        public float getPivotY() {
+            return isTreeValid() ? nGetPivotY(mNativePtr) : 0;
+        }
+
+        @SuppressWarnings("unused")
+        public void setPivotY(float pivotY) {
+            if (isTreeValid()) {
+                nSetPivotY(mNativePtr, pivotY);
+            }
+        }
+
+        @SuppressWarnings("unused")
+        public float getScaleX() {
+            return isTreeValid() ? nGetScaleX(mNativePtr) : 0;
+        }
+
+        @SuppressWarnings("unused")
+        public void setScaleX(float scaleX) {
+            if (isTreeValid()) {
+                nSetScaleX(mNativePtr, scaleX);
+            }
+        }
+
+        @SuppressWarnings("unused")
+        public float getScaleY() {
+            return isTreeValid() ? nGetScaleY(mNativePtr) : 0;
+        }
+
+        @SuppressWarnings("unused")
+        public void setScaleY(float scaleY) {
+            if (isTreeValid()) {
+                nSetScaleY(mNativePtr, scaleY);
+            }
+        }
+
+        @SuppressWarnings("unused")
+        public float getTranslateX() {
+            return isTreeValid() ? nGetTranslateX(mNativePtr) : 0;
+        }
+
+        @SuppressWarnings("unused")
+        public void setTranslateX(float translateX) {
+            if (isTreeValid()) {
+                nSetTranslateX(mNativePtr, translateX);
+            }
+        }
+
+        @SuppressWarnings("unused")
+        public float getTranslateY() {
+            return isTreeValid() ? nGetTranslateY(mNativePtr) : 0;
+        }
+
+        @SuppressWarnings("unused")
+        public void setTranslateY(float translateY) {
+            if (isTreeValid()) {
+                nSetTranslateY(mNativePtr, translateY);
+            }
+        }
+    }
+
+    /**
+     * Common Path information for clip path and normal path.
+     */
+    static abstract class VPath extends VObject {
+        protected PathParser.PathData mPathData = null;
+
+        String mPathName;
+        @Config int mChangingConfigurations;
+
+        private static final Property<VPath, PathParser.PathData> PATH_DATA =
+                new Property<VPath, PathParser.PathData>(PathParser.PathData.class, "pathData") {
+                    @Override
+                    public void set(VPath object, PathParser.PathData data) {
+                        object.setPathData(data);
+                    }
+
+                    @Override
+                    public PathParser.PathData get(VPath object) {
+                        return object.getPathData();
+                    }
+                };
+
+        Property getProperty(String propertyName) {
+            if (PATH_DATA.getName().equals(propertyName)) {
+                return PATH_DATA;
+            }
+            // property not found
+            return null;
+        }
+
+        public VPath() {
+            // Empty constructor.
+        }
+
+        public VPath(VPath copy) {
+            mPathName = copy.mPathName;
+            mChangingConfigurations = copy.mChangingConfigurations;
+            mPathData = copy.mPathData == null ? null : new PathParser.PathData(copy.mPathData);
+        }
+
+        public String getPathName() {
+            return mPathName;
+        }
+
+        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
+        @SuppressWarnings("unused")
+        public PathParser.PathData getPathData() {
+            return mPathData;
+        }
+
+        // TODO: Move the PathEvaluator and this setter and the getter above into native.
+        @SuppressWarnings("unused")
+        public void setPathData(PathParser.PathData pathData) {
+            mPathData.setPathData(pathData);
+            if (isTreeValid()) {
+                nSetPathData(getNativePtr(), mPathData.getNativePtr());
+            }
+        }
+    }
+
+    /**
+     * Clip path, which only has name and pathData.
+     */
+    private static class VClipPath extends VPath {
+        private final long mNativePtr;
+        private static final int NATIVE_ALLOCATION_SIZE = 120;
+
+        public VClipPath() {
+            mNativePtr = nCreateClipPath();
+        }
+
+        public VClipPath(VClipPath copy) {
+            super(copy);
+            mNativePtr = nCreateClipPath(copy.mNativePtr);
+        }
+
+        @Override
+        public long getNativePtr() {
+            return mNativePtr;
+        }
+
+        @Override
+        public void inflate(Resources r, AttributeSet attrs, Theme theme) {
+            final TypedArray a = obtainAttributes(r, theme, attrs,
+                    R.styleable.VectorDrawableClipPath);
+            updateStateFromTypedArray(a);
+            a.recycle();
+        }
+
+        @Override
+        public boolean canApplyTheme() {
+            return false;
+        }
+
+        @Override
+        public void applyTheme(Theme theme) {
+            // No-op.
+        }
+
+        @Override
+        public boolean onStateChange(int[] stateSet) {
+            return false;
+        }
+
+        @Override
+        public boolean isStateful() {
+            return false;
+        }
+
+        @Override
+        public boolean hasFocusStateSpecified() {
+            return false;
+        }
+
+        @Override
+        int getNativeSize() {
+            return NATIVE_ALLOCATION_SIZE;
+        }
+
+        private void updateStateFromTypedArray(TypedArray a) {
+            // Account for any configuration changes.
+            mChangingConfigurations |= a.getChangingConfigurations();
+
+            final String pathName = a.getString(R.styleable.VectorDrawableClipPath_name);
+            if (pathName != null) {
+                mPathName = pathName;
+                nSetName(mNativePtr, mPathName);
+            }
+
+            final String pathDataString = a.getString(R.styleable.VectorDrawableClipPath_pathData);
+            if (pathDataString != null) {
+                mPathData = new PathParser.PathData(pathDataString);
+                nSetPathString(mNativePtr, pathDataString, pathDataString.length());
+            }
+        }
+    }
+
+    /**
+     * Normal path, which contains all the fill / paint information.
+     */
+    static class VFullPath extends VPath {
+        private static final int STROKE_WIDTH_INDEX = 0;
+        private static final int STROKE_COLOR_INDEX = 1;
+        private static final int STROKE_ALPHA_INDEX = 2;
+        private static final int FILL_COLOR_INDEX = 3;
+        private static final int FILL_ALPHA_INDEX = 4;
+        private static final int TRIM_PATH_START_INDEX = 5;
+        private static final int TRIM_PATH_END_INDEX = 6;
+        private static final int TRIM_PATH_OFFSET_INDEX = 7;
+        private static final int STROKE_LINE_CAP_INDEX = 8;
+        private static final int STROKE_LINE_JOIN_INDEX = 9;
+        private static final int STROKE_MITER_LIMIT_INDEX = 10;
+        private static final int FILL_TYPE_INDEX = 11;
+        private static final int TOTAL_PROPERTY_COUNT = 12;
+
+        private static final int NATIVE_ALLOCATION_SIZE = 264;
+        // Property map for animatable attributes.
+        private final static HashMap<String, Integer> sPropertyIndexMap
+                = new HashMap<String, Integer> () {
+            {
+                put("strokeWidth", STROKE_WIDTH_INDEX);
+                put("strokeColor", STROKE_COLOR_INDEX);
+                put("strokeAlpha", STROKE_ALPHA_INDEX);
+                put("fillColor", FILL_COLOR_INDEX);
+                put("fillAlpha", FILL_ALPHA_INDEX);
+                put("trimPathStart", TRIM_PATH_START_INDEX);
+                put("trimPathEnd", TRIM_PATH_END_INDEX);
+                put("trimPathOffset", TRIM_PATH_OFFSET_INDEX);
+            }
+        };
+
+        // Below are the Properties that wrap the setters to avoid reflection overhead in animations
+        private static final Property<VFullPath, Float> STROKE_WIDTH =
+                new FloatProperty<VFullPath> ("strokeWidth") {
+                    @Override
+                    public void setValue(VFullPath object, float value) {
+                        object.setStrokeWidth(value);
+                    }
+
+                    @Override
+                    public Float get(VFullPath object) {
+                        return object.getStrokeWidth();
+                    }
+                };
+
+        private static final Property<VFullPath, Integer> STROKE_COLOR =
+                new IntProperty<VFullPath> ("strokeColor") {
+                    @Override
+                    public void setValue(VFullPath object, int value) {
+                        object.setStrokeColor(value);
+                    }
+
+                    @Override
+                    public Integer get(VFullPath object) {
+                        return object.getStrokeColor();
+                    }
+                };
+
+        private static final Property<VFullPath, Float> STROKE_ALPHA =
+                new FloatProperty<VFullPath> ("strokeAlpha") {
+                    @Override
+                    public void setValue(VFullPath object, float value) {
+                        object.setStrokeAlpha(value);
+                    }
+
+                    @Override
+                    public Float get(VFullPath object) {
+                        return object.getStrokeAlpha();
+                    }
+                };
+
+        private static final Property<VFullPath, Integer> FILL_COLOR =
+                new IntProperty<VFullPath>("fillColor") {
+                    @Override
+                    public void setValue(VFullPath object, int value) {
+                        object.setFillColor(value);
+                    }
+
+                    @Override
+                    public Integer get(VFullPath object) {
+                        return object.getFillColor();
+                    }
+                };
+
+        private static final Property<VFullPath, Float> FILL_ALPHA =
+                new FloatProperty<VFullPath> ("fillAlpha") {
+                    @Override
+                    public void setValue(VFullPath object, float value) {
+                        object.setFillAlpha(value);
+                    }
+
+                    @Override
+                    public Float get(VFullPath object) {
+                        return object.getFillAlpha();
+                    }
+                };
+
+        private static final Property<VFullPath, Float> TRIM_PATH_START =
+                new FloatProperty<VFullPath> ("trimPathStart") {
+                    @Override
+                    public void setValue(VFullPath object, float value) {
+                        object.setTrimPathStart(value);
+                    }
+
+                    @Override
+                    public Float get(VFullPath object) {
+                        return object.getTrimPathStart();
+                    }
+                };
+
+        private static final Property<VFullPath, Float> TRIM_PATH_END =
+                new FloatProperty<VFullPath> ("trimPathEnd") {
+                    @Override
+                    public void setValue(VFullPath object, float value) {
+                        object.setTrimPathEnd(value);
+                    }
+
+                    @Override
+                    public Float get(VFullPath object) {
+                        return object.getTrimPathEnd();
+                    }
+                };
+
+        private static final Property<VFullPath, Float> TRIM_PATH_OFFSET =
+                new FloatProperty<VFullPath> ("trimPathOffset") {
+                    @Override
+                    public void setValue(VFullPath object, float value) {
+                        object.setTrimPathOffset(value);
+                    }
+
+                    @Override
+                    public Float get(VFullPath object) {
+                        return object.getTrimPathOffset();
+                    }
+                };
+
+        private final static HashMap<String, Property> sPropertyMap
+                = new HashMap<String, Property> () {
+            {
+                put("strokeWidth", STROKE_WIDTH);
+                put("strokeColor", STROKE_COLOR);
+                put("strokeAlpha", STROKE_ALPHA);
+                put("fillColor", FILL_COLOR);
+                put("fillAlpha", FILL_ALPHA);
+                put("trimPathStart", TRIM_PATH_START);
+                put("trimPathEnd", TRIM_PATH_END);
+                put("trimPathOffset", TRIM_PATH_OFFSET);
+            }
+        };
+
+        // Temp array to store property data obtained from native getter.
+        private byte[] mPropertyData;
+        /////////////////////////////////////////////////////
+        // Variables below need to be copied (deep copy if applicable) for mutation.
+        private int[] mThemeAttrs;
+
+        ComplexColor mStrokeColors = null;
+        ComplexColor mFillColors = null;
+        private final long mNativePtr;
+
+        public VFullPath() {
+            mNativePtr = nCreateFullPath();
+        }
+
+        public VFullPath(VFullPath copy) {
+            super(copy);
+            mNativePtr = nCreateFullPath(copy.mNativePtr);
+            mThemeAttrs = copy.mThemeAttrs;
+            mStrokeColors = copy.mStrokeColors;
+            mFillColors = copy.mFillColors;
+        }
+
+        Property getProperty(String propertyName) {
+            Property p = super.getProperty(propertyName);
+            if (p != null) {
+                return p;
+            }
+            if (sPropertyMap.containsKey(propertyName)) {
+                return sPropertyMap.get(propertyName);
+            } else {
+                // property not found
+                return null;
+            }
+        }
+
+        int getPropertyIndex(String propertyName) {
+            if (!sPropertyIndexMap.containsKey(propertyName)) {
+                return -1;
+            } else {
+                return sPropertyIndexMap.get(propertyName);
+            }
+        }
+
+        @Override
+        public boolean onStateChange(int[] stateSet) {
+            boolean changed = false;
+
+            if (mStrokeColors != null && mStrokeColors instanceof ColorStateList) {
+                final int oldStrokeColor = getStrokeColor();
+                final int newStrokeColor =
+                        ((ColorStateList) mStrokeColors).getColorForState(stateSet, oldStrokeColor);
+                changed |= oldStrokeColor != newStrokeColor;
+                if (oldStrokeColor != newStrokeColor) {
+                    nSetStrokeColor(mNativePtr, newStrokeColor);
+                }
+            }
+
+            if (mFillColors != null && mFillColors instanceof ColorStateList) {
+                final int oldFillColor = getFillColor();
+                final int newFillColor = ((ColorStateList) mFillColors).getColorForState(stateSet, oldFillColor);
+                changed |= oldFillColor != newFillColor;
+                if (oldFillColor != newFillColor) {
+                    nSetFillColor(mNativePtr, newFillColor);
+                }
+            }
+
+            return changed;
+        }
+
+        @Override
+        public boolean isStateful() {
+            return mStrokeColors != null || mFillColors != null;
+        }
+
+        @Override
+        public boolean hasFocusStateSpecified() {
+            return (mStrokeColors != null && mStrokeColors instanceof ColorStateList &&
+                    ((ColorStateList) mStrokeColors).hasFocusStateSpecified()) &&
+                    (mFillColors != null && mFillColors instanceof ColorStateList &&
+                    ((ColorStateList) mFillColors).hasFocusStateSpecified());
+        }
+
+        @Override
+        int getNativeSize() {
+            return NATIVE_ALLOCATION_SIZE;
+        }
+
+        @Override
+        public long getNativePtr() {
+            return mNativePtr;
+        }
+
+        @Override
+        public void inflate(Resources r, AttributeSet attrs, Theme theme) {
+            final TypedArray a = obtainAttributes(r, theme, attrs,
+                    R.styleable.VectorDrawablePath);
+            updateStateFromTypedArray(a);
+            a.recycle();
+        }
+
+        private void updateStateFromTypedArray(TypedArray a) {
+            int byteCount = TOTAL_PROPERTY_COUNT * 4;
+            if (mPropertyData == null) {
+                // Lazy initialization: If the path is created through copy constructor, this may
+                // never get called.
+                mPropertyData = new byte[byteCount];
+            }
+            // The bulk getters/setters of property data (e.g. stroke width, color, etc) allows us
+            // to pull current values from native and store modifications with only two methods,
+            // minimizing JNI overhead.
+            boolean success = nGetFullPathProperties(mNativePtr, mPropertyData, byteCount);
+            if (!success) {
+                throw new RuntimeException("Error: inconsistent property count");
+            }
+
+            ByteBuffer properties = ByteBuffer.wrap(mPropertyData);
+            properties.order(ByteOrder.nativeOrder());
+            float strokeWidth = properties.getFloat(STROKE_WIDTH_INDEX * 4);
+            int strokeColor = properties.getInt(STROKE_COLOR_INDEX * 4);
+            float strokeAlpha = properties.getFloat(STROKE_ALPHA_INDEX * 4);
+            int fillColor =  properties.getInt(FILL_COLOR_INDEX * 4);
+            float fillAlpha = properties.getFloat(FILL_ALPHA_INDEX * 4);
+            float trimPathStart = properties.getFloat(TRIM_PATH_START_INDEX * 4);
+            float trimPathEnd = properties.getFloat(TRIM_PATH_END_INDEX * 4);
+            float trimPathOffset = properties.getFloat(TRIM_PATH_OFFSET_INDEX * 4);
+            int strokeLineCap =  properties.getInt(STROKE_LINE_CAP_INDEX * 4);
+            int strokeLineJoin = properties.getInt(STROKE_LINE_JOIN_INDEX * 4);
+            float strokeMiterLimit = properties.getFloat(STROKE_MITER_LIMIT_INDEX * 4);
+            int fillType = properties.getInt(FILL_TYPE_INDEX * 4);
+            Shader fillGradient = null;
+            Shader strokeGradient = null;
+            // Account for any configuration changes.
+            mChangingConfigurations |= a.getChangingConfigurations();
+
+            // Extract the theme attributes, if any.
+            mThemeAttrs = a.extractThemeAttrs();
+
+            final String pathName = a.getString(R.styleable.VectorDrawablePath_name);
+            if (pathName != null) {
+                mPathName = pathName;
+                nSetName(mNativePtr, mPathName);
+            }
+
+            final String pathString = a.getString(R.styleable.VectorDrawablePath_pathData);
+            if (pathString != null) {
+                mPathData = new PathParser.PathData(pathString);
+                nSetPathString(mNativePtr, pathString, pathString.length());
+            }
+
+            final ComplexColor fillColors = a.getComplexColor(
+                    R.styleable.VectorDrawablePath_fillColor);
+            if (fillColors != null) {
+                // If the colors is a gradient color, or the color state list is stateful, keep the
+                // colors information. Otherwise, discard the colors and keep the default color.
+                if (fillColors instanceof  GradientColor) {
+                    mFillColors = fillColors;
+                    fillGradient = ((GradientColor) fillColors).getShader();
+                } else if (fillColors.isStateful()) {
+                    mFillColors = fillColors;
+                } else {
+                    mFillColors = null;
+                }
+                fillColor = fillColors.getDefaultColor();
+            }
+
+            final ComplexColor strokeColors = a.getComplexColor(
+                    R.styleable.VectorDrawablePath_strokeColor);
+            if (strokeColors != null) {
+                // If the colors is a gradient color, or the color state list is stateful, keep the
+                // colors information. Otherwise, discard the colors and keep the default color.
+                if (strokeColors instanceof GradientColor) {
+                    mStrokeColors = strokeColors;
+                    strokeGradient = ((GradientColor) strokeColors).getShader();
+                } else if (strokeColors.isStateful()) {
+                    mStrokeColors = strokeColors;
+                } else {
+                    mStrokeColors = null;
+                }
+                strokeColor = strokeColors.getDefaultColor();
+            }
+            // Update the gradient info, even if the gradiet is null.
+            nUpdateFullPathFillGradient(mNativePtr,
+                    fillGradient != null ? fillGradient.getNativeInstance() : 0);
+            nUpdateFullPathStrokeGradient(mNativePtr,
+                    strokeGradient != null ? strokeGradient.getNativeInstance() : 0);
+
+            fillAlpha = a.getFloat(R.styleable.VectorDrawablePath_fillAlpha, fillAlpha);
+
+            strokeLineCap = a.getInt(
+                    R.styleable.VectorDrawablePath_strokeLineCap, strokeLineCap);
+            strokeLineJoin = a.getInt(
+                    R.styleable.VectorDrawablePath_strokeLineJoin, strokeLineJoin);
+            strokeMiterLimit = a.getFloat(
+                    R.styleable.VectorDrawablePath_strokeMiterLimit, strokeMiterLimit);
+            strokeAlpha = a.getFloat(R.styleable.VectorDrawablePath_strokeAlpha,
+                    strokeAlpha);
+            strokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth,
+                    strokeWidth);
+            trimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd,
+                    trimPathEnd);
+            trimPathOffset = a.getFloat(
+                    R.styleable.VectorDrawablePath_trimPathOffset, trimPathOffset);
+            trimPathStart = a.getFloat(
+                    R.styleable.VectorDrawablePath_trimPathStart, trimPathStart);
+            fillType = a.getInt(R.styleable.VectorDrawablePath_fillType, fillType);
+
+            nUpdateFullPathProperties(mNativePtr, strokeWidth, strokeColor, strokeAlpha,
+                    fillColor, fillAlpha, trimPathStart, trimPathEnd, trimPathOffset,
+                    strokeMiterLimit, strokeLineCap, strokeLineJoin, fillType);
+        }
+
+        @Override
+        public boolean canApplyTheme() {
+            if (mThemeAttrs != null) {
+                return true;
+            }
+
+            boolean fillCanApplyTheme = canComplexColorApplyTheme(mFillColors);
+            boolean strokeCanApplyTheme = canComplexColorApplyTheme(mStrokeColors);
+            if (fillCanApplyTheme || strokeCanApplyTheme) {
+                return true;
+            }
+            return false;
+
+        }
+
+        @Override
+        public void applyTheme(Theme t) {
+            // Resolve the theme attributes directly referred by the VectorDrawable.
+            if (mThemeAttrs != null) {
+                final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.VectorDrawablePath);
+                updateStateFromTypedArray(a);
+                a.recycle();
+            }
+
+            // Resolve the theme attributes in-directly referred by the VectorDrawable, for example,
+            // fillColor can refer to a color state list which itself needs to apply theme.
+            // And this is the reason we still want to keep partial update for the path's properties.
+            boolean fillCanApplyTheme = canComplexColorApplyTheme(mFillColors);
+            boolean strokeCanApplyTheme = canComplexColorApplyTheme(mStrokeColors);
+
+            if (fillCanApplyTheme) {
+                mFillColors = mFillColors.obtainForTheme(t);
+                if (mFillColors instanceof GradientColor) {
+                    nUpdateFullPathFillGradient(mNativePtr,
+                            ((GradientColor) mFillColors).getShader().getNativeInstance());
+                } else if (mFillColors instanceof ColorStateList) {
+                    nSetFillColor(mNativePtr, mFillColors.getDefaultColor());
+                }
+            }
+
+            if (strokeCanApplyTheme) {
+                mStrokeColors = mStrokeColors.obtainForTheme(t);
+                if (mStrokeColors instanceof GradientColor) {
+                    nUpdateFullPathStrokeGradient(mNativePtr,
+                            ((GradientColor) mStrokeColors).getShader().getNativeInstance());
+                } else if (mStrokeColors instanceof ColorStateList) {
+                    nSetStrokeColor(mNativePtr, mStrokeColors.getDefaultColor());
+                }
+            }
+        }
+
+        private boolean canComplexColorApplyTheme(ComplexColor complexColor) {
+            return complexColor != null && complexColor.canApplyTheme();
+        }
+
+        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
+        @SuppressWarnings("unused")
+        int getStrokeColor() {
+            return isTreeValid() ? nGetStrokeColor(mNativePtr) : 0;
+        }
+
+        @SuppressWarnings("unused")
+        void setStrokeColor(int strokeColor) {
+            mStrokeColors = null;
+            if (isTreeValid()) {
+                nSetStrokeColor(mNativePtr, strokeColor);
+            }
+        }
+
+        @SuppressWarnings("unused")
+        float getStrokeWidth() {
+            return isTreeValid() ? nGetStrokeWidth(mNativePtr) : 0;
+        }
+
+        @SuppressWarnings("unused")
+        void setStrokeWidth(float strokeWidth) {
+            if (isTreeValid()) {
+                nSetStrokeWidth(mNativePtr, strokeWidth);
+            }
+        }
+
+        @SuppressWarnings("unused")
+        float getStrokeAlpha() {
+            return isTreeValid() ? nGetStrokeAlpha(mNativePtr) : 0;
+        }
+
+        @SuppressWarnings("unused")
+        void setStrokeAlpha(float strokeAlpha) {
+            if (isTreeValid()) {
+                nSetStrokeAlpha(mNativePtr, strokeAlpha);
+            }
+        }
+
+        @SuppressWarnings("unused")
+        int getFillColor() {
+            return isTreeValid() ? nGetFillColor(mNativePtr) : 0;
+        }
+
+        @SuppressWarnings("unused")
+        void setFillColor(int fillColor) {
+            mFillColors = null;
+            if (isTreeValid()) {
+                nSetFillColor(mNativePtr, fillColor);
+            }
+        }
+
+        @SuppressWarnings("unused")
+        float getFillAlpha() {
+            return isTreeValid() ? nGetFillAlpha(mNativePtr) : 0;
+        }
+
+        @SuppressWarnings("unused")
+        void setFillAlpha(float fillAlpha) {
+            if (isTreeValid()) {
+                nSetFillAlpha(mNativePtr, fillAlpha);
+            }
+        }
+
+        @SuppressWarnings("unused")
+        float getTrimPathStart() {
+            return isTreeValid() ? nGetTrimPathStart(mNativePtr) : 0;
+        }
+
+        @SuppressWarnings("unused")
+        void setTrimPathStart(float trimPathStart) {
+            if (isTreeValid()) {
+                nSetTrimPathStart(mNativePtr, trimPathStart);
+            }
+        }
+
+        @SuppressWarnings("unused")
+        float getTrimPathEnd() {
+            return isTreeValid() ? nGetTrimPathEnd(mNativePtr) : 0;
+        }
+
+        @SuppressWarnings("unused")
+        void setTrimPathEnd(float trimPathEnd) {
+            if (isTreeValid()) {
+                nSetTrimPathEnd(mNativePtr, trimPathEnd);
+            }
+        }
+
+        @SuppressWarnings("unused")
+        float getTrimPathOffset() {
+            return isTreeValid() ? nGetTrimPathOffset(mNativePtr) : 0;
+        }
+
+        @SuppressWarnings("unused")
+        void setTrimPathOffset(float trimPathOffset) {
+            if (isTreeValid()) {
+                nSetTrimPathOffset(mNativePtr, trimPathOffset);
+            }
+        }
+    }
+
+    abstract static class VObject {
+        VirtualRefBasePtr mTreePtr = null;
+        boolean isTreeValid() {
+            return mTreePtr != null && mTreePtr.get() != 0;
+        }
+        void setTree(VirtualRefBasePtr ptr) {
+            mTreePtr = ptr;
+        }
+        abstract long getNativePtr();
+        abstract void inflate(Resources r, AttributeSet attrs, Theme theme);
+        abstract boolean canApplyTheme();
+        abstract void applyTheme(Theme t);
+        abstract boolean onStateChange(int[] state);
+        abstract boolean isStateful();
+        abstract boolean hasFocusStateSpecified();
+        abstract int getNativeSize();
+        abstract Property getProperty(String propertyName);
+    }
+
+    private static native int nDraw(long rendererPtr, long canvasWrapperPtr,
+            long colorFilterPtr, Rect bounds, boolean needsMirroring, boolean canReuseCache);
+    private static native boolean nGetFullPathProperties(long pathPtr, byte[] properties,
+            int length);
+    private static native void nSetName(long nodePtr, String name);
+    private static native boolean nGetGroupProperties(long groupPtr, float[] properties,
+            int length);
+    private static native void nSetPathString(long pathPtr, String pathString, int length);
+
+    // ------------- @FastNative ------------------
+
+    @FastNative
+    private static native long nCreateTree(long rootGroupPtr);
+    @FastNative
+    private static native long nCreateTreeFromCopy(long treeToCopy, long rootGroupPtr);
+    @FastNative
+    private static native void nSetRendererViewportSize(long rendererPtr, float viewportWidth,
+            float viewportHeight);
+    @FastNative
+    private static native boolean nSetRootAlpha(long rendererPtr, float alpha);
+    @FastNative
+    private static native float nGetRootAlpha(long rendererPtr);
+    @FastNative
+    private static native void nSetAllowCaching(long rendererPtr, boolean allowCaching);
+
+    @FastNative
+    private static native long nCreateFullPath();
+    @FastNative
+    private static native long nCreateFullPath(long nativeFullPathPtr);
+
+    @FastNative
+    private static native void nUpdateFullPathProperties(long pathPtr, float strokeWidth,
+            int strokeColor, float strokeAlpha, int fillColor, float fillAlpha, float trimPathStart,
+            float trimPathEnd, float trimPathOffset, float strokeMiterLimit, int strokeLineCap,
+            int strokeLineJoin, int fillType);
+    @FastNative
+    private static native void nUpdateFullPathFillGradient(long pathPtr, long fillGradientPtr);
+    @FastNative
+    private static native void nUpdateFullPathStrokeGradient(long pathPtr, long strokeGradientPtr);
+
+    @FastNative
+    private static native long nCreateClipPath();
+    @FastNative
+    private static native long nCreateClipPath(long clipPathPtr);
+
+    @FastNative
+    private static native long nCreateGroup();
+    @FastNative
+    private static native long nCreateGroup(long groupPtr);
+    @FastNative
+    private static native void nUpdateGroupProperties(long groupPtr, float rotate, float pivotX,
+            float pivotY, float scaleX, float scaleY, float translateX, float translateY);
+
+    @FastNative
+    private static native void nAddChild(long groupPtr, long nodePtr);
+
+    /**
+     * The setters and getters below for paths and groups are here temporarily, and will be
+     * removed once the animation in AVD is replaced with RenderNodeAnimator, in which case the
+     * animation will modify these properties in native. By then no JNI hopping would be necessary
+     * for VD during animation, and these setters and getters will be obsolete.
+     */
+    // Setters and getters during animation.
+    @FastNative
+    private static native float nGetRotation(long groupPtr);
+    @FastNative
+    private static native void nSetRotation(long groupPtr, float rotation);
+    @FastNative
+    private static native float nGetPivotX(long groupPtr);
+    @FastNative
+    private static native void nSetPivotX(long groupPtr, float pivotX);
+    @FastNative
+    private static native float nGetPivotY(long groupPtr);
+    @FastNative
+    private static native void nSetPivotY(long groupPtr, float pivotY);
+    @FastNative
+    private static native float nGetScaleX(long groupPtr);
+    @FastNative
+    private static native void nSetScaleX(long groupPtr, float scaleX);
+    @FastNative
+    private static native float nGetScaleY(long groupPtr);
+    @FastNative
+    private static native void nSetScaleY(long groupPtr, float scaleY);
+    @FastNative
+    private static native float nGetTranslateX(long groupPtr);
+    @FastNative
+    private static native void nSetTranslateX(long groupPtr, float translateX);
+    @FastNative
+    private static native float nGetTranslateY(long groupPtr);
+    @FastNative
+    private static native void nSetTranslateY(long groupPtr, float translateY);
+
+    // Setters and getters for VPath during animation.
+    @FastNative
+    private static native void nSetPathData(long pathPtr, long pathDataPtr);
+    @FastNative
+    private static native float nGetStrokeWidth(long pathPtr);
+    @FastNative
+    private static native void nSetStrokeWidth(long pathPtr, float width);
+    @FastNative
+    private static native int nGetStrokeColor(long pathPtr);
+    @FastNative
+    private static native void nSetStrokeColor(long pathPtr, int strokeColor);
+    @FastNative
+    private static native float nGetStrokeAlpha(long pathPtr);
+    @FastNative
+    private static native void nSetStrokeAlpha(long pathPtr, float alpha);
+    @FastNative
+    private static native int nGetFillColor(long pathPtr);
+    @FastNative
+    private static native void nSetFillColor(long pathPtr, int fillColor);
+    @FastNative
+    private static native float nGetFillAlpha(long pathPtr);
+    @FastNative
+    private static native void nSetFillAlpha(long pathPtr, float fillAlpha);
+    @FastNative
+    private static native float nGetTrimPathStart(long pathPtr);
+    @FastNative
+    private static native void nSetTrimPathStart(long pathPtr, float trimPathStart);
+    @FastNative
+    private static native float nGetTrimPathEnd(long pathPtr);
+    @FastNative
+    private static native void nSetTrimPathEnd(long pathPtr, float trimPathEnd);
+    @FastNative
+    private static native float nGetTrimPathOffset(long pathPtr);
+    @FastNative
+    private static native void nSetTrimPathOffset(long pathPtr, float trimPathOffset);
+}
diff --git a/android/graphics/drawable/VectorDrawable_Delegate.java b/android/graphics/drawable/VectorDrawable_Delegate.java
new file mode 100644
index 0000000..5fa7102
--- /dev/null
+++ b/android/graphics/drawable/VectorDrawable_Delegate.java
@@ -0,0 +1,1276 @@
+/*
+ * Copyright (C) 2016 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 com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.annotation.NonNull;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.BaseCanvas_Delegate;
+import android.graphics.Canvas_Delegate;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Paint.Cap;
+import android.graphics.Paint.Join;
+import android.graphics.Paint_Delegate;
+import android.graphics.Path;
+import android.graphics.PathMeasure;
+import android.graphics.Path_Delegate;
+import android.graphics.Rect;
+import android.graphics.Region.Op;
+import android.graphics.Shader_Delegate;
+import android.util.ArrayMap;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.MathUtils;
+import android.util.PathParser_Delegate;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+import static android.graphics.Canvas.CLIP_SAVE_FLAG;
+import static android.graphics.Canvas.MATRIX_SAVE_FLAG;
+import static android.graphics.Paint.Cap.BUTT;
+import static android.graphics.Paint.Cap.ROUND;
+import static android.graphics.Paint.Cap.SQUARE;
+import static android.graphics.Paint.Join.BEVEL;
+import static android.graphics.Paint.Join.MITER;
+import static android.graphics.Paint.Style;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link VectorDrawable}
+ * <p>
+ * Through the layoutlib_create tool, the original  methods of VectorDrawable have been replaced by
+ * calls to methods of the same name in this delegate class.
+ */
+@SuppressWarnings("unused")
+public class VectorDrawable_Delegate {
+    private static final String LOGTAG = VectorDrawable_Delegate.class.getSimpleName();
+    private static final boolean DBG_VECTOR_DRAWABLE = false;
+
+    private static final DelegateManager<VNativeObject> sPathManager =
+            new DelegateManager<>(VNativeObject.class);
+
+    private static long addNativeObject(VNativeObject object) {
+        long ptr = sPathManager.addNewDelegate(object);
+        object.setNativePtr(ptr);
+
+        return ptr;
+    }
+
+    /**
+     * Obtains styled attributes from the theme, if available, or unstyled resources if the theme is
+     * null.
+     */
+    private static TypedArray obtainAttributes(
+            Resources res, Theme theme, AttributeSet set, int[] attrs) {
+        if (theme == null) {
+            return res.obtainAttributes(set, attrs);
+        }
+        return theme.obtainStyledAttributes(set, attrs, 0, 0);
+    }
+
+    private static int applyAlpha(int color, float alpha) {
+        int alphaBytes = Color.alpha(color);
+        color &= 0x00FFFFFF;
+        color |= ((int) (alphaBytes * alpha)) << 24;
+        return color;
+    }
+
+    @LayoutlibDelegate
+    static long nCreateTree(long rootGroupPtr) {
+        return addNativeObject(new VPathRenderer_Delegate(rootGroupPtr));
+    }
+
+    @LayoutlibDelegate
+    static long nCreateTreeFromCopy(long rendererToCopyPtr, long rootGroupPtr) {
+        VPathRenderer_Delegate rendererToCopy = VNativeObject.getDelegate(rendererToCopyPtr);
+        return addNativeObject(new VPathRenderer_Delegate(rendererToCopy,
+                rootGroupPtr));
+    }
+
+    @LayoutlibDelegate
+    static void nSetRendererViewportSize(long rendererPtr, float viewportWidth,
+            float viewportHeight) {
+        VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
+        nativePathRenderer.mViewportWidth = viewportWidth;
+        nativePathRenderer.mViewportHeight = viewportHeight;
+    }
+
+    @LayoutlibDelegate
+    static boolean nSetRootAlpha(long rendererPtr, float alpha) {
+        VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
+        nativePathRenderer.setRootAlpha(alpha);
+
+        return true;
+    }
+
+    @LayoutlibDelegate
+    static float nGetRootAlpha(long rendererPtr) {
+        VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
+
+        return nativePathRenderer.getRootAlpha();
+    }
+
+    @LayoutlibDelegate
+    static void nSetAllowCaching(long rendererPtr, boolean allowCaching) {
+        // ignored
+    }
+
+    @LayoutlibDelegate
+    static int nDraw(long rendererPtr, long canvasWrapperPtr,
+            long colorFilterPtr, Rect bounds, boolean needsMirroring, boolean canReuseCache) {
+        VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
+
+        Canvas_Delegate.nSave(canvasWrapperPtr, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
+        Canvas_Delegate.nTranslate(canvasWrapperPtr, bounds.left, bounds.top);
+
+        if (needsMirroring) {
+            Canvas_Delegate.nTranslate(canvasWrapperPtr, bounds.width(), 0);
+            Canvas_Delegate.nScale(canvasWrapperPtr, -1.0f, 1.0f);
+        }
+
+        // At this point, canvas has been translated to the right position.
+        // And we use this bound for the destination rect for the drawBitmap, so
+        // we offset to (0, 0);
+        bounds.offsetTo(0, 0);
+        nativePathRenderer.draw(canvasWrapperPtr, colorFilterPtr, bounds.width(), bounds.height());
+
+        Canvas_Delegate.nRestore(canvasWrapperPtr);
+
+        return bounds.width() * bounds.height();
+    }
+
+    @LayoutlibDelegate
+    static long nCreateFullPath() {
+        return addNativeObject(new VFullPath_Delegate());
+    }
+
+    @LayoutlibDelegate
+    static long nCreateFullPath(long nativeFullPathPtr) {
+        VFullPath_Delegate original = VNativeObject.getDelegate(nativeFullPathPtr);
+        return addNativeObject(new VFullPath_Delegate(original));
+    }
+
+    @LayoutlibDelegate
+    static boolean nGetFullPathProperties(long pathPtr, byte[] propertiesData,
+            int length) {
+        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+
+        ByteBuffer properties = ByteBuffer.wrap(propertiesData);
+        properties.order(ByteOrder.nativeOrder());
+
+        properties.putFloat(VFullPath_Delegate.STROKE_WIDTH_INDEX * 4, path.getStrokeWidth());
+        properties.putInt(VFullPath_Delegate.STROKE_COLOR_INDEX * 4, path.getStrokeColor());
+        properties.putFloat(VFullPath_Delegate.STROKE_ALPHA_INDEX * 4, path.getStrokeAlpha());
+        properties.putInt(VFullPath_Delegate.FILL_COLOR_INDEX * 4, path.getFillColor());
+        properties.putFloat(VFullPath_Delegate.FILL_ALPHA_INDEX * 4, path.getStrokeAlpha());
+        properties.putFloat(VFullPath_Delegate.TRIM_PATH_START_INDEX * 4, path.getTrimPathStart());
+        properties.putFloat(VFullPath_Delegate.TRIM_PATH_END_INDEX * 4, path.getTrimPathEnd());
+        properties.putFloat(VFullPath_Delegate.TRIM_PATH_OFFSET_INDEX * 4,
+                path.getTrimPathOffset());
+        properties.putInt(VFullPath_Delegate.STROKE_LINE_CAP_INDEX * 4, path.getStrokeLineCap());
+        properties.putInt(VFullPath_Delegate.STROKE_LINE_JOIN_INDEX * 4, path.getStrokeLineJoin());
+        properties.putFloat(VFullPath_Delegate.STROKE_MITER_LIMIT_INDEX * 4,
+                path.getStrokeMiterlimit());
+        properties.putInt(VFullPath_Delegate.FILL_TYPE_INDEX * 4, path.getFillType());
+
+        return true;
+    }
+
+    @LayoutlibDelegate
+    static void nUpdateFullPathProperties(long pathPtr, float strokeWidth,
+            int strokeColor, float strokeAlpha, int fillColor, float fillAlpha, float trimPathStart,
+            float trimPathEnd, float trimPathOffset, float strokeMiterLimit, int strokeLineCap,
+            int strokeLineJoin, int fillType) {
+        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+
+        path.setStrokeWidth(strokeWidth);
+        path.setStrokeColor(strokeColor);
+        path.setStrokeAlpha(strokeAlpha);
+        path.setFillColor(fillColor);
+        path.setFillAlpha(fillAlpha);
+        path.setTrimPathStart(trimPathStart);
+        path.setTrimPathEnd(trimPathEnd);
+        path.setTrimPathOffset(trimPathOffset);
+        path.setStrokeMiterlimit(strokeMiterLimit);
+        path.setStrokeLineCap(strokeLineCap);
+        path.setStrokeLineJoin(strokeLineJoin);
+        path.setFillType(fillType);
+    }
+
+    @LayoutlibDelegate
+    static void nUpdateFullPathFillGradient(long pathPtr, long fillGradientPtr) {
+        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+
+        path.setFillGradient(fillGradientPtr);
+    }
+
+    @LayoutlibDelegate
+    static void nUpdateFullPathStrokeGradient(long pathPtr, long strokeGradientPtr) {
+        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+
+        path.setStrokeGradient(strokeGradientPtr);
+    }
+
+    @LayoutlibDelegate
+    static long nCreateClipPath() {
+        return addNativeObject(new VClipPath_Delegate());
+    }
+
+    @LayoutlibDelegate
+    static long nCreateClipPath(long clipPathPtr) {
+        VClipPath_Delegate original = VNativeObject.getDelegate(clipPathPtr);
+        return addNativeObject(new VClipPath_Delegate(original));
+    }
+
+    @LayoutlibDelegate
+    static long nCreateGroup() {
+        return addNativeObject(new VGroup_Delegate());
+    }
+
+    @LayoutlibDelegate
+    static long nCreateGroup(long groupPtr) {
+        VGroup_Delegate original = VNativeObject.getDelegate(groupPtr);
+        return addNativeObject(new VGroup_Delegate(original, new ArrayMap<>()));
+    }
+
+    @LayoutlibDelegate
+    static void nSetName(long nodePtr, String name) {
+        VNativeObject group = VNativeObject.getDelegate(nodePtr);
+        group.setName(name);
+    }
+
+    @LayoutlibDelegate
+    static boolean nGetGroupProperties(long groupPtr, float[] propertiesData,
+            int length) {
+        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+
+        FloatBuffer properties = FloatBuffer.wrap(propertiesData);
+
+        properties.put(VGroup_Delegate.ROTATE_INDEX, group.getRotation());
+        properties.put(VGroup_Delegate.PIVOT_X_INDEX, group.getPivotX());
+        properties.put(VGroup_Delegate.PIVOT_Y_INDEX, group.getPivotY());
+        properties.put(VGroup_Delegate.SCALE_X_INDEX, group.getScaleX());
+        properties.put(VGroup_Delegate.SCALE_Y_INDEX, group.getScaleY());
+        properties.put(VGroup_Delegate.TRANSLATE_X_INDEX, group.getTranslateX());
+        properties.put(VGroup_Delegate.TRANSLATE_Y_INDEX, group.getTranslateY());
+
+        return true;
+    }
+    @LayoutlibDelegate
+    static void nUpdateGroupProperties(long groupPtr, float rotate, float pivotX,
+            float pivotY, float scaleX, float scaleY, float translateX, float translateY) {
+        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+
+        group.setRotation(rotate);
+        group.setPivotX(pivotX);
+        group.setPivotY(pivotY);
+        group.setScaleX(scaleX);
+        group.setScaleY(scaleY);
+        group.setTranslateX(translateX);
+        group.setTranslateY(translateY);
+    }
+
+    @LayoutlibDelegate
+    static void nAddChild(long groupPtr, long nodePtr) {
+        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+        group.mChildren.add(VNativeObject.getDelegate(nodePtr));
+    }
+
+    @LayoutlibDelegate
+    static void nSetPathString(long pathPtr, String pathString, int length) {
+        VPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+        path.setPathData(PathParser_Delegate.createNodesFromPathData(pathString));
+    }
+
+    /**
+     * The setters and getters below for paths and groups are here temporarily, and will be removed
+     * once the animation in AVD is replaced with RenderNodeAnimator, in which case the animation
+     * will modify these properties in native. By then no JNI hopping would be necessary for VD
+     * during animation, and these setters and getters will be obsolete.
+     */
+    // Setters and getters during animation.
+    @LayoutlibDelegate
+    static float nGetRotation(long groupPtr) {
+        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+        return group.getRotation();
+    }
+
+    @LayoutlibDelegate
+    static void nSetRotation(long groupPtr, float rotation) {
+        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+        group.setRotation(rotation);
+    }
+
+    @LayoutlibDelegate
+    static float nGetPivotX(long groupPtr) {
+        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+        return group.getPivotX();
+    }
+
+    @LayoutlibDelegate
+    static void nSetPivotX(long groupPtr, float pivotX) {
+        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+        group.setPivotX(pivotX);
+    }
+
+    @LayoutlibDelegate
+    static float nGetPivotY(long groupPtr) {
+        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+        return group.getPivotY();
+    }
+
+    @LayoutlibDelegate
+    static void nSetPivotY(long groupPtr, float pivotY) {
+        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+        group.setPivotY(pivotY);
+    }
+
+    @LayoutlibDelegate
+    static float nGetScaleX(long groupPtr) {
+        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+        return group.getScaleX();
+    }
+
+    @LayoutlibDelegate
+    static void nSetScaleX(long groupPtr, float scaleX) {
+        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+        group.setScaleX(scaleX);
+    }
+
+    @LayoutlibDelegate
+    static float nGetScaleY(long groupPtr) {
+        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+        return group.getScaleY();
+    }
+
+    @LayoutlibDelegate
+    static void nSetScaleY(long groupPtr, float scaleY) {
+        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+        group.setScaleY(scaleY);
+    }
+
+    @LayoutlibDelegate
+    static float nGetTranslateX(long groupPtr) {
+        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+        return group.getTranslateX();
+    }
+
+    @LayoutlibDelegate
+    static void nSetTranslateX(long groupPtr, float translateX) {
+        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+        group.setTranslateX(translateX);
+    }
+
+    @LayoutlibDelegate
+    static float nGetTranslateY(long groupPtr) {
+        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+        return group.getTranslateY();
+    }
+
+    @LayoutlibDelegate
+    static void nSetTranslateY(long groupPtr, float translateY) {
+        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+        group.setTranslateY(translateY);
+    }
+
+    @LayoutlibDelegate
+    static void nSetPathData(long pathPtr, long pathDataPtr) {
+        VPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+        path.setPathData(PathParser_Delegate.getDelegate(pathDataPtr).getPathDataNodes());
+    }
+
+    @LayoutlibDelegate
+    static float nGetStrokeWidth(long pathPtr) {
+        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+        return path.getStrokeWidth();
+    }
+
+    @LayoutlibDelegate
+    static void nSetStrokeWidth(long pathPtr, float width) {
+        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+        path.setStrokeWidth(width);
+    }
+
+    @LayoutlibDelegate
+    static int nGetStrokeColor(long pathPtr) {
+        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+        return path.getStrokeColor();
+    }
+
+    @LayoutlibDelegate
+    static void nSetStrokeColor(long pathPtr, int strokeColor) {
+        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+        path.setStrokeColor(strokeColor);
+    }
+
+    @LayoutlibDelegate
+    static float nGetStrokeAlpha(long pathPtr) {
+        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+        return path.getStrokeAlpha();
+    }
+
+    @LayoutlibDelegate
+    static void nSetStrokeAlpha(long pathPtr, float alpha) {
+        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+        path.setStrokeAlpha(alpha);
+    }
+
+    @LayoutlibDelegate
+    static int nGetFillColor(long pathPtr) {
+        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+        return path.getFillColor();
+    }
+
+    @LayoutlibDelegate
+    static void nSetFillColor(long pathPtr, int fillColor) {
+        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+        path.setFillColor(fillColor);
+    }
+
+    @LayoutlibDelegate
+    static float nGetFillAlpha(long pathPtr) {
+        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+        return path.getFillAlpha();
+    }
+
+    @LayoutlibDelegate
+    static void nSetFillAlpha(long pathPtr, float fillAlpha) {
+        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+        path.setFillAlpha(fillAlpha);
+    }
+
+    @LayoutlibDelegate
+    static float nGetTrimPathStart(long pathPtr) {
+        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+        return path.getTrimPathStart();
+    }
+
+    @LayoutlibDelegate
+    static void nSetTrimPathStart(long pathPtr, float trimPathStart) {
+        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+        path.setTrimPathStart(trimPathStart);
+    }
+
+    @LayoutlibDelegate
+    static float nGetTrimPathEnd(long pathPtr) {
+        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+        return path.getTrimPathEnd();
+    }
+
+    @LayoutlibDelegate
+    static void nSetTrimPathEnd(long pathPtr, float trimPathEnd) {
+        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+        path.setTrimPathEnd(trimPathEnd);
+    }
+
+    @LayoutlibDelegate
+    static float nGetTrimPathOffset(long pathPtr) {
+        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+        return path.getTrimPathOffset();
+    }
+
+    @LayoutlibDelegate
+    static void nSetTrimPathOffset(long pathPtr, float trimPathOffset) {
+        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
+        path.setTrimPathOffset(trimPathOffset);
+    }
+
+    /**
+     * Base class for all the internal Delegates that does two functions:
+     * <ol>
+     *     <li>Serves as base class to store all the delegates in one {@link DelegateManager}
+     *     <li>Provides setName for all the classes. {@link VPathRenderer_Delegate} does actually
+     *     not need it
+     * </ol>
+     */
+    abstract static class VNativeObject {
+        long mNativePtr = 0;
+
+        @NonNull
+        static <T> T getDelegate(long nativePtr) {
+            //noinspection unchecked
+            T vNativeObject = (T) sPathManager.getDelegate(nativePtr);
+
+            assert vNativeObject != null;
+            return vNativeObject;
+        }
+
+        abstract void setName(String name);
+
+        void setNativePtr(long nativePtr) {
+            mNativePtr = nativePtr;
+        }
+
+        /**
+         * Method to explicitly dispose native objects
+         */
+        void dispose() {
+        }
+    }
+
+    private static class VClipPath_Delegate extends VPath_Delegate {
+        private VClipPath_Delegate() {
+            // Empty constructor.
+        }
+
+        private VClipPath_Delegate(VClipPath_Delegate copy) {
+            super(copy);
+        }
+
+        @Override
+        public boolean isClipPath() {
+            return true;
+        }
+    }
+
+    static class VFullPath_Delegate extends VPath_Delegate {
+        // These constants need to be kept in sync with their values in VectorDrawable.VFullPath
+        private static final int STROKE_WIDTH_INDEX = 0;
+        private static final int STROKE_COLOR_INDEX = 1;
+        private static final int STROKE_ALPHA_INDEX = 2;
+        private static final int FILL_COLOR_INDEX = 3;
+        private static final int FILL_ALPHA_INDEX = 4;
+        private static final int TRIM_PATH_START_INDEX = 5;
+        private static final int TRIM_PATH_END_INDEX = 6;
+        private static final int TRIM_PATH_OFFSET_INDEX = 7;
+        private static final int STROKE_LINE_CAP_INDEX = 8;
+        private static final int STROKE_LINE_JOIN_INDEX = 9;
+        private static final int STROKE_MITER_LIMIT_INDEX = 10;
+        private static final int FILL_TYPE_INDEX = 11;
+
+        private static final int LINECAP_BUTT = 0;
+        private static final int LINECAP_ROUND = 1;
+        private static final int LINECAP_SQUARE = 2;
+
+        private static final int LINEJOIN_MITER = 0;
+        private static final int LINEJOIN_ROUND = 1;
+        private static final int LINEJOIN_BEVEL = 2;
+
+        @NonNull
+        public Consumer<Float> getFloatPropertySetter(int propertyIdx) {
+            switch (propertyIdx) {
+                case STROKE_WIDTH_INDEX:
+                    return this::setStrokeWidth;
+                case STROKE_ALPHA_INDEX:
+                    return this::setStrokeAlpha;
+                case FILL_ALPHA_INDEX:
+                    return this::setFillAlpha;
+                case TRIM_PATH_START_INDEX:
+                    return this::setTrimPathStart;
+                case TRIM_PATH_END_INDEX:
+                    return this::setTrimPathEnd;
+                case TRIM_PATH_OFFSET_INDEX:
+                    return this::setTrimPathOffset;
+            }
+
+            assert false : ("Invalid VFullPath_Delegate property index " + propertyIdx);
+            return t -> {};
+        }
+
+        @NonNull
+        public Consumer<Integer> getIntPropertySetter(int propertyIdx) {
+            switch (propertyIdx) {
+                case STROKE_COLOR_INDEX:
+                    return this::setStrokeColor;
+                case FILL_COLOR_INDEX:
+                    return this::setFillColor;
+            }
+
+            assert false : ("Invalid VFullPath_Delegate property index " + propertyIdx);
+            return t -> {};
+        }
+
+        /////////////////////////////////////////////////////
+        // Variables below need to be copied (deep copy if applicable) for mutation.
+
+        int mStrokeColor = Color.TRANSPARENT;
+        float mStrokeWidth = 0;
+
+        int mFillColor = Color.TRANSPARENT;
+        long mStrokeGradient = 0;
+        long mFillGradient = 0;
+        float mStrokeAlpha = 1.0f;
+        float mFillAlpha = 1.0f;
+        float mTrimPathStart = 0;
+        float mTrimPathEnd = 1;
+        float mTrimPathOffset = 0;
+
+        Cap mStrokeLineCap = BUTT;
+        Join mStrokeLineJoin = MITER;
+        float mStrokeMiterlimit = 4;
+
+        int mFillType = 0; // WINDING(0) is the default value. See Path.FillType
+
+        private VFullPath_Delegate() {
+            // Empty constructor.
+        }
+
+        private VFullPath_Delegate(VFullPath_Delegate copy) {
+            super(copy);
+
+            mStrokeColor = copy.mStrokeColor;
+            mStrokeWidth = copy.mStrokeWidth;
+            mStrokeAlpha = copy.mStrokeAlpha;
+            mFillColor = copy.mFillColor;
+            mFillAlpha = copy.mFillAlpha;
+            mTrimPathStart = copy.mTrimPathStart;
+            mTrimPathEnd = copy.mTrimPathEnd;
+            mTrimPathOffset = copy.mTrimPathOffset;
+
+            mStrokeLineCap = copy.mStrokeLineCap;
+            mStrokeLineJoin = copy.mStrokeLineJoin;
+            mStrokeMiterlimit = copy.mStrokeMiterlimit;
+
+            mStrokeGradient = copy.mStrokeGradient;
+            mFillGradient = copy.mFillGradient;
+            mFillType = copy.mFillType;
+        }
+
+        private int getStrokeLineCap() {
+            switch (mStrokeLineCap) {
+                case BUTT:
+                    return LINECAP_BUTT;
+                case ROUND:
+                    return LINECAP_ROUND;
+                case SQUARE:
+                    return LINECAP_SQUARE;
+                default:
+                    assert false;
+            }
+
+            return -1;
+        }
+
+        private void setStrokeLineCap(int cap) {
+            switch (cap) {
+                case LINECAP_BUTT:
+                    mStrokeLineCap = BUTT;
+                    break;
+                case LINECAP_ROUND:
+                    mStrokeLineCap = ROUND;
+                    break;
+                case LINECAP_SQUARE:
+                    mStrokeLineCap = SQUARE;
+                    break;
+                default:
+                    assert false;
+            }
+        }
+
+        private int getStrokeLineJoin() {
+            switch (mStrokeLineJoin) {
+                case MITER:
+                    return LINEJOIN_MITER;
+                case ROUND:
+                    return LINEJOIN_ROUND;
+                case BEVEL:
+                    return LINEJOIN_BEVEL;
+                default:
+                    assert false;
+            }
+
+            return -1;
+        }
+
+        private void setStrokeLineJoin(int join) {
+            switch (join) {
+                case LINEJOIN_BEVEL:
+                    mStrokeLineJoin = BEVEL;
+                    break;
+                case LINEJOIN_MITER:
+                    mStrokeLineJoin = MITER;
+                    break;
+                case LINEJOIN_ROUND:
+                    mStrokeLineJoin = Join.ROUND;
+                    break;
+                default:
+                    assert false;
+            }
+        }
+
+        private int getStrokeColor() {
+            return mStrokeColor;
+        }
+
+        private void setStrokeColor(int strokeColor) {
+            mStrokeColor = strokeColor;
+        }
+
+        private float getStrokeWidth() {
+            return mStrokeWidth;
+        }
+
+        private void setStrokeWidth(float strokeWidth) {
+            mStrokeWidth = strokeWidth;
+        }
+
+        private float getStrokeAlpha() {
+            return mStrokeAlpha;
+        }
+
+        private void setStrokeAlpha(float strokeAlpha) {
+            mStrokeAlpha = strokeAlpha;
+        }
+
+        private int getFillColor() {
+            return mFillColor;
+        }
+
+        private void setFillColor(int fillColor) {
+            mFillColor = fillColor;
+        }
+
+        private float getFillAlpha() {
+            return mFillAlpha;
+        }
+
+        private void setFillAlpha(float fillAlpha) {
+            mFillAlpha = fillAlpha;
+        }
+
+        private float getTrimPathStart() {
+            return mTrimPathStart;
+        }
+
+        private void setTrimPathStart(float trimPathStart) {
+            mTrimPathStart = trimPathStart;
+        }
+
+        private float getTrimPathEnd() {
+            return mTrimPathEnd;
+        }
+
+        private void setTrimPathEnd(float trimPathEnd) {
+            mTrimPathEnd = trimPathEnd;
+        }
+
+        private float getTrimPathOffset() {
+            return mTrimPathOffset;
+        }
+
+        private void setTrimPathOffset(float trimPathOffset) {
+            mTrimPathOffset = trimPathOffset;
+        }
+
+        private void setStrokeMiterlimit(float limit) {
+            mStrokeMiterlimit = limit;
+        }
+
+        private float getStrokeMiterlimit() {
+            return mStrokeMiterlimit;
+        }
+
+        private void setStrokeGradient(long gradientPtr) {
+            mStrokeGradient = gradientPtr;
+        }
+
+        private void setFillGradient(long gradientPtr) {
+            mFillGradient = gradientPtr;
+        }
+
+        private void setFillType(int fillType) {
+            mFillType = fillType;
+        }
+
+        private int getFillType() {
+            return mFillType;
+        }
+    }
+
+    static class VGroup_Delegate extends VNativeObject {
+        // This constants need to be kept in sync with their definitions in VectorDrawable.Group
+        private static final int ROTATE_INDEX = 0;
+        private static final int PIVOT_X_INDEX = 1;
+        private static final int PIVOT_Y_INDEX = 2;
+        private static final int SCALE_X_INDEX = 3;
+        private static final int SCALE_Y_INDEX = 4;
+        private static final int TRANSLATE_X_INDEX = 5;
+        private static final int TRANSLATE_Y_INDEX = 6;
+
+        public Consumer<Float> getPropertySetter(int propertyIdx) {
+            switch (propertyIdx) {
+                case ROTATE_INDEX:
+                    return this::setRotation;
+                case PIVOT_X_INDEX:
+                    return this::setPivotX;
+                case PIVOT_Y_INDEX:
+                    return this::setPivotY;
+                case SCALE_X_INDEX:
+                    return this::setScaleX;
+                case SCALE_Y_INDEX:
+                    return this::setScaleY;
+                case TRANSLATE_X_INDEX:
+                    return this::setTranslateX;
+                case TRANSLATE_Y_INDEX:
+                    return this::setTranslateY;
+            }
+
+            assert false : ("Invalid VGroup_Delegate property index " + propertyIdx);
+            return t -> {};
+        }
+
+        /////////////////////////////////////////////////////
+        // Variables below need to be copied (deep copy if applicable) for mutation.
+        final ArrayList<Object> mChildren = new ArrayList<>();
+        // mStackedMatrix is only used temporarily when drawing, it combines all
+        // the parents' local matrices with the current one.
+        private final Matrix mStackedMatrix = new Matrix();
+        // mLocalMatrix is updated based on the update of transformation information,
+        // either parsed from the XML or by animation.
+        private final Matrix mLocalMatrix = new Matrix();
+        private float mRotate = 0;
+        private float mPivotX = 0;
+        private float mPivotY = 0;
+        private float mScaleX = 1;
+        private float mScaleY = 1;
+        private float mTranslateX = 0;
+        private float mTranslateY = 0;
+        private int mChangingConfigurations;
+        private String mGroupName = null;
+
+        private VGroup_Delegate(VGroup_Delegate copy, ArrayMap<String, Object> targetsMap) {
+            mRotate = copy.mRotate;
+            mPivotX = copy.mPivotX;
+            mPivotY = copy.mPivotY;
+            mScaleX = copy.mScaleX;
+            mScaleY = copy.mScaleY;
+            mTranslateX = copy.mTranslateX;
+            mTranslateY = copy.mTranslateY;
+            mGroupName = copy.mGroupName;
+            mChangingConfigurations = copy.mChangingConfigurations;
+            if (mGroupName != null) {
+                targetsMap.put(mGroupName, this);
+            }
+
+            mLocalMatrix.set(copy.mLocalMatrix);
+        }
+
+        private VGroup_Delegate() {
+        }
+
+        private void updateLocalMatrix() {
+            // The order we apply is the same as the
+            // RenderNode.cpp::applyViewPropertyTransforms().
+            mLocalMatrix.reset();
+            mLocalMatrix.postTranslate(-mPivotX, -mPivotY);
+            mLocalMatrix.postScale(mScaleX, mScaleY);
+            mLocalMatrix.postRotate(mRotate, 0, 0);
+            mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY);
+        }
+
+        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
+        private float getRotation() {
+            return mRotate;
+        }
+
+        private void setRotation(float rotation) {
+            if (rotation != mRotate) {
+                mRotate = rotation;
+                updateLocalMatrix();
+            }
+        }
+
+        private float getPivotX() {
+            return mPivotX;
+        }
+
+        private void setPivotX(float pivotX) {
+            if (pivotX != mPivotX) {
+                mPivotX = pivotX;
+                updateLocalMatrix();
+            }
+        }
+
+        private float getPivotY() {
+            return mPivotY;
+        }
+
+        private void setPivotY(float pivotY) {
+            if (pivotY != mPivotY) {
+                mPivotY = pivotY;
+                updateLocalMatrix();
+            }
+        }
+
+        private float getScaleX() {
+            return mScaleX;
+        }
+
+        private void setScaleX(float scaleX) {
+            if (scaleX != mScaleX) {
+                mScaleX = scaleX;
+                updateLocalMatrix();
+            }
+        }
+
+        private float getScaleY() {
+            return mScaleY;
+        }
+
+        private void setScaleY(float scaleY) {
+            if (scaleY != mScaleY) {
+                mScaleY = scaleY;
+                updateLocalMatrix();
+            }
+        }
+
+        private float getTranslateX() {
+            return mTranslateX;
+        }
+
+        private void setTranslateX(float translateX) {
+            if (translateX != mTranslateX) {
+                mTranslateX = translateX;
+                updateLocalMatrix();
+            }
+        }
+
+        private float getTranslateY() {
+            return mTranslateY;
+        }
+
+        private void setTranslateY(float translateY) {
+            if (translateY != mTranslateY) {
+                mTranslateY = translateY;
+                updateLocalMatrix();
+            }
+        }
+
+        @Override
+        public void setName(String name) {
+            mGroupName = name;
+        }
+
+        @Override
+        protected void dispose() {
+            mChildren.stream().filter(child -> child instanceof VNativeObject).forEach(child
+                    -> {
+                VNativeObject nativeObject = (VNativeObject) child;
+                if (nativeObject.mNativePtr != 0) {
+                    sPathManager.removeJavaReferenceFor(nativeObject.mNativePtr);
+                    nativeObject.mNativePtr = 0;
+                }
+                nativeObject.dispose();
+            });
+            mChildren.clear();
+        }
+
+        @Override
+        protected void finalize() throws Throwable {
+            super.finalize();
+        }
+    }
+
+    public static class VPath_Delegate extends VNativeObject {
+        protected PathParser_Delegate.PathDataNode[] mNodes = null;
+        String mPathName;
+        int mChangingConfigurations;
+
+        public VPath_Delegate() {
+            // Empty constructor.
+        }
+
+        public VPath_Delegate(VPath_Delegate copy) {
+            mPathName = copy.mPathName;
+            mChangingConfigurations = copy.mChangingConfigurations;
+            mNodes = copy.mNodes != null ? PathParser_Delegate.deepCopyNodes(copy.mNodes) : null;
+        }
+
+        public void toPath(Path path) {
+            path.reset();
+            if (mNodes != null) {
+                PathParser_Delegate.PathDataNode.nodesToPath(mNodes,
+                        Path_Delegate.getDelegate(path.mNativePath));
+            }
+        }
+
+        @Override
+        public void setName(String name) {
+            mPathName = name;
+        }
+
+        public boolean isClipPath() {
+            return false;
+        }
+
+        private void setPathData(PathParser_Delegate.PathDataNode[] nodes) {
+            if (!PathParser_Delegate.canMorph(mNodes, nodes)) {
+                // This should not happen in the middle of animation.
+                mNodes = PathParser_Delegate.deepCopyNodes(nodes);
+            } else {
+                PathParser_Delegate.updateNodes(mNodes, nodes);
+            }
+        }
+
+        @Override
+        void dispose() {
+            mNodes = null;
+        }
+    }
+
+    static class VPathRenderer_Delegate extends VNativeObject {
+        /* Right now the internal data structure is organized as a tree.
+         * Each node can be a group node, or a path.
+         * A group node can have groups or paths as children, but a path node has
+         * no children.
+         * One example can be:
+         *                 Root Group
+         *                /    |     \
+         *           Group    Path    Group
+         *          /     \             |
+         *         Path   Path         Path
+         *
+         */
+        // Variables that only used temporarily inside the draw() call, so there
+        // is no need for deep copying.
+        private final Path mPath;
+        private final Path mRenderPath;
+        private final Matrix mFinalPathMatrix = new Matrix();
+        private final long mRootGroupPtr;
+        private float mViewportWidth = 0;
+        private float mViewportHeight = 0;
+        private float mRootAlpha = 1.0f;
+        private Paint mStrokePaint;
+        private Paint mFillPaint;
+        private PathMeasure mPathMeasure;
+
+        private VPathRenderer_Delegate(long rootGroupPtr) {
+            mRootGroupPtr = rootGroupPtr;
+            mPath = new Path();
+            mRenderPath = new Path();
+        }
+
+        private VPathRenderer_Delegate(VPathRenderer_Delegate rendererToCopy,
+                long rootGroupPtr) {
+            this(rootGroupPtr);
+            mViewportWidth = rendererToCopy.mViewportWidth;
+            mViewportHeight = rendererToCopy.mViewportHeight;
+            mRootAlpha = rendererToCopy.mRootAlpha;
+        }
+
+        private float getRootAlpha() {
+            return mRootAlpha;
+        }
+
+        void setRootAlpha(float alpha) {
+            mRootAlpha = alpha;
+        }
+
+        private void drawGroupTree(VGroup_Delegate currentGroup, Matrix currentMatrix,
+                long canvasPtr, int w, int h, long filterPtr) {
+            // Calculate current group's matrix by preConcat the parent's and
+            // and the current one on the top of the stack.
+            // Basically the Mfinal = Mviewport * M0 * M1 * M2;
+            // Mi the local matrix at level i of the group tree.
+            currentGroup.mStackedMatrix.set(currentMatrix);
+            currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix);
+
+            // Save the current clip information, which is local to this group.
+            Canvas_Delegate.nSave(canvasPtr, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
+            // Draw the group tree in the same order as the XML file.
+            for (int i = 0; i < currentGroup.mChildren.size(); i++) {
+                Object child = currentGroup.mChildren.get(i);
+                if (child instanceof VGroup_Delegate) {
+                    VGroup_Delegate childGroup = (VGroup_Delegate) child;
+                    drawGroupTree(childGroup, currentGroup.mStackedMatrix,
+                            canvasPtr, w, h, filterPtr);
+                } else if (child instanceof VPath_Delegate) {
+                    VPath_Delegate childPath = (VPath_Delegate) child;
+                    drawPath(currentGroup, childPath, canvasPtr, w, h, filterPtr);
+                }
+            }
+            Canvas_Delegate.nRestore(canvasPtr);
+        }
+
+        public void draw(long canvasPtr, long filterPtr, int w, int h) {
+            // Traverse the tree in pre-order to draw.
+            drawGroupTree(VNativeObject.getDelegate(mRootGroupPtr), Matrix.IDENTITY_MATRIX, canvasPtr, w, h, filterPtr);
+        }
+
+        private void drawPath(VGroup_Delegate VGroup, VPath_Delegate VPath, long canvasPtr,
+                int w,
+                int h,
+                long filterPtr) {
+            final float scaleX = w / mViewportWidth;
+            final float scaleY = h / mViewportHeight;
+            final float minScale = Math.min(scaleX, scaleY);
+            final Matrix groupStackedMatrix = VGroup.mStackedMatrix;
+
+            mFinalPathMatrix.set(groupStackedMatrix);
+            mFinalPathMatrix.postScale(scaleX, scaleY);
+
+            final float matrixScale = getMatrixScale(groupStackedMatrix);
+            if (matrixScale == 0) {
+                // When either x or y is scaled to 0, we don't need to draw anything.
+                return;
+            }
+            VPath.toPath(mPath);
+            final Path path = mPath;
+
+            mRenderPath.reset();
+
+            if (VPath.isClipPath()) {
+                mRenderPath.addPath(path, mFinalPathMatrix);
+                Canvas_Delegate.nClipPath(canvasPtr, mRenderPath.mNativePath, Op
+                        .INTERSECT.nativeInt);
+            } else {
+                VFullPath_Delegate fullPath = (VFullPath_Delegate) VPath;
+                if (fullPath.mTrimPathStart != 0.0f || fullPath.mTrimPathEnd != 1.0f) {
+                    float start = (fullPath.mTrimPathStart + fullPath.mTrimPathOffset) % 1.0f;
+                    float end = (fullPath.mTrimPathEnd + fullPath.mTrimPathOffset) % 1.0f;
+
+                    if (mPathMeasure == null) {
+                        mPathMeasure = new PathMeasure();
+                    }
+                    mPathMeasure.setPath(mPath, false);
+
+                    float len = mPathMeasure.getLength();
+                    start = start * len;
+                    end = end * len;
+                    path.reset();
+                    if (start > end) {
+                        mPathMeasure.getSegment(start, len, path, true);
+                        mPathMeasure.getSegment(0f, end, path, true);
+                    } else {
+                        mPathMeasure.getSegment(start, end, path, true);
+                    }
+                    path.rLineTo(0, 0); // fix bug in measure
+                }
+                mRenderPath.addPath(path, mFinalPathMatrix);
+
+                if (fullPath.mFillColor != Color.TRANSPARENT) {
+                    if (mFillPaint == null) {
+                        mFillPaint = new Paint();
+                        mFillPaint.setStyle(Style.FILL);
+                        mFillPaint.setAntiAlias(true);
+                    }
+
+                    final Paint fillPaint = mFillPaint;
+                    fillPaint.setColor(applyAlpha(applyAlpha(fullPath.mFillColor, fullPath
+                      .mFillAlpha), getRootAlpha()));
+                    Paint_Delegate fillPaintDelegate = Paint_Delegate.getDelegate(fillPaint
+                            .getNativeInstance());
+                    // mFillPaint can not be null at this point so we will have a delegate
+                    assert fillPaintDelegate != null;
+                    fillPaintDelegate.setColorFilter(filterPtr);
+
+                    Shader_Delegate shaderDelegate =
+                            Shader_Delegate.getDelegate(fullPath.mFillGradient);
+                    if (shaderDelegate != null) {
+                        // If there is a shader, apply the local transformation to make sure
+                        // the gradient is transformed to match the viewport
+                        shaderDelegate.setLocalMatrix(mFinalPathMatrix.native_instance);
+                    }
+
+                    fillPaintDelegate.setShader(fullPath.mFillGradient);
+                    Path_Delegate.nSetFillType(mRenderPath.mNativePath, fullPath.mFillType);
+                    BaseCanvas_Delegate.nDrawPath(canvasPtr, mRenderPath.mNativePath, fillPaint
+                            .getNativeInstance());
+                    if (shaderDelegate != null) {
+                        // Remove the local matrix
+                        shaderDelegate.setLocalMatrix(0);
+                    }
+                }
+
+                if (fullPath.mStrokeColor != Color.TRANSPARENT) {
+                    if (mStrokePaint == null) {
+                        mStrokePaint = new Paint();
+                        mStrokePaint.setStyle(Style.STROKE);
+                        mStrokePaint.setAntiAlias(true);
+                    }
+
+                    final Paint strokePaint = mStrokePaint;
+                    if (fullPath.mStrokeLineJoin != null) {
+                        strokePaint.setStrokeJoin(fullPath.mStrokeLineJoin);
+                    }
+
+                    if (fullPath.mStrokeLineCap != null) {
+                        strokePaint.setStrokeCap(fullPath.mStrokeLineCap);
+                    }
+
+                    strokePaint.setStrokeMiter(fullPath.mStrokeMiterlimit);
+                    strokePaint.setColor(applyAlpha(applyAlpha(fullPath.mStrokeColor, fullPath
+                      .mStrokeAlpha), getRootAlpha()));
+                    Paint_Delegate strokePaintDelegate = Paint_Delegate.getDelegate(strokePaint
+                            .getNativeInstance());
+                    // mStrokePaint can not be null at this point so we will have a delegate
+                    assert strokePaintDelegate != null;
+                    strokePaintDelegate.setColorFilter(filterPtr);
+                    final float finalStrokeScale = minScale * matrixScale;
+                    strokePaint.setStrokeWidth(fullPath.mStrokeWidth * finalStrokeScale);
+                    strokePaintDelegate.setShader(fullPath.mStrokeGradient);
+                    BaseCanvas_Delegate.nDrawPath(canvasPtr, mRenderPath.mNativePath, strokePaint
+                            .getNativeInstance());
+                }
+            }
+        }
+
+        private float getMatrixScale(Matrix groupStackedMatrix) {
+            // Given unit vectors A = (0, 1) and B = (1, 0).
+            // After matrix mapping, we got A' and B'. Let theta = the angel b/t A' and B'.
+            // Therefore, the final scale we want is min(|A'| * sin(theta), |B'| * sin(theta)),
+            // which is (|A'| * |B'| * sin(theta)) / max (|A'|, |B'|);
+            // If  max (|A'|, |B'|) = 0, that means either x or y has a scale of 0.
+            //
+            // For non-skew case, which is most of the cases, matrix scale is computing exactly the
+            // scale on x and y axis, and take the minimal of these two.
+            // For skew case, an unit square will mapped to a parallelogram. And this function will
+            // return the minimal height of the 2 bases.
+            float[] unitVectors = new float[]{0, 1, 1, 0};
+            groupStackedMatrix.mapVectors(unitVectors);
+            float scaleX = MathUtils.mag(unitVectors[0], unitVectors[1]);
+            float scaleY = MathUtils.mag(unitVectors[2], unitVectors[3]);
+            float crossProduct = MathUtils.cross(unitVectors[0], unitVectors[1],
+                    unitVectors[2], unitVectors[3]);
+            float maxScale = MathUtils.max(scaleX, scaleY);
+
+            float matrixScale = 0;
+            if (maxScale > 0) {
+                matrixScale = MathUtils.abs(crossProduct) / maxScale;
+            }
+            if (DBG_VECTOR_DRAWABLE) {
+                Log.d(LOGTAG, "Scale x " + scaleX + " y " + scaleY + " final " + matrixScale);
+            }
+            return matrixScale;
+        }
+
+        @Override
+        public void setName(String name) {
+        }
+
+        @Override
+        protected void finalize() throws Throwable {
+            // The mRootGroupPtr is not explicitly freed by anything in the VectorDrawable so we
+            // need to free it here.
+            VNativeObject nativeObject = sPathManager.getDelegate(mRootGroupPtr);
+            sPathManager.removeJavaReferenceFor(mRootGroupPtr);
+            assert nativeObject != null;
+            nativeObject.dispose();
+
+            super.finalize();
+        }
+    }
+}
diff --git a/android/graphics/drawable/shapes/ArcShape.java b/android/graphics/drawable/shapes/ArcShape.java
new file mode 100644
index 0000000..85ba0a9
--- /dev/null
+++ b/android/graphics/drawable/shapes/ArcShape.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2007 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.shapes;
+
+import android.graphics.Canvas;
+import android.graphics.Outline;
+import android.graphics.Paint;
+
+/**
+ * Creates an arc shape. The arc shape starts at a specified angle and sweeps
+ * clockwise, drawing slices of pie.
+ * <p>
+ * The arc can be drawn to a {@link Canvas} with its own
+ * {@link #draw(Canvas, Paint)} method, but more graphical control is available
+ * if you instead pass the ArcShape to a
+ * {@link android.graphics.drawable.ShapeDrawable}.
+ */
+public class ArcShape extends RectShape {
+    private final float mStartAngle;
+    private final float mSweepAngle;
+
+    /**
+     * ArcShape constructor.
+     *
+     * @param startAngle the angle (in degrees) where the arc begins
+     * @param sweepAngle the sweep angle (in degrees). Anything equal to or
+     *                   greater than 360 results in a complete circle/oval.
+     */
+    public ArcShape(float startAngle, float sweepAngle) {
+        mStartAngle = startAngle;
+        mSweepAngle = sweepAngle;
+    }
+
+    /**
+     * @return the angle (in degrees) where the arc begins
+     */
+    public final float getStartAngle() {
+        return mStartAngle;
+    }
+
+    /**
+     * @return the sweep angle (in degrees)
+     */
+    public final float getSweepAngle() {
+        return mSweepAngle;
+    }
+
+    @Override
+    public void draw(Canvas canvas, Paint paint) {
+        canvas.drawArc(rect(), mStartAngle, mSweepAngle, true, paint);
+    }
+
+    @Override
+    public void getOutline(Outline outline) {
+        // Since we don't support concave outlines, arc shape does not attempt
+        // to provide an outline.
+    }
+
+    @Override
+    public ArcShape clone() throws CloneNotSupportedException {
+        return (ArcShape) super.clone();
+    }
+}
+
diff --git a/android/graphics/drawable/shapes/OvalShape.java b/android/graphics/drawable/shapes/OvalShape.java
new file mode 100644
index 0000000..fb87d28
--- /dev/null
+++ b/android/graphics/drawable/shapes/OvalShape.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2007 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.shapes;
+
+import android.graphics.Canvas;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.RectF;
+
+/**
+ * Defines an oval shape.
+ * <p>
+ * The oval can be drawn to a Canvas with its own draw() method,
+ * but more graphical control is available if you instead pass
+ * the OvalShape to a {@link android.graphics.drawable.ShapeDrawable}.
+ */
+public class OvalShape extends RectShape {
+
+    public OvalShape() {}
+
+    @Override
+    public void draw(Canvas canvas, Paint paint) {
+        canvas.drawOval(rect(), paint);
+    }
+
+    @Override
+    public void getOutline(Outline outline) {
+        final RectF rect = rect();
+        outline.setOval((int) Math.ceil(rect.left), (int) Math.ceil(rect.top),
+                (int) Math.floor(rect.right), (int) Math.floor(rect.bottom));
+    }
+
+    @Override
+    public OvalShape clone() throws CloneNotSupportedException {
+        return (OvalShape) super.clone();
+    }
+}
+
diff --git a/android/graphics/drawable/shapes/PathShape.java b/android/graphics/drawable/shapes/PathShape.java
new file mode 100644
index 0000000..ce5552b
--- /dev/null
+++ b/android/graphics/drawable/shapes/PathShape.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2007 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.shapes;
+
+import android.annotation.NonNull;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+
+/**
+ * Creates geometric paths, utilizing the {@link android.graphics.Path} class.
+ * <p>
+ * The path can be drawn to a Canvas with its own draw() method,
+ * but more graphical control is available if you instead pass
+ * the PathShape to a {@link android.graphics.drawable.ShapeDrawable}.
+ */
+public class PathShape extends Shape {
+    private final float mStdWidth;
+    private final float mStdHeight;
+
+    private Path mPath;
+
+    private float mScaleX; // cached from onResize
+    private float mScaleY; // cached from onResize
+
+    /**
+     * PathShape constructor.
+     *
+     * @param path a Path that defines the geometric paths for this shape
+     * @param stdWidth the standard width for the shape. Any changes to the
+     *                 width with resize() will result in a width scaled based
+     *                 on the new width divided by this width.
+     * @param stdHeight the standard height for the shape. Any changes to the
+     *                  height with resize() will result in a height scaled based
+     *                  on the new height divided by this height.
+     */
+    public PathShape(@NonNull Path path, float stdWidth, float stdHeight) {
+        mPath = path;
+        mStdWidth = stdWidth;
+        mStdHeight = stdHeight;
+    }
+
+    @Override
+    public void draw(Canvas canvas, Paint paint) {
+        canvas.save();
+        canvas.scale(mScaleX, mScaleY);
+        canvas.drawPath(mPath, paint);
+        canvas.restore();
+    }
+
+    @Override
+    protected void onResize(float width, float height) {
+        mScaleX = width / mStdWidth;
+        mScaleY = height / mStdHeight;
+    }
+
+    @Override
+    public PathShape clone() throws CloneNotSupportedException {
+        final PathShape shape = (PathShape) super.clone();
+        shape.mPath = new Path(mPath);
+        return shape;
+    }
+}
+
diff --git a/android/graphics/drawable/shapes/RectShape.java b/android/graphics/drawable/shapes/RectShape.java
new file mode 100644
index 0000000..e339a21
--- /dev/null
+++ b/android/graphics/drawable/shapes/RectShape.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2007 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.shapes;
+
+import android.graphics.Canvas;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.RectF;
+
+/**
+ * Defines a rectangle shape.
+ * <p>
+ * The rectangle can be drawn to a Canvas with its own draw() method,
+ * but more graphical control is available if you instead pass
+ * the RectShape to a {@link android.graphics.drawable.ShapeDrawable}.
+ */
+public class RectShape extends Shape {
+    private RectF mRect = new RectF();
+
+    public RectShape() {}
+    
+    @Override
+    public void draw(Canvas canvas, Paint paint) {
+        canvas.drawRect(mRect, paint);
+    }
+
+    @Override
+    public void getOutline(Outline outline) {
+        final RectF rect = rect();
+        outline.setRect((int) Math.ceil(rect.left), (int) Math.ceil(rect.top),
+                (int) Math.floor(rect.right), (int) Math.floor(rect.bottom));
+    }
+
+    @Override
+    protected void onResize(float width, float height) {
+        mRect.set(0, 0, width, height);
+    }
+
+    /**
+     * Returns the RectF that defines this rectangle's bounds.
+     */
+    protected final RectF rect() {
+        return mRect;
+    }
+
+    @Override
+    public RectShape clone() throws CloneNotSupportedException {
+        final RectShape shape = (RectShape) super.clone();
+        shape.mRect = new RectF(mRect);
+        return shape;
+    }
+}
diff --git a/android/graphics/drawable/shapes/RoundRectShape.java b/android/graphics/drawable/shapes/RoundRectShape.java
new file mode 100644
index 0000000..f5cbb24
--- /dev/null
+++ b/android/graphics/drawable/shapes/RoundRectShape.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2007 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.shapes;
+
+import android.annotation.Nullable;
+import android.graphics.Canvas;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+
+/**
+ * Creates a rounded-corner rectangle. Optionally, an inset (rounded) rectangle
+ * can be included (to make a sort of "O" shape).
+ * <p>
+ * The rounded rectangle can be drawn to a Canvas with its own draw() method,
+ * but more graphical control is available if you instead pass
+ * the RoundRectShape to a {@link android.graphics.drawable.ShapeDrawable}.
+ */
+public class RoundRectShape extends RectShape {
+    private float[] mOuterRadii;
+    private RectF mInset;
+    private float[] mInnerRadii;
+    
+    private RectF mInnerRect;
+    private Path mPath; // this is what we actually draw
+    
+    /**
+     * RoundRectShape constructor.
+     * <p>
+     * Specifies an outer (round)rect and an optional inner (round)rect.
+     *
+     * @param outerRadii An array of 8 radius values, for the outer roundrect. 
+     *                   The first two floats are for the top-left corner
+     *                   (remaining pairs correspond clockwise). For no rounded
+     *                   corners on the outer rectangle, pass {@code null}.
+     * @param inset A RectF that specifies the distance from the inner
+     *              rect to each side of the outer rect. For no inner, pass
+     *              {@code null}.
+     * @param innerRadii An array of 8 radius values, for the inner roundrect.
+     *                   The first two floats are for the top-left corner
+     *                   (remaining pairs correspond clockwise). For no rounded
+     *                   corners on the inner rectangle, pass {@code null}. If
+     *                   inset parameter is {@code null}, this parameter is
+     *                   ignored.
+     */
+    public RoundRectShape(@Nullable float[] outerRadii, @Nullable RectF inset,
+            @Nullable float[] innerRadii) {
+        if (outerRadii != null && outerRadii.length < 8) {
+            throw new ArrayIndexOutOfBoundsException("outer radii must have >= 8 values");
+        }
+        if (innerRadii != null && innerRadii.length < 8) {
+            throw new ArrayIndexOutOfBoundsException("inner radii must have >= 8 values");
+        }
+        mOuterRadii = outerRadii;
+        mInset = inset;
+        mInnerRadii = innerRadii;
+        
+        if (inset != null) {
+            mInnerRect = new RectF();
+        }
+        mPath = new Path();
+    }
+    
+    @Override
+    public void draw(Canvas canvas, Paint paint) {
+        canvas.drawPath(mPath, paint);
+    }
+
+    @Override
+    public void getOutline(Outline outline) {
+        if (mInnerRect != null) return; // have a hole, can't produce valid outline
+
+        float radius = 0;
+        if (mOuterRadii != null) {
+            radius = mOuterRadii[0];
+            for (int i = 1; i < 8; i++) {
+                if (mOuterRadii[i] != radius) {
+                    // can't call simple constructors, use path
+                    outline.setConvexPath(mPath);
+                    return;
+                }
+            }
+        }
+
+        final RectF rect = rect();
+        outline.setRoundRect((int) Math.ceil(rect.left), (int) Math.ceil(rect.top),
+                (int) Math.floor(rect.right), (int) Math.floor(rect.bottom), radius);
+    }
+
+    @Override
+    protected void onResize(float w, float h) {
+        super.onResize(w, h);
+
+        RectF r = rect();
+        mPath.reset();
+
+        if (mOuterRadii != null) {
+            mPath.addRoundRect(r, mOuterRadii, Path.Direction.CW);
+        } else {
+            mPath.addRect(r, Path.Direction.CW);
+        }
+        if (mInnerRect != null) {
+            mInnerRect.set(r.left + mInset.left, r.top + mInset.top,
+                           r.right - mInset.right, r.bottom - mInset.bottom);
+            if (mInnerRect.width() < w && mInnerRect.height() < h) {
+                if (mInnerRadii != null) {
+                    mPath.addRoundRect(mInnerRect, mInnerRadii, Path.Direction.CCW);
+                } else {
+                    mPath.addRect(mInnerRect, Path.Direction.CCW);
+                }
+            }
+        }
+    }
+
+    @Override
+    public RoundRectShape clone() throws CloneNotSupportedException {
+        final RoundRectShape shape = (RoundRectShape) super.clone();
+        shape.mOuterRadii = mOuterRadii != null ? mOuterRadii.clone() : null;
+        shape.mInnerRadii = mInnerRadii != null ? mInnerRadii.clone() : null;
+        shape.mInset = new RectF(mInset);
+        shape.mInnerRect = new RectF(mInnerRect);
+        shape.mPath = new Path(mPath);
+        return shape;
+    }
+}
diff --git a/android/graphics/drawable/shapes/Shape.java b/android/graphics/drawable/shapes/Shape.java
new file mode 100644
index 0000000..30b28f3
--- /dev/null
+++ b/android/graphics/drawable/shapes/Shape.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2007 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.shapes;
+
+import android.annotation.NonNull;
+import android.graphics.Canvas;
+import android.graphics.Outline;
+import android.graphics.Paint;
+
+/**
+ * Defines a generic graphical "shape."
+ * <p>
+ * Any Shape can be drawn to a Canvas with its own draw() method, but more
+ * graphical control is available if you instead pass it to a
+ * {@link android.graphics.drawable.ShapeDrawable}.
+ * <p>
+ * Custom Shape classes must implement {@link #clone()} and return an instance
+ * of the custom Shape class.
+ */
+public abstract class Shape implements Cloneable {
+    private float mWidth;
+    private float mHeight;
+
+    /**
+     * Returns the width of the Shape.
+     */
+    public final float getWidth() {
+        return mWidth;
+    }
+
+    /**
+     * Returns the height of the Shape.
+     */
+    public final float getHeight() {
+        return mHeight;
+    }
+
+    /**
+     * Draws this shape into the provided Canvas, with the provided Paint.
+     * <p>
+     * Before calling this, you must call {@link #resize(float,float)}.
+     *
+     * @param canvas the Canvas within which this shape should be drawn
+     * @param paint  the Paint object that defines this shape's characteristics
+     */
+    public abstract void draw(Canvas canvas, Paint paint);
+
+    /**
+     * Resizes the dimensions of this shape.
+     * <p>
+     * Must be called before {@link #draw(Canvas,Paint)}.
+     *
+     * @param width the width of the shape (in pixels)
+     * @param height the height of the shape (in pixels)
+     */
+    public final void resize(float width, float height) {
+        if (width < 0) {
+            width = 0;
+        }
+        if (height < 0) {
+            height =0;
+        }
+        if (mWidth != width || mHeight != height) {
+            mWidth = width;
+            mHeight = height;
+            onResize(width, height);
+        }
+    }
+
+    /**
+     * Checks whether the Shape is opaque.
+     * <p>
+     * Default impl returns {@code true}. Override if your subclass can be
+     * opaque.
+     *
+     * @return true if any part of the drawable is <em>not</em> opaque.
+     */
+    public boolean hasAlpha() {
+        return true;
+    }
+
+    /**
+     * Callback method called when {@link #resize(float,float)} is executed.
+     *
+     * @param width the new width of the Shape
+     * @param height the new height of the Shape
+     */
+    protected void onResize(float width, float height) {}
+
+    /**
+     * Computes the Outline of the shape and return it in the supplied Outline
+     * parameter. The default implementation does nothing and {@code outline}
+     * is not changed.
+     *
+     * @param outline the Outline to be populated with the result. Must be
+     *                non-{@code null}.
+     */
+    public void getOutline(@NonNull Outline outline) {}
+
+    @Override
+    public Shape clone() throws CloneNotSupportedException {
+        return (Shape) super.clone();
+    }
+}
diff --git a/android/graphics/fonts/FontVariationAxis.java b/android/graphics/fonts/FontVariationAxis.java
new file mode 100644
index 0000000..1b7408a
--- /dev/null
+++ b/android/graphics/fonts/FontVariationAxis.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2017 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.fonts;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.regex.Pattern;
+
+/**
+ * Class that holds information about single font variation axis.
+ */
+public final class FontVariationAxis {
+    private final int mTag;
+    private final String mTagString;
+    private final float mStyleValue;
+
+    /**
+     * Construct FontVariationAxis.
+     *
+     * The axis tag must contain four ASCII characters. Tag string that are longer or shorter than
+     * four characters, or contains characters outside of U+0020..U+007E are invalid.
+     *
+     * @throws IllegalArgumentException If given tag string is invalid.
+     */
+    public FontVariationAxis(@NonNull String tagString, float styleValue) {
+        if (!isValidTag(tagString)) {
+            throw new IllegalArgumentException("Illegal tag pattern: " + tagString);
+        }
+        mTag = makeTag(tagString);
+        mTagString = tagString;
+        mStyleValue = styleValue;
+    }
+
+    /**
+     * Returns the OpenType style tag value.
+     * @hide
+     */
+    public int getOpenTypeTagValue() {
+        return mTag;
+    }
+
+    /**
+     * Returns the variable font axis tag associated to this axis.
+     */
+    public String getTag() {
+        return mTagString;
+    }
+
+    /**
+     * Returns the style value associated to the given axis for this font.
+     */
+    public float getStyleValue() {
+        return mStyleValue;
+    }
+
+    /**
+     * Returns a valid font variation setting string for this object.
+     */
+    @Override
+    public @NonNull String toString() {
+        return "'" + mTagString + "' " + Float.toString(mStyleValue);
+    }
+
+    /**
+     * The 'tag' attribute value is read as four character values between U+0020 and U+007E
+     * inclusive.
+     */
+    private static final Pattern TAG_PATTERN = Pattern.compile("[\u0020-\u007E]{4}");
+
+    /**
+     * Returns true if 'tagString' is valid for font variation axis tag.
+     */
+    private static boolean isValidTag(String tagString) {
+        return tagString != null && TAG_PATTERN.matcher(tagString).matches();
+    }
+
+    /**
+     * The 'styleValue' attribute has an optional leading '-', followed by '<digits>',
+     * '<digits>.<digits>', or '.<digits>' where '<digits>' is one or more of [0-9].
+     */
+    private static final Pattern STYLE_VALUE_PATTERN =
+            Pattern.compile("-?(([0-9]+(\\.[0-9]+)?)|(\\.[0-9]+))");
+
+    private static boolean isValidValueFormat(String valueString) {
+        return valueString != null && STYLE_VALUE_PATTERN.matcher(valueString).matches();
+    }
+
+    /** @hide */
+    public static int makeTag(String tagString) {
+        final char c1 = tagString.charAt(0);
+        final char c2 = tagString.charAt(1);
+        final char c3 = tagString.charAt(2);
+        final char c4 = tagString.charAt(3);
+        return (c1 << 24) | (c2 << 16) | (c3 << 8) | c4;
+    }
+
+    /**
+     * Construct FontVariationAxis array from font variation settings.
+     *
+     * The settings string is constructed from multiple pairs of axis tag and style values. The axis
+     * tag must contain four ASCII characters and must be wrapped with single quotes (U+0027) or
+     * double quotes (U+0022). Axis strings that are longer or shorter than four characters, or
+     * contain characters outside of U+0020..U+007E are invalid. If a specified axis name is not
+     * defined in the font, the settings will be ignored.
+     *
+     * <pre>
+     *   FontVariationAxis.fromFontVariationSettings("'wdth' 1.0");
+     *   FontVariationAxis.fromFontVariationSettings("'AX  ' 1.0, 'FB  ' 2.0");
+     * </pre>
+     *
+     * @param settings font variation settings.
+     * @return FontVariationAxis[] the array of parsed font variation axis. {@code null} if settings
+     *                             has no font variation settings.
+     * @throws IllegalArgumentException If given string is not a valid font variation settings
+     *                                  format.
+     */
+    public static @Nullable FontVariationAxis[] fromFontVariationSettings(
+            @Nullable String settings) {
+        if (settings == null || settings.isEmpty()) {
+            return null;
+        }
+        final ArrayList<FontVariationAxis> axisList = new ArrayList<>();
+        final int length = settings.length();
+        for (int i = 0; i < length; i++) {
+            final char c = settings.charAt(i);
+            if (Character.isWhitespace(c)) {
+                continue;
+            }
+            if (!(c == '\'' || c == '"') || length < i + 6 || settings.charAt(i + 5) != c) {
+                throw new IllegalArgumentException(
+                        "Tag should be wrapped with double or single quote: " + settings);
+            }
+            final String tagString = settings.substring(i + 1, i + 5);
+
+            i += 6;  // Move to end of tag.
+            int endOfValueString = settings.indexOf(',', i);
+            if (endOfValueString == -1) {
+                endOfValueString = length;
+            }
+            final float value;
+            try {
+                // Float.parseFloat ignores leading/trailing whitespaces.
+                value = Float.parseFloat(settings.substring(i, endOfValueString));
+            } catch (NumberFormatException e) {
+                throw new IllegalArgumentException(
+                        "Failed to parse float string: " + e.getMessage());
+            }
+            axisList.add(new FontVariationAxis(tagString, value));
+            i = endOfValueString;
+        }
+        if (axisList.isEmpty()) {
+            return null;
+        }
+        return axisList.toArray(new FontVariationAxis[0]);
+    }
+
+    /**
+     * Stringify the array of FontVariationAxis.
+     *
+     * @param axes an array of FontVariationAxis.
+     * @return String a valid font variation settings string.
+     */
+    public static @NonNull String toFontVariationSettings(@Nullable FontVariationAxis[] axes) {
+        if (axes == null) {
+            return "";
+        }
+        return TextUtils.join(",", axes);
+    }
+}
+
diff --git a/android/graphics/pdf/PdfDocument.java b/android/graphics/pdf/PdfDocument.java
new file mode 100644
index 0000000..1b8336f
--- /dev/null
+++ b/android/graphics/pdf/PdfDocument.java
@@ -0,0 +1,459 @@
+/*
+ * Copyright (C) 2013 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.pdf;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+
+import dalvik.system.CloseGuard;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * <p>
+ * This class enables generating a PDF document from native Android content. You
+ * create a new document and then for every page you want to add you start a page,
+ * write content to the page, and finish the page. After you are done with all
+ * pages, you write the document to an output stream and close the document.
+ * After a document is closed you should not use it anymore. Note that pages are
+ * created one by one, i.e. you can have only a single page to which you are
+ * writing at any given time. This class is not thread safe.
+ * </p>
+ * <p>
+ * A typical use of the APIs looks like this:
+ * </p>
+ * <pre>
+ * // create a new document
+ * PdfDocument document = new PdfDocument();
+ *
+ * // crate a page description
+ * PageInfo pageInfo = new PageInfo.Builder(new Rect(0, 0, 100, 100), 1).create();
+ *
+ * // start a page
+ * Page page = document.startPage(pageInfo);
+ *
+ * // draw something on the page
+ * View content = getContentView();
+ * content.draw(page.getCanvas());
+ *
+ * // finish the page
+ * document.finishPage(page);
+ * . . .
+ * // add more pages
+ * . . .
+ * // write the document content
+ * document.writeTo(getOutputStream());
+ *
+ * // close the document
+ * document.close();
+ * </pre>
+ */
+public class PdfDocument {
+
+    // TODO: We need a constructor that will take an OutputStream to
+    // support online data serialization as opposed to the current
+    // on demand one. The current approach is fine until Skia starts
+    // to support online PDF generation at which point we need to
+    // handle this.
+
+    private final byte[] mChunk = new byte[4096];
+
+    private final CloseGuard mCloseGuard = CloseGuard.get();
+
+    private final List<PageInfo> mPages = new ArrayList<PageInfo>();
+
+    private long mNativeDocument;
+
+    private Page mCurrentPage;
+
+    /**
+     * Creates a new instance.
+     */
+    public PdfDocument() {
+        mNativeDocument = nativeCreateDocument();
+        mCloseGuard.open("close");
+    }
+
+    /**
+     * Starts a page using the provided {@link PageInfo}. After the page
+     * is created you can draw arbitrary content on the page's canvas which
+     * you can get by calling {@link Page#getCanvas()}. After you are done
+     * drawing the content you should finish the page by calling
+     * {@link #finishPage(Page)}. After the page is finished you should
+     * no longer access the page or its canvas.
+     * <p>
+     * <strong>Note:</strong> Do not call this method after {@link #close()}.
+     * Also do not call this method if the last page returned by this method
+     * is not finished by calling {@link #finishPage(Page)}.
+     * </p>
+     *
+     * @param pageInfo The page info. Cannot be null.
+     * @return A blank page.
+     *
+     * @see #finishPage(Page)
+     */
+    public Page startPage(PageInfo pageInfo) {
+        throwIfClosed();
+        throwIfCurrentPageNotFinished();
+        if (pageInfo == null) {
+            throw new IllegalArgumentException("page cannot be null");
+        }
+        Canvas canvas = new PdfCanvas(nativeStartPage(mNativeDocument, pageInfo.mPageWidth,
+                pageInfo.mPageHeight, pageInfo.mContentRect.left, pageInfo.mContentRect.top,
+                pageInfo.mContentRect.right, pageInfo.mContentRect.bottom));
+        mCurrentPage = new Page(canvas, pageInfo);
+        return mCurrentPage;
+    }
+
+    /**
+     * Finishes a started page. You should always finish the last started page.
+     * <p>
+     * <strong>Note:</strong> Do not call this method after {@link #close()}.
+     * You should not finish the same page more than once.
+     * </p>
+     *
+     * @param page The page. Cannot be null.
+     *
+     * @see #startPage(PageInfo)
+     */
+    public void finishPage(Page page) {
+        throwIfClosed();
+        if (page == null) {
+            throw new IllegalArgumentException("page cannot be null");
+        }
+        if (page != mCurrentPage) {
+            throw new IllegalStateException("invalid page");
+        }
+        if (page.isFinished()) {
+            throw new IllegalStateException("page already finished");
+        }
+        mPages.add(page.getInfo());
+        mCurrentPage = null;
+        nativeFinishPage(mNativeDocument);
+        page.finish();
+    }
+
+    /**
+     * Writes the document to an output stream. You can call this method
+     * multiple times.
+     * <p>
+     * <strong>Note:</strong> Do not call this method after {@link #close()}.
+     * Also do not call this method if a page returned by {@link #startPage(
+     * PageInfo)} is not finished by calling {@link #finishPage(Page)}.
+     * </p>
+     *
+     * @param out The output stream. Cannot be null.
+     *
+     * @throws IOException If an error occurs while writing.
+     */
+    public void writeTo(OutputStream out) throws IOException {
+        throwIfClosed();
+        throwIfCurrentPageNotFinished();
+        if (out == null) {
+            throw new IllegalArgumentException("out cannot be null!");
+        }
+        nativeWriteTo(mNativeDocument, out, mChunk);
+    }
+
+    /**
+     * Gets the pages of the document.
+     *
+     * @return The pages or an empty list.
+     */
+    public List<PageInfo> getPages() {
+        return Collections.unmodifiableList(mPages);
+    }
+
+    /**
+     * Closes this document. This method should be called after you
+     * are done working with the document. After this call the document
+     * is considered closed and none of its methods should be called.
+     * <p>
+     * <strong>Note:</strong> Do not call this method if the page
+     * returned by {@link #startPage(PageInfo)} is not finished by
+     * calling {@link #finishPage(Page)}.
+     * </p>
+     */
+    public void close() {
+        throwIfCurrentPageNotFinished();
+        dispose();
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mCloseGuard != null) {
+                mCloseGuard.warnIfOpen();
+            }
+
+            dispose();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private void dispose() {
+        if (mNativeDocument != 0) {
+            nativeClose(mNativeDocument);
+            mCloseGuard.close();
+            mNativeDocument = 0;
+        }
+    }
+
+    /**
+     * Throws an exception if the document is already closed.
+     */
+    private void throwIfClosed() {
+        if (mNativeDocument == 0) {
+            throw new IllegalStateException("document is closed!");
+        }
+    }
+
+    /**
+     * Throws an exception if the last started page is not finished.
+     */
+    private void throwIfCurrentPageNotFinished() {
+        if (mCurrentPage != null) {
+            throw new IllegalStateException("Current page not finished!");
+        }
+    }
+
+    private native long nativeCreateDocument();
+
+    private native void nativeClose(long nativeDocument);
+
+    private native void nativeFinishPage(long nativeDocument);
+
+    private native void nativeWriteTo(long nativeDocument, OutputStream out, byte[] chunk);
+
+    private static native long nativeStartPage(long nativeDocument, int pageWidth, int pageHeight,
+            int contentLeft, int contentTop, int contentRight, int contentBottom);
+
+    private final class PdfCanvas extends Canvas {
+
+        public PdfCanvas(long nativeCanvas) {
+            super(nativeCanvas);
+        }
+
+        @Override
+        public void setBitmap(Bitmap bitmap) {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    /**
+     * This class represents meta-data that describes a PDF {@link Page}.
+     */
+    public static final class PageInfo {
+        private int mPageWidth;
+        private int mPageHeight;
+        private Rect mContentRect;
+        private int mPageNumber;
+
+        /**
+         * Creates a new instance.
+         */
+        private PageInfo() {
+            /* do nothing */
+        }
+
+        /**
+         * Gets the page width in PostScript points (1/72th of an inch).
+         *
+         * @return The page width.
+         */
+        public int getPageWidth() {
+            return mPageWidth;
+        }
+
+        /**
+         * Gets the page height in PostScript points (1/72th of an inch).
+         *
+         * @return The page height.
+         */
+        public int getPageHeight() {
+            return mPageHeight;
+        }
+
+        /**
+         * Get the content rectangle in PostScript points (1/72th of an inch).
+         * This is the area that contains the page content and is relative to
+         * the page top left.
+         *
+         * @return The content rectangle.
+         */
+        public Rect getContentRect() {
+            return mContentRect;
+        }
+
+        /**
+         * Gets the page number.
+         *
+         * @return The page number.
+         */
+        public int getPageNumber() {
+            return mPageNumber;
+        }
+
+        /**
+         * Builder for creating a {@link PageInfo}.
+         */
+        public static final class Builder {
+            private final PageInfo mPageInfo = new PageInfo();
+
+            /**
+             * Creates a new builder with the mandatory page info attributes.
+             *
+             * @param pageWidth The page width in PostScript (1/72th of an inch).
+             * @param pageHeight The page height in PostScript (1/72th of an inch).
+             * @param pageNumber The page number.
+             */
+            public Builder(int pageWidth, int pageHeight, int pageNumber) {
+                if (pageWidth <= 0) {
+                    throw new IllegalArgumentException("page width must be positive");
+                }
+                if (pageHeight <= 0) {
+                    throw new IllegalArgumentException("page width must be positive");
+                }
+                if (pageNumber < 0) {
+                    throw new IllegalArgumentException("pageNumber must be non negative");
+                }
+                mPageInfo.mPageWidth = pageWidth;
+                mPageInfo.mPageHeight = pageHeight;
+                mPageInfo.mPageNumber = pageNumber;
+            }
+
+            /**
+             * Sets the content rectangle in PostScript point (1/72th of an inch).
+             * This is the area that contains the page content and is relative to
+             * the page top left.
+             *
+             * @param contentRect The content rectangle. Must fit in the page.
+             */
+            public Builder setContentRect(Rect contentRect) {
+                if (contentRect != null && (contentRect.left < 0
+                        || contentRect.top < 0
+                        || contentRect.right > mPageInfo.mPageWidth
+                        || contentRect.bottom > mPageInfo.mPageHeight)) {
+                    throw new IllegalArgumentException("contentRect does not fit the page");
+                }
+                mPageInfo.mContentRect = contentRect;
+                return this;
+            }
+
+            /**
+             * Creates a new {@link PageInfo}.
+             *
+             * @return The new instance.
+             */
+            public PageInfo create() {
+                if (mPageInfo.mContentRect == null) {
+                    mPageInfo.mContentRect = new Rect(0, 0,
+                            mPageInfo.mPageWidth, mPageInfo.mPageHeight);
+                }
+                return mPageInfo;
+            }
+        }
+    }
+
+    /**
+     * This class represents a PDF document page. It has associated
+     * a canvas on which you can draw content and is acquired by a
+     * call to {@link #getCanvas()}. It also has associated a
+     * {@link PageInfo} instance that describes its attributes. Also
+     * a page has 
+     */
+    public static final class Page {
+        private final PageInfo mPageInfo;
+        private Canvas mCanvas;
+
+        /**
+         * Creates a new instance.
+         *
+         * @param canvas The canvas of the page.
+         * @param pageInfo The info with meta-data.
+         */
+        private Page(Canvas canvas, PageInfo pageInfo) {
+            mCanvas = canvas;
+            mPageInfo = pageInfo;
+        }
+
+        /**
+         * Gets the {@link Canvas} of the page.
+         *
+         * <p>
+         * <strong>Note: </strong> There are some draw operations that are not yet
+         * supported by the canvas returned by this method. More specifically:
+         * <ul>
+         * <li>Inverse path clipping performed via {@link Canvas#clipPath(android.graphics.Path,
+         *     android.graphics.Region.Op) Canvas.clipPath(android.graphics.Path,
+         *     android.graphics.Region.Op)} for {@link
+         *     android.graphics.Region.Op#REVERSE_DIFFERENCE
+         *     Region.Op#REVERSE_DIFFERENCE} operations.</li>
+         * <li>{@link Canvas#drawVertices(android.graphics.Canvas.VertexMode, int,
+         *     float[], int, float[], int, int[], int, short[], int, int,
+         *     android.graphics.Paint) Canvas.drawVertices(
+         *     android.graphics.Canvas.VertexMode, int, float[], int, float[],
+         *     int, int[], int, short[], int, int, android.graphics.Paint)}</li>
+         * <li>Color filters set via {@link Paint#setColorFilter(
+         *     android.graphics.ColorFilter)}</li>
+         * <li>Mask filters set via {@link Paint#setMaskFilter(
+         *     android.graphics.MaskFilter)}</li>
+         * <li>Some XFER modes such as
+         *     {@link android.graphics.PorterDuff.Mode#SRC_ATOP PorterDuff.Mode SRC},
+         *     {@link android.graphics.PorterDuff.Mode#DST_ATOP PorterDuff.DST_ATOP},
+         *     {@link android.graphics.PorterDuff.Mode#XOR PorterDuff.XOR},
+         *     {@link android.graphics.PorterDuff.Mode#ADD PorterDuff.ADD}</li>
+         * </ul>
+         *
+         * @return The canvas if the page is not finished, null otherwise.
+         *
+         * @see PdfDocument#finishPage(Page)
+         */
+        public Canvas getCanvas() {
+            return mCanvas;
+        }
+
+        /**
+         * Gets the {@link PageInfo} with meta-data for the page.
+         *
+         * @return The page info.
+         *
+         * @see PdfDocument#finishPage(Page)
+         */
+        public PageInfo getInfo() {
+            return mPageInfo;
+        }
+
+        boolean isFinished() {
+            return mCanvas == null;
+        }
+
+        private void finish() {
+            if (mCanvas != null) {
+                mCanvas.release();
+                mCanvas = null;
+            }
+        }
+    }
+}
diff --git a/android/graphics/pdf/PdfEditor.java b/android/graphics/pdf/PdfEditor.java
new file mode 100644
index 0000000..0c509b7
--- /dev/null
+++ b/android/graphics/pdf/PdfEditor.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2014 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.pdf;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.OsConstants;
+import dalvik.system.CloseGuard;
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+
+import java.io.IOException;
+
+/**
+ * Class for editing PDF files.
+ *
+ * @hide
+ */
+public final class PdfEditor {
+
+    private final CloseGuard mCloseGuard = CloseGuard.get();
+
+    private long mNativeDocument;
+
+    private int mPageCount;
+
+    private ParcelFileDescriptor mInput;
+
+    /**
+     * Creates a new instance.
+     * <p>
+     * <strong>Note:</strong> The provided file descriptor must be <strong>seekable</strong>,
+     * i.e. its data being randomly accessed, e.g. pointing to a file. After finishing
+     * with this class you must call {@link #close()}.
+     * </p>
+     * <p>
+     * <strong>Note:</strong> This class takes ownership of the passed in file descriptor
+     * and is responsible for closing it when the editor is closed.
+     * </p>
+     *
+     * @param input Seekable file descriptor to read from.
+     *
+     * @throws java.io.IOException If an error occurs while reading the file.
+     * @throws java.lang.SecurityException If the file requires a password or
+     *         the security scheme is not supported.
+     *
+     * @see #close()
+     */
+    public PdfEditor(@NonNull ParcelFileDescriptor input) throws IOException {
+        if (input == null) {
+            throw new NullPointerException("input cannot be null");
+        }
+
+        final long size;
+        try {
+            Libcore.os.lseek(input.getFileDescriptor(), 0, OsConstants.SEEK_SET);
+            size = Libcore.os.fstat(input.getFileDescriptor()).st_size;
+        } catch (ErrnoException ee) {
+            throw new IllegalArgumentException("file descriptor not seekable");
+        }
+        mInput = input;
+
+        synchronized (PdfRenderer.sPdfiumLock) {
+            mNativeDocument = nativeOpen(mInput.getFd(), size);
+            try {
+                mPageCount = nativeGetPageCount(mNativeDocument);
+            } catch (Throwable t) {
+                nativeClose(mNativeDocument);
+                mNativeDocument = 0;
+                throw t;
+            }
+        }
+
+        mCloseGuard.open("close");
+    }
+
+    /**
+     * Gets the number of pages in the document.
+     *
+     * @return The page count.
+     */
+    public int getPageCount() {
+        throwIfClosed();
+        return mPageCount;
+    }
+
+    /**
+     * Removes the page with a given index.
+     *
+     * @param pageIndex The page to remove.
+     */
+    public void removePage(int pageIndex) {
+        throwIfClosed();
+        throwIfPageNotInDocument(pageIndex);
+
+        synchronized (PdfRenderer.sPdfiumLock) {
+            mPageCount = nativeRemovePage(mNativeDocument, pageIndex);
+        }
+    }
+
+    /**
+     * Sets a transformation and clip for a given page. The transformation matrix if
+     * non-null must be affine as per {@link android.graphics.Matrix#isAffine()}. If
+     * the clip is null, then no clipping is performed.
+     *
+     * @param pageIndex The page whose transform to set.
+     * @param transform The transformation to apply.
+     * @param clip The clip to apply.
+     */
+    public void setTransformAndClip(int pageIndex, @Nullable Matrix transform,
+            @Nullable Rect clip) {
+        throwIfClosed();
+        throwIfPageNotInDocument(pageIndex);
+        throwIfNotNullAndNotAfine(transform);
+        if (transform == null) {
+            transform = Matrix.IDENTITY_MATRIX;
+        }
+        if (clip == null) {
+            Point size = new Point();
+            getPageSize(pageIndex, size);
+
+            synchronized (PdfRenderer.sPdfiumLock) {
+                nativeSetTransformAndClip(mNativeDocument, pageIndex, transform.native_instance,
+                        0, 0, size.x, size.y);
+            }
+        } else {
+            synchronized (PdfRenderer.sPdfiumLock) {
+                nativeSetTransformAndClip(mNativeDocument, pageIndex, transform.native_instance,
+                        clip.left, clip.top, clip.right, clip.bottom);
+            }
+        }
+    }
+
+    /**
+     * Gets the size of a given page in mils (1/72").
+     *
+     * @param pageIndex The page index.
+     * @param outSize The size output.
+     */
+    public void getPageSize(int pageIndex, @NonNull Point outSize) {
+        throwIfClosed();
+        throwIfOutSizeNull(outSize);
+        throwIfPageNotInDocument(pageIndex);
+
+        synchronized (PdfRenderer.sPdfiumLock) {
+            nativeGetPageSize(mNativeDocument, pageIndex, outSize);
+        }
+    }
+
+    /**
+     * Gets the media box of a given page in mils (1/72").
+     *
+     * @param pageIndex The page index.
+     * @param outMediaBox The media box output.
+     */
+    public boolean getPageMediaBox(int pageIndex, @NonNull Rect outMediaBox) {
+        throwIfClosed();
+        throwIfOutMediaBoxNull(outMediaBox);
+        throwIfPageNotInDocument(pageIndex);
+
+        synchronized (PdfRenderer.sPdfiumLock) {
+            return nativeGetPageMediaBox(mNativeDocument, pageIndex, outMediaBox);
+        }
+    }
+
+    /**
+     * Sets the media box of a given page in mils (1/72").
+     *
+     * @param pageIndex The page index.
+     * @param mediaBox The media box.
+     */
+    public void setPageMediaBox(int pageIndex, @NonNull Rect mediaBox) {
+        throwIfClosed();
+        throwIfMediaBoxNull(mediaBox);
+        throwIfPageNotInDocument(pageIndex);
+
+        synchronized (PdfRenderer.sPdfiumLock) {
+            nativeSetPageMediaBox(mNativeDocument, pageIndex, mediaBox);
+        }
+    }
+
+    /**
+     * Gets the crop box of a given page in mils (1/72").
+     *
+     * @param pageIndex The page index.
+     * @param outCropBox The crop box output.
+     */
+    public boolean getPageCropBox(int pageIndex, @NonNull Rect outCropBox) {
+        throwIfClosed();
+        throwIfOutCropBoxNull(outCropBox);
+        throwIfPageNotInDocument(pageIndex);
+
+        synchronized (PdfRenderer.sPdfiumLock) {
+            return nativeGetPageCropBox(mNativeDocument, pageIndex, outCropBox);
+        }
+    }
+
+    /**
+     * Sets the crop box of a given page in mils (1/72").
+     *
+     * @param pageIndex The page index.
+     * @param cropBox The crop box.
+     */
+    public void setPageCropBox(int pageIndex, @NonNull Rect cropBox) {
+        throwIfClosed();
+        throwIfCropBoxNull(cropBox);
+        throwIfPageNotInDocument(pageIndex);
+
+        synchronized (PdfRenderer.sPdfiumLock) {
+            nativeSetPageCropBox(mNativeDocument, pageIndex, cropBox);
+        }
+    }
+
+    /**
+     * Gets whether the document prefers to be scaled for printing.
+     *
+     * @return Whether to scale the document.
+     */
+    public boolean shouldScaleForPrinting() {
+        throwIfClosed();
+
+        synchronized (PdfRenderer.sPdfiumLock) {
+            return nativeScaleForPrinting(mNativeDocument);
+        }
+    }
+
+    /**
+     * Writes the PDF file to the provided destination.
+     * <p>
+     * <strong>Note:</strong> This method takes ownership of the passed in file
+     * descriptor and is responsible for closing it when writing completes.
+     * </p>
+     * @param output The destination.
+     */
+    public void write(ParcelFileDescriptor output) throws IOException {
+        try {
+            throwIfClosed();
+
+            synchronized (PdfRenderer.sPdfiumLock) {
+                nativeWrite(mNativeDocument, output.getFd());
+            }
+        } finally {
+            IoUtils.closeQuietly(output);
+        }
+    }
+
+    /**
+     * Closes this editor. You should not use this instance
+     * after this method is called.
+     */
+    public void close() {
+        throwIfClosed();
+        doClose();
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mCloseGuard != null) {
+                mCloseGuard.warnIfOpen();
+            }
+
+            doClose();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private void doClose() {
+        if (mNativeDocument != 0) {
+            synchronized (PdfRenderer.sPdfiumLock) {
+                nativeClose(mNativeDocument);
+            }
+            mNativeDocument = 0;
+        }
+
+        if (mInput != null) {
+            IoUtils.closeQuietly(mInput);
+            mInput = null;
+        }
+        mCloseGuard.close();
+    }
+
+    private void throwIfClosed() {
+        if (mInput == null) {
+            throw new IllegalStateException("Already closed");
+        }
+    }
+
+    private void throwIfPageNotInDocument(int pageIndex) {
+        if (pageIndex < 0 || pageIndex >= mPageCount) {
+            throw new IllegalArgumentException("Invalid page index");
+        }
+    }
+
+    private void throwIfNotNullAndNotAfine(Matrix matrix) {
+        if (matrix != null && !matrix.isAffine()) {
+            throw new IllegalStateException("Matrix must be afine");
+        }
+    }
+
+    private void throwIfOutSizeNull(Point outSize) {
+        if (outSize == null) {
+            throw new NullPointerException("outSize cannot be null");
+        }
+    }
+
+    private void throwIfOutMediaBoxNull(Rect outMediaBox) {
+        if (outMediaBox == null) {
+            throw new NullPointerException("outMediaBox cannot be null");
+        }
+    }
+
+    private void throwIfMediaBoxNull(Rect mediaBox) {
+        if (mediaBox == null) {
+            throw new NullPointerException("mediaBox cannot be null");
+        }
+    }
+
+    private void throwIfOutCropBoxNull(Rect outCropBox) {
+        if (outCropBox == null) {
+            throw new NullPointerException("outCropBox cannot be null");
+        }
+    }
+
+    private void throwIfCropBoxNull(Rect cropBox) {
+        if (cropBox == null) {
+            throw new NullPointerException("cropBox cannot be null");
+        }
+    }
+
+    private static native long nativeOpen(int fd, long size);
+    private static native void nativeClose(long documentPtr);
+    private static native int nativeGetPageCount(long documentPtr);
+    private static native int nativeRemovePage(long documentPtr, int pageIndex);
+    private static native void nativeWrite(long documentPtr, int fd);
+    private static native void nativeSetTransformAndClip(long documentPtr, int pageIndex,
+            long transformPtr, int clipLeft, int clipTop, int clipRight, int clipBottom);
+    private static native void nativeGetPageSize(long documentPtr, int pageIndex, Point outSize);
+    private static native boolean nativeGetPageMediaBox(long documentPtr, int pageIndex,
+            Rect outMediaBox);
+    private static native void nativeSetPageMediaBox(long documentPtr, int pageIndex,
+            Rect mediaBox);
+    private static native boolean nativeGetPageCropBox(long documentPtr, int pageIndex,
+            Rect outMediaBox);
+    private static native void nativeSetPageCropBox(long documentPtr, int pageIndex,
+            Rect mediaBox);
+    private static native boolean nativeScaleForPrinting(long documentPtr);
+}
diff --git a/android/graphics/pdf/PdfRenderer.java b/android/graphics/pdf/PdfRenderer.java
new file mode 100644
index 0000000..c82ab0d
--- /dev/null
+++ b/android/graphics/pdf/PdfRenderer.java
@@ -0,0 +1,493 @@
+/*
+ * Copyright (C) 2014 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.pdf;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.OsConstants;
+import com.android.internal.util.Preconditions;
+import dalvik.system.CloseGuard;
+
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * <p>
+ * This class enables rendering a PDF document. This class is not thread safe.
+ * </p>
+ * <p>
+ * If you want to render a PDF, you create a renderer and for every page you want
+ * to render, you open the page, render it, and close the page. After you are done
+ * with rendering, you close the renderer. After the renderer is closed it should not
+ * be used anymore. Note that the pages are rendered one by one, i.e. you can have
+ * only a single page opened at any given time.
+ * </p>
+ * <p>
+ * A typical use of the APIs to render a PDF looks like this:
+ * </p>
+ * <pre>
+ * // create a new renderer
+ * PdfRenderer renderer = new PdfRenderer(getSeekableFileDescriptor());
+ *
+ * // let us just render all pages
+ * final int pageCount = renderer.getPageCount();
+ * for (int i = 0; i < pageCount; i++) {
+ *     Page page = renderer.openPage(i);
+ *
+ *     // say we render for showing on the screen
+ *     page.render(mBitmap, null, null, Page.RENDER_MODE_FOR_DISPLAY);
+ *
+ *     // do stuff with the bitmap
+ *
+ *     // close the page
+ *     page.close();
+ * }
+ *
+ * // close the renderer
+ * renderer.close();
+ * </pre>
+ *
+ * <h3>Print preview and print output</h3>
+ * <p>
+ * If you are using this class to rasterize a PDF for printing or show a print
+ * preview, it is recommended that you respect the following contract in order
+ * to provide a consistent user experience when seeing a preview and printing,
+ * i.e. the user sees a preview that is the same as the printout.
+ * </p>
+ * <ul>
+ * <li>
+ * Respect the property whether the document would like to be scaled for printing
+ * as per {@link #shouldScaleForPrinting()}.
+ * </li>
+ * <li>
+ * When scaling a document for printing the aspect ratio should be preserved.
+ * </li>
+ * <li>
+ * Do not inset the content with any margins from the {@link android.print.PrintAttributes}
+ * as the application is responsible to render it such that the margins are respected.
+ * </li>
+ * <li>
+ * If document page size is greater than the printed media size the content should
+ * be anchored to the upper left corner of the page for left-to-right locales and
+ * top right corner for right-to-left locales.
+ * </li>
+ * </ul>
+ *
+ * @see #close()
+ */
+public final class PdfRenderer implements AutoCloseable {
+    /**
+     * Any call the native pdfium code has to be single threaded as the library does not support
+     * parallel use.
+     */
+    final static Object sPdfiumLock = new Object();
+
+    private final CloseGuard mCloseGuard = CloseGuard.get();
+
+    private final Point mTempPoint = new Point();
+
+    private long mNativeDocument;
+
+    private final int mPageCount;
+
+    private ParcelFileDescriptor mInput;
+
+    private Page mCurrentPage;
+
+    /** @hide */
+    @IntDef({
+        Page.RENDER_MODE_FOR_DISPLAY,
+        Page.RENDER_MODE_FOR_PRINT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RenderMode {}
+
+    /**
+     * Creates a new instance.
+     * <p>
+     * <strong>Note:</strong> The provided file descriptor must be <strong>seekable</strong>,
+     * i.e. its data being randomly accessed, e.g. pointing to a file.
+     * </p>
+     * <p>
+     * <strong>Note:</strong> This class takes ownership of the passed in file descriptor
+     * and is responsible for closing it when the renderer is closed.
+     * </p>
+     * <p>
+     * If the file is from an untrusted source it is recommended to run the renderer in a separate,
+     * isolated process with minimal permissions to limit the impact of security exploits.
+     * </p>
+     *
+     * @param input Seekable file descriptor to read from.
+     *
+     * @throws java.io.IOException If an error occurs while reading the file.
+     * @throws java.lang.SecurityException If the file requires a password or
+     *         the security scheme is not supported.
+     */
+    public PdfRenderer(@NonNull ParcelFileDescriptor input) throws IOException {
+        if (input == null) {
+            throw new NullPointerException("input cannot be null");
+        }
+
+        final long size;
+        try {
+            Libcore.os.lseek(input.getFileDescriptor(), 0, OsConstants.SEEK_SET);
+            size = Libcore.os.fstat(input.getFileDescriptor()).st_size;
+        } catch (ErrnoException ee) {
+            throw new IllegalArgumentException("file descriptor not seekable");
+        }
+        mInput = input;
+
+        synchronized (sPdfiumLock) {
+            mNativeDocument = nativeCreate(mInput.getFd(), size);
+            try {
+                mPageCount = nativeGetPageCount(mNativeDocument);
+            } catch (Throwable t) {
+                nativeClose(mNativeDocument);
+                mNativeDocument = 0;
+                throw t;
+            }
+        }
+
+        mCloseGuard.open("close");
+    }
+
+    /**
+     * Closes this renderer. You should not use this instance
+     * after this method is called.
+     */
+    public void close() {
+        throwIfClosed();
+        throwIfPageOpened();
+        doClose();
+    }
+
+    /**
+     * Gets the number of pages in the document.
+     *
+     * @return The page count.
+     */
+    public int getPageCount() {
+        throwIfClosed();
+        return mPageCount;
+    }
+
+    /**
+     * Gets whether the document prefers to be scaled for printing.
+     * You should take this info account if the document is rendered
+     * for printing and the target media size differs from the page
+     * size.
+     *
+     * @return If to scale the document.
+     */
+    public boolean shouldScaleForPrinting() {
+        throwIfClosed();
+
+        synchronized (sPdfiumLock) {
+            return nativeScaleForPrinting(mNativeDocument);
+        }
+    }
+
+    /**
+     * Opens a page for rendering.
+     *
+     * @param index The page index.
+     * @return A page that can be rendered.
+     *
+     * @see android.graphics.pdf.PdfRenderer.Page#close() PdfRenderer.Page.close()
+     */
+    public Page openPage(int index) {
+        throwIfClosed();
+        throwIfPageOpened();
+        throwIfPageNotInDocument(index);
+        mCurrentPage = new Page(index);
+        return mCurrentPage;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mCloseGuard != null) {
+                mCloseGuard.warnIfOpen();
+            }
+
+            doClose();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private void doClose() {
+        if (mCurrentPage != null) {
+            mCurrentPage.close();
+            mCurrentPage = null;
+        }
+
+        if (mNativeDocument != 0) {
+            synchronized (sPdfiumLock) {
+                nativeClose(mNativeDocument);
+            }
+            mNativeDocument = 0;
+        }
+
+        if (mInput != null) {
+            IoUtils.closeQuietly(mInput);
+            mInput = null;
+        }
+        mCloseGuard.close();
+    }
+
+    private void throwIfClosed() {
+        if (mInput == null) {
+            throw new IllegalStateException("Already closed");
+        }
+    }
+
+    private void throwIfPageOpened() {
+        if (mCurrentPage != null) {
+            throw new IllegalStateException("Current page not closed");
+        }
+    }
+
+    private void throwIfPageNotInDocument(int pageIndex) {
+        if (pageIndex < 0 || pageIndex >= mPageCount) {
+            throw new IllegalArgumentException("Invalid page index");
+        }
+    }
+
+    /**
+     * This class represents a PDF document page for rendering.
+     */
+    public final class Page implements AutoCloseable {
+
+        private final CloseGuard mCloseGuard = CloseGuard.get();
+
+        /**
+         * Mode to render the content for display on a screen.
+         */
+        public static final int RENDER_MODE_FOR_DISPLAY = 1;
+
+        /**
+         * Mode to render the content for printing.
+         */
+        public static final int RENDER_MODE_FOR_PRINT = 2;
+
+        private final int mIndex;
+        private final int mWidth;
+        private final int mHeight;
+
+        private long mNativePage;
+
+        private Page(int index) {
+            Point size = mTempPoint;
+            synchronized (sPdfiumLock) {
+                mNativePage = nativeOpenPageAndGetSize(mNativeDocument, index, size);
+            }
+            mIndex = index;
+            mWidth = size.x;
+            mHeight = size.y;
+            mCloseGuard.open("close");
+        }
+
+        /**
+         * Gets the page index.
+         *
+         * @return The index.
+         */
+        public int getIndex() {
+            return  mIndex;
+        }
+
+        /**
+         * Gets the page width in points (1/72").
+         *
+         * @return The width in points.
+         */
+        public int getWidth() {
+            return mWidth;
+        }
+
+        /**
+         * Gets the page height in points (1/72").
+         *
+         * @return The height in points.
+         */
+        public int getHeight() {
+            return mHeight;
+        }
+
+        /**
+         * Renders a page to a bitmap.
+         * <p>
+         * You may optionally specify a rectangular clip in the bitmap bounds. No rendering
+         * outside the clip will be performed, hence it is your responsibility to initialize
+         * the bitmap outside the clip.
+         * </p>
+         * <p>
+         * You may optionally specify a matrix to transform the content from page coordinates
+         * which are in points (1/72") to bitmap coordinates which are in pixels. If this
+         * matrix is not provided this method will apply a transformation that will fit the
+         * whole page to the destination clip if provided or the destination bitmap if no
+         * clip is provided.
+         * </p>
+         * <p>
+         * The clip and transformation are useful for implementing tile rendering where the
+         * destination bitmap contains a portion of the image, for example when zooming.
+         * Another useful application is for printing where the size of the bitmap holding
+         * the page is too large and a client can render the page in stripes.
+         * </p>
+         * <p>
+         * <strong>Note: </strong> The destination bitmap format must be
+         * {@link Config#ARGB_8888 ARGB}.
+         * </p>
+         * <p>
+         * <strong>Note: </strong> The optional transformation matrix must be affine as per
+         * {@link android.graphics.Matrix#isAffine() Matrix.isAffine()}. Hence, you can specify
+         * rotation, scaling, translation but not a perspective transformation.
+         * </p>
+         *
+         * @param destination Destination bitmap to which to render.
+         * @param destClip Optional clip in the bitmap bounds.
+         * @param transform Optional transformation to apply when rendering.
+         * @param renderMode The render mode.
+         *
+         * @see #RENDER_MODE_FOR_DISPLAY
+         * @see #RENDER_MODE_FOR_PRINT
+         */
+        public void render(@NonNull Bitmap destination, @Nullable Rect destClip,
+                           @Nullable Matrix transform, @RenderMode int renderMode) {
+            if (mNativePage == 0) {
+                throw new NullPointerException();
+            }
+
+            destination = Preconditions.checkNotNull(destination, "bitmap null");
+
+            if (destination.getConfig() != Config.ARGB_8888) {
+                throw new IllegalArgumentException("Unsupported pixel format");
+            }
+
+            if (destClip != null) {
+                if (destClip.left < 0 || destClip.top < 0
+                        || destClip.right > destination.getWidth()
+                        || destClip.bottom > destination.getHeight()) {
+                    throw new IllegalArgumentException("destBounds not in destination");
+                }
+            }
+
+            if (transform != null && !transform.isAffine()) {
+                 throw new IllegalArgumentException("transform not affine");
+            }
+
+            if (renderMode != RENDER_MODE_FOR_PRINT && renderMode != RENDER_MODE_FOR_DISPLAY) {
+                throw new IllegalArgumentException("Unsupported render mode");
+            }
+
+            if (renderMode == RENDER_MODE_FOR_PRINT && renderMode == RENDER_MODE_FOR_DISPLAY) {
+                throw new IllegalArgumentException("Only single render mode supported");
+            }
+
+            final int contentLeft = (destClip != null) ? destClip.left : 0;
+            final int contentTop = (destClip != null) ? destClip.top : 0;
+            final int contentRight = (destClip != null) ? destClip.right
+                    : destination.getWidth();
+            final int contentBottom = (destClip != null) ? destClip.bottom
+                    : destination.getHeight();
+
+            // If transform is not set, stretch page to whole clipped area
+            if (transform == null) {
+                int clipWidth = contentRight - contentLeft;
+                int clipHeight = contentBottom - contentTop;
+
+                transform = new Matrix();
+                transform.postScale((float)clipWidth / getWidth(),
+                        (float)clipHeight / getHeight());
+                transform.postTranslate(contentLeft, contentTop);
+            }
+
+            final long transformPtr = transform.native_instance;
+
+            synchronized (sPdfiumLock) {
+                nativeRenderPage(mNativeDocument, mNativePage, destination, contentLeft,
+                        contentTop, contentRight, contentBottom, transformPtr, renderMode);
+            }
+        }
+
+        /**
+         * Closes this page.
+         *
+         * @see android.graphics.pdf.PdfRenderer#openPage(int)
+         */
+        @Override
+        public void close() {
+            throwIfClosed();
+            doClose();
+        }
+
+        @Override
+        protected void finalize() throws Throwable {
+            try {
+                if (mCloseGuard != null) {
+                    mCloseGuard.warnIfOpen();
+                }
+
+                doClose();
+            } finally {
+                super.finalize();
+            }
+        }
+
+        private void doClose() {
+            if (mNativePage != 0) {
+                synchronized (sPdfiumLock) {
+                    nativeClosePage(mNativePage);
+                }
+                mNativePage = 0;
+            }
+
+            mCloseGuard.close();
+            mCurrentPage = null;
+        }
+
+        private void throwIfClosed() {
+            if (mNativePage == 0) {
+                throw new IllegalStateException("Already closed");
+            }
+        }
+    }
+
+    private static native long nativeCreate(int fd, long size);
+    private static native void nativeClose(long documentPtr);
+    private static native int nativeGetPageCount(long documentPtr);
+    private static native boolean nativeScaleForPrinting(long documentPtr);
+    private static native void nativeRenderPage(long documentPtr, long pagePtr, Bitmap dest,
+            int clipLeft, int clipTop, int clipRight, int clipBottom, long transformPtr,
+            int renderMode);
+    private static native long nativeOpenPageAndGetSize(long documentPtr, int pageIndex,
+            Point outSize);
+    private static native void nativeClosePage(long pagePtr);
+}
diff --git a/android/graphics/perftests/CanvasPerfTest.java b/android/graphics/perftests/CanvasPerfTest.java
new file mode 100644
index 0000000..eed1db0
--- /dev/null
+++ b/android/graphics/perftests/CanvasPerfTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2016 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.perftests;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Bitmap.Config;
+import android.graphics.Paint;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.filters.LargeTest;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+@LargeTest
+public class CanvasPerfTest {
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Test
+    public void testBasicViewGroupDraw() {
+        // This test is a clone of BM_DisplayListCanvas_basicViewGroupDraw
+
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        RenderNode node = RenderNode.create("benchmark", null);
+        RenderNode child = RenderNode.create("child", null);
+        child.setLeftTopRightBottom(50, 50, 100, 100);
+
+        DisplayListCanvas canvas = node.start(100, 100);
+        node.end(canvas);
+        canvas = child.start(50, 50);
+        canvas.drawColor(Color.WHITE);
+        child.end(canvas);
+
+        while (state.keepRunning()) {
+            canvas = node.start(200, 200);
+            int save = canvas.save();
+            canvas.clipRect(1, 1, 199, 199);
+            canvas.insertReorderBarrier();
+            for (int i = 0; i < 5; i++) {
+                canvas.drawRenderNode(child);
+            }
+            canvas.insertInorderBarrier();
+            canvas.restoreToCount(save);
+            node.end(canvas);
+        }
+    }
+
+    @Test
+    public void testRecordSimpleBitmapView() {
+        // This test is a clone of BM_DisplayListCanvas_record_simpleBitmapView
+
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        RenderNode node = RenderNode.create("benchmark", null);
+
+        DisplayListCanvas canvas = node.start(100, 100);
+        node.end(canvas);
+        Bitmap bitmap = Bitmap.createBitmap(80, 80, Config.ARGB_8888);
+        Paint paint = new Paint();
+        paint.setColor(Color.BLACK);
+
+        while (state.keepRunning()) {
+            canvas = node.start(100, 100);
+            {
+                canvas.save();
+                canvas.drawRect(0, 0, 100, 100, paint);
+                canvas.restore();
+            }
+            {
+                canvas.save();
+                canvas.translate(10, 10);
+                canvas.drawBitmap(bitmap, 0, 0, null);
+                canvas.restore();
+            }
+            node.end(canvas);
+        }
+    }
+}
diff --git a/android/graphics/perftests/OutlinePerfTest.java b/android/graphics/perftests/OutlinePerfTest.java
new file mode 100644
index 0000000..3a4fc72
--- /dev/null
+++ b/android/graphics/perftests/OutlinePerfTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 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.perftests;
+
+import android.graphics.Outline;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.filters.LargeTest;
+import android.view.RenderNode;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+@LargeTest
+public class OutlinePerfTest {
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Test
+    public void testSetEmpty() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        Outline outline = new Outline();
+        while (state.keepRunning()) {
+            outline.setEmpty();
+        }
+    }
+
+    @Test
+    public void testSetRoundRect() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        Outline outline = new Outline();
+        while (state.keepRunning()) {
+            outline.setRoundRect(50, 50, 150, 150, 5);
+        }
+    }
+}
diff --git a/android/graphics/perftests/PaintHasGlyphPerfTest.java b/android/graphics/perftests/PaintHasGlyphPerfTest.java
new file mode 100644
index 0000000..26b8309
--- /dev/null
+++ b/android/graphics/perftests/PaintHasGlyphPerfTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2016 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.perftests;
+
+import android.graphics.Paint;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.perftests.utils.StubActivity;
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.Parameterized;
+
+@LargeTest
+@RunWith(Parameterized.class)
+public class PaintHasGlyphPerfTest {
+    @Parameters(name = "{0}")
+    public static Collection glyphStrings() {
+        return Arrays.asList(new Object[][] {
+            { "Latin", "A" },
+            { "Ligature", "fi" },
+            { "SurrogatePair", "\uD83D\uDE00" },  // U+1F600
+            { "Flags", "\uD83C\uDDFA\uD83C\uDDF8" },  // US
+            { "Ideograph_VariationSelector", "\u3402\uDB40\uDD00" },  // U+3402 U+E0100
+            { "Emoji_VariationSelector", "\u00A9\uFE0F" },
+            { "EmojiSequence",
+              // U+1F468 U+200D U+2764 U+FE0F U+200D U+1F48B U+200D U+1F468
+              "\uD83D\uDC68\u200D\u2764\uFE0F\u200D\uD83D\uDC8B\u200D\uD83D\uDC68" },
+        });
+    }
+
+    private final String mQuery;
+
+    public PaintHasGlyphPerfTest(String metricKey, String query) {
+        mQuery = query;
+    }
+
+    @Rule
+    public ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule(StubActivity.class);
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Test
+    public void testHasGlyph() {
+        Paint paint = new Paint();
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        while (state.keepRunning()) {
+            paint.hasGlyph(mQuery);
+        }
+    }
+}
diff --git a/android/graphics/perftests/PaintMeasureTextTest.java b/android/graphics/perftests/PaintMeasureTextTest.java
new file mode 100644
index 0000000..b81908c
--- /dev/null
+++ b/android/graphics/perftests/PaintMeasureTextTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2016 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.perftests;
+
+import android.graphics.Canvas;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.filters.LargeTest;
+import android.text.TextPaint;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+@LargeTest
+@RunWith(Parameterized.class)
+public class PaintMeasureTextTest {
+
+    private static final int USE_CACHE = 0;
+    private static final int DONT_USE_CACHE = 1;
+
+    @Parameterized.Parameters(name = "{0}")
+    public static Collection measureSpecs() {
+        return Arrays.asList(new Object[][] {
+                { "alphabet_cached", USE_CACHE, "a" },
+                { "alphabet_not_cached", DONT_USE_CACHE, "a" },
+                // U+4E80 is an ideograph.
+                { "ideograph_cached", USE_CACHE, "\u4E80" },
+                { "ideograph_not_cached", DONT_USE_CACHE, "\u4E80" },
+                // U+20B9F(\uD842\uDF9F) is an ideograph.
+                { "surrogate_pairs_cached", USE_CACHE, "\uD842\uDF9F" },
+                { "surrogate_pairs_not_cached", DONT_USE_CACHE, "\uD842\uDF9F" },
+                // U+303D is PART ALTERNATION MARK
+                { "emoji_cached", USE_CACHE, "\u231A" },
+                { "emoji_not_cached", DONT_USE_CACHE, "\u231A" },
+                // U+1F368(\uD83C\uDF68) is ICE CREAM
+                { "emoji_surrogate_pairs_cached", USE_CACHE, "\uD83C\uDF68" },
+                { "emoji_surrogate_pairs_not_cached", DONT_USE_CACHE, "\uD83C\uDF68" },
+        });
+    }
+
+    private final String mText;
+    private final int mCacheMode;
+
+    public PaintMeasureTextTest(String key, int cacheMode, String text) {
+        mText = text;
+        mCacheMode = cacheMode;
+    }
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Test
+    public void testMeasureTextPerf() {
+        TextPaint paint = new TextPaint();
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        if (mCacheMode == USE_CACHE) {
+            paint.measureText(mText);
+        } else {
+            Canvas.freeTextLayoutCaches();
+        }
+
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            if (mCacheMode == DONT_USE_CACHE) {
+                Canvas.freeTextLayoutCaches();
+            }
+            state.resumeTiming();
+
+            paint.measureText(mText);
+        }
+    }
+}
diff --git a/android/graphics/perftests/PathPerfTest.java b/android/graphics/perftests/PathPerfTest.java
new file mode 100644
index 0000000..7a49b4f
--- /dev/null
+++ b/android/graphics/perftests/PathPerfTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2016 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.perftests;
+
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.filters.LargeTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+@LargeTest
+public class PathPerfTest {
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Test
+    public void testReset() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        Path path = new Path();
+        while (state.keepRunning()) {
+            path.reset();
+        }
+    }
+
+    @Test
+    public void testAddReset() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        Path path = new Path();
+        while (state.keepRunning()) {
+            path.addRect(0, 0, 100, 100, Path.Direction.CW);
+            path.reset();
+        }
+    }
+
+    @Test
+    public void testRewind() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        Path path = new Path();
+        while (state.keepRunning()) {
+            path.rewind();
+        }
+    }
+
+    @Test
+    public void testAddRewind() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        Path path = new Path();
+        while (state.keepRunning()) {
+            path.addRect(0, 0, 100, 100, Path.Direction.CW);
+            path.rewind();
+        }
+    }
+
+    @Test
+    public void testIsEmpty() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        Path path = new Path();
+        path.addRect(0, 0, 100, 100, Path.Direction.CW);
+        while (state.keepRunning()) {
+            path.isEmpty();
+        }
+    }
+
+    @Test
+    public void testIsConvex() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        Path path = new Path();
+        path.addRect(0, 0, 100, 100, Path.Direction.CW);
+        while (state.keepRunning()) {
+            path.isConvex();
+        }
+    }
+
+    @Test
+    public void testGetSetFillType() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        Path path = new Path();
+        path.addRect(0, 0, 100, 100, Path.Direction.CW);
+        while (state.keepRunning()) {
+            path.setFillType(Path.FillType.EVEN_ODD);
+            path.getFillType();
+        }
+    }
+
+    @Test
+    public void testIsRect() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        Path path = new Path();
+        path.addRect(0, 0, 100, 100, Path.Direction.CW);
+        final RectF outRect = new RectF();
+        while (state.keepRunning()) {
+            path.isRect(outRect);
+        }
+    }
+}
diff --git a/android/graphics/perftests/RenderNodePerfTest.java b/android/graphics/perftests/RenderNodePerfTest.java
new file mode 100644
index 0000000..dfbabeb
--- /dev/null
+++ b/android/graphics/perftests/RenderNodePerfTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2016 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.perftests;
+
+import android.graphics.Outline;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.filters.LargeTest;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.ArrayList;
+
+@LargeTest
+public class RenderNodePerfTest {
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Test
+    public void testMeasureRenderNodeJniOverhead() {
+        final RenderNode node = RenderNode.create("benchmark", null);
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        while (state.keepRunning()) {
+            node.setTranslationX(1.0f);
+        }
+    }
+
+    @Test
+    public void testCreateRenderNodeNoName() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            RenderNode node = RenderNode.create(null, null);
+            node.destroy();
+        }
+    }
+
+    @Test
+    public void testCreateRenderNode() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            RenderNode node = RenderNode.create("LinearLayout", null);
+            node.destroy();
+        }
+    }
+
+    @Test
+    public void testIsValid() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        RenderNode node = RenderNode.create("LinearLayout", null);
+        while (state.keepRunning()) {
+            node.isValid();
+        }
+    }
+
+    @Test
+    public void testStartEnd() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        RenderNode node = RenderNode.create("LinearLayout", null);
+        while (state.keepRunning()) {
+            DisplayListCanvas canvas = node.start(100, 100);
+            node.end(canvas);
+        }
+    }
+
+    @Test
+    public void testStartEndDeepHierarchy() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        RenderNode[] nodes = new RenderNode[30];
+        DisplayListCanvas[] canvases = new DisplayListCanvas[nodes.length];
+        for (int i = 0; i < nodes.length; i++) {
+            nodes[i] = RenderNode.create("LinearLayout", null);
+        }
+
+        while (state.keepRunning()) {
+            for (int i = 0; i < nodes.length; i++) {
+                canvases[i] = nodes[i].start(100, 100);
+            }
+            for (int i = nodes.length - 1; i >= 0; i--) {
+                nodes[i].end(canvases[i]);
+            }
+        }
+    }
+
+    @Test
+    public void testHasIdentityMatrix() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        RenderNode node = RenderNode.create("LinearLayout", null);
+        while (state.keepRunning()) {
+            node.hasIdentityMatrix();
+        }
+    }
+
+    @Test
+    public void testSetOutline() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        RenderNode node = RenderNode.create("LinearLayout", null);
+        Outline a = new Outline();
+        a.setRoundRect(0, 0, 100, 100, 10);
+        Outline b = new Outline();
+        b.setRect(50, 50, 150, 150);
+        b.setAlpha(0.5f);
+
+        while (state.keepRunning()) {
+            node.setOutline(a);
+            node.setOutline(b);
+        }
+    }
+}
diff --git a/android/graphics/perftests/TypefaceCreatePerfTest.java b/android/graphics/perftests/TypefaceCreatePerfTest.java
new file mode 100644
index 0000000..11ee599
--- /dev/null
+++ b/android/graphics/perftests/TypefaceCreatePerfTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2016 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.perftests;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.graphics.Typeface;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class TypefaceCreatePerfTest {
+    // A font file name in asset directory.
+    private static final String TEST_FONT_NAME = "DancingScript-Regular.ttf";
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Test
+    public void testCreate_fromFamily() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        while (state.keepRunning()) {
+            Typeface face = Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL);
+        }
+    }
+
+    @Test
+    public void testCreate_fromFamilyName() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        while (state.keepRunning()) {
+            Typeface face = Typeface.create("monospace", Typeface.NORMAL);
+        }
+    }
+
+    @Test
+    public void testCreate_fromAsset() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final Context context = InstrumentationRegistry.getContext();
+        final AssetManager am = context.getAssets();
+
+        while (state.keepRunning()) {
+            Typeface face = Typeface.createFromAsset(am, TEST_FONT_NAME);
+        }
+    }
+
+    @Test
+    public void testCreate_fromFile() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final Context context = InstrumentationRegistry.getContext();
+        final AssetManager am = context.getAssets();
+
+        File outFile = null;
+        try {
+            outFile = File.createTempFile("example", "ttf", context.getCacheDir());
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+
+        try (InputStream in = am.open(TEST_FONT_NAME);
+                OutputStream out = new FileOutputStream(outFile)) {
+            byte[] buf = new byte[1024];
+            int n = 0;
+            while ((n = in.read(buf)) != -1) {
+                out.write(buf, 0, n);
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+
+        while (state.keepRunning()) {
+            Typeface face = Typeface.createFromFile(outFile);
+        }
+
+        outFile.delete();
+    }
+}
diff --git a/android/graphics/perftests/VectorDrawablePerfTest.java b/android/graphics/perftests/VectorDrawablePerfTest.java
new file mode 100644
index 0000000..5533782
--- /dev/null
+++ b/android/graphics/perftests/VectorDrawablePerfTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2016 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.perftests;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.drawable.VectorDrawable;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.BitmapUtils;
+import android.perftests.utils.PerfStatusReporter;
+import android.perftests.utils.StubActivity;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import com.android.perftests.core.R;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+import static junit.framework.Assert.assertTrue;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class VectorDrawablePerfTest {
+
+    private static final boolean DUMP_BITMAP = false;
+
+    private int[] mTestWidths = {1024, 512};
+    private int[] mTestHeights = {512, 1024};
+
+    @Rule
+    public ActivityTestRule<StubActivity> mActivityRule =
+            new ActivityTestRule(StubActivity.class);
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Test
+    public void testBitmapDrawPerf() {
+        int resId = R.drawable.vector_drawable01;
+        Activity activity = mActivityRule.getActivity();
+        VectorDrawable vd = (VectorDrawable) activity.getDrawable(resId);
+
+        int w = 1024, h = 1024;
+        Bitmap.Config conf = Bitmap.Config.ARGB_8888;
+        Bitmap bmp = Bitmap.createBitmap(w, h, conf);
+        Canvas canvas = new Canvas(bmp);
+
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        int i = 0;
+        while (state.keepRunning()) {
+            // Use different width / height each to force the vectorDrawable abandon the cache.
+            vd.setBounds(0, 0, mTestWidths[i % 2], mTestHeights[i % 2]);
+            i++;
+            vd.draw(canvas);
+        }
+
+        // Double check the bitmap pixels to make sure we draw correct content.
+        int backgroundColor = bmp.getPixel(w / 4, h / 2);
+        int objColor = bmp.getPixel(w / 8, h / 2 + 1);
+        int emptyColor = bmp.getPixel(w * 3 / 4, h * 3 / 4);
+        assertTrue("The background should be white", backgroundColor == Color.WHITE);
+        assertTrue("The object should be black", objColor == Color.BLACK);
+        assertTrue("The right bottom part should be empty", emptyColor == Color.TRANSPARENT);
+
+        if (DUMP_BITMAP) {
+            BitmapUtils.saveBitmapIntoPNG(activity, bmp, resId);
+        }
+    }
+}