Import Android SDK Platform P [4697573]
/google/data/ro/projects/android/fetch_artifact \
--bid 4697573 \
--target sdk_phone_armv7-win_sdk \
sdk-repo-linux-sources-4697573.zip
AndroidVersion.ApiLevel has been modified to appear as 28
Change-Id: If80578c3c657366cc9cf75f8db13d46e2dd4e077
diff --git a/android/graphics/BaseCanvas.java b/android/graphics/BaseCanvas.java
index 627d551..71ee6c2 100644
--- a/android/graphics/BaseCanvas.java
+++ b/android/graphics/BaseCanvas.java
@@ -22,8 +22,7 @@
import android.annotation.Size;
import android.graphics.Canvas.VertexMode;
import android.text.GraphicsOperations;
-import android.text.MeasuredParagraph;
-import android.text.MeasuredText;
+import android.text.PrecomputedText;
import android.text.SpannableString;
import android.text.SpannedString;
import android.text.TextUtils;
@@ -42,13 +41,19 @@
/**
* Should only be assigned in constructors (or setBitmap if software canvas),
* freed by NativeAllocation.
+ * @hide
*/
protected long mNativeCanvasWrapper;
/**
* Used to determine when compatibility scaling is in effect.
+ * @hide
*/
protected int mScreenDensity = Bitmap.DENSITY_NONE;
+
+ /**
+ * @hide
+ */
protected int mDensity = Bitmap.DENSITY_NONE;
private boolean mAllowHwBitmapsInSwMode = false;
@@ -455,8 +460,7 @@
throwIfHasHwBitmapInSwMode(paint);
nDrawTextRun(mNativeCanvasWrapper, text, index, count, contextIndex, contextCount,
- x, y, isRtl, paint.getNativeInstance(), 0 /* measured text */,
- 0 /* measured text offset */);
+ x, y, isRtl, paint.getNativeInstance(), 0 /* measured text */);
}
public void drawTextRun(@NonNull CharSequence text, int start, int end, int contextStart,
@@ -487,19 +491,16 @@
char[] buf = TemporaryBuffer.obtain(contextLen);
TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
long measuredTextPtr = 0;
- int measuredTextOffset = 0;
- if (text instanceof MeasuredText) {
- MeasuredText mt = (MeasuredText) text;
+ if (text instanceof PrecomputedText) {
+ PrecomputedText mt = (PrecomputedText) text;
int paraIndex = mt.findParaIndex(start);
if (end <= mt.getParagraphEnd(paraIndex)) {
- // Only suppor the same paragraph.
+ // Only suppor the text in the same paragraph.
measuredTextPtr = mt.getMeasuredParagraph(paraIndex).getNativePtr();
- measuredTextOffset = start - mt.getParagraphStart(paraIndex);
}
}
nDrawTextRun(mNativeCanvasWrapper, buf, start - contextStart, len,
- 0, contextLen, x, y, isRtl, paint.getNativeInstance(),
- measuredTextPtr, measuredTextOffset);
+ 0, contextLen, x, y, isRtl, paint.getNativeInstance(), measuredTextPtr);
TemporaryBuffer.recycle(buf);
}
}
@@ -541,10 +542,19 @@
return mAllowHwBitmapsInSwMode;
}
+ /**
+ * @hide
+ */
+ protected void onHwBitmapInSwMode() {
+ if (!mAllowHwBitmapsInSwMode) {
+ throw new IllegalArgumentException(
+ "Software rendering doesn't support hardware bitmaps");
+ }
+ }
+
private void throwIfHwBitmapInSwMode(Bitmap bitmap) {
- if (!mAllowHwBitmapsInSwMode && !isHardwareAccelerated()
- && bitmap.getConfig() == Bitmap.Config.HARDWARE) {
- throw new IllegalStateException("Software rendering doesn't support hardware bitmaps");
+ if (!isHardwareAccelerated() && bitmap.getConfig() == Bitmap.Config.HARDWARE) {
+ onHwBitmapInSwMode();
}
}
@@ -639,7 +649,7 @@
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,
- long nativeMeasuredText, int measuredTextOffset);
+ long nativePrecomputedText);
private static native void nDrawTextOnPath(long nativeCanvas, char[] text, int index, int count,
long nativePath, float hOffset, float vOffset, int bidiFlags, long nativePaint);
diff --git a/android/graphics/BaseCanvas_Delegate.java b/android/graphics/BaseCanvas_Delegate.java
index 89fccc7..9260099 100644
--- a/android/graphics/BaseCanvas_Delegate.java
+++ b/android/graphics/BaseCanvas_Delegate.java
@@ -507,7 +507,8 @@
@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) {
+ float x, float y, boolean isRtl, long paint,
+ long nativeMeasuredText, int measuredTextOffset) {
drawText(nativeCanvas, text, start, count, x, y, isRtl ? Paint.BIDI_RTL : Paint.BIDI_LTR, paint);
}
diff --git a/android/graphics/BidiRenderer.java b/android/graphics/BidiRenderer.java
index 3dc1d41..9a102c1 100644
--- a/android/graphics/BidiRenderer.java
+++ b/android/graphics/BidiRenderer.java
@@ -43,7 +43,10 @@
*/
@SuppressWarnings("deprecation")
public class BidiRenderer {
- private static String JAVA_VENDOR = System.getProperty("java.vendor");
+ private static final String JETBRAINS_VENDOR_ID = "JetBrains s.r.o";
+ private static final String JAVA_VENDOR = System.getProperty("java.vendor");
+ /** When scaleX is bigger than this, we need to apply the workaround for http://b.android.com/211659 */
+ private static final double SCALEX_WORKAROUND_LIMIT = 9;
private static class ScriptRun {
private final int start;
@@ -209,23 +212,42 @@
*/
private void render(int start, int limit, Font font, int flag, float[] advances,
int advancesIndex, boolean draw) {
+ FontRenderContext frc = mGraphics != null ? mGraphics.getFontRenderContext() :
+ Toolkit.getDefaultToolkit().getFontMetrics(font).getFontRenderContext();
- FontRenderContext frc;
- if (mGraphics != null) {
- frc = mGraphics.getFontRenderContext();
- } else {
- frc = Toolkit.getDefaultToolkit().getFontMetrics(font).getFontRenderContext();
+ boolean frcIsAntialiased = frc.isAntiAliased();
+ boolean useAntialiasing = mPaint.isAntiAliased();
- // Metrics obtained this way don't have anti-aliasing set. So,
- // we create a new FontRenderContext with anti-aliasing set.
+ if (frcIsAntialiased) {
+ if (!useAntialiasing) {
+ // The context has antialiasing enabled but the paint does not. We need to
+ // disable it
+ frc = new FontRenderContext(font.getTransform(), false,
+ frc.usesFractionalMetrics());
+ } else {
+ // In this case both the paint and the context antialising match but we need
+ // to check for a bug in the JDK
+ // Workaround for http://b.android.com/211659 (disable antialiasing)
+ if (font.isTransformed()) {
+ AffineTransform transform = font.getTransform();
+ if (transform.getScaleX() >= SCALEX_WORKAROUND_LIMIT &&
+ JETBRAINS_VENDOR_ID.equals(JAVA_VENDOR)) {
+ frc = new FontRenderContext(transform, false, frc.usesFractionalMetrics());
+ }
+ }
+ }
+ } else if (useAntialiasing) {
+ // The context does not have antialiasing enabled but the paint does. We need to
+ // enable it unless we need to avoid the JDK bug
+
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());
+ // Workaround for http://b.android.com/211659 (disable antialiasing)
+ if (transform.getScaleX() < SCALEX_WORKAROUND_LIMIT ||
+ !JETBRAINS_VENDOR_ID.equals(JAVA_VENDOR)) {
+ frc = new FontRenderContext(font.getTransform(), true, frc.usesFractionalMetrics());
}
}
+
GlyphVector gv = font.layoutGlyphVector(frc, mText, start, limit, flag);
int ng = gv.getNumGlyphs();
int[] ci = gv.getGlyphCharIndices(0, ng, null);
diff --git a/android/graphics/Bitmap.java b/android/graphics/Bitmap.java
index 0072012..e8ede94 100644
--- a/android/graphics/Bitmap.java
+++ b/android/graphics/Bitmap.java
@@ -29,6 +29,10 @@
import android.os.Trace;
import android.util.DisplayMetrics;
import android.util.Log;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+import android.view.ThreadedRenderer;
+
import libcore.util.NativeAllocationRegistry;
import java.io.OutputStream;
@@ -457,6 +461,11 @@
*
* This configuration may be useful when using opaque bitmaps
* that do not require high color fidelity.
+ *
+ * <p>Use this formula to pack into 16 bits:</p>
+ * <pre class="prettyprint">
+ * short color = (R & 0x1f) << 11 | (G & 0x3f) << 5 | (B & 0x1f);
+ * </pre>
*/
RGB_565 (3),
@@ -489,6 +498,11 @@
*
* This configuration is very flexible and offers the best
* quality. It should be used whenever possible.
+ *
+ * <p>Use this formula to pack into 32 bits:</p>
+ * <pre class="prettyprint">
+ * int color = (A & 0xff) << 24 | (B & 0xff) << 16 | (G & 0xff) << 8 | (R & 0xff);
+ * </pre>
*/
ARGB_8888 (5),
@@ -499,6 +513,11 @@
*
* This configuration is particularly suited for wide-gamut and
* HDR content.
+ *
+ * <p>Use this formula to pack into 64 bits:</p>
+ * <pre class="prettyprint">
+ * long color = (A & 0xffff) << 48 | (B & 0xffff) << 32 | (G & 0xffff) << 16 | (R & 0xffff);
+ * </pre>
*/
RGBA_F16 (6),
@@ -1171,6 +1190,82 @@
}
/**
+ * Creates a Bitmap from the given {@link Picture} source of recorded drawing commands.
+ *
+ * Equivalent to calling {@link #createBitmap(Picture, int, int, Config)} with
+ * width and height the same as the Picture's width and height and a Config.HARDWARE
+ * config.
+ *
+ * @param source The recorded {@link Picture} of drawing commands that will be
+ * drawn into the returned Bitmap.
+ * @return An immutable bitmap with a HARDWARE config whose contents are created
+ * from the recorded drawing commands in the Picture source.
+ */
+ public static @NonNull Bitmap createBitmap(@NonNull Picture source) {
+ return createBitmap(source, source.getWidth(), source.getHeight(), Config.HARDWARE);
+ }
+
+ /**
+ * Creates a Bitmap from the given {@link Picture} source of recorded drawing commands.
+ *
+ * The bitmap will be immutable with the given width and height. If the width and height
+ * are not the same as the Picture's width & height, the Picture will be scaled to
+ * fit the given width and height.
+ *
+ * @param source The recorded {@link Picture} of drawing commands that will be
+ * drawn into the returned Bitmap.
+ * @param width The width of the bitmap to create. The picture's width will be
+ * scaled to match if necessary.
+ * @param height The height of the bitmap to create. The picture's height will be
+ * scaled to match if necessary.
+ * @param config The {@link Config} of the created bitmap. If this is null then
+ * the bitmap will be {@link Config#HARDWARE}.
+ *
+ * @return An immutable bitmap with a HARDWARE config whose contents are created
+ * from the recorded drawing commands in the Picture source.
+ */
+ public static @NonNull Bitmap createBitmap(@NonNull Picture source, int width, int height,
+ @NonNull Config config) {
+ if (width <= 0 || height <= 0) {
+ throw new IllegalArgumentException("width & height must be > 0");
+ }
+ if (config == null) {
+ throw new IllegalArgumentException("Config must not be null");
+ }
+ if (source.requiresHardwareAcceleration() && config != Config.HARDWARE) {
+ StrictMode.noteSlowCall("GPU readback");
+ }
+ if (config == Config.HARDWARE || source.requiresHardwareAcceleration()) {
+ final RenderNode node = RenderNode.create("BitmapTemporary", null);
+ node.setLeftTopRightBottom(0, 0, width, height);
+ node.setClipToBounds(false);
+ final DisplayListCanvas canvas = node.start(width, height);
+ if (source.getWidth() != width || source.getHeight() != height) {
+ canvas.scale(width / (float) source.getWidth(),
+ height / (float) source.getHeight());
+ }
+ canvas.drawPicture(source);
+ node.end(canvas);
+ Bitmap bitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
+ if (config != Config.HARDWARE) {
+ bitmap = bitmap.copy(config, false);
+ }
+ return bitmap;
+ } else {
+ Bitmap bitmap = Bitmap.createBitmap(width, height, config);
+ Canvas canvas = new Canvas(bitmap);
+ if (source.getWidth() != width || source.getHeight() != height) {
+ canvas.scale(width / (float) source.getWidth(),
+ height / (float) source.getHeight());
+ }
+ canvas.drawPicture(source);
+ canvas.setBitmap(null);
+ bitmap.makeImmutable();
+ return bitmap;
+ }
+ }
+
+ /**
* Returns an optional array of private data, used by the UI system for
* some bitmaps. Not intended to be called by applications.
*/
@@ -1259,6 +1354,12 @@
return mIsMutable;
}
+ /** @hide */
+ public final void makeImmutable() {
+ // todo mIsMutable = false;
+ // todo nMakeImmutable();
+ }
+
/**
* <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
@@ -1568,6 +1669,8 @@
if (mColorSpace == null) {
if (nativeIsSRGB(mNativePtr)) {
mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
+ } else if (getConfig() == Config.HARDWARE && nativeIsSRGBLinear(mNativePtr)) {
+ mColorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB);
} else {
float[] xyz = new float[9];
float[] params = new float[7];
@@ -1991,5 +2094,6 @@
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 boolean nativeIsSRGBLinear(long nativePtr);
private static native void nativeCopyColorSpace(long srcBitmap, long dstBitmap);
}
diff --git a/android/graphics/BitmapFactory.java b/android/graphics/BitmapFactory.java
index f5bf754..7ea35e7 100644
--- a/android/graphics/BitmapFactory.java
+++ b/android/graphics/BitmapFactory.java
@@ -18,6 +18,8 @@
import static android.graphics.BitmapFactory.Options.validate;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Trace;
@@ -518,8 +520,9 @@
* 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) {
+ @Nullable
+ public static Bitmap decodeResourceStream(@Nullable Resources res, @Nullable TypedValue value,
+ @Nullable InputStream is, @Nullable Rect pad, @Nullable Options opts) {
validate(opts);
if (opts == null) {
opts = new Options();
@@ -707,7 +710,9 @@
* <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) {
+ @Nullable
+ public static Bitmap decodeStream(@Nullable InputStream is, @Nullable Rect outPadding,
+ @Nullable 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) {
@@ -742,7 +747,8 @@
* 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) {
+ private static Bitmap decodeStreamInternal(@NonNull InputStream is,
+ @Nullable Rect outPadding, @Nullable Options opts) {
// ASSERT(is != null);
byte [] tempStorage = null;
if (opts != null) tempStorage = opts.inTempStorage;
diff --git a/android/graphics/Canvas.java b/android/graphics/Canvas.java
index f5e8633..3cc92bc 100644
--- a/android/graphics/Canvas.java
+++ b/android/graphics/Canvas.java
@@ -47,6 +47,7 @@
* Canvas and Drawables</a> developer guide.</p></div>
*/
public class Canvas extends BaseCanvas {
+ private static int sCompatiblityVersion = 0;
/** @hide */
public static boolean sCompatibilityRestore = false;
/** @hide */
@@ -306,7 +307,7 @@
/**
* Restore the current matrix when restore() is called.
- *
+ * @removed
* @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}
@@ -318,6 +319,7 @@
/**
* Restore the current clip when restore() is called.
*
+ * @removed
* @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}
@@ -329,6 +331,7 @@
/**
* The layer requires a per-pixel alpha channel.
*
+ * @removed
* @deprecated This flag is ignored. Use the flagless version of {@link #saveLayer(RectF, Paint)}
* {@link #saveLayerAlpha(RectF, int)}.
*/
@@ -337,6 +340,7 @@
/**
* The layer requires full 8-bit precision for each color channel.
*
+ * @removed
* @deprecated This flag is ignored. Use the flagless version of {@link #saveLayer(RectF, Paint)}
* {@link #saveLayerAlpha(RectF, int)}.
*/
@@ -349,6 +353,7 @@
* <code>saveLayerAlpha()</code> variants. Not passing this flag generally
* triggers extremely poor performance with hardware accelerated rendering.
*
+ * @removed
* @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.
*
@@ -367,6 +372,14 @@
*/
public static final int ALL_SAVE_FLAG = 0x1F;
+ private static void checkValidSaveFlags(int saveFlags) {
+ if (sCompatiblityVersion >= Build.VERSION_CODES.P
+ && saveFlags != ALL_SAVE_FLAG) {
+ throw new IllegalArgumentException(
+ "Invalid Layer Save Flag - only ALL_SAVE_FLAGS is allowed");
+ }
+ }
+
/**
* Saves the current matrix and clip onto a private stack.
* <p>
@@ -393,6 +406,7 @@
* restore() is made, those calls will be forgotten, and the settings that
* existed before the save() will be reinstated.
*
+ * @removed
* @deprecated Use {@link #save()} instead.
* @param saveFlags flag bits that specify which parts of the Canvas state
* to save/restore
@@ -407,10 +421,8 @@
* 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
+ * using this method, especially if the bounds provided are large. 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>
@@ -424,6 +436,9 @@
* {@link Paint#getColorFilter() ColorFilter} are applied when the
* offscreen bitmap is drawn back when restore() is called.
*
+ * As of API Level API level {@value Build.VERSION_CODES#P} the only valid
+ * {@code saveFlags} is {@link #ALL_SAVE_FLAG}. All other flags are ignored.
+ *
* @deprecated Use {@link #saveLayer(RectF, Paint)} instead.
* @param bounds May be null. The maximum size the offscreen bitmap
* needs to be (in local coordinates)
@@ -437,7 +452,9 @@
if (bounds == null) {
bounds = new RectF(getClipBounds());
}
- return saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, paint, saveFlags);
+ checkValidSaveFlags(saveFlags);
+ return saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, paint,
+ ALL_SAVE_FLAG);
}
/**
@@ -471,15 +488,26 @@
}
/**
+ * @hide
+ */
+ public int saveUnclippedLayer(int left, int top, int right, int bottom) {
+ return nSaveLayer(mNativeCanvasWrapper, left, top, right, bottom, 0, 0);
+ }
+
+ /**
* Helper version of saveLayer() that takes 4 values rather than a RectF.
*
+ * As of API Level API level {@value Build.VERSION_CODES#P} the only valid
+ * {@code saveFlags} is {@link #ALL_SAVE_FLAG}. All other flags are ignored.
+ *
* @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) {
+ checkValidSaveFlags(saveFlags);
return nSaveLayer(mNativeCanvasWrapper, left, top, right, bottom,
paint != null ? paint.getNativeInstance() : 0,
- saveFlags);
+ ALL_SAVE_FLAG);
}
/**
@@ -495,10 +523,8 @@
* 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
+ * using this method, especially if the bounds provided are large. 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>
@@ -510,6 +536,9 @@
* The {@code alpha} parameter is applied when the offscreen bitmap is
* drawn back when restore() is called.
*
+ * As of API Level API level {@value Build.VERSION_CODES#P} the only valid
+ * {@code saveFlags} is {@link #ALL_SAVE_FLAG}. All other flags are ignored.
+ *
* @deprecated Use {@link #saveLayerAlpha(RectF, int)} instead.
* @param bounds The maximum size the offscreen bitmap needs to be
* (in local coordinates)
@@ -523,7 +552,9 @@
if (bounds == null) {
bounds = new RectF(getClipBounds());
}
- return saveLayerAlpha(bounds.left, bounds.top, bounds.right, bounds.bottom, alpha, saveFlags);
+ checkValidSaveFlags(saveFlags);
+ return saveLayerAlpha(bounds.left, bounds.top, bounds.right, bounds.bottom, alpha,
+ ALL_SAVE_FLAG);
}
/**
@@ -542,13 +573,17 @@
/**
* Helper for saveLayerAlpha() that takes 4 values instead of a RectF.
*
+ * As of API Level API level {@value Build.VERSION_CODES#P} the only valid
+ * {@code saveFlags} is {@link #ALL_SAVE_FLAG}. All other flags are ignored.
+ *
* @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) {
+ checkValidSaveFlags(saveFlags);
alpha = Math.min(255, Math.max(0, alpha));
return nSaveLayerAlpha(mNativeCanvasWrapper, left, top, right, bottom,
- alpha, saveFlags);
+ alpha, ALL_SAVE_FLAG);
}
/**
@@ -738,6 +773,14 @@
return m;
}
+ private static void checkValidClipOp(@NonNull Region.Op op) {
+ if (sCompatiblityVersion >= Build.VERSION_CODES.P
+ && op != Region.Op.INTERSECT && op != Region.Op.DIFFERENCE) {
+ throw new IllegalArgumentException(
+ "Invalid Region.Op - only INTERSECT and DIFFERENCE are allowed");
+ }
+ }
+
/**
* Modify the current clip with the specified rectangle.
*
@@ -750,9 +793,13 @@
* 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)};
+ *
+ * As of API Level API level {@value Build.VERSION_CODES#P} only {@link Region.Op#INTERSECT} and
+ * {@link Region.Op#DIFFERENCE} are valid Region.Op parameters.
*/
@Deprecated
public boolean clipRect(@NonNull RectF rect, @NonNull Region.Op op) {
+ checkValidClipOp(op);
return nClipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom,
op.nativeInt);
}
@@ -770,9 +817,13 @@
* 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)};
+ *
+ * As of API Level API level {@value Build.VERSION_CODES#P} only {@link Region.Op#INTERSECT} and
+ * {@link Region.Op#DIFFERENCE} are valid Region.Op parameters.
*/
@Deprecated
public boolean clipRect(@NonNull Rect rect, @NonNull Region.Op op) {
+ checkValidClipOp(op);
return nClipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom,
op.nativeInt);
}
@@ -846,10 +897,14 @@
* 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)};
+ *
+ * As of API Level API level {@value Build.VERSION_CODES#P} only {@link Region.Op#INTERSECT} and
+ * {@link Region.Op#DIFFERENCE} are valid Region.Op parameters.
*/
@Deprecated
public boolean clipRect(float left, float top, float right, float bottom,
@NonNull Region.Op op) {
+ checkValidClipOp(op);
return nClipRect(mNativeCanvasWrapper, left, top, right, bottom, op.nativeInt);
}
@@ -932,9 +987,13 @@
* 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)};
+ *
+ * As of API Level API level {@value Build.VERSION_CODES#P} only {@link Region.Op#INTERSECT} and
+ * {@link Region.Op#DIFFERENCE} are valid Region.Op parameters.
*/
@Deprecated
public boolean clipPath(@NonNull Path path, @NonNull Region.Op op) {
+ checkValidClipOp(op);
return nClipPath(mNativeCanvasWrapper, path.readOnlyNI(), op.nativeInt);
}
@@ -1219,10 +1278,17 @@
nFreeTextLayoutCaches();
}
+ /** @hide */
+ public static void setCompatibilityVersion(int apiLevel) {
+ sCompatiblityVersion = apiLevel;
+ nSetCompatibilityVersion(apiLevel);
+ }
+
private static native void nFreeCaches();
private static native void nFreeTextLayoutCaches();
private static native long nInitRaster(Bitmap bitmap);
private static native long nGetNativeFinalizer();
+ private static native void nSetCompatibilityVersion(int apiLevel);
// ---------------- @FastNative -------------------
@@ -1486,6 +1552,10 @@
* across the top of the bitmap from left to right. A more general version of this method is
* drawVertices().
*
+ * Prior to API level {@value Build.VERSION_CODES#P} vertOffset and colorOffset were ignored,
+ * effectively treating them as zeros. In API level {@value Build.VERSION_CODES#P} and above
+ * these parameters will be respected.
+ *
* @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
diff --git a/android/graphics/Canvas_Delegate.java b/android/graphics/Canvas_Delegate.java
index 9d8af38..a23244b 100644
--- a/android/graphics/Canvas_Delegate.java
+++ b/android/graphics/Canvas_Delegate.java
@@ -470,6 +470,11 @@
return sFinalizer;
}
+ @LayoutlibDelegate
+ /*package*/ static void nSetCompatibilityVersion(int apiLevel) {
+ // Unsupported by layoutlib, do nothing
+ }
+
private Canvas_Delegate(Bitmap_Delegate bitmap) {
super(bitmap);
}
diff --git a/android/graphics/EmbossMaskFilter.java b/android/graphics/EmbossMaskFilter.java
index a9e180f..003678a 100644
--- a/android/graphics/EmbossMaskFilter.java
+++ b/android/graphics/EmbossMaskFilter.java
@@ -20,12 +20,15 @@
/**
* Create an emboss maskfilter
*
+ * @deprecated This subclass is not supported and should not be instantiated.
+ *
* @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
*/
+ @Deprecated
public EmbossMaskFilter(float[] direction, float ambient, float specular, float blurRadius) {
if (direction.length < 3) {
throw new ArrayIndexOutOfBoundsException();
diff --git a/android/graphics/FontFamily.java b/android/graphics/FontFamily.java
index d77e601..c69eb32 100644
--- a/android/graphics/FontFamily.java
+++ b/android/graphics/FontFamily.java
@@ -19,11 +19,13 @@
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 libcore.util.NativeAllocationRegistry;
+
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -38,6 +40,14 @@
private static String TAG = "FontFamily";
+ private static final NativeAllocationRegistry sBuilderRegistry = new NativeAllocationRegistry(
+ FontFamily.class.getClassLoader(), nGetBuilderReleaseFunc(), 64);
+
+ private @Nullable Runnable mNativeBuilderCleaner;
+
+ private static final NativeAllocationRegistry sFamilyRegistry = new NativeAllocationRegistry(
+ FontFamily.class.getClassLoader(), nGetFamilyReleaseFunc(), 64);
+
/**
* @hide
*/
@@ -48,6 +58,7 @@
public FontFamily() {
mBuilderPtr = nInitBuilder(null, 0);
+ mNativeBuilderCleaner = sBuilderRegistry.registerNativeAllocation(this, mBuilderPtr);
}
public FontFamily(@Nullable String[] langs, int variant) {
@@ -60,6 +71,7 @@
langsString = TextUtils.join(",", langs);
}
mBuilderPtr = nInitBuilder(langsString, variant);
+ mNativeBuilderCleaner = sBuilderRegistry.registerNativeAllocation(this, mBuilderPtr);
}
/**
@@ -73,7 +85,11 @@
throw new IllegalStateException("This FontFamily is already frozen");
}
mNativePtr = nCreateFamily(mBuilderPtr);
+ mNativeBuilderCleaner.run();
mBuilderPtr = 0;
+ if (mNativePtr != 0) {
+ sFamilyRegistry.registerNativeAllocation(this, mNativePtr);
+ }
return mNativePtr != 0;
}
@@ -81,24 +97,10 @@
if (mBuilderPtr == 0) {
throw new IllegalStateException("This FontFamily is already frozen or abandoned");
}
- nAbort(mBuilderPtr);
+ mNativeBuilderCleaner.run();
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) {
@@ -160,25 +162,6 @@
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);
@@ -190,13 +173,10 @@
private static native long nCreateFamily(long mBuilderPtr);
@CriticalNative
- private static native void nAllowUnsupportedFont(long builderPtr);
+ private static native long nGetBuilderReleaseFunc();
@CriticalNative
- private static native void nAbort(long mBuilderPtr);
-
- @CriticalNative
- private static native void nUnrefFamily(long nativePtr);
+ private static native long nGetFamilyReleaseFunc();
// 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,
diff --git a/android/graphics/FontFamily_Delegate.java b/android/graphics/FontFamily_Delegate.java
index 1e3ebd7..1ad1f8f 100644
--- a/android/graphics/FontFamily_Delegate.java
+++ b/android/graphics/FontFamily_Delegate.java
@@ -27,7 +27,6 @@
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;
@@ -38,7 +37,6 @@
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;
@@ -453,14 +451,6 @@
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() {
diff --git a/android/graphics/FontListParser.java b/android/graphics/FontListParser.java
index 9f672e3..431d0e0 100644
--- a/android/graphics/FontListParser.java
+++ b/android/graphics/FontListParser.java
@@ -16,16 +16,13 @@
package android.graphics;
-import android.text.FontConfig;
import android.graphics.fonts.FontVariationAxis;
+import android.text.FontConfig;
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;
diff --git a/android/graphics/ImageDecoder.java b/android/graphics/ImageDecoder.java
index 3de050b..506eab5 100644
--- a/android/graphics/ImageDecoder.java
+++ b/android/graphics/ImageDecoder.java
@@ -16,50 +16,34 @@
package android.graphics;
-import static android.system.OsConstants.SEEK_CUR;
-import static android.system.OsConstants.SEEK_SET;
-
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RawRes;
import android.content.ContentResolver;
-import android.content.res.AssetFileDescriptor;
-import android.content.res.AssetManager;
+import android.content.res.AssetManager.AssetInputStream;
import android.content.res.Resources;
import android.graphics.drawable.AnimatedImageDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.NinePatchDrawable;
import android.net.Uri;
-import android.util.Size;
-import android.system.ErrnoException;
-import android.system.Os;
import android.util.DisplayMetrics;
+import android.util.Size;
import android.util.TypedValue;
-import libcore.io.IoUtils;
-import dalvik.system.CloseGuard;
-
import java.nio.ByteBuffer;
import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ArrayIndexOutOfBoundsException;
import java.lang.AutoCloseable;
import java.lang.NullPointerException;
-import java.lang.RuntimeException;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.SOURCE;
-import java.util.concurrent.atomic.AtomicBoolean;
/**
* Class for decoding images as {@link Bitmap}s or {@link Drawable}s.
*/
public final class ImageDecoder implements AutoCloseable {
+
/**
* Source of the encoded image data.
*/
@@ -100,7 +84,7 @@
@Override
public ImageDecoder createImageDecoder() throws IOException {
- return nCreate(mData, mOffset, mLength);
+ return new ImageDecoder();
}
}
@@ -112,12 +96,7 @@
@Override
public ImageDecoder createImageDecoder() throws IOException {
- if (!mBuffer.isDirect() && mBuffer.hasArray()) {
- int offset = mBuffer.arrayOffset() + mBuffer.position();
- int length = mBuffer.limit() - mBuffer.position();
- return nCreate(mBuffer.array(), offset, length);
- }
- return nCreate(mBuffer, mBuffer.position(), mBuffer.limit());
+ return new ImageDecoder();
}
}
@@ -132,89 +111,13 @@
@Override
public ImageDecoder createImageDecoder() throws IOException {
- AssetFileDescriptor assetFd = null;
- try {
- if (mUri.getScheme() == ContentResolver.SCHEME_CONTENT) {
- assetFd = mResolver.openTypedAssetFileDescriptor(mUri,
- "image/*", null);
- } else {
- assetFd = mResolver.openAssetFileDescriptor(mUri, "r");
- }
- } catch (FileNotFoundException e) {
- // Some images cannot be opened as AssetFileDescriptors (e.g.
- // bmp, ico). Open them as InputStreams.
- InputStream is = mResolver.openInputStream(mUri);
- if (is == null) {
- throw new FileNotFoundException(mUri.toString());
- }
-
- return createFromStream(is);
- }
-
- final FileDescriptor fd = assetFd.getFileDescriptor();
- final long offset = assetFd.getStartOffset();
-
- ImageDecoder decoder = null;
- try {
- try {
- Os.lseek(fd, offset, SEEK_SET);
- decoder = nCreate(fd);
- } catch (ErrnoException e) {
- decoder = createFromStream(new FileInputStream(fd));
- }
- } finally {
- if (decoder == null) {
- IoUtils.closeQuietly(assetFd);
- } else {
- decoder.mAssetFd = assetFd;
- }
- }
- return decoder;
+ return new ImageDecoder();
}
}
- @NonNull
- private static ImageDecoder createFromFile(@NonNull File file) throws IOException {
- FileInputStream stream = new FileInputStream(file);
- FileDescriptor fd = stream.getFD();
- try {
- Os.lseek(fd, 0, SEEK_CUR);
- } catch (ErrnoException e) {
- return createFromStream(stream);
- }
-
- ImageDecoder decoder = null;
- try {
- decoder = nCreate(fd);
- } finally {
- if (decoder == null) {
- IoUtils.closeQuietly(stream);
- } else {
- decoder.mInputStream = stream;
- }
- }
- return decoder;
- }
-
- @NonNull
- private static ImageDecoder createFromStream(@NonNull InputStream is) throws IOException {
- // Arbitrary size matches BitmapFactory.
- byte[] storage = new byte[16 * 1024];
- ImageDecoder decoder = null;
- try {
- decoder = nCreate(is, storage);
- } finally {
- if (decoder == null) {
- IoUtils.closeQuietly(is);
- } else {
- decoder.mInputStream = is;
- decoder.mTempStorage = storage;
- }
- }
-
- return decoder;
- }
-
+ /**
+ * For backwards compatibility, this does *not* close the InputStream.
+ */
private static class InputStreamSource extends Source {
InputStreamSource(Resources res, InputStream is, int inputDensity) {
if (is == null) {
@@ -237,16 +140,46 @@
@Override
public ImageDecoder createImageDecoder() throws IOException {
+ return new ImageDecoder();
+ }
+ }
- synchronized (this) {
- if (mInputStream == null) {
- throw new IOException("Cannot reuse InputStreamSource");
- }
- InputStream is = mInputStream;
- mInputStream = null;
- return createFromStream(is);
+ /**
+ * Takes ownership of the AssetInputStream.
+ *
+ * @hide
+ */
+ public static class AssetInputStreamSource extends Source {
+ public AssetInputStreamSource(@NonNull AssetInputStream ais,
+ @NonNull Resources res, @NonNull TypedValue value) {
+ mAssetInputStream = ais;
+ mResources = res;
+
+ if (value.density == TypedValue.DENSITY_DEFAULT) {
+ mDensity = DisplayMetrics.DENSITY_DEFAULT;
+ } else if (value.density != TypedValue.DENSITY_NONE) {
+ mDensity = value.density;
+ } else {
+ mDensity = Bitmap.DENSITY_NONE;
}
}
+
+ private AssetInputStream mAssetInputStream;
+ private final Resources mResources;
+ private final int mDensity;
+
+ @Override
+ public Resources getResources() { return mResources; }
+
+ @Override
+ public int getDensity() {
+ return mDensity;
+ }
+
+ @Override
+ public ImageDecoder createImageDecoder() throws IOException {
+ return new ImageDecoder();
+ }
}
private static class ResourceSource extends Source {
@@ -268,34 +201,7 @@
@Override
public ImageDecoder createImageDecoder() throws IOException {
- // This is just used in order to access the underlying Asset and
- // keep it alive. FIXME: Can we skip creating this object?
- InputStream is = null;
- ImageDecoder decoder = null;
- TypedValue value = new TypedValue();
- try {
- is = mResources.openRawResource(mResId, value);
-
- if (value.density == TypedValue.DENSITY_DEFAULT) {
- mResDensity = DisplayMetrics.DENSITY_DEFAULT;
- } else if (value.density != TypedValue.DENSITY_NONE) {
- mResDensity = value.density;
- }
-
- if (!(is instanceof AssetManager.AssetInputStream)) {
- // This should never happen.
- throw new RuntimeException("Resource is not an asset?");
- }
- long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
- decoder = nCreate(asset);
- } finally {
- if (decoder == null) {
- IoUtils.closeQuietly(is);
- } else {
- decoder.mInputStream = is;
- }
- }
- return decoder;
+ return new ImageDecoder();
}
}
@@ -308,7 +214,7 @@
@Override
public ImageDecoder createImageDecoder() throws IOException {
- return createFromFile(mFile);
+ return new ImageDecoder();
}
}
@@ -316,11 +222,9 @@
* Contains information about the encoded image.
*/
public static class ImageInfo {
- private final Size mSize;
private ImageDecoder mDecoder;
private ImageInfo(@NonNull ImageDecoder decoder) {
- mSize = new Size(decoder.mWidth, decoder.mHeight);
mDecoder = decoder;
}
@@ -329,7 +233,7 @@
*/
@NonNull
public Size getSize() {
- return mSize;
+ return new Size(0, 0);
}
/**
@@ -337,7 +241,17 @@
*/
@NonNull
public String getMimeType() {
- return mDecoder.getMimeType();
+ return "";
+ }
+
+ /**
+ * Whether the image is animated.
+ *
+ * <p>Calling {@link #decodeDrawable} will return an
+ * {@link AnimatedImageDrawable}.</p>
+ */
+ public boolean isAnimated() {
+ return mDecoder.mAnimated;
}
};
@@ -350,7 +264,7 @@
* Optional listener supplied to {@link #decodeDrawable} or
* {@link #decodeBitmap}.
*/
- public static interface OnHeaderDecodedListener {
+ public interface OnHeaderDecodedListener {
/**
* Called when the header is decoded and the size is known.
*
@@ -358,7 +272,7 @@
* @param info Information about the encoded image.
* @param source that created the decoder.
*/
- public void onHeaderDecoded(@NonNull ImageDecoder decoder,
+ void onHeaderDecoded(@NonNull ImageDecoder decoder,
@NonNull ImageInfo info, @NonNull Source source);
};
@@ -379,15 +293,14 @@
public static final int ERROR_SOURCE_ERROR = 3;
@Retention(SOURCE)
- @IntDef({ ERROR_SOURCE_EXCEPTION, ERROR_SOURCE_INCOMPLETE, ERROR_SOURCE_ERROR })
- public @interface Error {};
+ public @interface Error {}
/**
* Optional listener supplied to the ImageDecoder.
*
* Without this listener, errors will throw {@link java.io.IOException}.
*/
- public static interface OnPartialImageListener {
+ public interface OnPartialImageListener {
/**
* Called when there is only a partial image to display.
*
@@ -402,62 +315,14 @@
* with partial data. False (which is the default) to abort the
* decode and throw {@link java.io.IOException}.
*/
- public boolean onPartialImage(@Error int error, @NonNull Source source);
- };
-
- // Fields
- private long mNativePtr;
- private final int mWidth;
- private final int mHeight;
- private final boolean mAnimated;
-
- private int mDesiredWidth;
- private int mDesiredHeight;
- private int mAllocator = ALLOCATOR_DEFAULT;
- private boolean mRequireUnpremultiplied = false;
- private boolean mMutable = false;
- private boolean mPreferRamOverQuality = false;
- private boolean mAsAlphaMask = false;
- private Rect mCropRect;
- private Source mSource;
-
- private PostProcessor mPostProcessor;
- private OnPartialImageListener mOnPartialImageListener;
-
- // Objects for interacting with the input.
- private InputStream mInputStream;
- private byte[] mTempStorage;
- private AssetFileDescriptor mAssetFd;
- private final AtomicBoolean mClosed = new AtomicBoolean();
- private final CloseGuard mCloseGuard = CloseGuard.get();
-
- /**
- * Private constructor called by JNI. {@link #close} must be
- * called after decoding to delete native resources.
- */
- @SuppressWarnings("unused")
- private ImageDecoder(long nativePtr, int width, int height,
- boolean animated) {
- mNativePtr = nativePtr;
- mWidth = width;
- mHeight = height;
- mDesiredWidth = width;
- mDesiredHeight = height;
- mAnimated = animated;
- mCloseGuard.open("close");
+ boolean onPartialImage(@Error int error, @NonNull Source source);
}
- @Override
- protected void finalize() throws Throwable {
- try {
- if (mCloseGuard != null) {
- mCloseGuard.warnIfOpen();
- }
+ private boolean mAnimated;
+ private Rect mOutPaddingRect;
- close();
- } finally {
- super.finalize();
- }
+ public ImageDecoder() {
+ mAnimated = true; // This is too avoid throwing an exception in AnimatedImageDrawable
}
/**
@@ -471,7 +336,7 @@
* {@link #decodeDrawable} or {@link #decodeBitmap}.
*/
@NonNull
- public static Source createSource(@NonNull Resources res, @RawRes int resId)
+ public static Source createSource(@NonNull Resources res, int resId)
{
return new ResourceSource(res, resId);
}
@@ -505,9 +370,6 @@
@NonNull
public static Source createSource(@NonNull byte[] data, int offset,
int length) throws ArrayIndexOutOfBoundsException {
- if (data == null) {
- throw new NullPointerException("null byte[] in createSource!");
- }
if (offset < 0 || length < 0 || offset >= data.length ||
offset + length > data.length) {
throw new ArrayIndexOutOfBoundsException(
@@ -580,15 +442,7 @@
*/
@NonNull
public Size getSampledSize(int sampleSize) {
- if (sampleSize <= 0) {
- throw new IllegalArgumentException("sampleSize must be positive! "
- + "provided " + sampleSize);
- }
- if (mNativePtr == 0) {
- throw new IllegalStateException("ImageDecoder is closed!");
- }
-
- return nGetSampledSize(mNativePtr, sampleSize);
+ return new Size(0, 0);
}
// Modifiers
@@ -599,13 +453,6 @@
* @param height must be greater than 0.
*/
public void setResize(int width, int height) {
- if (width <= 0 || height <= 0) {
- throw new IllegalArgumentException("Dimensions must be positive! "
- + "provided (" + width + ", " + height + ")");
- }
-
- mDesiredWidth = width;
- mDesiredHeight = height;
}
/**
@@ -617,12 +464,6 @@
* @param sampleSize Sampling rate of the encoded image.
*/
public void setResize(int sampleSize) {
- Size size = this.getSampledSize(sampleSize);
- this.setResize(size.getWidth(), size.getHeight());
- }
-
- private boolean requestedResize() {
- return mWidth != mDesiredWidth || mHeight != mDesiredHeight;
}
// These need to stay in sync with ImageDecoder.cpp's Allocator enum.
@@ -663,8 +504,6 @@
/** @hide **/
@Retention(SOURCE)
- @IntDef({ ALLOCATOR_DEFAULT, ALLOCATOR_SOFTWARE, ALLOCATOR_SHARED_MEMORY,
- ALLOCATOR_HARDWARE })
public @interface Allocator {};
/**
@@ -674,11 +513,8 @@
*
* @param allocator Type of allocator to use.
*/
- public void setAllocator(@Allocator int allocator) {
- if (allocator < ALLOCATOR_DEFAULT || allocator > ALLOCATOR_HARDWARE) {
- throw new IllegalArgumentException("invalid allocator " + allocator);
- }
- mAllocator = allocator;
+ public ImageDecoder setAllocator(@Allocator int allocator) {
+ return this;
}
/**
@@ -693,8 +529,8 @@
* {@link #decodeDrawable}; attempting to decode an unpremultiplied
* {@link Drawable} will throw an {@link java.lang.IllegalStateException}.
*/
- public void setRequireUnpremultiplied(boolean requireUnpremultiplied) {
- mRequireUnpremultiplied = requireUnpremultiplied;
+ public ImageDecoder setRequireUnpremultiplied(boolean requireUnpremultiplied) {
+ return this;
}
/**
@@ -710,8 +546,8 @@
* {@link Canvas} will be recorded immediately and then applied to each
* frame.</p>
*/
- public void setPostProcessor(@Nullable PostProcessor p) {
- mPostProcessor = p;
+ public ImageDecoder setPostProcessor(@Nullable PostProcessor p) {
+ return this;
}
/**
@@ -720,8 +556,8 @@
* Will be called if there is an error in the input. Without one, a
* partial {@link Bitmap} will be created.
*/
- public void setOnPartialImageListener(@Nullable OnPartialImageListener l) {
- mOnPartialImageListener = l;
+ public ImageDecoder setOnPartialImageListener(@Nullable OnPartialImageListener l) {
+ return this;
}
/**
@@ -736,8 +572,21 @@
* {@link BitmapRegionDecoder#decodeRegion}. This supports all formats,
* but merely crops the output.</p>
*/
- public void setCrop(@Nullable Rect subset) {
- mCropRect = subset;
+ public ImageDecoder setCrop(@Nullable Rect subset) {
+ return this;
+ }
+
+ /**
+ * Set a Rect for retrieving nine patch padding.
+ *
+ * If the image is a nine patch, this Rect will be set to the padding
+ * rectangle during decode. Otherwise it will not be modified.
+ *
+ * @hide
+ */
+ public ImageDecoder setOutPaddingRect(@NonNull Rect outPadding) {
+ mOutPaddingRect = outPadding;
+ return this;
}
/**
@@ -756,8 +605,8 @@
* order to modify. Attempting to decode a mutable {@link Drawable} will
* throw an {@link java.lang.IllegalStateException}.</p>
*/
- public void setMutable(boolean mutable) {
- mMutable = mutable;
+ public ImageDecoder setMutable(boolean mutable) {
+ return this;
}
/**
@@ -768,8 +617,8 @@
* an opaque {@link Bitmap}, this may result in a {@link Bitmap.Config}
* with no alpha information.
*/
- public void setPreferRamOverQuality(boolean preferRamOverQuality) {
- mPreferRamOverQuality = preferRamOverQuality;
+ public ImageDecoder setPreferRamOverQuality(boolean preferRamOverQuality) {
+ return this;
}
/**
@@ -784,84 +633,12 @@
* {@link #decodeBitmap} throwing an
* {@link java.lang.IllegalStateException}.</p>
*/
- public void setAsAlphaMask(boolean asAlphaMask) {
- mAsAlphaMask = asAlphaMask;
+ public ImageDecoder setAsAlphaMask(boolean asAlphaMask) {
+ return this;
}
@Override
public void close() {
- mCloseGuard.close();
- if (!mClosed.compareAndSet(false, true)) {
- return;
- }
- nClose(mNativePtr);
- mNativePtr = 0;
-
- IoUtils.closeQuietly(mInputStream);
- IoUtils.closeQuietly(mAssetFd);
-
- mInputStream = null;
- mAssetFd = null;
- mTempStorage = null;
- }
-
- private void checkState() {
- if (mNativePtr == 0) {
- throw new IllegalStateException("Cannot use closed ImageDecoder!");
- }
-
- checkSubset(mDesiredWidth, mDesiredHeight, mCropRect);
-
- if (mAllocator == ALLOCATOR_HARDWARE) {
- if (mMutable) {
- throw new IllegalStateException("Cannot make mutable HARDWARE Bitmap!");
- }
- if (mAsAlphaMask) {
- throw new IllegalStateException("Cannot make HARDWARE Alpha mask Bitmap!");
- }
- }
-
- if (mPostProcessor != null && mRequireUnpremultiplied) {
- throw new IllegalStateException("Cannot draw to unpremultiplied pixels!");
- }
- }
-
- private static void checkSubset(int width, int height, Rect r) {
- if (r == null) {
- return;
- }
- if (r.left < 0 || r.top < 0 || r.right > width || r.bottom > height) {
- throw new IllegalStateException("Subset " + r + " not contained by "
- + "scaled image bounds: (" + width + " x " + height + ")");
- }
- }
-
- @NonNull
- private Bitmap decodeBitmap() throws IOException {
- checkState();
- // nDecodeBitmap calls onPartialImage only if mOnPartialImageListener
- // exists
- ImageDecoder partialImagePtr = mOnPartialImageListener == null ? null : this;
- // nDecodeBitmap calls postProcessAndRelease only if mPostProcessor
- // exists.
- ImageDecoder postProcessPtr = mPostProcessor == null ? null : this;
- return nDecodeBitmap(mNativePtr, partialImagePtr,
- postProcessPtr, mDesiredWidth, mDesiredHeight, mCropRect,
- mMutable, mAllocator, mRequireUnpremultiplied,
- mPreferRamOverQuality, mAsAlphaMask);
-
- }
-
- private void callHeaderDecoded(@Nullable OnHeaderDecodedListener listener,
- @NonNull Source src) {
- if (listener != null) {
- ImageInfo info = new ImageInfo(this);
- try {
- listener.onHeaderDecoded(this, info, src);
- } finally {
- info.mDecoder = null;
- }
- }
}
/**
@@ -879,57 +656,8 @@
@NonNull
public static Drawable decodeDrawable(@NonNull Source src,
@Nullable OnHeaderDecodedListener listener) throws IOException {
- try (ImageDecoder decoder = src.createImageDecoder()) {
- decoder.mSource = src;
- decoder.callHeaderDecoded(listener, src);
-
- if (decoder.mRequireUnpremultiplied) {
- // Though this could be supported (ignored) for opaque images,
- // it seems better to always report this error.
- throw new IllegalStateException("Cannot decode a Drawable " +
- "with unpremultiplied pixels!");
- }
-
- if (decoder.mMutable) {
- throw new IllegalStateException("Cannot decode a mutable " +
- "Drawable!");
- }
-
- // this call potentially manipulates the decoder so it must be performed prior to
- // decoding the bitmap and after decode set the density on the resulting bitmap
- final int srcDensity = computeDensity(src, decoder);
- if (decoder.mAnimated) {
- // AnimatedImageDrawable calls postProcessAndRelease only if
- // mPostProcessor exists.
- ImageDecoder postProcessPtr = decoder.mPostProcessor == null ?
- null : decoder;
- Drawable d = new AnimatedImageDrawable(decoder.mNativePtr,
- postProcessPtr, decoder.mDesiredWidth,
- decoder.mDesiredHeight, srcDensity,
- src.computeDstDensity(), decoder.mCropRect,
- decoder.mInputStream, decoder.mAssetFd);
- // d has taken ownership of these objects.
- decoder.mInputStream = null;
- decoder.mAssetFd = null;
- return d;
- }
-
- Bitmap bm = decoder.decodeBitmap();
- bm.setDensity(srcDensity);
-
- Resources res = src.getResources();
- byte[] np = bm.getNinePatchChunk();
- if (np != null && NinePatch.isNinePatchChunk(np)) {
- Rect opticalInsets = new Rect();
- bm.getOpticalInsets(opticalInsets);
- Rect padding = new Rect();
- nGetPadding(decoder.mNativePtr, padding);
- return new NinePatchDrawable(res, bm, np, padding,
- opticalInsets, null);
- }
-
- return new BitmapDrawable(res, bm);
- }
+ Bitmap bitmap = decodeBitmap(src, listener);
+ return new BitmapDrawable(src.getResources(), bitmap);
}
/**
@@ -956,55 +684,14 @@
@NonNull
public static Bitmap decodeBitmap(@NonNull Source src,
@Nullable OnHeaderDecodedListener listener) throws IOException {
- try (ImageDecoder decoder = src.createImageDecoder()) {
- decoder.mSource = src;
- decoder.callHeaderDecoded(listener, src);
-
- // this call potentially manipulates the decoder so it must be performed prior to
- // decoding the bitmap
- final int srcDensity = computeDensity(src, decoder);
- Bitmap bm = decoder.decodeBitmap();
- bm.setDensity(srcDensity);
- return bm;
+ TypedValue value = new TypedValue();
+ value.density = src.getDensity();
+ ImageDecoder decoder = src.createImageDecoder();
+ if (listener != null) {
+ listener.onHeaderDecoded(decoder, new ImageInfo(decoder), src);
}
- }
-
- // This method may modify the decoder so it must be called prior to performing the decode
- private static int computeDensity(@NonNull Source src, @NonNull ImageDecoder decoder) {
- // if the caller changed the size then we treat the density as unknown
- if (decoder.requestedResize()) {
- return Bitmap.DENSITY_NONE;
- }
-
- // 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.
- Resources res = src.getResources();
- final int srcDensity = src.getDensity();
- if (res != null && res.getDisplayMetrics().noncompatDensityDpi == srcDensity) {
- return srcDensity;
- }
-
- // downscale the bitmap if the asset has a higher density than the default
- final int dstDensity = src.computeDstDensity();
- if (srcDensity != Bitmap.DENSITY_NONE && srcDensity > dstDensity) {
- float scale = (float) dstDensity / srcDensity;
- int scaledWidth = (int) (decoder.mWidth * scale + 0.5f);
- int scaledHeight = (int) (decoder.mHeight * scale + 0.5f);
- decoder.setResize(scaledWidth, scaledHeight);
- return dstDensity;
- }
-
- return srcDensity;
- }
-
- @NonNull
- private String getMimeType() {
- return nGetMimeType(mNativePtr);
+ return BitmapFactory.decodeResourceStream(src.getResources(), value,
+ ((InputStreamSource) src).mInputStream, decoder.mOutPaddingRect, null);
}
/**
@@ -1014,48 +701,4 @@
public static Bitmap decodeBitmap(@NonNull Source src) throws IOException {
return decodeBitmap(src, null);
}
-
- /**
- * Private method called by JNI.
- */
- @SuppressWarnings("unused")
- private int postProcessAndRelease(@NonNull Canvas canvas) {
- try {
- return mPostProcessor.onPostProcess(canvas);
- } finally {
- canvas.release();
- }
- }
-
- /**
- * Private method called by JNI.
- */
- @SuppressWarnings("unused")
- private boolean onPartialImage(@Error int error) {
- return mOnPartialImageListener.onPartialImage(error, mSource);
- }
-
- private static native ImageDecoder nCreate(long asset) throws IOException;
- private static native ImageDecoder nCreate(ByteBuffer buffer,
- int position,
- int limit) throws IOException;
- private static native ImageDecoder nCreate(byte[] data, int offset,
- int length) throws IOException;
- private static native ImageDecoder nCreate(InputStream is, byte[] storage);
- // The fd must be seekable.
- private static native ImageDecoder nCreate(FileDescriptor fd) throws IOException;
- @NonNull
- private static native Bitmap nDecodeBitmap(long nativePtr,
- @Nullable ImageDecoder partialImageListener,
- @Nullable ImageDecoder postProcessor,
- int width, int height,
- @Nullable Rect cropRect, boolean mutable,
- int allocator, boolean requireUnpremul,
- boolean preferRamOverQuality, boolean asAlphaMask)
- throws IOException;
- private static native Size nGetSampledSize(long nativePtr,
- int sampleSize);
- private static native void nGetPadding(long nativePtr, @NonNull Rect outRect);
- private static native void nClose(long nativePtr);
- private static native String nGetMimeType(long nativePtr);
}
diff --git a/android/graphics/ImageDecoder_Delegate.java b/android/graphics/ImageDecoder_Delegate.java
deleted file mode 100644
index d9fe9bf..0000000
--- a/android/graphics/ImageDecoder_Delegate.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.graphics;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.graphics.ImageDecoder.InputStreamSource;
-import android.graphics.ImageDecoder.OnHeaderDecodedListener;
-import android.graphics.ImageDecoder.Source;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.TypedValue;
-
-import java.io.IOException;
-
-public class ImageDecoder_Delegate {
- @LayoutlibDelegate
- static Bitmap decodeBitmap(@NonNull Source src, @Nullable OnHeaderDecodedListener listener)
- throws IOException {
- TypedValue value = new TypedValue();
- value.density = src.getDensity();
- return BitmapFactory.decodeResourceStream(src.getResources(), value,
- ((InputStreamSource) src).mInputStream, null, null);
- }
-
- @LayoutlibDelegate
- static Bitmap decodeBitmap(@NonNull Source src) throws IOException {
- return decodeBitmap(src, null);
- }
-
- @LayoutlibDelegate
- static Bitmap decodeBitmap(ImageDecoder thisDecoder) {
- return null;
- }
-
- @LayoutlibDelegate
- static Drawable decodeDrawable(@NonNull Source src, @Nullable OnHeaderDecodedListener listener)
- throws IOException {
- Bitmap bitmap = decodeBitmap(src, listener);
- return new BitmapDrawable(src.getResources(), bitmap);
- }
-
- @LayoutlibDelegate
- static Drawable decodeDrawable(@NonNull Source src) throws IOException {
- return decodeDrawable(src, null);
- }
-}
diff --git a/android/graphics/Matrix_Delegate.java b/android/graphics/Matrix_Delegate.java
index 354f919..5ae181d 100644
--- a/android/graphics/Matrix_Delegate.java
+++ b/android/graphics/Matrix_Delegate.java
@@ -118,8 +118,7 @@
return true;
}
- public static float[] makeValues(AffineTransform matrix) {
- float[] values = new float[MATRIX_SIZE];
+ private static float[] setValues(AffineTransform matrix, float[] values) {
values[0] = (float) matrix.getScaleX();
values[1] = (float) matrix.getShearX();
values[2] = (float) matrix.getTranslateX();
@@ -133,6 +132,10 @@
return values;
}
+ public static float[] makeValues(AffineTransform matrix) {
+ return setValues(matrix, new float[MATRIX_SIZE]);
+ }
+
public static Matrix_Delegate make(AffineTransform matrix) {
return new Matrix_Delegate(makeValues(matrix));
}
@@ -616,12 +619,7 @@
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();
+ setValues(inverseTransform, inv_mtx.mValues);
return true;
} catch (NoninvertibleTransformException e) {
diff --git a/android/graphics/Movie.java b/android/graphics/Movie.java
index c8f86c6..83857be 100644
--- a/android/graphics/Movie.java
+++ b/android/graphics/Movie.java
@@ -17,9 +17,13 @@
package android.graphics;
import android.content.res.AssetManager;
-import java.io.InputStream;
-import java.io.FileInputStream;
+import java.io.FileInputStream;
+import java.io.InputStream;
+
+/**
+ * @deprecated Prefer {@link android.graphics.drawable.AnimatedImageDrawable}.
+ */
public class Movie {
private long mNativeMovie;
diff --git a/android/graphics/Paint.java b/android/graphics/Paint.java
index 5a80ee2..42dac38 100644
--- a/android/graphics/Paint.java
+++ b/android/graphics/Paint.java
@@ -19,10 +19,8 @@
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;
@@ -33,14 +31,13 @@
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
+import libcore.util.NativeAllocationRegistry;
+
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.
@@ -2838,6 +2835,16 @@
return result;
}
+ /**
+ * Returns true of the passed {@link Paint} will have the same effect on text measurement
+ *
+ * @param other A {@link Paint} object.
+ * @return true if the other {@link Paint} has the same effect on text measurement.
+ */
+ public boolean equalsForTextMeasurement(@NonNull Paint other) {
+ return nEqualsForTextMeasurement(mNativePaint, other.mNativePaint);
+ }
+
// regular JNI
private static native long nGetNativeFinalizer();
private static native long nInit();
@@ -3005,4 +3012,6 @@
private static native float nGetStrikeThruThickness(long paintPtr);
@CriticalNative
private static native void nSetTextSize(long paintPtr, float textSize);
+ @CriticalNative
+ private static native boolean nEqualsForTextMeasurement(long leftPaintPtr, long rightPaintPtr);
}
diff --git a/android/graphics/Paint_Delegate.java b/android/graphics/Paint_Delegate.java
index 4f05176..dae966d 100644
--- a/android/graphics/Paint_Delegate.java
+++ b/android/graphics/Paint_Delegate.java
@@ -1197,6 +1197,11 @@
return (1.0f / 18.0f) * nGetTextSize(paintPtr);
}
+ @LayoutlibDelegate
+ /*package*/ static boolean nEqualsForTextMeasurement(long leftPaintPtr, long rightPaintPtr) {
+ return leftPaintPtr == rightPaintPtr;
+ }
+
// ---- Private delegate/helper methods ----
/*package*/ Paint_Delegate() {
diff --git a/android/graphics/Path.java b/android/graphics/Path.java
index 098cdc6..cd0862c 100644
--- a/android/graphics/Path.java
+++ b/android/graphics/Path.java
@@ -24,6 +24,8 @@
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
+import libcore.util.NativeAllocationRegistry;
+
/**
* The Path class encapsulates compound (multiple contour) geometric paths
* consisting of straight line segments, quadratic curves, and cubic curves.
@@ -32,10 +34,14 @@
* text on a path.
*/
public class Path {
+
+ private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+ Path.class.getClassLoader(), nGetFinalizer(), 48 /* dummy size */);
+
/**
* @hide
*/
- public long mNativePath;
+ public final long mNativePath;
/**
* @hide
@@ -52,6 +58,7 @@
*/
public Path() {
mNativePath = nInit();
+ sRegistry.registerNativeAllocation(this, mNativePath);
}
/**
@@ -69,6 +76,7 @@
}
}
mNativePath = nInit(valNative);
+ sRegistry.registerNativeAllocation(this, mNativePath);
}
/**
@@ -297,7 +305,7 @@
* a rectangle
* @return true if the path specifies a rectangle
*/
- public boolean isRect(RectF rect) {
+ public boolean isRect(@Nullable RectF rect) {
return nIsRect(mNativePath, rect);
}
@@ -771,15 +779,6 @@
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;
@@ -820,7 +819,7 @@
private static native long nInit();
private static native long nInit(long nPath);
- private static native void nFinalize(long nPath);
+ private static native long nGetFinalizer();
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);
diff --git a/android/graphics/Picture.java b/android/graphics/Picture.java
index 08eeaff..d01ff6f 100644
--- a/android/graphics/Picture.java
+++ b/android/graphics/Picture.java
@@ -31,8 +31,9 @@
* be replayed on a hardware accelerated canvas.</p>
*/
public class Picture {
- private Canvas mRecordingCanvas;
+ private PictureCanvas mRecordingCanvas;
private long mNativePicture;
+ private boolean mRequiresHwAcceleration;
private static final int WORKING_STREAM_STORAGE = 16 * 1024;
@@ -78,8 +79,12 @@
* into it.
*/
public Canvas beginRecording(int width, int height) {
+ if (mRecordingCanvas != null) {
+ throw new IllegalStateException("Picture already recording, must call #endRecording()");
+ }
long ni = nativeBeginRecording(mNativePicture, width, height);
- mRecordingCanvas = new RecordingCanvas(this, ni);
+ mRecordingCanvas = new PictureCanvas(this, ni);
+ mRequiresHwAcceleration = false;
return mRecordingCanvas;
}
@@ -91,6 +96,7 @@
*/
public void endRecording() {
if (mRecordingCanvas != null) {
+ mRequiresHwAcceleration = mRecordingCanvas.mHoldsHwBitmap;
mRecordingCanvas = null;
nativeEndRecording(mNativePicture);
}
@@ -113,6 +119,18 @@
}
/**
+ * Indicates whether or not this Picture contains recorded commands that only work when
+ * drawn to a hardware-accelerated canvas. If this returns true then this Picture can only
+ * be drawn to another Picture or to a Canvas where canvas.isHardwareAccelerated() is true.
+ *
+ * @return true if the Picture can only be drawn to a hardware-accelerated canvas,
+ * false otherwise.
+ */
+ public boolean requiresHardwareAcceleration() {
+ return mRequiresHwAcceleration;
+ }
+
+ /**
* Draw this picture on the canvas.
* <p>
* Prior to {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this call could
@@ -129,6 +147,9 @@
if (mRecordingCanvas != null) {
endRecording();
}
+ if (mRequiresHwAcceleration && !canvas.isHardwareAccelerated()) {
+ canvas.onHwBitmapInSwMode();
+ }
nativeDraw(canvas.getNativeCanvasWrapper(), mNativePicture);
}
@@ -164,8 +185,7 @@
if (stream == null) {
throw new NullPointerException();
}
- if (!nativeWriteToStream(mNativePicture, stream,
- new byte[WORKING_STREAM_STORAGE])) {
+ if (!nativeWriteToStream(mNativePicture, stream, new byte[WORKING_STREAM_STORAGE])) {
throw new RuntimeException();
}
}
@@ -182,12 +202,15 @@
OutputStream stream, byte[] storage);
private static native void nativeDestructor(long nativePicture);
- private static class RecordingCanvas extends Canvas {
+ private static class PictureCanvas extends Canvas {
private final Picture mPicture;
+ boolean mHoldsHwBitmap;
- public RecordingCanvas(Picture pict, long nativeCanvas) {
+ public PictureCanvas(Picture pict, long nativeCanvas) {
super(nativeCanvas);
mPicture = pict;
+ // Disable bitmap density scaling. This matches DisplayListCanvas.
+ mDensity = 0;
}
@Override
@@ -202,5 +225,10 @@
}
super.drawPicture(picture);
}
+
+ @Override
+ protected void onHwBitmapInSwMode() {
+ mHoldsHwBitmap = true;
+ }
}
}
diff --git a/android/graphics/SurfaceTexture.java b/android/graphics/SurfaceTexture.java
index 97edf22..1eebd26 100644
--- a/android/graphics/SurfaceTexture.java
+++ b/android/graphics/SurfaceTexture.java
@@ -318,13 +318,17 @@
* 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.
+ * <p>This timestamp is in nanoseconds, and is normally monotonically increasing. The timestamp
+ * should be unaffected by time-of-day adjustments. 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.</p>
+ *
+ * <p>For camera sources, timestamps should be strictly monotonic. Timestamps from MediaPlayer
+ * sources may be reset when the playback position is set. For EGL and Vulkan producers, the
+ * timestamp is the desired present time set with the EGL_ANDROID_presentation_time or
+ * VK_GOOGLE_display_timing extensions.</p>
*/
public long getTimestamp() {
diff --git a/android/graphics/Typeface.java b/android/graphics/Typeface.java
index ef41507..20c22b7 100644
--- a/android/graphics/Typeface.java
+++ b/android/graphics/Typeface.java
@@ -16,25 +16,18 @@
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 static android.content.res.FontResourcesParser.FontFamilyFilesResourceEntry;
+import static android.content.res.FontResourcesParser.FontFileResourceEntry;
+import static android.content.res.FontResourcesParser.ProviderResourceEntry;
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;
@@ -49,7 +42,9 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
-import libcore.io.IoUtils;
+import dalvik.annotation.optimization.CriticalNative;
+
+import libcore.util.NativeAllocationRegistry;
import org.xmlpull.v1.XmlPullParserException;
@@ -58,18 +53,17 @@
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.InputStream;
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.
@@ -81,6 +75,9 @@
private static String TAG = "Typeface";
+ private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+ Typeface.class.getClassLoader(), nativeGetReleaseFunc(), 64);
+
/** The default NORMAL typeface object */
public static final Typeface DEFAULT;
/**
@@ -130,6 +127,11 @@
*/
public long native_instance;
+ /** @hide */
+ @IntDef(value = {NORMAL, BOLD, ITALIC, BOLD_ITALIC})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Style {}
+
// Style
public static final int NORMAL = 0;
public static final int BOLD = 1;
@@ -137,8 +139,15 @@
public static final int BOLD_ITALIC = 3;
/** @hide */ public static final int STYLE_MASK = 0x03;
- private int mStyle = 0;
- private int mWeight = 0;
+ private @Style int mStyle = 0;
+
+ /**
+ * A maximum value for the weight value.
+ * @hide
+ */
+ public static final int MAX_WEIGHT = 1000;
+
+ private @IntRange(from = 0, to = MAX_WEIGHT) 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
@@ -158,15 +167,13 @@
nativeSetDefault(t.native_instance);
}
- // TODO: Make this public API. (b/64852739)
- /** @hide */
- @VisibleForTesting
- public int getWeight() {
+ /** Returns the typeface's weight value */
+ public @IntRange(from = 0, to = 1000) int getWeight() {
return mWeight;
}
/** Returns the typeface's intrinsic style attributes */
- public int getStyle() {
+ public @Style int getStyle() {
return mStyle;
}
@@ -394,7 +401,7 @@
* 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 fonts The array of {@link FontsContract.FontInfo}
* @param buffers The mapping from URI to buffers to be used during building.
* @hide
*/
@@ -672,7 +679,7 @@
* e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
* @return The best matching typeface.
*/
- public static Typeface create(String familyName, int style) {
+ public static Typeface create(String familyName, @Style int style) {
return create(sSystemFontMap.get(familyName), style);
}
@@ -693,7 +700,7 @@
* e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
* @return The best matching typeface.
*/
- public static Typeface create(Typeface family, int style) {
+ public static Typeface create(Typeface family, @Style int style) {
if ((style & ~STYLE_MASK) != 0) {
style = NORMAL;
}
@@ -789,7 +796,7 @@
*
* @return the default typeface that corresponds to the style
*/
- public static Typeface defaultFromStyle(int style) {
+ public static Typeface defaultFromStyle(@Style int style) {
return sDefaults[style];
}
@@ -801,39 +808,18 @@
* @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;
+ Preconditions.checkNotNull(path); // for backward compatibility
+ Preconditions.checkNotNull(mgr);
- 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();
- }
+ Typeface typeface = new Builder(mgr, path).build();
+ if (typeface != null) return typeface;
+ // check if the file exists, and throw an exception for backward compatibility
+ try (InputStream inputStream = mgr.open(path)) {
+ } catch (IOException e) {
+ throw new RuntimeException("Font asset not found " + path);
}
- throw new RuntimeException("Font asset not found " + path);
+
+ return Typeface.DEFAULT;
}
/**
@@ -851,13 +837,22 @@
/**
* Create a new typeface from the specified font file.
*
- * @param path The path to the font data.
+ * @param file The path to the font data.
* @return The new typeface.
*/
- public static Typeface createFromFile(@Nullable File path) {
+ public static Typeface createFromFile(@Nullable File file) {
// For the compatibility reasons, leaving possible NPE here.
// See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull
- return createFromFile(path.getAbsolutePath());
+
+ Typeface typeface = new Builder(file).build();
+ if (typeface != null) return typeface;
+
+ // check if the file exists, and throw an exception for backward compatibility
+ if (!file.exists()) {
+ throw new RuntimeException("Font asset not found " + file.getAbsolutePath());
+ }
+
+ return Typeface.DEFAULT;
}
/**
@@ -867,22 +862,8 @@
* @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);
+ Preconditions.checkNotNull(path); // for backward compatibility
+ return createFromFile(new File(path));
}
/**
@@ -900,18 +881,25 @@
}
/**
+ * This method is used by supportlib-v27.
+ * TODO: Remove private API use in supportlib: http://b/72665240
+ */
+ private static Typeface createFromFamiliesWithDefault(FontFamily[] families, int weight,
+ int italic) {
+ return createFromFamiliesWithDefault(families, DEFAULT_FAMILY, weight, italic);
+ }
+
+ /**
* 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 weight the weight for this family. 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. 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,
@@ -937,6 +925,7 @@
}
native_instance = ni;
+ sRegistry.registerNativeAllocation(this, native_instance);
mStyle = nativeGetStyle(ni);
mWeight = nativeGetWeight(ni);
}
@@ -1146,16 +1135,6 @@
}
@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;
@@ -1199,10 +1178,18 @@
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);
+
+ @CriticalNative
+ private static native void nativeSetDefault(long nativePtr);
+
+ @CriticalNative
+ private static native int nativeGetStyle(long nativePtr);
+
+ @CriticalNative
+ private static native int nativeGetWeight(long nativePtr);
+
+ @CriticalNative
+ private static native long nativeGetReleaseFunc();
}
diff --git a/android/graphics/drawable/AdaptiveIconDrawable.java b/android/graphics/drawable/AdaptiveIconDrawable.java
index 1d0cfa5..fdd638a 100644
--- a/android/graphics/drawable/AdaptiveIconDrawable.java
+++ b/android/graphics/drawable/AdaptiveIconDrawable.java
@@ -44,6 +44,7 @@
import android.util.PathParser;
import com.android.internal.R;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -668,13 +669,7 @@
@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);
- }
- }
+ mPaint.setAlpha(alpha);
}
@Override
diff --git a/android/graphics/drawable/AnimatedImageDrawable.java b/android/graphics/drawable/AnimatedImageDrawable.java
index 3034a10..898939e 100644
--- a/android/graphics/drawable/AnimatedImageDrawable.java
+++ b/android/graphics/drawable/AnimatedImageDrawable.java
@@ -20,39 +20,266 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.ImageDecoder;
import android.graphics.PixelFormat;
import android.graphics.Rect;
-import android.os.SystemClock;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.AttributeSet;
import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.View;
-import libcore.io.IoUtils;
+import com.android.internal.R;
+
+import dalvik.annotation.optimization.FastNative;
+
import libcore.util.NativeAllocationRegistry;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.IOException;
import java.io.InputStream;
-import java.lang.Runnable;
+import java.util.ArrayList;
/**
- * @hide
+ * {@link Drawable} for drawing animated images (like GIF).
+ *
+ * <p>The framework handles decoding subsequent frames in another thread and
+ * updating when necessary. The drawable will only animate while it is being
+ * displayed.</p>
+ *
+ * <p>Created by {@link ImageDecoder#decodeDrawable}. A user needs to call
+ * {@link #start} to start the animation.</p>
+ *
+ * <p>It can also be defined in XML using the <code><animated-image></code>
+ * element.</p>
+ *
+ * @attr ref android.R.styleable#AnimatedImageDrawable_src
+ * @attr ref android.R.styleable#AnimatedImageDrawable_autoStart
+ * @attr ref android.R.styleable#AnimatedImageDrawable_repeatCount
+ * @attr ref android.R.styleable#AnimatedImageDrawable_autoMirrored
*/
-public class AnimatedImageDrawable extends Drawable implements Animatable {
- private final long mNativePtr;
- private final InputStream mInputStream;
- private final AssetFileDescriptor mAssetFd;
+public class AnimatedImageDrawable extends Drawable implements Animatable2 {
+ private int mIntrinsicWidth;
+ private int mIntrinsicHeight;
- private final int mIntrinsicWidth;
- private final int mIntrinsicHeight;
+ private boolean mStarting;
- private Runnable mRunnable = new Runnable() {
- @Override
- public void run() {
- invalidateSelf();
+ private Handler mHandler;
+
+ private class State {
+ State(long nativePtr, InputStream is, AssetFileDescriptor afd) {
+ mNativePtr = nativePtr;
+ mInputStream = is;
+ mAssetFd = afd;
}
- };
+
+ final long mNativePtr;
+
+ // These just keep references so the native code can continue using them.
+ private final InputStream mInputStream;
+ private final AssetFileDescriptor mAssetFd;
+
+ int[] mThemeAttrs = null;
+ boolean mAutoMirrored = false;
+ int mRepeatCount = REPEAT_UNDEFINED;
+ }
+
+ private State mState;
+
+ private Runnable mRunnable;
+
+ private ColorFilter mColorFilter;
+
+ /**
+ * Pass this to {@link #setRepeatCount} to repeat infinitely.
+ *
+ * <p>{@link Animatable2.AnimationCallback#onAnimationEnd} will never be
+ * called unless there is an error.</p>
+ */
+ public static final int REPEAT_INFINITE = -1;
+
+ /** @removed
+ * @deprecated Replaced with REPEAT_INFINITE to match other APIs.
+ */
+ @java.lang.Deprecated
+ public static final int LOOP_INFINITE = REPEAT_INFINITE;
+
+ private static final int REPEAT_UNDEFINED = -2;
+
+ /**
+ * Specify the number of times to repeat the animation.
+ *
+ * <p>By default, the repeat count in the encoded data is respected. If set
+ * to {@link #REPEAT_INFINITE}, the animation will repeat as long as it is
+ * displayed. If the value is {@code 0}, the animation will play once.</p>
+ *
+ * <p>This call replaces the current repeat count. If the encoded data
+ * specified a repeat count of {@code 2} (meaning that
+ * {@link #getRepeatCount()} returns {@code 2}, the animation will play
+ * three times. Calling {@code setRepeatCount(1)} will result in playing only
+ * twice and {@link #getRepeatCount()} returning {@code 1}.</p>
+ *
+ * <p>If the animation is already playing, the iterations that have already
+ * occurred count towards the new count. If the animation has already
+ * repeated the appropriate number of times (or more), it will finish its
+ * current iteration and then stop.</p>
+ */
+ public void setRepeatCount(@IntRange(from = REPEAT_INFINITE) int repeatCount) {
+ if (repeatCount < REPEAT_INFINITE) {
+ throw new IllegalArgumentException("invalid value passed to setRepeatCount"
+ + repeatCount);
+ }
+ if (mState.mRepeatCount != repeatCount) {
+ mState.mRepeatCount = repeatCount;
+ if (mState.mNativePtr != 0) {
+ nSetRepeatCount(mState.mNativePtr, repeatCount);
+ }
+ }
+ }
+
+ /** @removed
+ * @deprecated Replaced with setRepeatCount to match other APIs.
+ */
+ @java.lang.Deprecated
+ public void setLoopCount(int loopCount) {
+ setRepeatCount(loopCount);
+ }
+
+ /**
+ * Retrieve the number of times the animation will repeat.
+ *
+ * <p>By default, the repeat count in the encoded data is respected. If the
+ * value is {@link #REPEAT_INFINITE}, the animation will repeat as long as
+ * it is displayed. If the value is {@code 0}, it will play once.</p>
+ *
+ * <p>Calling {@link #setRepeatCount} will make future calls to this method
+ * return the value passed to {@link #setRepeatCount}.</p>
+ */
+ public int getRepeatCount() {
+ if (mState.mNativePtr == 0) {
+ throw new IllegalStateException("called getRepeatCount on empty AnimatedImageDrawable");
+ }
+ if (mState.mRepeatCount == REPEAT_UNDEFINED) {
+ mState.mRepeatCount = nGetRepeatCount(mState.mNativePtr);
+
+ }
+ return mState.mRepeatCount;
+ }
+
+ /** @removed
+ * @deprecated Replaced with getRepeatCount to match other APIs.
+ */
+ @java.lang.Deprecated
+ public int getLoopCount(int loopCount) {
+ return getRepeatCount();
+ }
+
+ /**
+ * Create an empty AnimatedImageDrawable.
+ */
+ public AnimatedImageDrawable() {
+ mState = new State(0, null, null);
+ }
+
+ @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.AnimatedImageDrawable);
+ updateStateFromTypedArray(a, mSrcDensityOverride);
+ }
+
+ private void updateStateFromTypedArray(TypedArray a, int srcDensityOverride)
+ throws XmlPullParserException {
+ State oldState = mState;
+ final Resources r = a.getResources();
+ final int srcResId = a.getResourceId(R.styleable.AnimatedImageDrawable_src, 0);
+ if (srcResId != 0) {
+ // Follow the density handling in BitmapDrawable.
+ final TypedValue value = new TypedValue();
+ r.getValueForDensity(srcResId, srcDensityOverride, value, true);
+ 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;
+ }
+ }
+
+ int density = Bitmap.DENSITY_NONE;
+ if (value.density == TypedValue.DENSITY_DEFAULT) {
+ density = DisplayMetrics.DENSITY_DEFAULT;
+ } else if (value.density != TypedValue.DENSITY_NONE) {
+ density = value.density;
+ }
+
+ Drawable drawable = null;
+ try {
+ InputStream is = r.openRawResource(srcResId, value);
+ ImageDecoder.Source source = ImageDecoder.createSource(r, is, density);
+ drawable = ImageDecoder.decodeDrawable(source, (decoder, info, src) -> {
+ if (!info.isAnimated()) {
+ throw new IllegalArgumentException("image is not animated");
+ }
+ });
+ } catch (IOException e) {
+ throw new XmlPullParserException(a.getPositionDescription() +
+ ": <animated-image> requires a valid 'src' attribute", null, e);
+ }
+
+ if (!(drawable instanceof AnimatedImageDrawable)) {
+ throw new XmlPullParserException(a.getPositionDescription() +
+ ": <animated-image> did not decode animated");
+ }
+
+ // This may have previously been set without a src if we were waiting for a
+ // theme.
+ final int repeatCount = mState.mRepeatCount;
+ // Transfer the state of other to this one. other will be discarded.
+ AnimatedImageDrawable other = (AnimatedImageDrawable) drawable;
+ mState = other.mState;
+ other.mState = null;
+ mIntrinsicWidth = other.mIntrinsicWidth;
+ mIntrinsicHeight = other.mIntrinsicHeight;
+ if (repeatCount != REPEAT_UNDEFINED) {
+ this.setRepeatCount(repeatCount);
+ }
+ }
+
+ mState.mThemeAttrs = a.extractThemeAttrs();
+ if (mState.mNativePtr == 0 && (mState.mThemeAttrs == null
+ || mState.mThemeAttrs[R.styleable.AnimatedImageDrawable_src] == 0)) {
+ throw new XmlPullParserException(a.getPositionDescription() +
+ ": <animated-image> requires a valid 'src' attribute");
+ }
+
+ mState.mAutoMirrored = a.getBoolean(
+ R.styleable.AnimatedImageDrawable_autoMirrored, oldState.mAutoMirrored);
+
+ int repeatCount = a.getInt(
+ R.styleable.AnimatedImageDrawable_repeatCount, REPEAT_UNDEFINED);
+ if (repeatCount != REPEAT_UNDEFINED) {
+ this.setRepeatCount(repeatCount);
+ }
+
+ boolean autoStart = a.getBoolean(
+ R.styleable.AnimatedImageDrawable_autoStart, false);
+ if (autoStart && mState.mNativePtr != 0) {
+ this.start();
+ }
+ }
/**
* @hide
@@ -80,30 +307,13 @@
mIntrinsicHeight = cropRect.height();
}
- mNativePtr = nCreate(nativeImageDecoder, decoder, width, height, cropRect);
- mInputStream = inputStream;
- mAssetFd = afd;
+ mState = new State(nCreate(nativeImageDecoder, decoder, width, height, cropRect),
+ inputStream, afd);
- // FIXME: Use the right size for the native allocation.
- long nativeSize = 200;
+ final long nativeSize = nNativeByteSize(mState.mNativePtr);
NativeAllocationRegistry registry = new NativeAllocationRegistry(
AnimatedImageDrawable.class.getClassLoader(), nGetNativeFinalizer(), nativeSize);
- registry.registerNativeAllocation(this, mNativePtr);
- }
-
- @Override
- protected void finalize() throws Throwable {
- // FIXME: It's a shame that we have *both* a native finalizer and a Java
- // one. The native one is necessary to report how much memory is being
- // used natively, and this one is necessary to close the input. An
- // alternative might be to read the entire stream ahead of time, so we
- // can eliminate the Java finalizer.
- try {
- IoUtils.closeQuietly(mInputStream);
- IoUtils.closeQuietly(mAssetFd);
- } finally {
- super.finalize();
- }
+ registry.registerNativeAllocation(mState, mState.mNativePtr);
}
@Override
@@ -116,36 +326,76 @@
return mIntrinsicHeight;
}
+ // nDraw returns -1 if the animation has finished.
+ private static final int FINISHED = -1;
+
@Override
public void draw(@NonNull Canvas canvas) {
- long nextUpdate = nDraw(mNativePtr, canvas.getNativeCanvasWrapper());
+ if (mState.mNativePtr == 0) {
+ throw new IllegalStateException("called draw on empty AnimatedImageDrawable");
+ }
+
+ if (mStarting) {
+ mStarting = false;
+
+ postOnAnimationStart();
+ }
+
+ long nextUpdate = nDraw(mState.mNativePtr, canvas.getNativeCanvasWrapper());
// a value <= 0 indicates that the drawable is stopped or that renderThread
// will manage the animation
if (nextUpdate > 0) {
+ if (mRunnable == null) {
+ mRunnable = this::invalidateSelf;
+ }
scheduleSelf(mRunnable, nextUpdate);
+ } else if (nextUpdate == FINISHED) {
+ // This means the animation was drawn in software mode and ended.
+ postOnAnimationEnd();
}
}
@Override
- public void setAlpha(@IntRange(from=0,to=255) int alpha) {
+ public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {
if (alpha < 0 || alpha > 255) {
throw new IllegalArgumentException("Alpha must be between 0 and"
+ " 255! provided " + alpha);
}
- nSetAlpha(mNativePtr, alpha);
+
+ if (mState.mNativePtr == 0) {
+ throw new IllegalStateException("called setAlpha on empty AnimatedImageDrawable");
+ }
+
+ nSetAlpha(mState.mNativePtr, alpha);
invalidateSelf();
}
@Override
public int getAlpha() {
- return nGetAlpha(mNativePtr);
+ if (mState.mNativePtr == 0) {
+ throw new IllegalStateException("called getAlpha on empty AnimatedImageDrawable");
+ }
+ return nGetAlpha(mState.mNativePtr);
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
- long nativeFilter = colorFilter == null ? 0 : colorFilter.getNativeInstance();
- nSetColorFilter(mNativePtr, nativeFilter);
- invalidateSelf();
+ if (mState.mNativePtr == 0) {
+ throw new IllegalStateException("called setColorFilter on empty AnimatedImageDrawable");
+ }
+
+ if (colorFilter != mColorFilter) {
+ mColorFilter = colorFilter;
+ long nativeFilter = colorFilter == null ? 0 : colorFilter.getNativeInstance();
+ nSetColorFilter(mState.mNativePtr, nativeFilter);
+ invalidateSelf();
+ }
+ }
+
+ @Override
+ @Nullable
+ public ColorFilter getColorFilter() {
+ return mColorFilter;
}
@Override
@@ -153,39 +403,220 @@
return PixelFormat.TRANSLUCENT;
}
- // TODO: Add a Constant State?
- // @Override
- // public @Nullable ConstantState getConstantState() {}
-
-
- // Animatable overrides
@Override
- public boolean isRunning() {
- return nIsRunning(mNativePtr);
- }
-
- @Override
- public void start() {
- if (nStart(mNativePtr)) {
- invalidateSelf();
+ public void setAutoMirrored(boolean mirrored) {
+ if (mState.mAutoMirrored != mirrored) {
+ mState.mAutoMirrored = mirrored;
+ if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL && mState.mNativePtr != 0) {
+ nSetMirrored(mState.mNativePtr, mirrored);
+ invalidateSelf();
+ }
}
}
@Override
- public void stop() {
- nStop(mNativePtr);
+ public boolean onLayoutDirectionChanged(int layoutDirection) {
+ if (!mState.mAutoMirrored || mState.mNativePtr == 0) {
+ return false;
+ }
+
+ final boolean mirror = layoutDirection == View.LAYOUT_DIRECTION_RTL;
+ nSetMirrored(mState.mNativePtr, mirror);
+ return true;
}
+ @Override
+ public final boolean isAutoMirrored() {
+ return mState.mAutoMirrored;
+ }
+
+ @Override
+ public boolean setVisible(boolean visible, boolean restart) {
+ if (!super.setVisible(visible, restart)) {
+ return false;
+ }
+
+ if (mState.mNativePtr == 0) {
+ throw new IllegalStateException("called setVisible on empty AnimatedImageDrawable");
+ }
+
+ if (!visible) {
+ nMarkInvisible(mState.mNativePtr);
+ }
+
+ return true;
+ }
+
+ // Animatable overrides
+ /**
+ * Return whether the animation is currently running.
+ *
+ * <p>When this drawable is created, this will return {@code false}. A client
+ * needs to call {@link #start} to start the animation.</p>
+ */
+ @Override
+ public boolean isRunning() {
+ if (mState.mNativePtr == 0) {
+ throw new IllegalStateException("called isRunning on empty AnimatedImageDrawable");
+ }
+ return nIsRunning(mState.mNativePtr);
+ }
+
+ /**
+ * Start the animation.
+ *
+ * <p>Does nothing if the animation is already running. If the animation is stopped,
+ * this will reset it.</p>
+ *
+ * <p>When the drawable is drawn, starting the animation,
+ * {@link Animatable2.AnimationCallback#onAnimationStart} will be called.</p>
+ */
+ @Override
+ public void start() {
+ if (mState.mNativePtr == 0) {
+ throw new IllegalStateException("called start on empty AnimatedImageDrawable");
+ }
+
+ if (nStart(mState.mNativePtr)) {
+ mStarting = true;
+ invalidateSelf();
+ }
+ }
+
+ /**
+ * Stop the animation.
+ *
+ * <p>If the animation is stopped, it will continue to display the frame
+ * it was displaying when stopped.</p>
+ */
+ @Override
+ public void stop() {
+ if (mState.mNativePtr == 0) {
+ throw new IllegalStateException("called stop on empty AnimatedImageDrawable");
+ }
+ if (nStop(mState.mNativePtr)) {
+ postOnAnimationEnd();
+ }
+ }
+
+ // Animatable2 overrides
+ private ArrayList<Animatable2.AnimationCallback> mAnimationCallbacks = null;
+
+ @Override
+ public void registerAnimationCallback(@NonNull AnimationCallback callback) {
+ if (callback == null) {
+ return;
+ }
+
+ if (mAnimationCallbacks == null) {
+ mAnimationCallbacks = new ArrayList<Animatable2.AnimationCallback>();
+ nSetOnAnimationEndListener(mState.mNativePtr, this);
+ }
+
+ if (!mAnimationCallbacks.contains(callback)) {
+ mAnimationCallbacks.add(callback);
+ }
+ }
+
+ @Override
+ public boolean unregisterAnimationCallback(@NonNull AnimationCallback callback) {
+ if (callback == null || mAnimationCallbacks == null
+ || !mAnimationCallbacks.remove(callback)) {
+ return false;
+ }
+
+ if (mAnimationCallbacks.isEmpty()) {
+ clearAnimationCallbacks();
+ }
+
+ return true;
+ }
+
+ @Override
+ public void clearAnimationCallbacks() {
+ if (mAnimationCallbacks != null) {
+ mAnimationCallbacks = null;
+ nSetOnAnimationEndListener(mState.mNativePtr, null);
+ }
+ }
+
+ private void postOnAnimationStart() {
+ if (mAnimationCallbacks == null) {
+ return;
+ }
+
+ getHandler().post(() -> {
+ for (Animatable2.AnimationCallback callback : mAnimationCallbacks) {
+ callback.onAnimationStart(this);
+ }
+ });
+ }
+
+ private void postOnAnimationEnd() {
+ if (mAnimationCallbacks == null) {
+ return;
+ }
+
+ getHandler().post(() -> {
+ for (Animatable2.AnimationCallback callback : mAnimationCallbacks) {
+ callback.onAnimationEnd(this);
+ }
+ });
+ }
+
+ private Handler getHandler() {
+ if (mHandler == null) {
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+ return mHandler;
+ }
+
+ /**
+ * Called by JNI.
+ *
+ * The JNI code has already posted this to the thread that created the
+ * callback, so no need to post.
+ */
+ @SuppressWarnings("unused")
+ private void onAnimationEnd() {
+ if (mAnimationCallbacks != null) {
+ for (Animatable2.AnimationCallback callback : mAnimationCallbacks) {
+ callback.onAnimationEnd(this);
+ }
+ }
+ }
+
+
private static native long nCreate(long nativeImageDecoder,
@Nullable ImageDecoder decoder, int width, int height, Rect cropRect)
throws IOException;
+ @FastNative
private static native long nGetNativeFinalizer();
private static native long nDraw(long nativePtr, long canvasNativePtr);
+ @FastNative
private static native void nSetAlpha(long nativePtr, int alpha);
+ @FastNative
private static native int nGetAlpha(long nativePtr);
+ @FastNative
private static native void nSetColorFilter(long nativePtr, long nativeFilter);
+ @FastNative
private static native boolean nIsRunning(long nativePtr);
+ // Return whether the animation started.
+ @FastNative
private static native boolean nStart(long nativePtr);
- private static native void nStop(long nativePtr);
+ @FastNative
+ private static native boolean nStop(long nativePtr);
+ @FastNative
+ private static native int nGetRepeatCount(long nativePtr);
+ @FastNative
+ private static native void nSetRepeatCount(long nativePtr, int repeatCount);
+ // Pass the drawable down to native so it can call onAnimationEnd.
+ private static native void nSetOnAnimationEndListener(long nativePtr,
+ @Nullable AnimatedImageDrawable drawable);
+ @FastNative
private static native long nNativeByteSize(long nativePtr);
+ @FastNative
+ private static native void nMarkInvisible(long nativePtr);
+ @FastNative
+ private static native void nSetMirrored(long nativePtr, boolean mirror);
}
diff --git a/android/graphics/drawable/AnimatedVectorDrawable.java b/android/graphics/drawable/AnimatedVectorDrawable.java
index e74dc6d..54358e3 100644
--- a/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -201,10 +201,10 @@
* android:drawable="@drawable/vectordrawable" >
* <target
* android:name="rotationGroup"
- * android:animation="@anim/rotation" />
+ * android:animation="@animator/rotation" />
* <target
* android:name="v"
- * android:animation="@anim/path_morph" />
+ * android:animation="@animator/path_morph" />
* </animated-vector>
* </pre>
* </li>
diff --git a/android/graphics/drawable/BitmapDrawable.java b/android/graphics/drawable/BitmapDrawable.java
index 7ad062a..44b783b 100644
--- a/android/graphics/drawable/BitmapDrawable.java
+++ b/android/graphics/drawable/BitmapDrawable.java
@@ -27,6 +27,7 @@
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
+import android.graphics.ImageDecoder;
import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Outline;
@@ -49,6 +50,7 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -111,7 +113,7 @@
*/
@Deprecated
public BitmapDrawable() {
- mBitmapState = new BitmapState((Bitmap) null);
+ init(new BitmapState((Bitmap) null), null);
}
/**
@@ -124,8 +126,7 @@
@SuppressWarnings("unused")
@Deprecated
public BitmapDrawable(Resources res) {
- mBitmapState = new BitmapState((Bitmap) null);
- mBitmapState.mTargetDensity = mTargetDensity;
+ init(new BitmapState((Bitmap) null), res);
}
/**
@@ -135,7 +136,7 @@
*/
@Deprecated
public BitmapDrawable(Bitmap bitmap) {
- this(new BitmapState(bitmap), null);
+ init(new BitmapState(bitmap), null);
}
/**
@@ -143,8 +144,7 @@
* the display metrics of the resources.
*/
public BitmapDrawable(Resources res, Bitmap bitmap) {
- this(new BitmapState(bitmap), res);
- mBitmapState.mTargetDensity = mTargetDensity;
+ init(new BitmapState(bitmap), res);
}
/**
@@ -154,10 +154,7 @@
*/
@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);
- }
+ this(null, filepath);
}
/**
@@ -165,10 +162,21 @@
*/
@SuppressWarnings({ "unused", "ChainingConstructorIgnoresParameter" })
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);
+ Bitmap bitmap = null;
+ try (FileInputStream stream = new FileInputStream(filepath)) {
+ bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(res, stream),
+ (decoder, info, src) -> {
+ decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+ });
+ } catch (Exception e) {
+ /* do nothing. This matches the behavior of BitmapFactory.decodeFile()
+ If the exception happened on decode, mBitmapState.mBitmap will be null.
+ */
+ } finally {
+ init(new BitmapState(bitmap), res);
+ if (mBitmapState.mBitmap == null) {
+ android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
+ }
}
}
@@ -179,10 +187,7 @@
*/
@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);
- }
+ this(null, is);
}
/**
@@ -190,10 +195,21 @@
*/
@SuppressWarnings({ "unused", "ChainingConstructorIgnoresParameter" })
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);
+ Bitmap bitmap = null;
+ try {
+ bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(res, is),
+ (decoder, info, src) -> {
+ decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+ });
+ } catch (Exception e) {
+ /* do nothing. This matches the behavior of BitmapFactory.decodeStream()
+ If the exception happened on decode, mBitmapState.mBitmap will be null.
+ */
+ } finally {
+ init(new BitmapState(bitmap), res);
+ if (mBitmapState.mBitmap == null) {
+ android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
+ }
}
}
@@ -812,9 +828,19 @@
}
}
+ int density = Bitmap.DENSITY_NONE;
+ if (value.density == TypedValue.DENSITY_DEFAULT) {
+ density = DisplayMetrics.DENSITY_DEFAULT;
+ } else if (value.density != TypedValue.DENSITY_NONE) {
+ density = value.density;
+ }
+
Bitmap bitmap = null;
try (InputStream is = r.openRawResource(srcResId, value)) {
- bitmap = BitmapFactory.decodeResourceStream(r, value, is, null, null);
+ ImageDecoder.Source source = ImageDecoder.createSource(r, is, density);
+ bitmap = ImageDecoder.decodeBitmap(source, (decoder, info, src) -> {
+ decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+ });
} catch (Exception e) {
// Do nothing and pick up the error below.
}
@@ -1013,14 +1039,21 @@
}
}
+ private BitmapDrawable(BitmapState state, Resources res) {
+ init(state, res);
+ }
+
/**
- * The one constructor to rule them all. This is called by all public
+ * The one helper to rule them all. This is called by all public & private
* constructors to set the state and initialize local properties.
*/
- private BitmapDrawable(BitmapState state, Resources res) {
+ private void init(BitmapState state, Resources res) {
mBitmapState = state;
-
updateLocalState(res);
+
+ if (mBitmapState != null && res != null) {
+ mBitmapState.mTargetDensity = mTargetDensity;
+ }
}
/**
diff --git a/android/graphics/drawable/Drawable.java b/android/graphics/drawable/Drawable.java
index f17cd76..8af2fd8 100644
--- a/android/graphics/drawable/Drawable.java
+++ b/android/graphics/drawable/Drawable.java
@@ -37,6 +37,7 @@
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
+import android.graphics.ImageDecoder;
import android.graphics.Insets;
import android.graphics.NinePatch;
import android.graphics.Outline;
@@ -50,11 +51,13 @@
import android.os.Trace;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
+import android.util.Log;
import android.util.StateSet;
import android.util.TypedValue;
import android.util.Xml;
import android.view.View;
+import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
@@ -1168,13 +1171,21 @@
/**
* Create a drawable from an inputstream, using the given resources and
* value to determine density information.
+ *
+ * @deprecated Prefer the version without an Options object.
*/
- public static Drawable createFromResourceStream(Resources res, TypedValue value,
- InputStream is, String srcName, BitmapFactory.Options opts) {
+ @Nullable
+ public static Drawable createFromResourceStream(@Nullable Resources res,
+ @Nullable TypedValue value, @Nullable InputStream is, @Nullable String srcName,
+ @Nullable BitmapFactory.Options opts) {
if (is == null) {
return null;
}
+ if (opts == null) {
+ return getBitmapDrawable(res, value, is);
+ }
+
/* 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
@@ -1190,7 +1201,6 @@
// 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) {
@@ -1207,6 +1217,33 @@
return null;
}
+ private static Drawable getBitmapDrawable(Resources res, TypedValue value, InputStream is) {
+ try {
+ ImageDecoder.Source source = null;
+ if (value != null) {
+ int density = Bitmap.DENSITY_NONE;
+ if (value.density == TypedValue.DENSITY_DEFAULT) {
+ density = DisplayMetrics.DENSITY_DEFAULT;
+ } else if (value.density != TypedValue.DENSITY_NONE) {
+ density = value.density;
+ }
+ source = ImageDecoder.createSource(res, is, density);
+ } else {
+ source = ImageDecoder.createSource(res, is);
+ }
+
+ return ImageDecoder.decodeDrawable(source, (decoder, info, src) -> {
+ decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+ });
+ } catch (IOException e) {
+ /* do nothing.
+ If the exception happened on decode, the drawable will be null.
+ */
+ Log.e("Drawable", "Unable to decode stream: " + e);
+ }
+ return null;
+ }
+
/**
* Create a drawable from an XML document. For more information on how to
* create resources in XML, see
@@ -1306,11 +1343,10 @@
}
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, pathName);
- try {
- Bitmap bm = BitmapFactory.decodeFile(pathName);
- if (bm != null) {
- return drawableFromBitmap(null, bm, null, null, null, pathName);
- }
+ try (FileInputStream stream = new FileInputStream(pathName)) {
+ return getBitmapDrawable(null, null, stream);
+ } catch(IOException e) {
+ // Do nothing; we will just return null if the FileInputStream had an error
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
diff --git a/android/graphics/drawable/DrawableInflater.java b/android/graphics/drawable/DrawableInflater.java
index eea7048..0ee9071 100644
--- a/android/graphics/drawable/DrawableInflater.java
+++ b/android/graphics/drawable/DrawableInflater.java
@@ -185,6 +185,8 @@
return new BitmapDrawable();
case "nine-patch":
return new NinePatchDrawable();
+ case "animated-image":
+ return new AnimatedImageDrawable();
default:
return null;
}
diff --git a/android/graphics/drawable/GradientDrawable.java b/android/graphics/drawable/GradientDrawable.java
index 6c3aea2..dfdddb2 100644
--- a/android/graphics/drawable/GradientDrawable.java
+++ b/android/graphics/drawable/GradientDrawable.java
@@ -42,6 +42,7 @@
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.SweepGradient;
+import android.graphics.Xfermode;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -814,6 +815,24 @@
}
}
+ /**
+ * @param mode to draw this drawable with
+ * @hide
+ */
+ @Override
+ public void setXfermode(@Nullable Xfermode mode) {
+ super.setXfermode(mode);
+ mFillPaint.setXfermode(mode);
+ }
+
+ /**
+ * @param aa to draw this drawable with
+ * @hide
+ */
+ public void setAntiAlias(boolean aa) {
+ mFillPaint.setAntiAlias(aa);
+ }
+
private void buildPathIfDirty() {
final GradientState st = mGradientState;
if (mPathIsDirty) {
@@ -2074,6 +2093,7 @@
}
mRadius = radius;
mRadiusArray = null;
+ computeOpacity();
}
public void setCornerRadii(float[] radii) {
@@ -2081,6 +2101,7 @@
if (radii == null) {
mRadius = 0;
}
+ computeOpacity();
}
public void setSize(int width, int height) {
diff --git a/android/graphics/drawable/Icon.java b/android/graphics/drawable/Icon.java
index 749b759..361fe0b 100644
--- a/android/graphics/drawable/Icon.java
+++ b/android/graphics/drawable/Icon.java
@@ -18,11 +18,14 @@
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
-import android.content.res.ColorStateList;
+import android.annotation.IdRes;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -60,17 +63,40 @@
public final class Icon implements Parcelable {
private static final String TAG = "Icon";
- /** @hide */
+ /**
+ * An icon that was created using {@link Icon#createWithBitmap(Bitmap)}.
+ * @see #getType
+ */
public static final int TYPE_BITMAP = 1;
- /** @hide */
+ /**
+ * An icon that was created using {@link Icon#createWithResource}.
+ * @see #getType
+ */
public static final int TYPE_RESOURCE = 2;
- /** @hide */
+ /**
+ * An icon that was created using {@link Icon#createWithData(byte[], int, int)}.
+ * @see #getType
+ */
public static final int TYPE_DATA = 3;
- /** @hide */
+ /**
+ * An icon that was created using {@link Icon#createWithContentUri}
+ * or {@link Icon#createWithFilePath(String)}.
+ * @see #getType
+ */
public static final int TYPE_URI = 4;
- /** @hide */
+ /**
+ * An icon that was created using {@link Icon#createWithAdaptiveBitmap}.
+ * @see #getType
+ */
public static final int TYPE_ADAPTIVE_BITMAP = 5;
+ /**
+ * @hide
+ */
+ @IntDef({TYPE_BITMAP, TYPE_RESOURCE, TYPE_DATA, TYPE_URI, TYPE_ADAPTIVE_BITMAP})
+ public @interface IconType {
+ }
+
private static final int VERSION_STREAM_SERIALIZER = 1;
private final int mType;
@@ -99,14 +125,12 @@
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
+ * Gets the type of the icon provided.
+ * <p>
+ * Note that new types may be added later, so callers should guard against other
+ * types being returned.
*/
+ @IconType
public int getType() {
return mType;
}
@@ -179,9 +203,13 @@
}
/**
- * @return The package containing resources for this {@link #TYPE_RESOURCE} Icon.
- * @hide
+ * Gets the package used to create this icon.
+ * <p>
+ * Only valid for icons of type {@link #TYPE_RESOURCE}.
+ * Note: This package may not be available if referenced in the future, and it is
+ * up to the caller to ensure safety if this package is re-used and/or persisted.
*/
+ @NonNull
public String getResPackage() {
if (mType != TYPE_RESOURCE) {
throw new IllegalStateException("called getResPackage() on " + this);
@@ -190,9 +218,13 @@
}
/**
- * @return The resource ID for this {@link #TYPE_RESOURCE} Icon.
- * @hide
+ * Gets the resource used to create this icon.
+ * <p>
+ * Only valid for icons of type {@link #TYPE_RESOURCE}.
+ * Note: This resource may not be available if the application changes at all, and it is
+ * up to the caller to ensure safety if this resource is re-used and/or persisted.
*/
+ @IdRes
public int getResId() {
if (mType != TYPE_RESOURCE) {
throw new IllegalStateException("called getResId() on " + this);
@@ -212,9 +244,13 @@
}
/**
- * @return The {@link android.net.Uri} for this {@link #TYPE_URI} Icon.
- * @hide
+ * Gets the uri used to create this icon.
+ * <p>
+ * Only valid for icons of type {@link #TYPE_URI}.
+ * Note: This uri may not be available in the future, and it is
+ * up to the caller to ensure safety if this uri is re-used and/or persisted.
*/
+ @NonNull
public Uri getUri() {
return Uri.parse(getUriString());
}
diff --git a/android/graphics/drawable/NinePatchDrawable.java b/android/graphics/drawable/NinePatchDrawable.java
index 1790020..66f2a31 100644
--- a/android/graphics/drawable/NinePatchDrawable.java
+++ b/android/graphics/drawable/NinePatchDrawable.java
@@ -24,9 +24,9 @@
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.ImageDecoder;
import android.graphics.Insets;
import android.graphics.NinePatch;
import android.graphics.Outline;
@@ -211,7 +211,8 @@
restoreAlpha = -1;
}
- final boolean needsDensityScaling = canvas.getDensity() == 0;
+ final boolean needsDensityScaling = canvas.getDensity() == 0
+ && Bitmap.DENSITY_NONE != state.mNinePatch.getDensity();
if (needsDensityScaling) {
restoreToCount = restoreToCount >= 0 ? restoreToCount : canvas.save();
@@ -421,10 +422,6 @@
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;
@@ -433,7 +430,17 @@
final TypedValue value = new TypedValue();
final InputStream is = r.openRawResource(srcResId, value);
- bitmap = BitmapFactory.decodeResourceStream(r, value, is, padding, options);
+ int density = Bitmap.DENSITY_NONE;
+ if (value.density == TypedValue.DENSITY_DEFAULT) {
+ density = DisplayMetrics.DENSITY_DEFAULT;
+ } else if (value.density != TypedValue.DENSITY_NONE) {
+ density = value.density;
+ }
+ ImageDecoder.Source source = ImageDecoder.createSource(r, is, density);
+ bitmap = ImageDecoder.decodeBitmap(source, (decoder, info, src) -> {
+ decoder.setOutPaddingRect(padding);
+ decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+ });
is.close();
} catch (IOException e) {
@@ -660,8 +667,9 @@
return;
}
- final int sourceDensity = ninePatch.getDensity();
final int targetDensity = mTargetDensity;
+ final int sourceDensity = ninePatch.getDensity() == Bitmap.DENSITY_NONE ?
+ targetDensity : ninePatch.getDensity();
final Insets sourceOpticalInsets = mNinePatchState.mOpticalInsets;
if (sourceOpticalInsets != Insets.NONE) {
diff --git a/android/graphics/drawable/RippleBackground.java b/android/graphics/drawable/RippleBackground.java
index 41d3698..2812abe 100644
--- a/android/graphics/drawable/RippleBackground.java
+++ b/android/graphics/drawable/RippleBackground.java
@@ -78,9 +78,10 @@
}
private void onStateChanged() {
- float newOpacity = 0.0f;
- if (mHovered) newOpacity += .25f;
- if (mFocused) newOpacity += .75f;
+ // Hover = .2 * alpha
+ // Focus = .6 * alpha
+ // Focused + Hovered = .6 * alpha
+ float newOpacity = mFocused ? .6f : mHovered ? .2f : 0f;
if (mAnimator != null) {
mAnimator.cancel();
mAnimator = null;
diff --git a/android/graphics/drawable/RippleForeground.java b/android/graphics/drawable/RippleForeground.java
index 4129868..a8dc34a 100644
--- a/android/graphics/drawable/RippleForeground.java
+++ b/android/graphics/drawable/RippleForeground.java
@@ -110,6 +110,7 @@
// Take 60% of the maximum of the width and height, then divided half to get the radius.
mStartRadius = Math.max(bounds.width(), bounds.height()) * 0.3f;
+ clampStartingPosition();
}
@Override
@@ -350,7 +351,7 @@
final float cY = mBounds.exactCenterY();
final float dX = mStartingX - cX;
final float dY = mStartingY - cY;
- final float r = mTargetRadius;
+ final float r = mTargetRadius - mStartRadius;
if (dX * dX + dY * dY > r * r) {
// Point is outside the circle, clamp to the perimeter.
final double angle = Math.atan2(dY, dX);