summaryrefslogtreecommitdiff
path: root/graphics/java/android
diff options
context:
space:
mode:
Diffstat (limited to 'graphics/java/android')
-rw-r--r--graphics/java/android/graphics/BLASTBufferQueue.java68
-rw-r--r--graphics/java/android/graphics/Bitmap.java67
-rw-r--r--graphics/java/android/graphics/Canvas.java60
-rw-r--r--graphics/java/android/graphics/ColorSpace.java66
-rw-r--r--graphics/java/android/graphics/FontFamily.java47
-rw-r--r--graphics/java/android/graphics/GraphicBuffer.java10
-rw-r--r--graphics/java/android/graphics/GraphicsStatsService.java564
-rw-r--r--graphics/java/android/graphics/HardwareRenderer.java41
-rw-r--r--graphics/java/android/graphics/HardwareRendererObserver.java103
-rw-r--r--graphics/java/android/graphics/ImageDecoder.java105
-rw-r--r--graphics/java/android/graphics/Outline.java41
-rw-r--r--graphics/java/android/graphics/ParcelableColorSpace.java183
-rw-r--r--graphics/java/android/graphics/Path.java6
-rw-r--r--graphics/java/android/graphics/Point.java2
-rw-r--r--graphics/java/android/graphics/PointF.java12
-rw-r--r--graphics/java/android/graphics/PorterDuff.java2
-rw-r--r--graphics/java/android/graphics/Rect.java2
-rw-r--r--graphics/java/android/graphics/RenderNode.java32
-rw-r--r--graphics/java/android/graphics/RuntimeShader.java90
-rw-r--r--graphics/java/android/graphics/SurfaceTexture.java55
-rw-r--r--graphics/java/android/graphics/animation/FallbackLUTInterpolator.java80
-rw-r--r--graphics/java/android/graphics/animation/HasNativeInterpolator.java37
-rw-r--r--graphics/java/android/graphics/animation/NativeInterpolator.java29
-rw-r--r--graphics/java/android/graphics/animation/NativeInterpolatorFactory.java67
-rw-r--r--graphics/java/android/graphics/animation/RenderNodeAnimator.java513
-rw-r--r--graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java18
-rw-r--r--graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java6
-rw-r--r--graphics/java/android/graphics/drawable/DrawableWrapper.java7
-rw-r--r--graphics/java/android/graphics/drawable/GradientDrawable.java149
-rw-r--r--graphics/java/android/graphics/drawable/Icon.java106
-rw-r--r--graphics/java/android/graphics/drawable/RippleForeground.java2
-rw-r--r--graphics/java/android/graphics/drawable/shapes/RoundRectShape.java2
-rw-r--r--graphics/java/android/graphics/fonts/Font.java111
-rw-r--r--graphics/java/android/graphics/fonts/SystemFonts.java1
-rw-r--r--graphics/java/android/graphics/pdf/PdfDocument.java4
-rw-r--r--graphics/java/android/graphics/pdf/TEST_MAPPING7
36 files changed, 2378 insertions, 317 deletions
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
new file mode 100644
index 000000000000..4c7e960eb0a4
--- /dev/null
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+/**
+ * @hide
+ */
+public final class BLASTBufferQueue {
+ // Note: This field is accessed by native code.
+ private long mNativeObject; // BLASTBufferQueue*
+
+ private static native long nativeCreate(long surfaceControl, long width, long height,
+ boolean tripleBufferingEnabled);
+ private static native void nativeDestroy(long ptr);
+ private static native Surface nativeGetSurface(long ptr);
+ private static native void nativeSetNextTransaction(long ptr, long transactionPtr);
+ private static native void nativeUpdate(long ptr, long surfaceControl, long width, long height);
+
+ /** Create a new connection with the surface flinger. */
+ public BLASTBufferQueue(SurfaceControl sc, int width, int height,
+ boolean tripleBufferingEnabled) {
+ mNativeObject = nativeCreate(sc.mNativeObject, width, height, tripleBufferingEnabled);
+ }
+
+ public void destroy() {
+ nativeDestroy(mNativeObject);
+ }
+
+ public Surface getSurface() {
+ return nativeGetSurface(mNativeObject);
+ }
+
+ public void setNextTransaction(SurfaceControl.Transaction t) {
+ nativeSetNextTransaction(mNativeObject, t.mNativeObject);
+ }
+
+ public void update(SurfaceControl sc, int width, int height) {
+ nativeUpdate(mNativeObject, sc.mNativeObject, width, height);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mNativeObject != 0) {
+ nativeDestroy(mNativeObject);
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+}
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index a8b7e1fa0113..f7877590869a 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -1359,9 +1359,44 @@ public final class Bitmap implements Parcelable {
* Specifies the known formats a bitmap can be compressed into
*/
public enum CompressFormat {
- JPEG (0),
- PNG (1),
- WEBP (2);
+ /**
+ * Compress to the JPEG format. {@code quality} of {@code 0} means
+ * compress for the smallest size. {@code 100} means compress for max
+ * visual quality.
+ */
+ JPEG (0),
+ /**
+ * Compress to the PNG format. PNG is lossless, so {@code quality} is
+ * ignored.
+ */
+ PNG (1),
+ /**
+ * Compress to the WEBP format. {@code quality} of {@code 0} means
+ * compress for the smallest size. {@code 100} means compress for max
+ * visual quality. As of {@link android.os.Build.VERSION_CODES#Q}, a
+ * value of {@code 100} results in a file in the lossless WEBP format.
+ * Otherwise the file will be in the lossy WEBP format.
+ *
+ * @deprecated in favor of the more explicit
+ * {@link CompressFormat#WEBP_LOSSY} and
+ * {@link CompressFormat#WEBP_LOSSLESS}.
+ */
+ @Deprecated
+ WEBP (2),
+ /**
+ * Compress to the WEBP lossy format. {@code quality} of {@code 0} means
+ * compress for the smallest size. {@code 100} means compress for max
+ * visual quality.
+ */
+ WEBP_LOSSY (3),
+ /**
+ * Compress to the WEBP lossless format. {@code quality} refers to how
+ * much effort to put into compression. A value of {@code 0} means to
+ * compress quickly, resulting in a relatively large file size.
+ * {@code 100} means to spend more time compressing, resulting in a
+ * smaller file.
+ */
+ WEBP_LOSSLESS (4);
CompressFormat(int nativeInt) {
this.nativeInt = nativeInt;
@@ -1385,10 +1420,8 @@ public final class Bitmap implements Parcelable {
* pixels).
*
* @param format The format of the compressed image
- * @param quality Hint to the compressor, 0-100. 0 meaning compress for
- * small size, 100 meaning compress for max quality. Some
- * formats, like PNG which is lossless, will ignore the
- * quality setting
+ * @param quality Hint to the compressor, 0-100. The value is interpreted
+ * differently depending on the {@link CompressFormat}.
* @param stream The outputstream to write the compressed data.
* @return true if successfully compressed to the specified stream.
*/
@@ -2099,7 +2132,7 @@ public final class Bitmap implements Parcelable {
public void writeToParcel(Parcel p, int flags) {
checkRecycled("Can't parcel a recycled bitmap");
noteHardwareBitmapSlowCall();
- if (!nativeWriteToParcel(mNativePtr, isMutable(), mDensity, p)) {
+ if (!nativeWriteToParcel(mNativePtr, mDensity, p)) {
throw new RuntimeException("native writeToParcel failed");
}
}
@@ -2207,7 +2240,20 @@ public final class Bitmap implements Parcelable {
*/
@UnsupportedAppUsage
public GraphicBuffer createGraphicBufferHandle() {
- return nativeCreateGraphicBufferHandle(mNativePtr);
+ return GraphicBuffer.createFromHardwareBuffer(getHardwareBuffer());
+ }
+
+ /**
+ * @return {@link HardwareBuffer} which is internally used by hardware bitmap
+ *
+ * Note: the HardwareBuffer does *not* have an associated {@link ColorSpace}.
+ * To render this object the same as its rendered with this Bitmap, you
+ * should also call {@link getColorSpace}.
+ *
+ * @hide
+ */
+ public HardwareBuffer getHardwareBuffer() {
+ return nativeGetHardwareBuffer(mNativePtr);
}
//////////// native methods
@@ -2252,7 +2298,6 @@ public final class Bitmap implements Parcelable {
private static native Bitmap nativeCreateFromParcel(Parcel p);
// returns true on success
private static native boolean nativeWriteToParcel(long nativeBitmap,
- boolean isMutable,
int density,
Parcel p);
// returns a new bitmap built from the native bitmap's alpha, and the paint
@@ -2275,7 +2320,7 @@ public final class Bitmap implements Parcelable {
private static native Bitmap nativeCopyPreserveInternalConfig(long nativeBitmap);
private static native Bitmap nativeWrapHardwareBufferBitmap(HardwareBuffer buffer,
long nativeColorSpace);
- private static native GraphicBuffer nativeCreateGraphicBufferHandle(long nativeBitmap);
+ private static native HardwareBuffer nativeGetHardwareBuffer(long nativeBitmap);
private static native ColorSpace nativeComputeColorSpace(long nativePtr);
private static native void nativeSetColorSpace(long nativePtr, long nativeColorSpace);
private static native boolean nativeIsSRGB(long nativePtr);
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index 9a0ca3e4ad9b..d949444d44d6 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -1162,6 +1162,7 @@ public class Canvas extends BaseCanvas {
* @see #quickReject(float, float, float, float, EdgeType)
* @see #quickReject(Path, EdgeType)
* @see #quickReject(RectF, EdgeType)
+ * @deprecated quickReject no longer uses this.
*/
public enum EdgeType {
@@ -1197,13 +1198,30 @@ public class Canvas extends BaseCanvas {
* non-antialiased ({@link Canvas.EdgeType#BW}).
* @return true if the rect (transformed by the canvas' matrix)
* does not intersect with the canvas' clip
+ * @deprecated The EdgeType is ignored. Use {@link #quickReject(RectF)} instead.
*/
+ @Deprecated
public boolean quickReject(@NonNull RectF rect, @NonNull EdgeType type) {
return nQuickReject(mNativeCanvasWrapper,
rect.left, rect.top, rect.right, rect.bottom);
}
/**
+ * Return true if the specified rectangle, after being transformed by the
+ * current matrix, would lie completely outside of the current clip. Call
+ * this to check if an area you intend to draw into is clipped out (and
+ * therefore you can skip making the draw calls).
+ *
+ * @param rect the rect to compare with the current clip
+ * @return true if the rect (transformed by the canvas' matrix)
+ * does not intersect with the canvas' clip
+ */
+ public boolean quickReject(@NonNull RectF rect) {
+ return nQuickReject(mNativeCanvasWrapper,
+ rect.left, rect.top, rect.right, rect.bottom);
+ }
+
+ /**
* Return true if the specified path, after being transformed by the
* current matrix, would lie completely outside of the current clip. Call
* this to check if an area you intend to draw into is clipped out (and
@@ -1217,12 +1235,30 @@ public class Canvas extends BaseCanvas {
* non-antialiased ({@link Canvas.EdgeType#BW}).
* @return true if the path (transformed by the canvas' matrix)
* does not intersect with the canvas' clip
+ * @deprecated The EdgeType is ignored. Use {@link #quickReject(Path)} instead.
*/
+ @Deprecated
public boolean quickReject(@NonNull Path path, @NonNull EdgeType type) {
return nQuickReject(mNativeCanvasWrapper, path.readOnlyNI());
}
/**
+ * Return true if the specified path, after being transformed by the
+ * current matrix, would lie completely outside of the current clip. Call
+ * this to check if an area you intend to draw into is clipped out (and
+ * therefore you can skip making the draw calls). Note: for speed it may
+ * return false even if the path itself might not intersect the clip
+ * (i.e. the bounds of the path intersects, but the path does not).
+ *
+ * @param path The path to compare with the current clip
+ * @return true if the path (transformed by the canvas' matrix)
+ * does not intersect with the canvas' clip
+ */
+ public boolean quickReject(@NonNull Path path) {
+ return nQuickReject(mNativeCanvasWrapper, path.readOnlyNI());
+ }
+
+ /**
* Return true if the specified rectangle, after being transformed by the
* current matrix, would lie completely outside of the current clip. Call
* this to check if an area you intend to draw into is clipped out (and
@@ -1241,13 +1277,37 @@ public class Canvas extends BaseCanvas {
* non-antialiased ({@link Canvas.EdgeType#BW}).
* @return true if the rect (transformed by the canvas' matrix)
* does not intersect with the canvas' clip
+ * @deprecated The EdgeType is ignored. Use {@link #quickReject(float, float, float, float)}
+ * instead.
*/
+ @Deprecated
public boolean quickReject(float left, float top, float right, float bottom,
@NonNull EdgeType type) {
return nQuickReject(mNativeCanvasWrapper, left, top, right, bottom);
}
/**
+ * Return true if the specified rectangle, after being transformed by the
+ * current matrix, would lie completely outside of the current clip. Call
+ * this to check if an area you intend to draw into is clipped out (and
+ * therefore you can skip making the draw calls).
+ *
+ * @param left The left side of the rectangle to compare with the
+ * current clip
+ * @param top The top of the rectangle to compare with the current
+ * clip
+ * @param right The right side of the rectangle to compare with the
+ * current clip
+ * @param bottom The bottom of the rectangle to compare with the
+ * current clip
+ * @return true if the rect (transformed by the canvas' matrix)
+ * does not intersect with the canvas' clip
+ */
+ public boolean quickReject(float left, float top, float right, float bottom) {
+ return nQuickReject(mNativeCanvasWrapper, left, top, right, bottom);
+ }
+
+ /**
* Return the bounds of the current clip (in local coordinates) in the
* bounds parameter, and return true if it is non-empty. This can be useful
* in a way similar to quickReject, in that it tells you that drawing
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 9c4b5e8b0165..1aeafa391b41 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -199,6 +199,11 @@ public abstract class ColorSpace {
private static final float[] SRGB_PRIMARIES = { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f };
private static final float[] NTSC_1953_PRIMARIES = { 0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f };
+ /**
+ * A gray color space does not have meaningful primaries, so we use this arbitrary set.
+ */
+ private static final float[] GRAY_PRIMARIES = { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f };
+
private static final float[] ILLUMINANT_D50_XYZ = { 0.964212f, 1.0f, 0.825188f };
private static final Rgb.TransferParameters SRGB_TRANSFER_PARAMETERS =
@@ -867,7 +872,8 @@ public abstract class ColorSpace {
}
}
- private ColorSpace(
+ /** @hide */
+ ColorSpace(
@NonNull String name,
@NonNull Model model,
@IntRange(from = MIN_ID, to = MAX_ID) int id) {
@@ -1380,9 +1386,9 @@ public abstract class ColorSpace {
*/
@NonNull
static ColorSpace get(@IntRange(from = MIN_ID, to = MAX_ID) int index) {
- if (index < 0 || index >= Named.values().length) {
+ if (index < 0 || index >= sNamedColorSpaces.length) {
throw new IllegalArgumentException("Invalid ID, must be in the range [0.." +
- Named.values().length + ")");
+ sNamedColorSpaces.length + ")");
}
return sNamedColorSpaces[index];
}
@@ -1456,6 +1462,7 @@ public abstract class ColorSpace {
"sRGB IEC61966-2.1",
SRGB_PRIMARIES,
ILLUMINANT_D65,
+ null,
SRGB_TRANSFER_PARAMETERS,
Named.SRGB.ordinal()
);
@@ -1490,6 +1497,7 @@ public abstract class ColorSpace {
"Rec. ITU-R BT.709-5",
new float[] { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f },
ILLUMINANT_D65,
+ null,
new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45),
Named.BT709.ordinal()
);
@@ -1497,6 +1505,7 @@ public abstract class ColorSpace {
"Rec. ITU-R BT.2020-1",
new float[] { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f },
ILLUMINANT_D65,
+ null,
new Rgb.TransferParameters(1 / 1.0993, 0.0993 / 1.0993, 1 / 4.5, 0.08145, 1 / 0.45),
Named.BT2020.ordinal()
);
@@ -1512,6 +1521,7 @@ public abstract class ColorSpace {
"Display P3",
new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
ILLUMINANT_D65,
+ null,
SRGB_TRANSFER_PARAMETERS,
Named.DISPLAY_P3.ordinal()
);
@@ -1519,6 +1529,7 @@ public abstract class ColorSpace {
"NTSC (1953)",
NTSC_1953_PRIMARIES,
ILLUMINANT_C,
+ null,
new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45),
Named.NTSC_1953.ordinal()
);
@@ -1526,6 +1537,7 @@ public abstract class ColorSpace {
"SMPTE-C RGB",
new float[] { 0.630f, 0.340f, 0.310f, 0.595f, 0.155f, 0.070f },
ILLUMINANT_D65,
+ null,
new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45),
Named.SMPTE_C.ordinal()
);
@@ -1541,6 +1553,7 @@ public abstract class ColorSpace {
"ROMM RGB ISO 22028-2:2013",
new float[] { 0.7347f, 0.2653f, 0.1596f, 0.8404f, 0.0366f, 0.0001f },
ILLUMINANT_D50,
+ null,
new Rgb.TransferParameters(1.0, 0.0, 1 / 16.0, 0.031248, 1.8),
Named.PRO_PHOTO_RGB.ordinal()
);
@@ -2470,7 +2483,11 @@ public abstract class ColorSpace {
@NonNull @Size(min = 1) String name,
@NonNull @Size(9) float[] toXYZ,
@NonNull TransferParameters function) {
- this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ), function, MIN_ID);
+ // Note: when isGray() returns false, this passes null for the transform for
+ // consistency with other constructors, which compute the transform from the primaries
+ // and white point.
+ this(name, isGray(toXYZ) ? GRAY_PRIMARIES : computePrimaries(toXYZ),
+ computeWhitePoint(toXYZ), isGray(toXYZ) ? toXYZ : null, function, MIN_ID);
}
/**
@@ -2510,7 +2527,7 @@ public abstract class ColorSpace {
@NonNull @Size(min = 6, max = 9) float[] primaries,
@NonNull @Size(min = 2, max = 3) float[] whitePoint,
@NonNull TransferParameters function) {
- this(name, primaries, whitePoint, function, MIN_ID);
+ this(name, primaries, whitePoint, null, function, MIN_ID);
}
/**
@@ -2533,6 +2550,8 @@ public abstract class ColorSpace {
* @param name Name of the color space, cannot be null, its length must be >= 1
* @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
* @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats
+ * @param transform Computed transform matrix that converts from RGB to XYZ, or
+ * {@code null} to compute it from {@code primaries} and {@code whitePoint}.
* @param function Parameters for the transfer functions
* @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID}
*
@@ -2551,9 +2570,10 @@ public abstract class ColorSpace {
@NonNull @Size(min = 1) String name,
@NonNull @Size(min = 6, max = 9) float[] primaries,
@NonNull @Size(min = 2, max = 3) float[] whitePoint,
+ @Nullable @Size(9) float[] transform,
@NonNull TransferParameters function,
@IntRange(from = MIN_ID, to = MAX_ID) int id) {
- this(name, primaries, whitePoint, null,
+ this(name, primaries, whitePoint, transform,
function.e == 0.0 && function.f == 0.0 ?
x -> rcpResponse(x, function.a, function.b,
function.c, function.d, function.g) :
@@ -2845,7 +2865,7 @@ public abstract class ColorSpace {
*
* @return The destination array passed as a parameter
*
- * @see #getWhitePoint(float[])
+ * @see #getWhitePoint()
*/
@NonNull
@Size(min = 2)
@@ -2863,7 +2883,7 @@ public abstract class ColorSpace {
*
* @return A new non-null array of 2 floats
*
- * @see #getWhitePoint()
+ * @see #getWhitePoint(float[])
*/
@NonNull
@Size(2)
@@ -2877,12 +2897,16 @@ public abstract class ColorSpace {
* destination. The x and y components of the first primary are written
* in the array at positions 0 and 1 respectively.
*
+ * <p>Note: Some ColorSpaces represent gray profiles. The concept of
+ * primaries for such a ColorSpace does not make sense, so we use a special
+ * set of primaries that are all 1s.</p>
+ *
* @param primaries The destination array, cannot be null, its length
* must be >= 6
*
* @return The destination array passed as a parameter
*
- * @see #getPrimaries(float[])
+ * @see #getPrimaries()
*/
@NonNull
@Size(min = 6)
@@ -2897,9 +2921,13 @@ public abstract class ColorSpace {
* the destination. The x and y components of the first primary are
* written in the array at positions 0 and 1 respectively.
*
+ * <p>Note: Some ColorSpaces represent gray profiles. The concept of
+ * primaries for such a ColorSpace does not make sense, so we use a special
+ * set of primaries that are all 1s.</p>
+ *
* @return A new non-null array of 2 floats
*
- * @see #getWhitePoint()
+ * @see #getPrimaries(float[])
*/
@NonNull
@Size(6)
@@ -2921,7 +2949,7 @@ public abstract class ColorSpace {
*
* @return The destination array passed as a parameter
*
- * @see #getInverseTransform()
+ * @see #getTransform()
*/
@NonNull
@Size(min = 9)
@@ -2941,7 +2969,7 @@ public abstract class ColorSpace {
*
* @return A new array of 9 floats
*
- * @see #getInverseTransform(float[])
+ * @see #getTransform(float[])
*/
@NonNull
@Size(9)
@@ -2963,7 +2991,7 @@ public abstract class ColorSpace {
*
* @return The destination array passed as a parameter
*
- * @see #getTransform()
+ * @see #getInverseTransform()
*/
@NonNull
@Size(min = 9)
@@ -2983,7 +3011,7 @@ public abstract class ColorSpace {
*
* @return A new array of 9 floats
*
- * @see #getTransform(float[])
+ * @see #getInverseTransform(float[])
*/
@NonNull
@Size(9)
@@ -3286,6 +3314,16 @@ public abstract class ColorSpace {
return true;
}
+ /**
+ * Report whether this matrix is a special gray matrix.
+ * @param toXYZ A XYZD50 matrix. Skia uses a special form for a gray profile.
+ * @return true if this is a special gray matrix.
+ */
+ private static boolean isGray(@NonNull @Size(9) float[] toXYZ) {
+ return toXYZ.length == 9 && toXYZ[1] == 0 && toXYZ[2] == 0 && toXYZ[3] == 0
+ && toXYZ[5] == 0 && toXYZ[6] == 0 && toXYZ[7] == 0;
+ }
+
private static boolean compare(double point, @NonNull DoubleUnaryOperator a,
@NonNull DoubleUnaryOperator b) {
double rA = a.applyAsDouble(point);
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
index 254892013d51..f50de1665453 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -19,9 +19,10 @@ package android.graphics;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetManager;
+import android.graphics.fonts.Font;
import android.graphics.fonts.FontVariationAxis;
+import android.os.Build;
import android.text.TextUtils;
-import android.util.Log;
import dalvik.annotation.optimization.CriticalNative;
@@ -59,7 +60,8 @@ public class FontFamily {
*
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public long mNativePtr;
// Points native font family builder. Must be zero after freezing this family.
@@ -68,7 +70,8 @@ public class FontFamily {
/**
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public FontFamily() {
mBuilderPtr = nInitBuilder(null, 0);
mNativeBuilderCleaner = sBuilderRegistry.registerNativeAllocation(this, mBuilderPtr);
@@ -77,7 +80,8 @@ public class FontFamily {
/**
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public FontFamily(@Nullable String[] langs, int variant) {
final String langsString;
if (langs == null || langs.length == 0) {
@@ -99,7 +103,8 @@ public class FontFamily {
*
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public boolean freeze() {
if (mBuilderPtr == 0) {
throw new IllegalStateException("This FontFamily is already frozen");
@@ -116,7 +121,8 @@ public class FontFamily {
/**
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public void abortCreation() {
if (mBuilderPtr == 0) {
throw new IllegalStateException("This FontFamily is already frozen or abandoned");
@@ -128,7 +134,8 @@ public class FontFamily {
/**
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public boolean addFont(String path, int ttcIndex, FontVariationAxis[] axes, int weight,
int italic) {
if (mBuilderPtr == 0) {
@@ -145,7 +152,6 @@ public class FontFamily {
}
return nAddFont(mBuilderPtr, fontBuffer, ttcIndex, weight, italic);
} catch (IOException e) {
- Log.e(TAG, "Error mapping font file " + path);
return false;
}
}
@@ -153,7 +159,8 @@ public class FontFamily {
/**
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public boolean addFontFromBuffer(ByteBuffer font, int ttcIndex, FontVariationAxis[] axes,
int weight, int italic) {
if (mBuilderPtr == 0) {
@@ -181,25 +188,21 @@ public class FontFamily {
*
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public boolean addFontFromAssetManager(AssetManager mgr, String path, int cookie,
boolean isAsset, int ttcIndex, int weight, int isItalic,
FontVariationAxis[] axes) {
if (mBuilderPtr == 0) {
throw new IllegalStateException("Unable to call addFontFromAsset after freezing.");
}
- if (axes != null) {
- for (FontVariationAxis axis : axes) {
- nAddAxisValue(mBuilderPtr, axis.getOpenTypeTagValue(), axis.getStyleValue());
- }
- }
- return nAddFontFromAssetManager(mBuilderPtr, mgr, path, cookie, isAsset, ttcIndex, weight,
- isItalic);
- }
- // 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);
+ try {
+ ByteBuffer buffer = Font.Builder.createBuffer(mgr, path, isAsset, cookie);
+ return addFontFromBuffer(buffer, ttcIndex, axes, weight, isItalic);
+ } catch (IOException e) {
+ return false;
+ }
}
private static native long nInitBuilder(String langs, int variant);
@@ -218,8 +221,6 @@ public class FontFamily {
int weight, int isItalic);
private static native boolean nAddFontWeightStyle(long builderPtr, ByteBuffer font,
int ttcIndex, int weight, int isItalic);
- private static native boolean nAddFontFromAssetManager(long builderPtr, AssetManager mgr,
- String path, int cookie, boolean isAsset, int ttcIndex, int weight, int isItalic);
// The added axis values are only valid for the next nAddFont* method call.
@CriticalNative
diff --git a/graphics/java/android/graphics/GraphicBuffer.java b/graphics/java/android/graphics/GraphicBuffer.java
index 99fa5eef7bbd..0430847857b6 100644
--- a/graphics/java/android/graphics/GraphicBuffer.java
+++ b/graphics/java/android/graphics/GraphicBuffer.java
@@ -17,6 +17,7 @@
package android.graphics;
import android.compat.annotation.UnsupportedAppUsage;
+import android.hardware.HardwareBuffer;
import android.os.Parcel;
import android.os.Parcelable;
@@ -110,6 +111,14 @@ public class GraphicBuffer implements Parcelable {
}
/**
+ * For Bitmap until all usages are updated to AHB
+ * @hide
+ */
+ public static final GraphicBuffer createFromHardwareBuffer(HardwareBuffer buffer) {
+ return nCreateFromHardwareBuffer(buffer);
+ }
+
+ /**
* Returns the width of this buffer in pixels.
*/
public int getWidth() {
@@ -305,4 +314,5 @@ public class GraphicBuffer implements Parcelable {
private static native boolean nLockCanvas(long nativeObject, Canvas canvas, Rect dirty);
private static native boolean nUnlockCanvasAndPost(long nativeObject, Canvas canvas);
private static native long nWrapGraphicBuffer(long nativeObject);
+ private static native GraphicBuffer nCreateFromHardwareBuffer(HardwareBuffer buffer);
}
diff --git a/graphics/java/android/graphics/GraphicsStatsService.java b/graphics/java/android/graphics/GraphicsStatsService.java
new file mode 100644
index 000000000000..2d6848b618a1
--- /dev/null
+++ b/graphics/java/android/graphics/GraphicsStatsService.java
@@ -0,0 +1,564 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import android.app.AlarmManager;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SharedMemory;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.system.ErrnoException;
+import android.util.Log;
+import android.view.IGraphicsStats;
+import android.view.IGraphicsStatsCallback;
+
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FastPrintWriter;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.HashSet;
+import java.util.TimeZone;
+
+/**
+ * This service's job is to collect aggregate rendering profile data. It
+ * does this by allowing rendering processes to request an ashmem buffer
+ * to place their stats into.
+ *
+ * Buffers are rotated on a daily (in UTC) basis and only the 3 most-recent days
+ * are kept.
+ *
+ * The primary consumer of this is incident reports and automated metric checking. It is not
+ * intended for end-developer consumption, for that we have gfxinfo.
+ *
+ * Buffer rotation process:
+ * 1) Alarm fires
+ * 2) onRotateGraphicsStatsBuffer() is sent to all active processes
+ * 3) Upon receiving the callback, the process will stop using the previous ashmem buffer and
+ * request a new one.
+ * 4) When that request is received we now know that the ashmem region is no longer in use so
+ * it gets queued up for saving to disk and a new ashmem region is created and returned
+ * for the process to use.
+ *
+ * @hide */
+public class GraphicsStatsService extends IGraphicsStats.Stub {
+ public static final String GRAPHICS_STATS_SERVICE = "graphicsstats";
+
+ private static final String TAG = "GraphicsStatsService";
+
+ private static final int SAVE_BUFFER = 1;
+ private static final int DELETE_OLD = 2;
+
+ private static final int AID_STATSD = 1066; // Statsd uid is set to 1066 forever.
+
+ // This isn't static because we need this to happen after registerNativeMethods, however
+ // the class is loaded (and thus static ctor happens) before that occurs.
+ private final int mAshmemSize = nGetAshmemSize();
+ private final byte[] mZeroData = new byte[mAshmemSize];
+
+ private final Context mContext;
+ private final AppOpsManager mAppOps;
+ private final AlarmManager mAlarmManager;
+ private final Object mLock = new Object();
+ private ArrayList<ActiveBuffer> mActive = new ArrayList<>();
+ private File mGraphicsStatsDir;
+ private final Object mFileAccessLock = new Object();
+ private Handler mWriteOutHandler;
+ private boolean mRotateIsScheduled = false;
+
+ public GraphicsStatsService(Context context) {
+ mContext = context;
+ mAppOps = context.getSystemService(AppOpsManager.class);
+ mAlarmManager = context.getSystemService(AlarmManager.class);
+ File systemDataDir = new File(Environment.getDataDirectory(), "system");
+ mGraphicsStatsDir = new File(systemDataDir, "graphicsstats");
+ mGraphicsStatsDir.mkdirs();
+ if (!mGraphicsStatsDir.exists()) {
+ throw new IllegalStateException("Graphics stats directory does not exist: "
+ + mGraphicsStatsDir.getAbsolutePath());
+ }
+ HandlerThread bgthread = new HandlerThread("GraphicsStats-disk",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ bgthread.start();
+
+ mWriteOutHandler = new Handler(bgthread.getLooper(), new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case SAVE_BUFFER:
+ saveBuffer((HistoricalBuffer) msg.obj);
+ break;
+ case DELETE_OLD:
+ deleteOldBuffers();
+ break;
+ }
+ return true;
+ }
+ });
+ nativeInit();
+ }
+
+ /**
+ * Current rotation policy is to rotate at midnight UTC. We don't specify RTC_WAKEUP because
+ * rotation can be delayed if there's otherwise no activity. However exact is used because
+ * we don't want the system to delay it by TOO much.
+ */
+ private void scheduleRotateLocked() {
+ if (mRotateIsScheduled) {
+ return;
+ }
+ mRotateIsScheduled = true;
+ Calendar calendar = normalizeDate(System.currentTimeMillis());
+ calendar.add(Calendar.DATE, 1);
+ mAlarmManager.setExact(AlarmManager.RTC, calendar.getTimeInMillis(), TAG, this::onAlarm,
+ mWriteOutHandler);
+ }
+
+ private void onAlarm() {
+ // We need to make a copy since some of the callbacks won't be proxy and thus
+ // can result in a re-entrant acquisition of mLock that would result in a modification
+ // of mActive during iteration.
+ ActiveBuffer[] activeCopy;
+ synchronized (mLock) {
+ mRotateIsScheduled = false;
+ scheduleRotateLocked();
+ activeCopy = mActive.toArray(new ActiveBuffer[0]);
+ }
+ for (ActiveBuffer active : activeCopy) {
+ try {
+ active.mCallback.onRotateGraphicsStatsBuffer();
+ } catch (RemoteException e) {
+ Log.w(TAG, String.format("Failed to notify '%s' (pid=%d) to rotate buffers",
+ active.mInfo.mPackageName, active.mPid), e);
+ }
+ }
+ // Give a few seconds for everyone to rotate before doing the cleanup
+ mWriteOutHandler.sendEmptyMessageDelayed(DELETE_OLD, 10000);
+ }
+
+ @Override
+ public ParcelFileDescriptor requestBufferForProcess(String packageName,
+ IGraphicsStatsCallback token) throws RemoteException {
+ int uid = Binder.getCallingUid();
+ int pid = Binder.getCallingPid();
+ ParcelFileDescriptor pfd = null;
+ long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ mAppOps.checkPackage(uid, packageName);
+ PackageInfo info = mContext.getPackageManager().getPackageInfoAsUser(
+ packageName,
+ 0,
+ UserHandle.getUserId(uid));
+ synchronized (mLock) {
+ pfd = requestBufferForProcessLocked(token, uid, pid, packageName,
+ info.getLongVersionCode());
+ }
+ } catch (PackageManager.NameNotFoundException ex) {
+ throw new RemoteException("Unable to find package: '" + packageName + "'");
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ return pfd;
+ }
+
+ // If lastFullDay is true, pullGraphicsStats returns stats for the last complete day/24h period
+ // that does not include today. If lastFullDay is false, pullGraphicsStats returns stats for the
+ // current day.
+ // This method is invoked from native code only.
+ @SuppressWarnings({"UnusedDeclaration"})
+ private void pullGraphicsStats(boolean lastFullDay, long pulledData) throws RemoteException {
+ int uid = Binder.getCallingUid();
+
+ // DUMP and PACKAGE_USAGE_STATS permissions are required to invoke this method.
+ // TODO: remove exception for statsd daemon after required permissions are granted. statsd
+ // TODO: should have these permissions granted by data/etc/platform.xml, but it does not.
+ if (uid != AID_STATSD) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new FastPrintWriter(sw);
+ if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) {
+ pw.flush();
+ throw new RemoteException(sw.toString());
+ }
+ }
+
+ long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ pullGraphicsStatsImpl(lastFullDay, pulledData);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ private void pullGraphicsStatsImpl(boolean lastFullDay, long pulledData) {
+ long targetDay;
+ if (lastFullDay) {
+ // Get stats from yesterday. Stats stay constant, because the day is over.
+ targetDay = normalizeDate(System.currentTimeMillis() - 86400000).getTimeInMillis();
+ } else {
+ // Get stats from today. Stats may change as more apps are run today.
+ targetDay = normalizeDate(System.currentTimeMillis()).getTimeInMillis();
+ }
+
+ // Find active buffers for targetDay.
+ ArrayList<HistoricalBuffer> buffers;
+ synchronized (mLock) {
+ buffers = new ArrayList<>(mActive.size());
+ for (int i = 0; i < mActive.size(); i++) {
+ ActiveBuffer buffer = mActive.get(i);
+ if (buffer.mInfo.mStartTime == targetDay) {
+ try {
+ buffers.add(new HistoricalBuffer(buffer));
+ } catch (IOException ex) {
+ // Ignore
+ }
+ }
+ }
+ }
+
+ // Dump active and historic buffers for targetDay in a serialized
+ // GraphicsStatsServiceDumpProto proto.
+ long dump = nCreateDump(-1, true);
+ try {
+ synchronized (mFileAccessLock) {
+ HashSet<File> skipList = dumpActiveLocked(dump, buffers);
+ buffers.clear();
+ String subPath = String.format("%d", targetDay);
+ File dateDir = new File(mGraphicsStatsDir, subPath);
+ if (dateDir.exists()) {
+ for (File pkg : dateDir.listFiles()) {
+ for (File version : pkg.listFiles()) {
+ File data = new File(version, "total");
+ if (skipList.contains(data)) {
+ continue;
+ }
+ nAddToDump(dump, data.getAbsolutePath());
+ }
+ }
+ }
+ }
+ } finally {
+ nFinishDumpInMemory(dump, pulledData, lastFullDay);
+ }
+ }
+
+ private ParcelFileDescriptor requestBufferForProcessLocked(IGraphicsStatsCallback token,
+ int uid, int pid, String packageName, long versionCode) throws RemoteException {
+ ActiveBuffer buffer = fetchActiveBuffersLocked(token, uid, pid, packageName, versionCode);
+ scheduleRotateLocked();
+ return buffer.getPfd();
+ }
+
+ private Calendar normalizeDate(long timestamp) {
+ Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+ calendar.setTimeInMillis(timestamp);
+ calendar.set(Calendar.HOUR_OF_DAY, 0);
+ calendar.set(Calendar.MINUTE, 0);
+ calendar.set(Calendar.SECOND, 0);
+ calendar.set(Calendar.MILLISECOND, 0);
+ return calendar;
+ }
+
+ private File pathForApp(BufferInfo info) {
+ String subPath = String.format("%d/%s/%d/total",
+ normalizeDate(info.mStartTime).getTimeInMillis(), info.mPackageName,
+ info.mVersionCode);
+ return new File(mGraphicsStatsDir, subPath);
+ }
+
+ private void saveBuffer(HistoricalBuffer buffer) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
+ "saving graphicsstats for " + buffer.mInfo.mPackageName);
+ }
+ synchronized (mFileAccessLock) {
+ File path = pathForApp(buffer.mInfo);
+ File parent = path.getParentFile();
+ parent.mkdirs();
+ if (!parent.exists()) {
+ Log.w(TAG, "Unable to create path: '" + parent.getAbsolutePath() + "'");
+ return;
+ }
+ nSaveBuffer(path.getAbsolutePath(), buffer.mInfo.mPackageName,
+ buffer.mInfo.mVersionCode, buffer.mInfo.mStartTime, buffer.mInfo.mEndTime,
+ buffer.mData);
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ }
+
+ private void deleteRecursiveLocked(File file) {
+ if (file.isDirectory()) {
+ for (File child : file.listFiles()) {
+ deleteRecursiveLocked(child);
+ }
+ }
+ if (!file.delete()) {
+ Log.w(TAG, "Failed to delete '" + file.getAbsolutePath() + "'!");
+ }
+ }
+
+ private void deleteOldBuffers() {
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "deleting old graphicsstats buffers");
+ synchronized (mFileAccessLock) {
+ File[] files = mGraphicsStatsDir.listFiles();
+ if (files == null || files.length <= 3) {
+ return;
+ }
+ long[] sortedDates = new long[files.length];
+ for (int i = 0; i < files.length; i++) {
+ try {
+ sortedDates[i] = Long.parseLong(files[i].getName());
+ } catch (NumberFormatException ex) {
+ // Skip unrecognized folders
+ }
+ }
+ if (sortedDates.length <= 3) {
+ return;
+ }
+ Arrays.sort(sortedDates);
+ for (int i = 0; i < sortedDates.length - 3; i++) {
+ deleteRecursiveLocked(new File(mGraphicsStatsDir, Long.toString(sortedDates[i])));
+ }
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ }
+
+ private void addToSaveQueue(ActiveBuffer buffer) {
+ try {
+ HistoricalBuffer data = new HistoricalBuffer(buffer);
+ Message.obtain(mWriteOutHandler, SAVE_BUFFER, data).sendToTarget();
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to copy graphicsstats from " + buffer.mInfo.mPackageName, e);
+ }
+ buffer.closeAllBuffers();
+ }
+
+ private void processDied(ActiveBuffer buffer) {
+ synchronized (mLock) {
+ mActive.remove(buffer);
+ }
+ addToSaveQueue(buffer);
+ }
+
+ private ActiveBuffer fetchActiveBuffersLocked(IGraphicsStatsCallback token, int uid, int pid,
+ String packageName, long versionCode) throws RemoteException {
+ int size = mActive.size();
+ long today = normalizeDate(System.currentTimeMillis()).getTimeInMillis();
+ for (int i = 0; i < size; i++) {
+ ActiveBuffer buffer = mActive.get(i);
+ if (buffer.mPid == pid
+ && buffer.mUid == uid) {
+ // If the buffer is too old we remove it and return a new one
+ if (buffer.mInfo.mStartTime < today) {
+ buffer.binderDied();
+ break;
+ } else {
+ return buffer;
+ }
+ }
+ }
+ // Didn't find one, need to create it
+ try {
+ ActiveBuffer buffers = new ActiveBuffer(token, uid, pid, packageName, versionCode);
+ mActive.add(buffers);
+ return buffers;
+ } catch (IOException ex) {
+ throw new RemoteException("Failed to allocate space");
+ }
+ }
+
+ private HashSet<File> dumpActiveLocked(long dump, ArrayList<HistoricalBuffer> buffers) {
+ HashSet<File> skipFiles = new HashSet<>(buffers.size());
+ for (int i = 0; i < buffers.size(); i++) {
+ HistoricalBuffer buffer = buffers.get(i);
+ File path = pathForApp(buffer.mInfo);
+ skipFiles.add(path);
+ nAddToDump(dump, path.getAbsolutePath(), buffer.mInfo.mPackageName,
+ buffer.mInfo.mVersionCode, buffer.mInfo.mStartTime, buffer.mInfo.mEndTime,
+ buffer.mData);
+ }
+ return skipFiles;
+ }
+
+ private void dumpHistoricalLocked(long dump, HashSet<File> skipFiles) {
+ for (File date : mGraphicsStatsDir.listFiles()) {
+ for (File pkg : date.listFiles()) {
+ for (File version : pkg.listFiles()) {
+ File data = new File(version, "total");
+ if (skipFiles.contains(data)) {
+ continue;
+ }
+ nAddToDump(dump, data.getAbsolutePath());
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+ if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, fout)) return;
+ boolean dumpProto = false;
+ for (String str : args) {
+ if ("--proto".equals(str)) {
+ dumpProto = true;
+ break;
+ }
+ }
+ ArrayList<HistoricalBuffer> buffers;
+ synchronized (mLock) {
+ buffers = new ArrayList<>(mActive.size());
+ for (int i = 0; i < mActive.size(); i++) {
+ try {
+ buffers.add(new HistoricalBuffer(mActive.get(i)));
+ } catch (IOException ex) {
+ // Ignore
+ }
+ }
+ }
+ long dump = nCreateDump(fd.getInt$(), dumpProto);
+ try {
+ synchronized (mFileAccessLock) {
+ HashSet<File> skipList = dumpActiveLocked(dump, buffers);
+ buffers.clear();
+ dumpHistoricalLocked(dump, skipList);
+ }
+ } finally {
+ nFinishDump(dump);
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ nativeDestructor();
+ }
+
+ private native void nativeInit();
+ private static native void nativeDestructor();
+
+ private static native int nGetAshmemSize();
+ private static native long nCreateDump(int outFd, boolean isProto);
+ private static native void nAddToDump(long dump, String path, String packageName,
+ long versionCode, long startTime, long endTime, byte[] data);
+ private static native void nAddToDump(long dump, String path);
+ private static native void nFinishDump(long dump);
+ private static native void nFinishDumpInMemory(long dump, long pulledData, boolean lastFullDay);
+ private static native void nSaveBuffer(String path, String packageName, long versionCode,
+ long startTime, long endTime, byte[] data);
+
+ private final class BufferInfo {
+ final String mPackageName;
+ final long mVersionCode;
+ long mStartTime;
+ long mEndTime;
+
+ BufferInfo(String packageName, long versionCode, long startTime) {
+ this.mPackageName = packageName;
+ this.mVersionCode = versionCode;
+ this.mStartTime = startTime;
+ }
+ }
+
+ private final class ActiveBuffer implements DeathRecipient {
+ final BufferInfo mInfo;
+ final int mUid;
+ final int mPid;
+ final IGraphicsStatsCallback mCallback;
+ final IBinder mToken;
+ SharedMemory mProcessBuffer;
+ ByteBuffer mMapping;
+
+ ActiveBuffer(IGraphicsStatsCallback token, int uid, int pid, String packageName,
+ long versionCode)
+ throws RemoteException, IOException {
+ mInfo = new BufferInfo(packageName, versionCode, System.currentTimeMillis());
+ mUid = uid;
+ mPid = pid;
+ mCallback = token;
+ mToken = mCallback.asBinder();
+ mToken.linkToDeath(this, 0);
+ try {
+ mProcessBuffer = SharedMemory.create("GFXStats-" + pid, mAshmemSize);
+ mMapping = mProcessBuffer.mapReadWrite();
+ } catch (ErrnoException ex) {
+ ex.rethrowAsIOException();
+ }
+ mMapping.position(0);
+ mMapping.put(mZeroData, 0, mAshmemSize);
+ }
+
+ @Override
+ public void binderDied() {
+ mToken.unlinkToDeath(this, 0);
+ processDied(this);
+ }
+
+ void closeAllBuffers() {
+ if (mMapping != null) {
+ SharedMemory.unmap(mMapping);
+ mMapping = null;
+ }
+ if (mProcessBuffer != null) {
+ mProcessBuffer.close();
+ mProcessBuffer = null;
+ }
+ }
+
+ ParcelFileDescriptor getPfd() {
+ try {
+ return mProcessBuffer.getFdDup();
+ } catch (IOException ex) {
+ throw new IllegalStateException("Failed to get PFD from memory file", ex);
+ }
+ }
+
+ void readBytes(byte[] buffer, int count) throws IOException {
+ if (mMapping == null) {
+ throw new IOException("SharedMemory has been deactivated");
+ }
+ mMapping.position(0);
+ mMapping.get(buffer, 0, count);
+ }
+ }
+
+ private final class HistoricalBuffer {
+ final BufferInfo mInfo;
+ final byte[] mData = new byte[mAshmemSize];
+ HistoricalBuffer(ActiveBuffer active) throws IOException {
+ mInfo = active.mInfo;
+ mInfo.mEndTime = System.currentTimeMillis();
+ active.readBytes(mData, mAshmemSize);
+ }
+ }
+}
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index b6b2d4e1c46a..d08bfcf45a5c 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -28,7 +28,6 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import android.util.TimeUtils;
-import android.view.FrameMetricsObserver;
import android.view.IGraphicsStats;
import android.view.IGraphicsStatsCallback;
import android.view.NativeVectorDrawableAnimator;
@@ -38,8 +37,6 @@ import android.view.SurfaceHolder;
import android.view.TextureLayer;
import android.view.animation.AnimationUtils;
-import com.android.internal.util.VirtualRefBasePtr;
-
import java.io.File;
import java.io.FileDescriptor;
import java.lang.annotation.Retention;
@@ -160,7 +157,7 @@ public class HardwareRenderer {
public HardwareRenderer() {
mRootNode = RenderNode.adopt(nCreateRootRenderNode());
mRootNode.setClipToBounds(false);
- mNativeProxy = nCreateProxy(!mOpaque, mRootNode.mNativeRenderNode);
+ mNativeProxy = nCreateProxy(!mOpaque, mIsWideGamut, mRootNode.mNativeRenderNode);
if (mNativeProxy == 0) {
throw new OutOfMemoryError("Unable to create hardware renderer");
}
@@ -286,10 +283,24 @@ public class HardwareRenderer {
* non-null then {@link Surface#isValid()} must be true.
*/
public void setSurface(@Nullable Surface surface) {
+ setSurface(surface, false);
+ }
+
+ /**
+ * See {@link #setSurface(Surface)}
+ *
+ * @hide
+ * @param discardBuffer determines whether the surface will attempt to preserve its contents
+ * between frames. If set to true the renderer will attempt to preserve
+ * the contents of the buffer between frames if the implementation allows
+ * it. If set to false no attempt will be made to preserve the buffer's
+ * contents between frames.
+ */
+ public void setSurface(@Nullable Surface surface, boolean discardBuffer) {
if (surface != null && !surface.isValid()) {
throw new IllegalArgumentException("Surface is invalid. surface.isValid() == false.");
}
- nSetSurface(mNativeProxy, surface);
+ nSetSurface(mNativeProxy, surface, discardBuffer);
}
/**
@@ -584,9 +595,8 @@ public class HardwareRenderer {
*
* @hide
*/
- public void addFrameMetricsObserver(FrameMetricsObserver observer) {
- long nativeObserver = nAddFrameMetricsObserver(mNativeProxy, observer);
- observer.mNative = new VirtualRefBasePtr(nativeObserver);
+ public void addObserver(HardwareRendererObserver observer) {
+ nAddObserver(mNativeProxy, observer.getNativeInstance());
}
/**
@@ -594,9 +604,8 @@ public class HardwareRenderer {
*
* @hide
*/
- public void removeFrameMetricsObserver(FrameMetricsObserver observer) {
- nRemoveFrameMetricsObserver(mNativeProxy, observer.mNative.get());
- observer.mNative = null;
+ public void removeObserver(HardwareRendererObserver observer) {
+ nRemoveObserver(mNativeProxy, observer.getNativeInstance());
}
/**
@@ -1076,7 +1085,8 @@ public class HardwareRenderer {
private static native long nCreateRootRenderNode();
- private static native long nCreateProxy(boolean translucent, long rootRenderNode);
+ private static native long nCreateProxy(boolean translucent, boolean isWideGamut,
+ long rootRenderNode);
private static native void nDeleteProxy(long nativeProxy);
@@ -1084,7 +1094,7 @@ public class HardwareRenderer {
private static native void nSetName(long nativeProxy, String name);
- private static native void nSetSurface(long nativeProxy, Surface window);
+ private static native void nSetSurface(long nativeProxy, Surface window, boolean discardBuffer);
private static native boolean nPause(long nativeProxy);
@@ -1156,10 +1166,9 @@ public class HardwareRenderer {
private static native void nSetFrameCompleteCallback(long nativeProxy,
FrameCompleteCallback callback);
- private static native long nAddFrameMetricsObserver(long nativeProxy,
- FrameMetricsObserver observer);
+ private static native void nAddObserver(long nativeProxy, long nativeObserver);
- private static native void nRemoveFrameMetricsObserver(long nativeProxy, long nativeObserver);
+ private static native void nRemoveObserver(long nativeProxy, long nativeObserver);
private static native int nCopySurfaceInto(Surface surface,
int srcLeft, int srcTop, int srcRight, int srcBottom, long bitmapHandle);
diff --git a/graphics/java/android/graphics/HardwareRendererObserver.java b/graphics/java/android/graphics/HardwareRendererObserver.java
new file mode 100644
index 000000000000..da9d03c639f7
--- /dev/null
+++ b/graphics/java/android/graphics/HardwareRendererObserver.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+
+import com.android.internal.util.VirtualRefBasePtr;
+
+/**
+ * Provides streaming access to frame stats information from HardwareRenderer to apps.
+ *
+ * @hide
+ */
+public class HardwareRendererObserver {
+ private final long[] mFrameMetrics;
+ private final Handler mHandler;
+ private final OnFrameMetricsAvailableListener mListener;
+ private VirtualRefBasePtr mNativePtr;
+
+ /**
+ * Interface for clients that want frame timing information for each frame rendered.
+ * @hide
+ */
+ public interface OnFrameMetricsAvailableListener {
+ /**
+ * Called when information is available for the previously rendered frame.
+ *
+ * Reports can be dropped if this callback takes too long to execute, as the report producer
+ * cannot wait for the consumer to complete.
+ *
+ * It is highly recommended that clients copy the metrics array within this method
+ * and defer additional computation or storage to another thread to avoid unnecessarily
+ * dropping reports.
+ *
+ * @param dropCountSinceLastInvocation the number of reports dropped since the last time
+ * this callback was invoked.
+ */
+ void onFrameMetricsAvailable(int dropCountSinceLastInvocation);
+ }
+
+ /**
+ * Creates a FrameMetricsObserver
+ *
+ * @param frameMetrics the available metrics. This array is reused on every call to the listener
+ * and thus <strong>this reference should only be used within the scope of the listener callback
+ * as data is not guaranteed to be valid outside the scope of that method</strong>.
+ * @param handler the Handler to use when invoking callbacks
+ */
+ public HardwareRendererObserver(@NonNull OnFrameMetricsAvailableListener listener,
+ @NonNull long[] frameMetrics, @NonNull Handler handler) {
+ if (handler == null || handler.getLooper() == null) {
+ throw new NullPointerException("handler and its looper cannot be null");
+ }
+
+ if (handler.getLooper().getQueue() == null) {
+ throw new IllegalStateException("invalid looper, null message queue\n");
+ }
+
+ mFrameMetrics = frameMetrics;
+ mHandler = handler;
+ mListener = listener;
+ mNativePtr = new VirtualRefBasePtr(nCreateObserver());
+ }
+
+ /*package*/ long getNativeInstance() {
+ return mNativePtr.get();
+ }
+
+ // Called by native on the provided Handler
+ @SuppressWarnings("unused")
+ private void notifyDataAvailable() {
+ mHandler.post(() -> {
+ boolean hasMoreData = true;
+ while (hasMoreData) {
+ // a drop count of -1 is a sentinel that no more buffers are available
+ int dropCount = nGetNextBuffer(mNativePtr.get(), mFrameMetrics);
+ if (dropCount >= 0) {
+ mListener.onFrameMetricsAvailable(dropCount);
+ } else {
+ hasMoreData = false;
+ }
+ }
+ });
+ }
+
+ private native long nCreateObserver();
+ private static native int nGetNextBuffer(long nativePtr, long[] data);
+}
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index dbdb6971c6c5..c8f065ad094c 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -70,9 +70,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
* {@link Bitmap} objects.
*
* <p>To use it, first create a {@link Source Source} using one of the
- * {@code createSource} overloads. For example, to decode from a {@link File}, call
- * {@link #createSource(File)} and pass the result to {@link #decodeDrawable(Source)}
- * or {@link #decodeBitmap(Source)}:
+ * {@code createSource} overloads. For example, to decode from a {@link Uri}, call
+ * {@link #createSource(ContentResolver, Uri)} and pass the result to
+ * {@link #decodeDrawable(Source)} or {@link #decodeBitmap(Source)}:
*
* <pre class="prettyprint">
* File file = new File(...);
@@ -214,7 +214,7 @@ public final class ImageDecoder implements AutoCloseable {
/* @hide */
@NonNull
- abstract ImageDecoder createImageDecoder() throws IOException;
+ abstract ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException;
};
private static class ByteArraySource extends Source {
@@ -228,8 +228,8 @@ public final class ImageDecoder implements AutoCloseable {
private final int mLength;
@Override
- public ImageDecoder createImageDecoder() throws IOException {
- return nCreate(mData, mOffset, mLength, this);
+ public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException {
+ return nCreate(mData, mOffset, mLength, preferAnimation, this);
}
}
@@ -240,14 +240,14 @@ public final class ImageDecoder implements AutoCloseable {
private final ByteBuffer mBuffer;
@Override
- public ImageDecoder createImageDecoder() throws IOException {
+ public ImageDecoder createImageDecoder(boolean preferAnimation) 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, this);
+ return nCreate(mBuffer.array(), offset, length, preferAnimation, this);
}
ByteBuffer buffer = mBuffer.slice();
- return nCreate(buffer, buffer.position(), buffer.limit(), this);
+ return nCreate(buffer, buffer.position(), buffer.limit(), preferAnimation, this);
}
}
@@ -267,16 +267,20 @@ public final class ImageDecoder implements AutoCloseable {
Resources getResources() { return mResources; }
@Override
- public ImageDecoder createImageDecoder() throws IOException {
+ public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException {
AssetFileDescriptor assetFd = null;
try {
- if (mUri.getScheme() == ContentResolver.SCHEME_CONTENT) {
+ if (ContentResolver.SCHEME_CONTENT.equals(mUri.getScheme())) {
assetFd = mResolver.openTypedAssetFileDescriptor(mUri,
"image/*", null);
} else {
assetFd = mResolver.openAssetFileDescriptor(mUri, "r");
}
} catch (FileNotFoundException e) {
+ // Handled below, along with the case where assetFd was set to null.
+ }
+
+ if (assetFd == null) {
// Some images cannot be opened as AssetFileDescriptors (e.g.
// bmp, ico). Open them as InputStreams.
InputStream is = mResolver.openInputStream(mUri);
@@ -284,26 +288,27 @@ public final class ImageDecoder implements AutoCloseable {
throw new FileNotFoundException(mUri.toString());
}
- return createFromStream(is, true, this);
+ return createFromStream(is, true, preferAnimation, this);
}
- return createFromAssetFileDescriptor(assetFd, this);
+
+ return createFromAssetFileDescriptor(assetFd, preferAnimation, this);
}
}
@NonNull
private static ImageDecoder createFromFile(@NonNull File file,
- @NonNull Source source) throws IOException {
+ boolean preferAnimation, @NonNull Source source) throws IOException {
FileInputStream stream = new FileInputStream(file);
FileDescriptor fd = stream.getFD();
try {
Os.lseek(fd, 0, SEEK_CUR);
} catch (ErrnoException e) {
- return createFromStream(stream, true, source);
+ return createFromStream(stream, true, preferAnimation, source);
}
ImageDecoder decoder = null;
try {
- decoder = nCreate(fd, source);
+ decoder = nCreate(fd, preferAnimation, source);
} finally {
if (decoder == null) {
IoUtils.closeQuietly(stream);
@@ -317,12 +322,12 @@ public final class ImageDecoder implements AutoCloseable {
@NonNull
private static ImageDecoder createFromStream(@NonNull InputStream is,
- boolean closeInputStream, Source source) throws IOException {
+ boolean closeInputStream, boolean preferAnimation, Source source) throws IOException {
// Arbitrary size matches BitmapFactory.
byte[] storage = new byte[16 * 1024];
ImageDecoder decoder = null;
try {
- decoder = nCreate(is, storage, source);
+ decoder = nCreate(is, storage, preferAnimation, source);
} finally {
if (decoder == null) {
if (closeInputStream) {
@@ -340,7 +345,10 @@ public final class ImageDecoder implements AutoCloseable {
@NonNull
private static ImageDecoder createFromAssetFileDescriptor(@NonNull AssetFileDescriptor assetFd,
- Source source) throws IOException {
+ boolean preferAnimation, Source source) throws IOException {
+ if (assetFd == null) {
+ throw new FileNotFoundException();
+ }
final FileDescriptor fd = assetFd.getFileDescriptor();
final long offset = assetFd.getStartOffset();
@@ -348,9 +356,9 @@ public final class ImageDecoder implements AutoCloseable {
try {
try {
Os.lseek(fd, offset, SEEK_SET);
- decoder = nCreate(fd, source);
+ decoder = nCreate(fd, preferAnimation, source);
} catch (ErrnoException e) {
- decoder = createFromStream(new FileInputStream(fd), true, source);
+ decoder = createFromStream(new FileInputStream(fd), true, preferAnimation, source);
}
} finally {
if (decoder == null) {
@@ -388,7 +396,7 @@ public final class ImageDecoder implements AutoCloseable {
public int getDensity() { return mInputDensity; }
@Override
- public ImageDecoder createImageDecoder() throws IOException {
+ public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException {
synchronized (this) {
if (mInputStream == null) {
@@ -396,7 +404,7 @@ public final class ImageDecoder implements AutoCloseable {
}
InputStream is = mInputStream;
mInputStream = null;
- return createFromStream(is, false, this);
+ return createFromStream(is, false, preferAnimation, this);
}
}
}
@@ -434,14 +442,14 @@ public final class ImageDecoder implements AutoCloseable {
}
@Override
- public ImageDecoder createImageDecoder() throws IOException {
+ public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException {
synchronized (this) {
if (mAssetInputStream == null) {
throw new IOException("Cannot reuse AssetInputStreamSource");
}
AssetInputStream ais = mAssetInputStream;
mAssetInputStream = null;
- return createFromAsset(ais, this);
+ return createFromAsset(ais, preferAnimation, this);
}
}
}
@@ -469,7 +477,7 @@ public final class ImageDecoder implements AutoCloseable {
}
@Override
- public ImageDecoder createImageDecoder() throws IOException {
+ public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException {
TypedValue value = new TypedValue();
// This is just used in order to access the underlying Asset and
// keep it alive.
@@ -483,7 +491,7 @@ public final class ImageDecoder implements AutoCloseable {
}
}
- return createFromAsset((AssetInputStream) is, this);
+ return createFromAsset((AssetInputStream) is, preferAnimation, this);
}
}
@@ -491,11 +499,11 @@ public final class ImageDecoder implements AutoCloseable {
* ImageDecoder will own the AssetInputStream.
*/
private static ImageDecoder createFromAsset(AssetInputStream ais,
- Source source) throws IOException {
+ boolean preferAnimation, Source source) throws IOException {
ImageDecoder decoder = null;
try {
long asset = ais.getNativeAsset();
- decoder = nCreate(asset, source);
+ decoder = nCreate(asset, preferAnimation, source);
} finally {
if (decoder == null) {
IoUtils.closeQuietly(ais);
@@ -517,9 +525,9 @@ public final class ImageDecoder implements AutoCloseable {
private final String mFileName;
@Override
- public ImageDecoder createImageDecoder() throws IOException {
+ public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException {
InputStream is = mAssets.open(mFileName);
- return createFromAsset((AssetInputStream) is, this);
+ return createFromAsset((AssetInputStream) is, preferAnimation, this);
}
}
@@ -531,8 +539,8 @@ public final class ImageDecoder implements AutoCloseable {
private final File mFile;
@Override
- public ImageDecoder createImageDecoder() throws IOException {
- return createFromFile(mFile, this);
+ public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException {
+ return createFromFile(mFile, preferAnimation, this);
}
}
@@ -544,7 +552,7 @@ public final class ImageDecoder implements AutoCloseable {
private final Callable<AssetFileDescriptor> mCallable;
@Override
- public ImageDecoder createImageDecoder() throws IOException {
+ public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException {
AssetFileDescriptor assetFd = null;
try {
assetFd = mCallable.call();
@@ -555,7 +563,7 @@ public final class ImageDecoder implements AutoCloseable {
throw new IOException(e);
}
}
- return createFromAssetFileDescriptor(assetFd, this);
+ return createFromAssetFileDescriptor(assetFd, preferAnimation, this);
}
}
@@ -1024,7 +1032,11 @@ public final class ImageDecoder implements AutoCloseable {
/**
* Create a new {@link Source Source} from a {@link java.io.File}.
- *
+ * <p>
+ * This method should only be used for files that you have direct access to;
+ * if you'd like to work with files hosted outside your app, use an API like
+ * {@link #createSource(Callable)} or
+ * {@link #createSource(ContentResolver, Uri)}.
* @return a new Source object, which can be passed to
* {@link #decodeDrawable decodeDrawable} or
* {@link #decodeBitmap decodeBitmap}.
@@ -1669,6 +1681,9 @@ public final class ImageDecoder implements AutoCloseable {
if (r == null) {
return;
}
+ if (r.width() <= 0 || r.height() <= 0) {
+ throw new IllegalStateException("Subset " + r + " is empty/unsorted");
+ }
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 + ")");
@@ -1740,7 +1755,7 @@ public final class ImageDecoder implements AutoCloseable {
@NonNull
private static Drawable decodeDrawableImpl(@NonNull Source src,
@Nullable OnHeaderDecodedListener listener) throws IOException {
- try (ImageDecoder decoder = src.createImageDecoder()) {
+ try (ImageDecoder decoder = src.createImageDecoder(true /*preferAnimation*/)) {
decoder.mSource = src;
decoder.callHeaderDecoded(listener, src);
@@ -1844,7 +1859,7 @@ public final class ImageDecoder implements AutoCloseable {
@NonNull
private static Bitmap decodeBitmapImpl(@NonNull Source src,
@Nullable OnHeaderDecodedListener listener) throws IOException {
- try (ImageDecoder decoder = src.createImageDecoder()) {
+ try (ImageDecoder decoder = src.createImageDecoder(false /*preferAnimation*/)) {
decoder.mSource = src;
decoder.callHeaderDecoded(listener, src);
@@ -1971,15 +1986,17 @@ public final class ImageDecoder implements AutoCloseable {
}
}
- private static native ImageDecoder nCreate(long asset, Source src) throws IOException;
- private static native ImageDecoder nCreate(ByteBuffer buffer, int position,
- int limit, Source src) throws IOException;
+ private static native ImageDecoder nCreate(long asset,
+ boolean preferAnimation, Source src) throws IOException;
+ private static native ImageDecoder nCreate(ByteBuffer buffer, int position, int limit,
+ boolean preferAnimation, Source src) throws IOException;
private static native ImageDecoder nCreate(byte[] data, int offset, int length,
- Source src) throws IOException;
+ boolean preferAnimation, Source src) throws IOException;
private static native ImageDecoder nCreate(InputStream is, byte[] storage,
- Source src) throws IOException;
+ boolean preferAnimation, Source src) throws IOException;
// The fd must be seekable.
- private static native ImageDecoder nCreate(FileDescriptor fd, Source src) throws IOException;
+ private static native ImageDecoder nCreate(FileDescriptor fd,
+ boolean preferAnimation, Source src) throws IOException;
@NonNull
private static native Bitmap nDecodeBitmap(long nativePtr,
@NonNull ImageDecoder decoder,
diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java
index 91a60c327bf0..5858e3988486 100644
--- a/graphics/java/android/graphics/Outline.java
+++ b/graphics/java/android/graphics/Outline.java
@@ -43,7 +43,7 @@ public final class Outline {
/** @hide */
public static final int MODE_ROUND_RECT = 1;
/** @hide */
- public static final int MODE_CONVEX_PATH = 2;
+ public static final int MODE_PATH = 2;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -51,7 +51,7 @@ public final class Outline {
value = {
MODE_EMPTY,
MODE_ROUND_RECT,
- MODE_CONVEX_PATH,
+ MODE_PATH,
})
public @interface Mode {}
@@ -60,7 +60,7 @@ public final class Outline {
public int mMode = MODE_EMPTY;
/**
- * Only guaranteed to be non-null when mode == MODE_CONVEX_PATH
+ * Only guaranteed to be non-null when mode == MODE_PATH
*
* @hide
*/
@@ -124,7 +124,7 @@ public final class Outline {
* @see android.view.View#setClipToOutline(boolean)
*/
public boolean canClip() {
- return mMode != MODE_CONVEX_PATH;
+ return mMode != MODE_PATH;
}
/**
@@ -157,7 +157,7 @@ public final class Outline {
*/
public void set(@NonNull Outline src) {
mMode = src.mMode;
- if (src.mMode == MODE_CONVEX_PATH) {
+ if (src.mMode == MODE_PATH) {
if (mPath == null) {
mPath = new Path();
}
@@ -194,7 +194,7 @@ public final class Outline {
return;
}
- if (mMode == MODE_CONVEX_PATH) {
+ if (mMode == MODE_PATH) {
// rewind here to avoid thrashing the allocations, but could alternately clear ref
mPath.rewind();
}
@@ -213,7 +213,7 @@ public final class Outline {
/**
* Populates {@code outBounds} with the outline bounds, if set, and returns
* {@code true}. If no outline bounds are set, or if a path has been set
- * via {@link #setConvexPath(Path)}, returns {@code false}.
+ * via {@link #setPath(Path)}, returns {@code false}.
*
* @param outRect the rect to populate with the outline bounds, if set
* @return {@code true} if {@code outBounds} was populated with outline
@@ -229,7 +229,7 @@ public final class Outline {
/**
* Returns the rounded rect radius, if set, or a value less than 0 if a path has
- * been set via {@link #setConvexPath(Path)}. A return value of {@code 0}
+ * been set via {@link #setPath(Path)}. A return value of {@code 0}
* indicates a non-rounded rect.
*
* @return the rounded rect radius, or value < 0
@@ -259,7 +259,7 @@ public final class Outline {
mPath.rewind();
}
- mMode = MODE_CONVEX_PATH;
+ mMode = MODE_PATH;
mPath.addOval(left, top, right, bottom, Path.Direction.CW);
mRect.setEmpty();
mRadius = RADIUS_UNDEFINED;
@@ -279,9 +279,24 @@ public final class Outline {
* @param convexPath used to construct the Outline. As of
* {@link android.os.Build.VERSION_CODES#Q}, it is no longer required to be
* convex.
+ *
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#Q}, the restriction
+ * that the path must be convex is removed. However, the API is misnamed until
+ * {@link android.os.Build.VERSION_CODES#R}, when {@link #setPath} is
+ * introduced. Use {@link #setPath} instead.
*/
+ @Deprecated
public void setConvexPath(@NonNull Path convexPath) {
- if (convexPath.isEmpty()) {
+ setPath(convexPath);
+ }
+
+ /**
+ * Sets the Outline to a {@link android.graphics.Path path}.
+ *
+ * @param path used to construct the Outline.
+ */
+ public void setPath(@NonNull Path path) {
+ if (path.isEmpty()) {
setEmpty();
return;
}
@@ -290,8 +305,8 @@ public final class Outline {
mPath = new Path();
}
- mMode = MODE_CONVEX_PATH;
- mPath.set(convexPath);
+ mMode = MODE_PATH;
+ mPath.set(path);
mRect.setEmpty();
mRadius = RADIUS_UNDEFINED;
}
@@ -302,7 +317,7 @@ public final class Outline {
public void offset(int dx, int dy) {
if (mMode == MODE_ROUND_RECT) {
mRect.offset(dx, dy);
- } else if (mMode == MODE_CONVEX_PATH) {
+ } else if (mMode == MODE_PATH) {
mPath.offset(dx, dy);
}
}
diff --git a/graphics/java/android/graphics/ParcelableColorSpace.java b/graphics/java/android/graphics/ParcelableColorSpace.java
new file mode 100644
index 000000000000..f9033a53d7e6
--- /dev/null
+++ b/graphics/java/android/graphics/ParcelableColorSpace.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A {@link Parcelable} {@link ColorSpace}. In order to enable parceling, the ColorSpace
+ * must be either a {@link ColorSpace.Named Named} ColorSpace or a {@link ColorSpace.Rgb} instance
+ * that has an ICC parametric transfer function as returned by {@link Rgb#getTransferParameters()}.
+ * TODO: Make public
+ * @hide
+ */
+public final class ParcelableColorSpace extends ColorSpace implements Parcelable {
+ private final ColorSpace mColorSpace;
+
+ /**
+ * Checks if the given ColorSpace is able to be parceled. A ColorSpace can only be
+ * parceled if it is a {@link ColorSpace.Named Named} ColorSpace or a {@link ColorSpace.Rgb}
+ * instance that has an ICC parametric transfer function as returned by
+ * {@link Rgb#getTransferParameters()}
+ */
+ public static boolean isParcelable(@NonNull ColorSpace colorSpace) {
+ if (colorSpace.getId() == ColorSpace.MIN_ID) {
+ if (!(colorSpace instanceof ColorSpace.Rgb)) {
+ return false;
+ }
+ ColorSpace.Rgb rgb = (ColorSpace.Rgb) colorSpace;
+ if (rgb.getTransferParameters() == null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Constructs a new ParcelableColorSpace that wraps the provided ColorSpace.
+ *
+ * @param colorSpace The ColorSpace to wrap. The ColorSpace must be either named or be an
+ * RGB ColorSpace with an ICC parametric transfer function.
+ * @throws IllegalArgumentException If the provided ColorSpace does not satisfy the requirements
+ * to be parceled. See {@link #isParcelable(ColorSpace)}.
+ */
+ public ParcelableColorSpace(@NonNull ColorSpace colorSpace) {
+ super(colorSpace.getName(), colorSpace.getModel(), colorSpace.getId());
+ mColorSpace = colorSpace;
+
+ if (mColorSpace.getId() == ColorSpace.MIN_ID) {
+ if (!(mColorSpace instanceof ColorSpace.Rgb)) {
+ throw new IllegalArgumentException(
+ "Unable to parcel unknown ColorSpaces that are not ColorSpace.Rgb");
+ }
+ ColorSpace.Rgb rgb = (ColorSpace.Rgb) mColorSpace;
+ if (rgb.getTransferParameters() == null) {
+ throw new IllegalArgumentException("ColorSpace must use an ICC "
+ + "parametric transfer function to be parcelable");
+ }
+ }
+ }
+
+ public @NonNull ColorSpace getColorSpace() {
+ return mColorSpace;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ final int id = mColorSpace.getId();
+ dest.writeInt(id);
+ if (id == ColorSpace.MIN_ID) {
+ // Not a named color space. We have to actually write, like, stuff. And things. Ugh.
+ // Cast is safe because this was asserted in the constructor
+ ColorSpace.Rgb rgb = (ColorSpace.Rgb) mColorSpace;
+ dest.writeString(rgb.getName());
+ dest.writeFloatArray(rgb.getPrimaries());
+ dest.writeFloatArray(rgb.getWhitePoint());
+ ColorSpace.Rgb.TransferParameters transferParameters = rgb.getTransferParameters();
+ dest.writeDouble(transferParameters.a);
+ dest.writeDouble(transferParameters.b);
+ dest.writeDouble(transferParameters.c);
+ dest.writeDouble(transferParameters.d);
+ dest.writeDouble(transferParameters.e);
+ dest.writeDouble(transferParameters.f);
+ dest.writeDouble(transferParameters.g);
+ }
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<ParcelableColorSpace> CREATOR =
+ new Parcelable.Creator<ParcelableColorSpace>() {
+
+ public @NonNull ParcelableColorSpace createFromParcel(@NonNull Parcel in) {
+ final int id = in.readInt();
+ if (id == ColorSpace.MIN_ID) {
+ String name = in.readString();
+ float[] primaries = in.createFloatArray();
+ float[] whitePoint = in.createFloatArray();
+ double a = in.readDouble();
+ double b = in.readDouble();
+ double c = in.readDouble();
+ double d = in.readDouble();
+ double e = in.readDouble();
+ double f = in.readDouble();
+ double g = in.readDouble();
+ ColorSpace.Rgb.TransferParameters function =
+ new ColorSpace.Rgb.TransferParameters(a, b, c, d, e, f, g);
+ return new ParcelableColorSpace(
+ new ColorSpace.Rgb(name, primaries, whitePoint, function));
+ } else {
+ return new ParcelableColorSpace(ColorSpace.get(id));
+ }
+ }
+
+ public ParcelableColorSpace[] newArray(int size) {
+ return new ParcelableColorSpace[size];
+ }
+ };
+
+ @Override
+ public boolean isWideGamut() {
+ return mColorSpace.isWideGamut();
+ }
+
+ @Override
+ public float getMinValue(int component) {
+ return mColorSpace.getMinValue(component);
+ }
+
+ @Override
+ public float getMaxValue(int component) {
+ return mColorSpace.getMaxValue(component);
+ }
+
+ @Override
+ public @NonNull float[] toXyz(@NonNull float[] v) {
+ return mColorSpace.toXyz(v);
+ }
+
+ @Override
+ public @NonNull float[] fromXyz(@NonNull float[] v) {
+ return mColorSpace.fromXyz(v);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ParcelableColorSpace other = (ParcelableColorSpace) o;
+ return mColorSpace.equals(other.mColorSpace);
+ }
+
+ @Override
+ public int hashCode() {
+ return mColorSpace.hashCode();
+ }
+
+ /** @hide */
+ @Override
+ long getNativeInstance() {
+ return mColorSpace.getNativeInstance();
+ }
+}
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index 1362fd864d29..84fe39290681 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -209,7 +209,13 @@ public class Path {
* points, and cache the result.
*
* @return True if the path is convex.
+ *
+ * @deprecated This method is not reliable. The way convexity is computed may change from
+ * release to release, and convexity could change based on a matrix as well. This method was
+ * useful when non-convex Paths were unable to be used in certain contexts, but that is no
+ * longer the case.
*/
+ @Deprecated
public boolean isConvex() {
return nIsConvex(mNativePath);
}
diff --git a/graphics/java/android/graphics/Point.java b/graphics/java/android/graphics/Point.java
index 3614f3bcb3be..9f71a0fedd05 100644
--- a/graphics/java/android/graphics/Point.java
+++ b/graphics/java/android/graphics/Point.java
@@ -132,7 +132,7 @@ public class Point implements Parcelable {
* @param fieldId Field Id of the Rect as defined in the parent message
* @hide
*/
- public void writeToProto(@NonNull ProtoOutputStream protoOutputStream, long fieldId) {
+ public void dumpDebug(@NonNull ProtoOutputStream protoOutputStream, long fieldId) {
final long token = protoOutputStream.start(fieldId);
protoOutputStream.write(PointProto.X, x);
protoOutputStream.write(PointProto.Y, y);
diff --git a/graphics/java/android/graphics/PointF.java b/graphics/java/android/graphics/PointF.java
index f1f48adc7018..ed9df145f880 100644
--- a/graphics/java/android/graphics/PointF.java
+++ b/graphics/java/android/graphics/PointF.java
@@ -38,6 +38,18 @@ public class PointF implements Parcelable {
this.x = p.x;
this.y = p.y;
}
+
+ /**
+ * Create a new PointF initialized with the values in the specified
+ * PointF (which is left unmodified).
+ *
+ * @param p The point whose values are copied into the new
+ * point.
+ */
+ public PointF(@NonNull PointF p) {
+ this.x = p.x;
+ this.y = p.y;
+ }
/**
* Set the point's x and y coordinates
diff --git a/graphics/java/android/graphics/PorterDuff.java b/graphics/java/android/graphics/PorterDuff.java
index 1275cb9ca4f9..eb940e2f9017 100644
--- a/graphics/java/android/graphics/PorterDuff.java
+++ b/graphics/java/android/graphics/PorterDuff.java
@@ -30,8 +30,6 @@ public class PorterDuff {
/**
* {@usesMathJax}
*
- * <h3>Porter-Duff</h3>
- *
* <p>The name of the parent class is an homage to the work of Thomas Porter and
* Tom Duff, presented in their seminal 1984 paper titled "Compositing Digital Images".
* In this paper, the authors describe 12 compositing operators that govern how to
diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java
index 270725ab9805..081b851d1333 100644
--- a/graphics/java/android/graphics/Rect.java
+++ b/graphics/java/android/graphics/Rect.java
@@ -239,7 +239,7 @@ public final class Rect implements Parcelable {
* @param fieldId Field Id of the Rect as defined in the parent message
* @hide
*/
- public void writeToProto(@NonNull ProtoOutputStream protoOutputStream, long fieldId) {
+ public void dumpDebug(@NonNull ProtoOutputStream protoOutputStream, long fieldId) {
final long token = protoOutputStream.start(fieldId);
protoOutputStream.write(RectProto.LEFT, left);
protoOutputStream.write(RectProto.TOP, top);
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index c3c56dbbc6aa..22f5489be3b1 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -22,8 +22,8 @@ import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.animation.RenderNodeAnimator;
import android.view.NativeVectorDrawableAnimator;
-import android.view.RenderNodeAnimator;
import android.view.Surface;
import android.view.View;
@@ -63,7 +63,7 @@ import java.lang.annotation.RetentionPolicy;
* <h3>Creating a RenderNode</h3>
* <pre class="prettyprint">
* RenderNode renderNode = new RenderNode("myRenderNode");
- * renderNode.setLeftTopRightBottom(0, 0, 50, 50); // Set the size to 50x50
+ * renderNode.setPosition(0, 0, 50, 50); // Set the size to 50x50
* RecordingCanvas canvas = renderNode.beginRecording();
* try {
* // Draw with the canvas
@@ -104,7 +104,7 @@ import java.lang.annotation.RetentionPolicy;
* <pre class="prettyprint">
* private void createDisplayList() {
* mRenderNode = new RenderNode("MyRenderNode");
- * mRenderNode.setLeftTopRightBottom(0, 0, width, height);
+ * mRenderNode.setPosition(0, 0, width, height);
* RecordingCanvas canvas = mRenderNode.beginRecording();
* try {
* for (Bitmap b : mBitmaps) {
@@ -687,8 +687,8 @@ public final class RenderNode {
outline.mRect.left, outline.mRect.top,
outline.mRect.right, outline.mRect.bottom,
outline.mRadius, outline.mAlpha);
- case Outline.MODE_CONVEX_PATH:
- return nSetOutlineConvexPath(mNativeRenderNode, outline.mPath.mNativePath,
+ case Outline.MODE_PATH:
+ return nSetOutlinePath(mNativeRenderNode, outline.mPath.mNativePath,
outline.mAlpha);
}
@@ -1380,7 +1380,22 @@ public final class RenderNode {
* @return Approximate memory usage in bytes.
*/
public @BytesLong long computeApproximateMemoryUsage() {
- return nGetDebugSize(mNativeRenderNode);
+ return nGetUsageSize(mNativeRenderNode);
+ }
+
+ /**
+ * Gets the approximate amount of memory allocated for the RenderNode for debug purposes.
+ * Does not include the memory allocated by any child RenderNodes nor any bitmaps, only the
+ * memory allocated for this RenderNode and any data it owns.
+ *
+ * The difference between this and {@link #computeApproximateMemoryUsage()} is this includes
+ * memory allocated but not used. In particular structures such as DisplayLists are similar
+ * to things like ArrayLists - they need to resize as commands are added to them. As such,
+ * memory used can be less than memory allocated.
+ *
+ * @hide */
+ public @BytesLong long computeApproximateMemoryAllocated() {
+ return nGetAllocatedSize(mNativeRenderNode);
}
/**
@@ -1485,7 +1500,8 @@ public final class RenderNode {
private static native void nOutput(long renderNode);
- private static native int nGetDebugSize(long renderNode);
+ private static native int nGetUsageSize(long renderNode);
+ private static native int nGetAllocatedSize(long renderNode);
private static native void nRequestPositionUpdates(long renderNode,
PositionUpdateListener callback);
@@ -1604,7 +1620,7 @@ public final class RenderNode {
int right, int bottom, float radius, float alpha);
@CriticalNative
- private static native boolean nSetOutlineConvexPath(long renderNode, long nativePath,
+ private static native boolean nSetOutlinePath(long renderNode, long nativePath,
float alpha);
@CriticalNative
diff --git a/graphics/java/android/graphics/RuntimeShader.java b/graphics/java/android/graphics/RuntimeShader.java
new file mode 100644
index 000000000000..5a3f2a96e31d
--- /dev/null
+++ b/graphics/java/android/graphics/RuntimeShader.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import libcore.util.NativeAllocationRegistry;
+
+/**
+ * Shader that calculates pixel output with a program (fragment shader) running on a GPU.
+ * @hide
+ */
+public class RuntimeShader extends Shader {
+
+ private static class NoImagePreloadHolder {
+ public static final NativeAllocationRegistry sRegistry =
+ NativeAllocationRegistry.createMalloced(
+ RuntimeShader.class.getClassLoader(), nativeGetFinalizer());
+ }
+
+ private byte[] mUniforms;
+ private boolean mIsOpaque;
+
+ /**
+ * Current native shader factory instance.
+ */
+ private long mNativeInstanceRuntimeShaderFactory;
+
+ /**
+ * Creates a new RuntimeShader.
+ *
+ * @param sksl The text of SKSL program to run on the GPU.
+ * @param uniforms Array of parameters passed by the SKSL shader. Array size depends
+ * on number of uniforms declared by sksl.
+ * @param isOpaque True if all pixels have alpha 1.0f.
+ */
+ public RuntimeShader(@NonNull String sksl, @Nullable byte[] uniforms, boolean isOpaque) {
+ this(sksl, uniforms, isOpaque, ColorSpace.get(ColorSpace.Named.SRGB));
+ }
+
+ private RuntimeShader(@NonNull String sksl, @Nullable byte[] uniforms, boolean isOpaque,
+ ColorSpace colorSpace) {
+ super(colorSpace);
+ mUniforms = uniforms;
+ mIsOpaque = isOpaque;
+ mNativeInstanceRuntimeShaderFactory = nativeCreateShaderFactory(sksl);
+ NoImagePreloadHolder.sRegistry.registerNativeAllocation(this,
+ mNativeInstanceRuntimeShaderFactory);
+ }
+
+ /**
+ * Sets new value for shader parameters.
+ *
+ * @param uniforms Array of parameters passed by the SKSL shader. Array size depends
+ * on number of uniforms declared by mSksl.
+ */
+ public void updateUniforms(@Nullable byte[] uniforms) {
+ mUniforms = uniforms;
+ discardNativeInstance();
+ }
+
+ @Override
+ long createNativeInstance(long nativeMatrix) {
+ return nativeCreate(mNativeInstanceRuntimeShaderFactory, nativeMatrix, mUniforms,
+ colorSpace().getNativeInstance(), mIsOpaque);
+ }
+
+ private static native long nativeCreate(long shaderFactory, long matrix, byte[] inputs,
+ long colorSpaceHandle, boolean isOpaque);
+
+ private static native long nativeCreateShaderFactory(String sksl);
+
+ private static native long nativeGetFinalizer();
+}
+
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index 697daa8b7b70..228d03a1dd10 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -120,7 +120,7 @@ public class SurfaceTexture {
/**
* Construct a new SurfaceTexture to stream images to a given OpenGL texture.
- *
+ * <p>
* In single buffered mode the application is responsible for serializing access to the image
* content buffer. Each time the image content is to be updated, the
* {@link #releaseTexImage()} method must be called before the image content producer takes
@@ -143,7 +143,7 @@ public class SurfaceTexture {
/**
* Construct a new SurfaceTexture to stream images to a given OpenGL texture.
- *
+ * <p>
* In single buffered mode the application is responsible for serializing access to the image
* content buffer. Each time the image content is to be updated, the
* {@link #releaseTexImage()} method must be called before the image content producer takes
@@ -152,7 +152,7 @@ public class SurfaceTexture {
* must be called before each ANativeWindow_lock, or that call will fail. When producing
* image content with OpenGL ES, {@link #releaseTexImage()} must be called before the first
* OpenGL ES function call each frame.
- *
+ * <p>
* Unlike {@link #SurfaceTexture(int, boolean)}, which takes an OpenGL texture object name,
* this constructor creates the SurfaceTexture in detached mode. A texture name must be passed
* in using {@link #attachToGLContext} before calling {@link #releaseTexImage()} and producing
@@ -222,15 +222,15 @@ public class SurfaceTexture {
* method. Both video and camera based image producers do override the size. This method may
* be used to set the image size when producing images with {@link android.graphics.Canvas} (via
* {@link android.view.Surface#lockCanvas}), or OpenGL ES (via an EGLSurface).
- *
+ * <p>
* The new default buffer size will take effect the next time the image producer requests a
* buffer to fill. For {@link android.graphics.Canvas} this will be the next time {@link
* android.view.Surface#lockCanvas} is called. For OpenGL ES, the EGLSurface should be
* destroyed (via eglDestroySurface), made not-current (via eglMakeCurrent), and then recreated
- * (via eglCreateWindowSurface) to ensure that the new default size has taken effect.
- *
+ * (via {@code eglCreateWindowSurface}) to ensure that the new default size has taken effect.
+ * <p>
* The width and height parameters must be no greater than the minimum of
- * GL_MAX_VIEWPORT_DIMS and GL_MAX_TEXTURE_SIZE (see
+ * {@code GL_MAX_VIEWPORT_DIMS} and {@code GL_MAX_TEXTURE_SIZE} (see
* {@link javax.microedition.khronos.opengles.GL10#glGetIntegerv glGetIntegerv}).
* An error due to invalid dimensions might not be reported until
* updateTexImage() is called.
@@ -242,7 +242,7 @@ public class SurfaceTexture {
/**
* Update the texture image to the most recent frame from the image stream. This may only be
* called while the OpenGL ES context that owns the texture is current on the calling thread.
- * It will implicitly bind its texture to the GL_TEXTURE_EXTERNAL_OES texture target.
+ * It will implicitly bind its texture to the {@code GL_TEXTURE_EXTERNAL_OES} texture target.
*/
public void updateTexImage() {
nativeUpdateTexImage();
@@ -251,6 +251,7 @@ public class SurfaceTexture {
/**
* Releases the the texture content. This is needed in single buffered mode to allow the image
* content producer to take ownership of the image buffer.
+ * <p>
* For more information see {@link #SurfaceTexture(int, boolean)}.
*/
public void releaseTexImage() {
@@ -263,7 +264,7 @@ public class SurfaceTexture {
* ES texture object will be deleted as a result of this call. After calling this method all
* calls to {@link #updateTexImage} will throw an {@link java.lang.IllegalStateException} until
* a successful call to {@link #attachToGLContext} is made.
- *
+ * <p>
* This can be used to access the SurfaceTexture image contents from multiple OpenGL ES
* contexts. Note, however, that the image contents are only accessible from one OpenGL ES
* context at a time.
@@ -279,8 +280,8 @@ public class SurfaceTexture {
* Attach the SurfaceTexture to the OpenGL ES context that is current on the calling thread. A
* new OpenGL ES texture object is created and populated with the SurfaceTexture image frame
* that was current at the time of the last call to {@link #detachFromGLContext}. This new
- * texture is bound to the GL_TEXTURE_EXTERNAL_OES texture target.
- *
+ * texture is bound to the {@code GL_TEXTURE_EXTERNAL_OES} texture target.
+ * <p>
* This can be used to access the SurfaceTexture image contents from multiple OpenGL ES
* contexts. Note, however, that the image contents are only accessible from one OpenGL ES
* context at a time.
@@ -297,15 +298,20 @@ public class SurfaceTexture {
/**
* Retrieve the 4x4 texture coordinate transform matrix associated with the texture image set by
- * the most recent call to updateTexImage.
- *
+ * the most recent call to {@link #updateTexImage}.
+ * <p>
* This transform matrix maps 2D homogeneous texture coordinates of the form (s, t, 0, 1) with s
* and t in the inclusive range [0, 1] to the texture coordinate that should be used to sample
* that location from the texture. Sampling the texture outside of the range of this transform
* is undefined.
- *
+ * <p>
* The matrix is stored in column-major order so that it may be passed directly to OpenGL ES via
- * the glLoadMatrixf or glUniformMatrix4fv functions.
+ * the {@code glLoadMatrixf} or {@code glUniformMatrix4fv} functions.
+ * <p>
+ * If the underlying buffer has a crop associated with it, the transformation will also include
+ * a slight scale to cut off a 1-texel border around the edge of the crop. This ensures that
+ * when the texture is bilinear sampled that no texels outside of the buffer's valid region
+ * are accessed by the GPU, avoiding any sampling artifacts when scaling.
*
* @param mtx the array into which the 4x4 matrix will be stored. The array must have exactly
* 16 elements.
@@ -321,7 +327,7 @@ public class SurfaceTexture {
/**
* Retrieve the timestamp associated with the texture image set by the most recent call to
- * updateTexImage.
+ * {@link #updateTexImage}.
*
* <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
@@ -332,8 +338,8 @@ public class SurfaceTexture {
*
* <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>
+ * timestamp is the desired present time set with the {@code EGL_ANDROID_presentation_time} or
+ * {@code VK_GOOGLE_display_timing} extensions.</p>
*/
public long getTimestamp() {
@@ -341,16 +347,17 @@ public class SurfaceTexture {
}
/**
- * release() frees all the buffers and puts the SurfaceTexture into the
+ * {@code release()} frees all the buffers and puts the SurfaceTexture into the
* 'abandoned' state. Once put in this state the SurfaceTexture can never
* leave it. When in the 'abandoned' state, all methods of the
- * IGraphicBufferProducer interface will fail with the NO_INIT error.
- *
+ * {@code IGraphicBufferProducer} interface will fail with the {@code NO_INIT}
+ * error.
+ * <p>
* Note that while calling this method causes all the buffers to be freed
* from the perspective of the the SurfaceTexture, if there are additional
* references on the buffers (e.g. if a buffer is referenced by a client or
* by OpenGL ES as a texture) then those buffer will remain allocated.
- *
+ * <p>
* Always call this method when you are done with SurfaceTexture. Failing
* to do so may delay resource deallocation for a significant amount of
* time.
@@ -362,7 +369,7 @@ public class SurfaceTexture {
}
/**
- * Returns true if the SurfaceTexture was released.
+ * Returns {@code true} if the SurfaceTexture was released.
*
* @see #release()
*/
@@ -395,7 +402,7 @@ public class SurfaceTexture {
}
/**
- * Returns true if the SurfaceTexture is single-buffered
+ * Returns {@code true} if the SurfaceTexture is single-buffered.
* @hide
*/
public boolean isSingleBuffered() {
diff --git a/graphics/java/android/graphics/animation/FallbackLUTInterpolator.java b/graphics/java/android/graphics/animation/FallbackLUTInterpolator.java
new file mode 100644
index 000000000000..36062c141ed2
--- /dev/null
+++ b/graphics/java/android/graphics/animation/FallbackLUTInterpolator.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.animation;
+
+import android.animation.TimeInterpolator;
+import android.util.TimeUtils;
+import android.view.Choreographer;
+
+/**
+ * Interpolator that builds a lookup table to use. This is a fallback for
+ * building a native interpolator from a TimeInterpolator that is not marked
+ * with {@link HasNativeInterpolator}
+ *
+ * This implements TimeInterpolator to allow for easier interop with Animators
+ * @hide
+ */
+@HasNativeInterpolator
+public class FallbackLUTInterpolator implements NativeInterpolator, TimeInterpolator {
+
+ // If the duration of an animation is more than 300 frames, we cap the sample size to 300.
+ private static final int MAX_SAMPLE_POINTS = 300;
+ private TimeInterpolator mSourceInterpolator;
+ private final float[] mLut;
+
+ /**
+ * Used to cache the float[] LUT for use across multiple native
+ * interpolator creation
+ */
+ public FallbackLUTInterpolator(TimeInterpolator interpolator, long duration) {
+ mSourceInterpolator = interpolator;
+ mLut = createLUT(interpolator, duration);
+ }
+
+ private static float[] createLUT(TimeInterpolator interpolator, long duration) {
+ long frameIntervalNanos = Choreographer.getInstance().getFrameIntervalNanos();
+ int animIntervalMs = (int) (frameIntervalNanos / TimeUtils.NANOS_PER_MS);
+ // We need 2 frame values as the minimal.
+ int numAnimFrames = Math.max(2, (int) Math.ceil(((double) duration) / animIntervalMs));
+ numAnimFrames = Math.min(numAnimFrames, MAX_SAMPLE_POINTS);
+ float[] values = new float[numAnimFrames];
+ float lastFrame = numAnimFrames - 1;
+ for (int i = 0; i < numAnimFrames; i++) {
+ float inValue = i / lastFrame;
+ values[i] = interpolator.getInterpolation(inValue);
+ }
+ return values;
+ }
+
+ @Override
+ public long createNativeInterpolator() {
+ return NativeInterpolatorFactory.createLutInterpolator(mLut);
+ }
+
+ /**
+ * Used to create a one-shot float[] LUT & native interpolator
+ */
+ public static long createNativeInterpolator(TimeInterpolator interpolator, long duration) {
+ float[] lut = createLUT(interpolator, duration);
+ return NativeInterpolatorFactory.createLutInterpolator(lut);
+ }
+
+ @Override
+ public float getInterpolation(float input) {
+ return mSourceInterpolator.getInterpolation(input);
+ }
+}
diff --git a/graphics/java/android/graphics/animation/HasNativeInterpolator.java b/graphics/java/android/graphics/animation/HasNativeInterpolator.java
new file mode 100644
index 000000000000..c53d5a42524d
--- /dev/null
+++ b/graphics/java/android/graphics/animation/HasNativeInterpolator.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.animation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This is a class annotation that signals that it is safe to create
+ * a native interpolator counterpart via {@link NativeInterpolator}
+ *
+ * The idea here is to prevent subclasses of interpolators from being treated as a
+ * NativeInterpolator, and instead have them fall back to the LUT & LERP
+ * method like a custom interpolator.
+ *
+ * @hide
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface HasNativeInterpolator {
+}
diff --git a/graphics/java/android/graphics/animation/NativeInterpolator.java b/graphics/java/android/graphics/animation/NativeInterpolator.java
new file mode 100644
index 000000000000..1e6fea8da466
--- /dev/null
+++ b/graphics/java/android/graphics/animation/NativeInterpolator.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.animation;
+
+/**
+ * @hide
+ */
+public interface NativeInterpolator {
+ /**
+ * Generates a native interpolator object that can be used by HardwareRenderer to draw
+ * RenderNodes.
+ * @return ptr to native object
+ */
+ long createNativeInterpolator();
+}
diff --git a/graphics/java/android/graphics/animation/NativeInterpolatorFactory.java b/graphics/java/android/graphics/animation/NativeInterpolatorFactory.java
new file mode 100644
index 000000000000..38866145ee50
--- /dev/null
+++ b/graphics/java/android/graphics/animation/NativeInterpolatorFactory.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.animation;
+
+import android.animation.TimeInterpolator;
+
+/**
+ * Static utility class for constructing native interpolators to keep the
+ * JNI simpler
+ *
+ * @hide
+ */
+public final class NativeInterpolatorFactory {
+ private NativeInterpolatorFactory() {}
+
+ /**
+ * Create a native interpolator from the provided param generating a LUT variant if a native
+ * implementation does not exist.
+ */
+ public static long createNativeInterpolator(TimeInterpolator interpolator, long
+ duration) {
+ if (interpolator == null) {
+ return createLinearInterpolator();
+ } else if (RenderNodeAnimator.isNativeInterpolator(interpolator)) {
+ return ((NativeInterpolator) interpolator).createNativeInterpolator();
+ } else {
+ return FallbackLUTInterpolator.createNativeInterpolator(interpolator, duration);
+ }
+ }
+
+ /** Creates a specialized native interpolator for Accelerate/Decelerate */
+ public static native long createAccelerateDecelerateInterpolator();
+ /** Creates a specialized native interpolator for Accelerate */
+ public static native long createAccelerateInterpolator(float factor);
+ /** Creates a specialized native interpolator for Anticipate */
+ public static native long createAnticipateInterpolator(float tension);
+ /** Creates a specialized native interpolator for Anticipate with Overshoot */
+ public static native long createAnticipateOvershootInterpolator(float tension);
+ /** Creates a specialized native interpolator for Bounce */
+ public static native long createBounceInterpolator();
+ /** Creates a specialized native interpolator for Cycle */
+ public static native long createCycleInterpolator(float cycles);
+ /** Creates a specialized native interpolator for Decelerate */
+ public static native long createDecelerateInterpolator(float factor);
+ /** Creates a specialized native interpolator for Linear interpolation */
+ public static native long createLinearInterpolator();
+ /** Creates a specialized native interpolator for Overshoot */
+ public static native long createOvershootInterpolator(float tension);
+ /** Creates a specialized native interpolator for along traveling along a Path */
+ public static native long createPathInterpolator(float[] x, float[] y);
+ /** Creates a specialized native interpolator for LUT */
+ public static native long createLutInterpolator(float[] values);
+}
diff --git a/graphics/java/android/graphics/animation/RenderNodeAnimator.java b/graphics/java/android/graphics/animation/RenderNodeAnimator.java
new file mode 100644
index 000000000000..282b2f959fc4
--- /dev/null
+++ b/graphics/java/android/graphics/animation/RenderNodeAnimator.java
@@ -0,0 +1,513 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.animation;
+
+import android.animation.Animator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.graphics.CanvasProperty;
+import android.graphics.Paint;
+import android.graphics.RecordingCanvas;
+import android.graphics.RenderNode;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.Choreographer;
+
+import com.android.internal.util.VirtualRefBasePtr;
+
+import java.util.ArrayList;
+
+/**
+ * @hide
+ */
+public class RenderNodeAnimator extends Animator {
+ // Keep in sync with enum RenderProperty in Animator.h
+ public static final int TRANSLATION_X = 0;
+ public static final int TRANSLATION_Y = 1;
+ public static final int TRANSLATION_Z = 2;
+ public static final int SCALE_X = 3;
+ public static final int SCALE_Y = 4;
+ public static final int ROTATION = 5;
+ public static final int ROTATION_X = 6;
+ public static final int ROTATION_Y = 7;
+ public static final int X = 8;
+ public static final int Y = 9;
+ public static final int Z = 10;
+ public static final int ALPHA = 11;
+ // The last value in the enum, used for array size initialization
+ public static final int LAST_VALUE = ALPHA;
+
+ // Keep in sync with enum PaintFields in Animator.h
+ public static final int PAINT_STROKE_WIDTH = 0;
+
+ /**
+ * Field for the Paint alpha channel, which should be specified as a value
+ * between 0 and 255.
+ */
+ public static final int PAINT_ALPHA = 1;
+
+ private VirtualRefBasePtr mNativePtr;
+
+ private Handler mHandler;
+ private RenderNode mTarget;
+ private ViewListener mViewListener;
+ private int mRenderProperty = -1;
+ private float mFinalValue;
+ private TimeInterpolator mInterpolator;
+
+ private static final int STATE_PREPARE = 0;
+ private static final int STATE_DELAYED = 1;
+ private static final int STATE_RUNNING = 2;
+ private static final int STATE_FINISHED = 3;
+ private int mState = STATE_PREPARE;
+
+ private long mUnscaledDuration = 300;
+ private long mUnscaledStartDelay = 0;
+ // If this is true, we will run any start delays on the UI thread. This is
+ // the safe default, and is necessary to ensure start listeners fire at
+ // the correct time. Animators created by RippleDrawable (the
+ // CanvasProperty<> ones) do not have this expectation, and as such will
+ // set this to false so that the renderthread handles the startdelay instead
+ private final boolean mUiThreadHandlesDelay;
+ private long mStartDelay = 0;
+ private long mStartTime;
+
+ /**
+ * Interface used by the view system to update the view hierarchy in conjunction
+ * with this animator.
+ */
+ public interface ViewListener {
+ /** notify the listener that an alpha animation has begun. */
+ void onAlphaAnimationStart(float finalAlpha);
+ /** notify the listener that the animator has mutated a value that requires invalidation */
+ void invalidateParent(boolean forceRedraw);
+ }
+
+ public RenderNodeAnimator(int property, float finalValue) {
+ mRenderProperty = property;
+ mFinalValue = finalValue;
+ mUiThreadHandlesDelay = true;
+ init(nCreateAnimator(property, finalValue));
+ }
+
+ public RenderNodeAnimator(CanvasProperty<Float> property, float finalValue) {
+ init(nCreateCanvasPropertyFloatAnimator(
+ property.getNativeContainer(), finalValue));
+ mUiThreadHandlesDelay = false;
+ }
+
+ /**
+ * Creates a new render node animator for a field on a Paint property.
+ *
+ * @param property The paint property to target
+ * @param paintField Paint field to animate, one of {@link #PAINT_ALPHA} or
+ * {@link #PAINT_STROKE_WIDTH}
+ * @param finalValue The target value for the property
+ */
+ public RenderNodeAnimator(CanvasProperty<Paint> property, int paintField, float finalValue) {
+ init(nCreateCanvasPropertyPaintAnimator(
+ property.getNativeContainer(), paintField, finalValue));
+ mUiThreadHandlesDelay = false;
+ }
+
+ public RenderNodeAnimator(int x, int y, float startRadius, float endRadius) {
+ init(nCreateRevealAnimator(x, y, startRadius, endRadius));
+ mUiThreadHandlesDelay = true;
+ }
+
+ private void init(long ptr) {
+ mNativePtr = new VirtualRefBasePtr(ptr);
+ }
+
+ private void checkMutable() {
+ if (mState != STATE_PREPARE) {
+ throw new IllegalStateException("Animator has already started, cannot change it now!");
+ }
+ if (mNativePtr == null) {
+ throw new IllegalStateException("Animator's target has been destroyed "
+ + "(trying to modify an animation after activity destroy?)");
+ }
+ }
+
+ static boolean isNativeInterpolator(TimeInterpolator interpolator) {
+ return interpolator.getClass().isAnnotationPresent(HasNativeInterpolator.class);
+ }
+
+ private void applyInterpolator() {
+ if (mInterpolator == null || mNativePtr == null) return;
+
+ long ni;
+ if (isNativeInterpolator(mInterpolator)) {
+ ni = ((NativeInterpolator) mInterpolator).createNativeInterpolator();
+ } else {
+ long duration = nGetDuration(mNativePtr.get());
+ ni = FallbackLUTInterpolator.createNativeInterpolator(mInterpolator, duration);
+ }
+ nSetInterpolator(mNativePtr.get(), ni);
+ }
+
+ @Override
+ public void start() {
+ if (mTarget == null) {
+ throw new IllegalStateException("Missing target!");
+ }
+
+ if (mState != STATE_PREPARE) {
+ throw new IllegalStateException("Already started!");
+ }
+
+ mState = STATE_DELAYED;
+ if (mHandler == null) {
+ mHandler = new Handler(true);
+ }
+ applyInterpolator();
+
+ if (mNativePtr == null) {
+ // It's dead, immediately cancel
+ cancel();
+ } else if (mStartDelay <= 0 || !mUiThreadHandlesDelay) {
+ nSetStartDelay(mNativePtr.get(), mStartDelay);
+ doStart();
+ } else {
+ getHelper().addDelayedAnimation(this);
+ }
+ }
+
+ private void doStart() {
+ // Alpha is a special snowflake that has the canonical value stored
+ // in mTransformationInfo instead of in RenderNode, so we need to update
+ // it with the final value here.
+ if (mRenderProperty == RenderNodeAnimator.ALPHA && mViewListener != null) {
+ mViewListener.onAlphaAnimationStart(mFinalValue);
+ }
+
+ moveToRunningState();
+
+ if (mViewListener != null) {
+ // Kick off a frame to start the process
+ mViewListener.invalidateParent(false);
+ }
+ }
+
+ private void moveToRunningState() {
+ mState = STATE_RUNNING;
+ if (mNativePtr != null) {
+ nStart(mNativePtr.get());
+ }
+ notifyStartListeners();
+ }
+
+ private void notifyStartListeners() {
+ final ArrayList<AnimatorListener> listeners = cloneListeners();
+ final int numListeners = listeners == null ? 0 : listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ listeners.get(i).onAnimationStart(this);
+ }
+ }
+
+ @Override
+ public void cancel() {
+ if (mState != STATE_PREPARE && mState != STATE_FINISHED) {
+ if (mState == STATE_DELAYED) {
+ getHelper().removeDelayedAnimation(this);
+ moveToRunningState();
+ }
+
+ final ArrayList<AnimatorListener> listeners = cloneListeners();
+ final int numListeners = listeners == null ? 0 : listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ listeners.get(i).onAnimationCancel(this);
+ }
+
+ end();
+ }
+ }
+
+ @Override
+ public void end() {
+ if (mState != STATE_FINISHED) {
+ if (mState < STATE_RUNNING) {
+ getHelper().removeDelayedAnimation(this);
+ doStart();
+ }
+ if (mNativePtr != null) {
+ nEnd(mNativePtr.get());
+ if (mViewListener != null) {
+ // Kick off a frame to flush the state change
+ mViewListener.invalidateParent(false);
+ }
+ } else {
+ // It's already dead, jump to onFinish
+ onFinished();
+ }
+ }
+ }
+
+ @Override
+ public void pause() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void resume() {
+ throw new UnsupportedOperationException();
+ }
+
+ /** @hide */
+ public void setViewListener(ViewListener listener) {
+ mViewListener = listener;
+ }
+
+ /** Sets the animation target to the owning view of the RecordingCanvas */
+ public final void setTarget(RecordingCanvas canvas) {
+ setTarget(canvas.mNode);
+ }
+
+ /** Sets the node that is to be the target of this animation */
+ protected void setTarget(RenderNode node) {
+ checkMutable();
+ if (mTarget != null) {
+ throw new IllegalStateException("Target already set!");
+ }
+ nSetListener(mNativePtr.get(), this);
+ mTarget = node;
+ mTarget.addAnimator(this);
+ }
+
+ /** Set the start value for the animation */
+ public void setStartValue(float startValue) {
+ checkMutable();
+ nSetStartValue(mNativePtr.get(), startValue);
+ }
+
+ @Override
+ public void setStartDelay(long startDelay) {
+ checkMutable();
+ if (startDelay < 0) {
+ throw new IllegalArgumentException("startDelay must be positive; " + startDelay);
+ }
+ mUnscaledStartDelay = startDelay;
+ mStartDelay = (long) (ValueAnimator.getDurationScale() * startDelay);
+ }
+
+ @Override
+ public long getStartDelay() {
+ return mUnscaledStartDelay;
+ }
+
+ @Override
+ public RenderNodeAnimator setDuration(long duration) {
+ checkMutable();
+ if (duration < 0) {
+ throw new IllegalArgumentException("duration must be positive; " + duration);
+ }
+ mUnscaledDuration = duration;
+ nSetDuration(mNativePtr.get(), (long) (duration * ValueAnimator.getDurationScale()));
+ return this;
+ }
+
+ @Override
+ public long getDuration() {
+ return mUnscaledDuration;
+ }
+
+ @Override
+ public long getTotalDuration() {
+ return mUnscaledDuration + mUnscaledStartDelay;
+ }
+
+ @Override
+ public boolean isRunning() {
+ return mState == STATE_DELAYED || mState == STATE_RUNNING;
+ }
+
+ @Override
+ public boolean isStarted() {
+ return mState != STATE_PREPARE;
+ }
+
+ @Override
+ public void setInterpolator(TimeInterpolator interpolator) {
+ checkMutable();
+ mInterpolator = interpolator;
+ }
+
+ @Override
+ public TimeInterpolator getInterpolator() {
+ return mInterpolator;
+ }
+
+ protected void onFinished() {
+ if (mState == STATE_PREPARE) {
+ // Unlikely but possible, the native side has been destroyed
+ // before we have started.
+ releaseNativePtr();
+ return;
+ }
+ if (mState == STATE_DELAYED) {
+ getHelper().removeDelayedAnimation(this);
+ notifyStartListeners();
+ }
+ mState = STATE_FINISHED;
+
+ final ArrayList<AnimatorListener> listeners = cloneListeners();
+ final int numListeners = listeners == null ? 0 : listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ listeners.get(i).onAnimationEnd(this);
+ }
+
+ // Release the native object, as it has a global reference to us. This
+ // breaks the cyclic reference chain, and allows this object to be
+ // GC'd
+ releaseNativePtr();
+ }
+
+ private void releaseNativePtr() {
+ if (mNativePtr != null) {
+ mNativePtr.release();
+ mNativePtr = null;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private ArrayList<AnimatorListener> cloneListeners() {
+ ArrayList<AnimatorListener> listeners = getListeners();
+ if (listeners != null) {
+ listeners = (ArrayList<AnimatorListener>) listeners.clone();
+ }
+ return listeners;
+ }
+
+ public long getNativeAnimator() {
+ return mNativePtr.get();
+ }
+
+ /**
+ * @return true if the animator was started, false if still delayed
+ */
+ private boolean processDelayed(long frameTimeMs) {
+ if (mStartTime == 0) {
+ mStartTime = frameTimeMs;
+ } else if ((frameTimeMs - mStartTime) >= mStartDelay) {
+ doStart();
+ return true;
+ }
+ return false;
+ }
+
+ private static DelayedAnimationHelper getHelper() {
+ DelayedAnimationHelper helper = sAnimationHelper.get();
+ if (helper == null) {
+ helper = new DelayedAnimationHelper();
+ sAnimationHelper.set(helper);
+ }
+ return helper;
+ }
+
+ private static ThreadLocal<DelayedAnimationHelper> sAnimationHelper =
+ new ThreadLocal<DelayedAnimationHelper>();
+
+ private static class DelayedAnimationHelper implements Runnable {
+
+ private ArrayList<RenderNodeAnimator> mDelayedAnims = new ArrayList<RenderNodeAnimator>();
+ private final Choreographer mChoreographer;
+ private boolean mCallbackScheduled;
+
+ DelayedAnimationHelper() {
+ mChoreographer = Choreographer.getInstance();
+ }
+
+ public void addDelayedAnimation(RenderNodeAnimator animator) {
+ mDelayedAnims.add(animator);
+ scheduleCallback();
+ }
+
+ public void removeDelayedAnimation(RenderNodeAnimator animator) {
+ mDelayedAnims.remove(animator);
+ }
+
+ private void scheduleCallback() {
+ if (!mCallbackScheduled) {
+ mCallbackScheduled = true;
+ mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
+ }
+ }
+
+ @Override
+ public void run() {
+ long frameTimeMs = mChoreographer.getFrameTime();
+ mCallbackScheduled = false;
+
+ int end = 0;
+ for (int i = 0; i < mDelayedAnims.size(); i++) {
+ RenderNodeAnimator animator = mDelayedAnims.get(i);
+ if (!animator.processDelayed(frameTimeMs)) {
+ if (end != i) {
+ mDelayedAnims.set(end, animator);
+ }
+ end++;
+ }
+ }
+ while (mDelayedAnims.size() > end) {
+ mDelayedAnims.remove(mDelayedAnims.size() - 1);
+ }
+
+ if (mDelayedAnims.size() > 0) {
+ scheduleCallback();
+ }
+ }
+ }
+
+ // Called by native
+ private static void callOnFinished(RenderNodeAnimator animator) {
+ if (animator.mHandler != null) {
+ animator.mHandler.post(animator::onFinished);
+ } else {
+ new Handler(Looper.getMainLooper(), null, true).post(animator::onFinished);
+ }
+ }
+
+ @Override
+ public Animator clone() {
+ throw new IllegalStateException("Cannot clone this animator");
+ }
+
+ @Override
+ public void setAllowRunningAsynchronously(boolean mayRunAsync) {
+ checkMutable();
+ nSetAllowRunningAsync(mNativePtr.get(), mayRunAsync);
+ }
+
+ private static native long nCreateAnimator(int property, float finalValue);
+ private static native long nCreateCanvasPropertyFloatAnimator(
+ long canvasProperty, float finalValue);
+ private static native long nCreateCanvasPropertyPaintAnimator(
+ long canvasProperty, int paintField, float finalValue);
+ private static native long nCreateRevealAnimator(
+ int x, int y, float startRadius, float endRadius);
+
+ private static native void nSetStartValue(long nativePtr, float startValue);
+ private static native void nSetDuration(long nativePtr, long duration);
+ private static native long nGetDuration(long nativePtr);
+ private static native void nSetStartDelay(long nativePtr, long startDelay);
+ private static native void nSetInterpolator(long animPtr, long interpolatorPtr);
+ private static native void nSetAllowRunningAsync(long animPtr, boolean mayRunAsync);
+ private static native void nSetListener(long animPtr, RenderNodeAnimator listener);
+
+ private static native void nStart(long animPtr);
+ private static native void nEnd(long animPtr);
+}
diff --git a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
index fab96a1e9fbd..31ad81b9c346 100644
--- a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
+++ b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
@@ -16,6 +16,7 @@
package android.graphics.drawable;
+import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
@@ -223,6 +224,7 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
final int deviceDensity = Drawable.resolveDensity(r, 0);
state.setDensity(deviceDensity);
state.mSrcDensityOverride = mSrcDensityOverride;
+ state.mSourceDrawableId = Resources.getAttributeSetSourceResId(attrs);
final ChildDrawable[] array = state.mChildren;
for (int i = 0; i < state.mChildren.length; i++) {
@@ -385,7 +387,7 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
@Override
public void getOutline(@NonNull Outline outline) {
- outline.setConvexPath(mMask);
+ outline.setPath(mMask);
}
/** @hide */
@@ -446,6 +448,17 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
}
/**
+ * If the drawable was inflated from XML, this returns the resource ID for the drawable
+ *
+ * @hide
+ */
+ @DrawableRes
+ public int getSourceDrawableResId() {
+ final LayerState state = mLayerState;
+ return state == null ? Resources.ID_NULL : state.mSourceDrawableId;
+ }
+
+ /**
* Inflates child layers using the specified parser.
*/
private void inflateLayers(@NonNull Resources r, @NonNull XmlPullParser parser,
@@ -944,6 +957,8 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
@Config int mChangingConfigurations;
@Config int mChildrenChangingConfigurations;
+ @DrawableRes int mSourceDrawableId = Resources.ID_NULL;
+
private boolean mCheckedOpacity;
private int mOpacity;
@@ -960,6 +975,7 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
mChangingConfigurations = orig.mChangingConfigurations;
mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
+ mSourceDrawableId = orig.mSourceDrawableId;
for (int i = 0; i < N_CHILDREN; i++) {
final ChildDrawable or = origChildDrawable[i];
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index 1acf6c512fbd..9fb72cf08b51 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -42,6 +42,7 @@ import android.graphics.PixelFormat;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RenderNode;
+import android.graphics.animation.NativeInterpolatorFactory;
import android.os.Build;
import android.os.Handler;
import android.util.ArrayMap;
@@ -54,7 +55,6 @@ import android.util.Property;
import android.util.TimeUtils;
import android.view.Choreographer;
import android.view.NativeVectorDrawableAnimator;
-import android.view.RenderNodeAnimatorSetHelper;
import android.view.View;
import com.android.internal.R;
@@ -1532,7 +1532,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
long startDelay = extraDelay + animator.getStartDelay();
TimeInterpolator interpolator = animator.getInterpolator();
long nativeInterpolator =
- RenderNodeAnimatorSetHelper.createNativeInterpolator(interpolator, duration);
+ NativeInterpolatorFactory.createNativeInterpolator(interpolator, duration);
startDelay *= ValueAnimator.getDurationScale();
duration *= ValueAnimator.getDurationScale();
@@ -1548,7 +1548,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
* to the last seen RenderNode target and start right away.
*/
protected void recordLastSeenTarget(RecordingCanvas canvas) {
- final RenderNode node = RenderNodeAnimatorSetHelper.getTarget(canvas);
+ final RenderNode node = canvas.mNode;
mLastSeenTarget = new WeakReference<RenderNode>(node);
// Add the animator to the list of animators on every draw
if (mInitialized || mPendingAnimationActions.size() > 0) {
diff --git a/graphics/java/android/graphics/drawable/DrawableWrapper.java b/graphics/java/android/graphics/drawable/DrawableWrapper.java
index e8cb42e75ea2..e197e7123fed 100644
--- a/graphics/java/android/graphics/drawable/DrawableWrapper.java
+++ b/graphics/java/android/graphics/drawable/DrawableWrapper.java
@@ -364,6 +364,13 @@ public abstract class DrawableWrapper extends Drawable implements Drawable.Callb
}
@Override
+ public void jumpToCurrentState() {
+ if (mDrawable != null) {
+ mDrawable.jumpToCurrentState();
+ }
+ }
+
+ @Override
protected boolean onLevelChange(int level) {
return mDrawable != null && mDrawable.setLevel(level);
}
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index b50ec0d5e3fd..f053f392b97e 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -97,6 +97,14 @@ import java.lang.annotation.RetentionPolicy;
* @attr ref android.R.styleable#GradientDrawablePadding_bottom
*/
public class GradientDrawable extends Drawable {
+
+ /**
+ * Flag to determine if we should wrap negative gradient angle measurements
+ * for API levels that support it
+ * @hide
+ */
+ public static boolean sWrapNegativeAngleMeasurements = true;
+
/**
* Shape is a rectangle, possibly with rounded corners
*/
@@ -151,6 +159,9 @@ public class GradientDrawable extends Drawable {
/** Radius is a fraction of the bounds size. */
private static final int RADIUS_TYPE_FRACTION_PARENT = 2;
+ /** Default orientation for GradientDrawable **/
+ private static final Orientation DEFAULT_ORIENTATION = Orientation.TOP_BOTTOM;
+
/** @hide */
@IntDef({RADIUS_TYPE_PIXELS, RADIUS_TYPE_FRACTION, RADIUS_TYPE_FRACTION_PARENT})
@Retention(RetentionPolicy.SOURCE)
@@ -207,7 +218,7 @@ public class GradientDrawable extends Drawable {
}
public GradientDrawable() {
- this(new GradientState(Orientation.TOP_BOTTOM, null), null);
+ this(new GradientState(DEFAULT_ORIENTATION, null), null);
}
/**
@@ -637,7 +648,7 @@ public class GradientDrawable extends Drawable {
* @see #setOrientation(Orientation)
*/
public Orientation getOrientation() {
- return mGradientState.getOrientation();
+ return mGradientState.mOrientation;
}
/**
@@ -653,7 +664,7 @@ public class GradientDrawable extends Drawable {
* @see #getOrientation()
*/
public void setOrientation(Orientation orientation) {
- mGradientState.setOrientation(orientation);
+ mGradientState.mOrientation = orientation;
mGradientIsDirty = true;
invalidateSelf();
}
@@ -1270,7 +1281,7 @@ public class GradientDrawable extends Drawable {
if (st.mGradient == LINEAR_GRADIENT) {
final float level = st.mUseLevel ? getLevel() / 10000.0f : 1.0f;
- switch (st.getOrientation()) {
+ switch (st.mOrientation) {
case TOP_BOTTOM:
x0 = r.left; y0 = r.top;
x1 = x0; y1 = level * r.bottom;
@@ -1757,7 +1768,49 @@ public class GradientDrawable extends Drawable {
}
int angle = (int) a.getFloat(R.styleable.GradientDrawableGradient_angle, st.mAngle);
- st.mAngle = ((angle % 360) + 360) % 360; // offset negative angle measures
+
+ // GradientDrawable historically has not parsed negative angle measurements and always
+ // stays on the default orientation for API levels older than Q.
+ // Only configure the orientation if the angle is greater than zero.
+ // Otherwise fallback on Orientation.TOP_BOTTOM
+ // In Android Q and later, actually wrap the negative angle measurement to the correct
+ // value
+ if (sWrapNegativeAngleMeasurements) {
+ st.mAngle = ((angle % 360) + 360) % 360; // offset negative angle measures
+ } else {
+ st.mAngle = angle % 360;
+ }
+
+ if (st.mAngle >= 0) {
+ switch (st.mAngle) {
+ case 0:
+ st.mOrientation = Orientation.LEFT_RIGHT;
+ break;
+ case 45:
+ st.mOrientation = Orientation.BL_TR;
+ break;
+ case 90:
+ st.mOrientation = Orientation.BOTTOM_TOP;
+ break;
+ case 135:
+ st.mOrientation = Orientation.BR_TL;
+ break;
+ case 180:
+ st.mOrientation = Orientation.RIGHT_LEFT;
+ break;
+ case 225:
+ st.mOrientation = Orientation.TR_BL;
+ break;
+ case 270:
+ st.mOrientation = Orientation.TOP_BOTTOM;
+ break;
+ case 315:
+ st.mOrientation = Orientation.TL_BR;
+ break;
+ }
+ } else {
+ st.mOrientation = DEFAULT_ORIENTATION;
+ }
final TypedValue tv = a.peekValue(R.styleable.GradientDrawableGradient_gradientRadius);
if (tv != null) {
@@ -1862,7 +1915,7 @@ public class GradientDrawable extends Drawable {
case RECTANGLE:
if (st.mRadiusArray != null) {
buildPathIfDirty();
- outline.setConvexPath(mPath);
+ outline.setPath(mPath);
return;
}
@@ -1981,7 +2034,7 @@ public class GradientDrawable extends Drawable {
int[] mAttrPadding;
public GradientState(Orientation orientation, int[] gradientColors) {
- setOrientation(orientation);
+ mOrientation = orientation;
setGradientColors(gradientColors);
}
@@ -2184,93 +2237,11 @@ public class GradientDrawable extends Drawable {
mCenterY = y;
}
- public void setOrientation(Orientation orientation) {
- // Update the angle here so that subsequent attempts to obtain the orientation
- // from the angle overwrite previously configured values during inflation
- mAngle = getAngleFromOrientation(orientation);
- mOrientation = orientation;
- }
-
@NonNull
public Orientation getOrientation() {
- updateGradientStateOrientation();
return mOrientation;
}
- /**
- * Update the orientation of the gradient based on the given angle only if the type is
- * {@link #LINEAR_GRADIENT}
- */
- private void updateGradientStateOrientation() {
- if (mGradient == LINEAR_GRADIENT) {
- int angle = mAngle;
- if (angle % 45 != 0) {
- throw new IllegalArgumentException("Linear gradient requires 'angle' attribute "
- + "to be a multiple of 45");
- }
-
- Orientation orientation;
- switch (angle) {
- case 0:
- orientation = Orientation.LEFT_RIGHT;
- break;
- case 45:
- orientation = Orientation.BL_TR;
- break;
- case 90:
- orientation = Orientation.BOTTOM_TOP;
- break;
- case 135:
- orientation = Orientation.BR_TL;
- break;
- case 180:
- orientation = Orientation.RIGHT_LEFT;
- break;
- case 225:
- orientation = Orientation.TR_BL;
- break;
- case 270:
- orientation = Orientation.TOP_BOTTOM;
- break;
- case 315:
- orientation = Orientation.TL_BR;
- break;
- default:
- // Should not get here as exception is thrown above if angle is not multiple
- // of 45 degrees
- orientation = Orientation.LEFT_RIGHT;
- break;
- }
- mOrientation = orientation;
- }
- }
-
- private int getAngleFromOrientation(@Nullable Orientation orientation) {
- if (orientation != null) {
- switch (orientation) {
- default:
- case LEFT_RIGHT:
- return 0;
- case BL_TR:
- return 45;
- case BOTTOM_TOP:
- return 90;
- case BR_TL:
- return 135;
- case RIGHT_LEFT:
- return 180;
- case TR_BL:
- return 225;
- case TOP_BOTTOM:
- return 270;
- case TL_BR:
- return 315;
- }
- } else {
- return 0;
- }
- }
-
public void setGradientColors(@Nullable int[] colors) {
mGradientColors = colors;
mSolidColors = null;
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index 63662099c4c8..cc7182c3fd1c 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -92,11 +92,17 @@ public final class Icon implements Parcelable {
* @see #getType
*/
public static final int TYPE_ADAPTIVE_BITMAP = 5;
+ /**
+ * An icon that was created using {@link Icon#createWithAdaptiveBitmapContentUri}.
+ * @see #getType
+ */
+ public static final int TYPE_URI_ADAPTIVE_BITMAP = 6;
/**
* @hide
*/
- @IntDef({TYPE_BITMAP, TYPE_RESOURCE, TYPE_DATA, TYPE_URI, TYPE_ADAPTIVE_BITMAP})
+ @IntDef({TYPE_BITMAP, TYPE_RESOURCE, TYPE_DATA, TYPE_URI, TYPE_ADAPTIVE_BITMAP,
+ TYPE_URI_ADAPTIVE_BITMAP})
public @interface IconType {
}
@@ -113,12 +119,14 @@ public final class Icon implements Parcelable {
// based on the value of mType.
// TYPE_BITMAP: Bitmap
+ // TYPE_ADAPTIVE_BITMAP: Bitmap
// TYPE_RESOURCE: Resources
// TYPE_DATA: DataBytes
private Object mObj1;
// TYPE_RESOURCE: package name
// TYPE_URI: uri string
+ // TYPE_URI_ADAPTIVE_BITMAP: uri string
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private String mString1;
@@ -141,7 +149,8 @@ public final class Icon implements Parcelable {
}
/**
- * @return The {@link android.graphics.Bitmap} held by this {@link #TYPE_BITMAP} Icon.
+ * @return The {@link android.graphics.Bitmap} held by this {@link #TYPE_BITMAP} or
+ * {@link #TYPE_ADAPTIVE_BITMAP} Icon.
* @hide
*/
@UnsupportedAppUsage
@@ -243,11 +252,12 @@ public final class Icon implements Parcelable {
}
/**
- * @return The URI (as a String) for this {@link #TYPE_URI} Icon.
+ * @return The URI (as a String) for this {@link #TYPE_URI} or {@link #TYPE_URI_ADAPTIVE_BITMAP}
+ * Icon.
* @hide
*/
public String getUriString() {
- if (mType != TYPE_URI) {
+ if (mType != TYPE_URI && mType != TYPE_URI_ADAPTIVE_BITMAP) {
throw new IllegalStateException("called getUriString() on " + this);
}
return mString1;
@@ -256,7 +266,7 @@ public final class Icon implements Parcelable {
/**
* Gets the uri used to create this icon.
* <p>
- * Only valid for icons of type {@link #TYPE_URI}.
+ * Only valid for icons of type {@link #TYPE_URI} and {@link #TYPE_URI_ADAPTIVE_BITMAP}.
* 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.
*/
@@ -272,6 +282,7 @@ public final class Icon implements Parcelable {
case TYPE_DATA: return "DATA";
case TYPE_RESOURCE: return "RESOURCE";
case TYPE_URI: return "URI";
+ case TYPE_URI_ADAPTIVE_BITMAP: return "URI_MASKABLE";
default: return "UNKNOWN";
}
}
@@ -380,28 +391,39 @@ public final class Icon implements Parcelable {
BitmapFactory.decodeByteArray(getDataBytes(), getDataOffset(), getDataLength())
);
case TYPE_URI:
- final Uri uri = getUri();
- final String scheme = uri.getScheme();
- InputStream is = null;
- if (ContentResolver.SCHEME_CONTENT.equals(scheme)
- || ContentResolver.SCHEME_FILE.equals(scheme)) {
- try {
- is = context.getContentResolver().openInputStream(uri);
- } catch (Exception e) {
- Log.w(TAG, "Unable to load image from URI: " + uri, e);
- }
- } else {
- try {
- is = new FileInputStream(new File(mString1));
- } catch (FileNotFoundException e) {
- Log.w(TAG, "Unable to load image from path: " + uri, e);
- }
- }
+ InputStream is = getUriInputStream(context);
if (is != null) {
return new BitmapDrawable(context.getResources(),
BitmapFactory.decodeStream(is));
}
break;
+ case TYPE_URI_ADAPTIVE_BITMAP:
+ is = getUriInputStream(context);
+ if (is != null) {
+ return new AdaptiveIconDrawable(null, new BitmapDrawable(context.getResources(),
+ BitmapFactory.decodeStream(is)));
+ }
+ break;
+ }
+ return null;
+ }
+
+ private InputStream getUriInputStream(Context context) {
+ final Uri uri = getUri();
+ final String scheme = uri.getScheme();
+ if (ContentResolver.SCHEME_CONTENT.equals(scheme)
+ || ContentResolver.SCHEME_FILE.equals(scheme)) {
+ try {
+ return context.getContentResolver().openInputStream(uri);
+ } catch (Exception e) {
+ Log.w(TAG, "Unable to load image from URI: " + uri, e);
+ }
+ } else {
+ try {
+ return new FileInputStream(new File(mString1));
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, "Unable to load image from path: " + uri, e);
+ }
}
return null;
}
@@ -475,6 +497,7 @@ public final class Icon implements Parcelable {
dataStream.writeInt(getResId());
break;
case TYPE_URI:
+ case TYPE_URI_ADAPTIVE_BITMAP:
dataStream.writeUTF(getUriString());
break;
}
@@ -513,6 +536,9 @@ public final class Icon implements Parcelable {
case TYPE_URI:
final String uriOrPath = inputStream.readUTF();
return createWithContentUri(uriOrPath);
+ case TYPE_URI_ADAPTIVE_BITMAP:
+ final String uri = inputStream.readUTF();
+ return createWithAdaptiveBitmapContentUri(uri);
}
}
return null;
@@ -545,6 +571,7 @@ public final class Icon implements Parcelable {
return getResId() == otherIcon.getResId()
&& Objects.equals(getResPackage(), otherIcon.getResPackage());
case TYPE_URI:
+ case TYPE_URI_ADAPTIVE_BITMAP:
return Objects.equals(getUriString(), otherIcon.getUriString());
}
return false;
@@ -665,12 +692,40 @@ public final class Icon implements Parcelable {
if (uri == null) {
throw new IllegalArgumentException("Uri must not be null.");
}
- final Icon rep = new Icon(TYPE_URI);
- rep.mString1 = uri.toString();
+ return createWithContentUri(uri.toString());
+ }
+
+ /**
+ * Create an Icon pointing to an image file specified by URI. Image file should follow the icon
+ * design guideline defined by {@link AdaptiveIconDrawable}.
+ *
+ * @param uri A uri referring to local content:// or file:// image data.
+ */
+ @NonNull
+ public static Icon createWithAdaptiveBitmapContentUri(@NonNull String uri) {
+ if (uri == null) {
+ throw new IllegalArgumentException("Uri must not be null.");
+ }
+ final Icon rep = new Icon(TYPE_URI_ADAPTIVE_BITMAP);
+ rep.mString1 = uri;
return rep;
}
/**
+ * Create an Icon pointing to an image file specified by URI. Image file should follow the icon
+ * design guideline defined by {@link AdaptiveIconDrawable}.
+ *
+ * @param uri A uri referring to local content:// or file:// image data.
+ */
+ @NonNull
+ public static Icon createWithAdaptiveBitmapContentUri(@NonNull Uri uri) {
+ if (uri == null) {
+ throw new IllegalArgumentException("Uri must not be null.");
+ }
+ return createWithAdaptiveBitmapContentUri(uri.toString());
+ }
+
+ /**
* Store a color to use whenever this Icon is drawn.
*
* @param tint a color, as in {@link Drawable#setTint(int)}
@@ -758,6 +813,7 @@ public final class Icon implements Parcelable {
}
break;
case TYPE_URI:
+ case TYPE_URI_ADAPTIVE_BITMAP:
sb.append(" uri=").append(getUriString());
break;
}
@@ -809,6 +865,7 @@ public final class Icon implements Parcelable {
mObj1 = a;
break;
case TYPE_URI:
+ case TYPE_URI_ADAPTIVE_BITMAP:
final String uri = in.readString();
mString1 = uri;
break;
@@ -840,6 +897,7 @@ public final class Icon implements Parcelable {
dest.writeBlob(getDataBytes(), getDataOffset(), getDataLength());
break;
case TYPE_URI:
+ case TYPE_URI_ADAPTIVE_BITMAP:
dest.writeString(getUriString());
break;
}
diff --git a/graphics/java/android/graphics/drawable/RippleForeground.java b/graphics/java/android/graphics/drawable/RippleForeground.java
index cce9ba31929f..0f376957c8ff 100644
--- a/graphics/java/android/graphics/drawable/RippleForeground.java
+++ b/graphics/java/android/graphics/drawable/RippleForeground.java
@@ -25,9 +25,9 @@ import android.graphics.CanvasProperty;
import android.graphics.Paint;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
+import android.graphics.animation.RenderNodeAnimator;
import android.util.FloatProperty;
import android.util.MathUtils;
-import android.view.RenderNodeAnimator;
import android.view.animation.AnimationUtils;
import android.view.animation.LinearInterpolator;
import android.view.animation.PathInterpolator;
diff --git a/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java b/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java
index 475e0bb70f2b..28ba60577fb1 100644
--- a/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java
+++ b/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java
@@ -94,7 +94,7 @@ public class RoundRectShape extends RectShape {
for (int i = 1; i < 8; i++) {
if (mOuterRadii[i] != radius) {
// can't call simple constructors, use path
- outline.setConvexPath(mPath);
+ outline.setPath(mPath);
return;
}
}
diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java
index 552088f7c478..b09082e65ca4 100644
--- a/graphics/java/android/graphics/fonts/Font.java
+++ b/graphics/java/android/graphics/fonts/Font.java
@@ -19,6 +19,7 @@ package android.graphics.fonts;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.LocaleList;
@@ -35,7 +36,9 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.InputStream;
import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.Objects;
@@ -54,10 +57,6 @@ public final class Font {
* A builder class for creating new Font.
*/
public static final class Builder {
- private static final NativeAllocationRegistry sAssetByteBufferRegistry =
- NativeAllocationRegistry.createMalloced(ByteBuffer.class.getClassLoader(),
- nGetReleaseNativeAssetFunc());
-
private static final NativeAllocationRegistry sFontRegistry =
NativeAllocationRegistry.createMalloced(Font.class.getClassLoader(),
nGetReleaseNativeFont());
@@ -151,7 +150,11 @@ public final class Font {
* @param path the file name of the font data in the asset directory
*/
public Builder(@NonNull AssetManager am, @NonNull String path) {
- this(am, path, true /* is asset */, 0 /* cookie */);
+ try {
+ mBuffer = createBuffer(am, path, true /* is asset */, 0 /* cookie */);
+ } catch (IOException e) {
+ mException = e;
+ }
}
/**
@@ -165,18 +168,11 @@ public final class Font {
*/
public Builder(@NonNull AssetManager am, @NonNull String path, boolean isAsset,
int cookie) {
- final long nativeAsset = nGetNativeAsset(am, path, isAsset, cookie);
- if (nativeAsset == 0) {
- mException = new FileNotFoundException("Unable to open " + path);
- return;
- }
- final ByteBuffer b = nGetAssetBuffer(nativeAsset);
- sAssetByteBufferRegistry.registerNativeAllocation(b, nativeAsset);
- if (b == null) {
- mException = new FileNotFoundException(path + " not found");
- return;
+ try {
+ mBuffer = createBuffer(am, path, isAsset, cookie);
+ } catch (IOException e) {
+ mException = e;
}
- mBuffer = b;
}
/**
@@ -199,19 +195,66 @@ public final class Font {
mException = new FileNotFoundException(resId + " must be font file.");
return;
}
- final long nativeAsset = nGetNativeAsset(res.getAssets(), str, false /* is asset */,
- value.assetCookie);
- if (nativeAsset == 0) {
- mException = new FileNotFoundException("Unable to open " + str);
- return;
+
+ try {
+ mBuffer = createBuffer(res.getAssets(), str, false, value.assetCookie);
+ } catch (IOException e) {
+ mException = e;
}
- final ByteBuffer b = nGetAssetBuffer(nativeAsset);
- sAssetByteBufferRegistry.registerNativeAllocation(b, nativeAsset);
- if (b == null) {
- mException = new FileNotFoundException(str + " not found");
- return;
+ }
+
+ /**
+ * Creates a buffer containing font data using the assetManager and other
+ * provided inputs.
+ *
+ * @param am the application's asset manager
+ * @param path the file name of the font data in the asset directory
+ * @param isAsset true if the undelying data is in asset
+ * @param cookie set asset cookie
+ * @return buffer containing the contents of the file
+ *
+ * @hide
+ */
+ public static ByteBuffer createBuffer(@NonNull AssetManager am, @NonNull String path,
+ boolean isAsset, int cookie) throws IOException {
+ Preconditions.checkNotNull(am, "assetManager can not be null");
+ Preconditions.checkNotNull(path, "path can not be null");
+
+ // Attempt to open as FD, which should work unless the asset is compressed
+ AssetFileDescriptor assetFD;
+ try {
+ if (isAsset) {
+ assetFD = am.openFd(path);
+ } else if (cookie > 0) {
+ assetFD = am.openNonAssetFd(cookie, path);
+ } else {
+ assetFD = am.openNonAssetFd(path);
+ }
+
+ try (FileInputStream fis = assetFD.createInputStream()) {
+ final FileChannel fc = fis.getChannel();
+ long startOffset = assetFD.getStartOffset();
+ long declaredLength = assetFD.getDeclaredLength();
+ return fc.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength);
+ }
+ } catch (IOException e) {
+ // failed to open as FD so now we will attempt to open as an input stream
+ }
+
+ try (InputStream assetStream = isAsset ? am.open(path, AssetManager.ACCESS_BUFFER)
+ : am.openNonAsset(cookie, path, AssetManager.ACCESS_BUFFER)) {
+
+ int capacity = assetStream.available();
+ ByteBuffer buffer = ByteBuffer.allocateDirect(capacity);
+ buffer.order(ByteOrder.nativeOrder());
+ assetStream.read(buffer.array(), buffer.arrayOffset(), assetStream.available());
+
+ if (assetStream.read() != -1) {
+ throw new IOException("Unable to access full contents of " + path);
+ }
+
+ return buffer;
}
- mBuffer = b;
}
/**
@@ -396,15 +439,6 @@ public final class Font {
}
/**
- * Native methods for accessing underlying buffer in Asset
- */
- private static native long nGetNativeAsset(
- @NonNull AssetManager am, @NonNull String path, boolean isAsset, int cookie);
- private static native ByteBuffer nGetAssetBuffer(long nativeAsset);
- @CriticalNative
- private static native long nGetReleaseNativeAssetFunc();
-
- /**
* Native methods for creating Font
*/
private static native long nInitBuilder();
@@ -519,12 +553,13 @@ public final class Font {
}
Font f = (Font) o;
return mFontStyle.equals(f.mFontStyle) && f.mTtcIndex == mTtcIndex
- && Arrays.equals(f.mAxes, mAxes) && f.mBuffer.equals(mBuffer);
+ && Arrays.equals(f.mAxes, mAxes) && f.mBuffer.equals(mBuffer)
+ && Objects.equals(f.mLocaleList, mLocaleList);
}
@Override
public int hashCode() {
- return Objects.hash(mFontStyle, mTtcIndex, Arrays.hashCode(mAxes), mBuffer);
+ return Objects.hash(mFontStyle, mTtcIndex, Arrays.hashCode(mAxes), mBuffer, mLocaleList);
}
@Override
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index 4a9cf14d04a5..95a8417b6f7f 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -105,7 +105,6 @@ public final class SystemFonts {
final long fontSize = fileChannel.size();
return fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
} catch (IOException e) {
- Log.e(TAG, "Error mapping font file " + fullPath);
return null;
}
}
diff --git a/graphics/java/android/graphics/pdf/PdfDocument.java b/graphics/java/android/graphics/pdf/PdfDocument.java
index 1b8336f54637..58421ab5ccd9 100644
--- a/graphics/java/android/graphics/pdf/PdfDocument.java
+++ b/graphics/java/android/graphics/pdf/PdfDocument.java
@@ -46,8 +46,8 @@ import java.util.List;
* // create a new document
* PdfDocument document = new PdfDocument();
*
- * // crate a page description
- * PageInfo pageInfo = new PageInfo.Builder(new Rect(0, 0, 100, 100), 1).create();
+ * // create a page description
+ * PageInfo pageInfo = new PageInfo.Builder(100, 100, 1).create();
*
* // start a page
* Page page = document.startPage(pageInfo);
diff --git a/graphics/java/android/graphics/pdf/TEST_MAPPING b/graphics/java/android/graphics/pdf/TEST_MAPPING
new file mode 100644
index 000000000000..d763598f5ba0
--- /dev/null
+++ b/graphics/java/android/graphics/pdf/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsPdfTestCases"
+ }
+ ]
+}