summaryrefslogtreecommitdiff
path: root/graphics
diff options
context:
space:
mode:
Diffstat (limited to 'graphics')
-rw-r--r--graphics/TEST_MAPPING7
-rw-r--r--graphics/java/android/graphics/BaseCanvas.java150
-rw-r--r--graphics/java/android/graphics/BaseRecordingCanvas.java700
-rw-r--r--graphics/java/android/graphics/Bitmap.java420
-rw-r--r--graphics/java/android/graphics/BitmapFactory.java75
-rw-r--r--graphics/java/android/graphics/BitmapRegionDecoder.java11
-rw-r--r--graphics/java/android/graphics/BitmapShader.java18
-rw-r--r--graphics/java/android/graphics/BlendMode.java588
-rw-r--r--graphics/java/android/graphics/BlendModeColorFilter.java84
-rw-r--r--graphics/java/android/graphics/Camera.java13
-rw-r--r--graphics/java/android/graphics/Canvas.java250
-rw-r--r--graphics/java/android/graphics/CanvasProperty.java3
-rw-r--r--graphics/java/android/graphics/ColorFilter.java5
-rw-r--r--graphics/java/android/graphics/ColorMatrixColorFilter.java4
-rw-r--r--graphics/java/android/graphics/ColorSpace.java333
-rw-r--r--graphics/java/android/graphics/ComposeShader.java31
-rw-r--r--graphics/java/android/graphics/FontFamily.java43
-rw-r--r--graphics/java/android/graphics/FontListParser.java44
-rw-r--r--graphics/java/android/graphics/FrameInfo.java128
-rw-r--r--graphics/java/android/graphics/GraphicBuffer.java44
-rw-r--r--graphics/java/android/graphics/HardwareRenderer.java1183
-rw-r--r--graphics/java/android/graphics/ImageDecoder.java188
-rw-r--r--graphics/java/android/graphics/ImageFormat.java73
-rw-r--r--graphics/java/android/graphics/Insets.aidl20
-rw-r--r--graphics/java/android/graphics/Insets.java94
-rw-r--r--graphics/java/android/graphics/LightingColorFilter.java3
-rw-r--r--graphics/java/android/graphics/LinearGradient.java148
-rw-r--r--graphics/java/android/graphics/Matrix.java12
-rw-r--r--graphics/java/android/graphics/Movie.java5
-rw-r--r--graphics/java/android/graphics/NinePatch.java11
-rw-r--r--graphics/java/android/graphics/Outline.java12
-rw-r--r--graphics/java/android/graphics/Paint.java871
-rw-r--r--graphics/java/android/graphics/Path.java55
-rw-r--r--graphics/java/android/graphics/Picture.java57
-rw-r--r--graphics/java/android/graphics/PixelFormat.java6
-rw-r--r--graphics/java/android/graphics/Point.java24
-rw-r--r--graphics/java/android/graphics/PointF.java12
-rw-r--r--graphics/java/android/graphics/PorterDuff.java5
-rw-r--r--graphics/java/android/graphics/PorterDuffColorFilter.java48
-rw-r--r--graphics/java/android/graphics/PorterDuffXfermode.java1
-rw-r--r--graphics/java/android/graphics/RadialGradient.java140
-rw-r--r--graphics/java/android/graphics/RecordingCanvas.java320
-rw-r--r--graphics/java/android/graphics/Rect.java107
-rw-r--r--graphics/java/android/graphics/RectF.java46
-rw-r--r--graphics/java/android/graphics/Region.java51
-rw-r--r--graphics/java/android/graphics/RenderNode.java1746
-rw-r--r--graphics/java/android/graphics/Shader.java88
-rw-r--r--graphics/java/android/graphics/SurfaceTexture.java7
-rw-r--r--graphics/java/android/graphics/SweepGradient.java125
-rw-r--r--graphics/java/android/graphics/TableMaskFilter.java3
-rw-r--r--graphics/java/android/graphics/TemporaryBuffer.java3
-rw-r--r--graphics/java/android/graphics/Typeface.java784
-rw-r--r--graphics/java/android/graphics/Xfermode.java3
-rw-r--r--graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java61
-rw-r--r--graphics/java/android/graphics/drawable/AnimatedImageDrawable.java16
-rw-r--r--graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java3
-rw-r--r--graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java5
-rw-r--r--graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java35
-rw-r--r--graphics/java/android/graphics/drawable/AnimationDrawable.java2
-rw-r--r--graphics/java/android/graphics/drawable/BitmapDrawable.java49
-rw-r--r--graphics/java/android/graphics/drawable/ClipDrawable.java2
-rw-r--r--graphics/java/android/graphics/drawable/ColorDrawable.java65
-rw-r--r--graphics/java/android/graphics/drawable/ColorStateListDrawable.java306
-rw-r--r--graphics/java/android/graphics/drawable/Drawable.java135
-rw-r--r--graphics/java/android/graphics/drawable/DrawableContainer.java27
-rw-r--r--graphics/java/android/graphics/drawable/DrawableInflater.java2
-rw-r--r--graphics/java/android/graphics/drawable/DrawableWrapper.java32
-rw-r--r--graphics/java/android/graphics/drawable/GradientDrawable.java435
-rw-r--r--graphics/java/android/graphics/drawable/Icon.java45
-rw-r--r--graphics/java/android/graphics/drawable/InsetDrawable.java13
-rw-r--r--graphics/java/android/graphics/drawable/LayerDrawable.java24
-rw-r--r--graphics/java/android/graphics/drawable/NinePatchDrawable.java40
-rw-r--r--graphics/java/android/graphics/drawable/RippleComponent.java7
-rw-r--r--graphics/java/android/graphics/drawable/RippleDrawable.java11
-rw-r--r--graphics/java/android/graphics/drawable/RippleForeground.java10
-rw-r--r--graphics/java/android/graphics/drawable/RotateDrawable.java2
-rw-r--r--graphics/java/android/graphics/drawable/ScaleDrawable.java2
-rw-r--r--graphics/java/android/graphics/drawable/ShapeDrawable.java39
-rw-r--r--graphics/java/android/graphics/drawable/StateListDrawable.java37
-rw-r--r--graphics/java/android/graphics/drawable/TransitionDrawable.java4
-rw-r--r--graphics/java/android/graphics/drawable/VectorDrawable.java67
-rw-r--r--graphics/java/android/graphics/drawable/shapes/ArcShape.java23
-rw-r--r--graphics/java/android/graphics/drawable/shapes/PathShape.java27
-rw-r--r--graphics/java/android/graphics/drawable/shapes/RectShape.java22
-rw-r--r--graphics/java/android/graphics/drawable/shapes/RoundRectShape.java30
-rw-r--r--graphics/java/android/graphics/drawable/shapes/Shape.java20
-rw-r--r--graphics/java/android/graphics/fonts/Font.java541
-rw-r--r--graphics/java/android/graphics/fonts/FontCustomizationParser.java105
-rw-r--r--graphics/java/android/graphics/fonts/FontFamily.java172
-rw-r--r--graphics/java/android/graphics/fonts/FontFileUtil.java134
-rw-r--r--graphics/java/android/graphics/fonts/FontStyle.java267
-rw-r--r--graphics/java/android/graphics/fonts/FontVariationAxis.java22
-rw-r--r--graphics/java/android/graphics/fonts/SystemFonts.java319
-rw-r--r--graphics/java/android/graphics/pdf/PdfEditor.java1
-rw-r--r--graphics/java/android/graphics/pdf/PdfRenderer.java10
-rw-r--r--graphics/java/android/graphics/text/LineBreaker.java537
-rw-r--r--graphics/java/android/graphics/text/MeasuredText.java385
-rw-r--r--graphics/proto/Android.bp11
-rw-r--r--graphics/proto/game_driver.proto31
-rw-r--r--graphics/proto/jarjar-rules.txt1
100 files changed, 11544 insertions, 1792 deletions
diff --git a/graphics/TEST_MAPPING b/graphics/TEST_MAPPING
new file mode 100644
index 000000000000..10bd0ee906fd
--- /dev/null
+++ b/graphics/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsGraphicsTestCases"
+ }
+ ]
+}
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index 71ee6c2b4421..6e7f286d19a7 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -17,20 +17,23 @@
package android.graphics;
import android.annotation.ColorInt;
+import android.annotation.ColorLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
+import android.annotation.UnsupportedAppUsage;
import android.graphics.Canvas.VertexMode;
+import android.graphics.text.MeasuredText;
import android.text.GraphicsOperations;
+import android.text.MeasuredParagraph;
import android.text.PrecomputedText;
import android.text.SpannableString;
import android.text.SpannedString;
import android.text.TextUtils;
-import android.view.RecordingCanvas;
/**
* This class is a base class for Canvas's drawing operations. Any modifications here
- * should be accompanied by a similar modification to {@link RecordingCanvas}.
+ * should be accompanied by a similar modification to {@link BaseRecordingCanvas}.
*
* The purpose of this class is to minimize the cost of deciding between regular JNI
* and @FastNative JNI to just the virtual call that Canvas already has.
@@ -43,6 +46,7 @@ public abstract class BaseCanvas {
* freed by NativeAllocation.
* @hide
*/
+ @UnsupportedAppUsage
protected long mNativeCanvasWrapper;
/**
@@ -81,7 +85,7 @@ public abstract class BaseCanvas {
// ---------------------------------------------------------------------------
// Drawing methods
- // These are also implemented in DisplayListCanvas so that we can
+ // These are also implemented in RecordingCanvas so that we can
// selectively apply on them
// Everything below here is copy/pasted from Canvas.java
// The JNI registration is handled by android_view_Canvas.cpp
@@ -108,14 +112,14 @@ public abstract class BaseCanvas {
public void drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint) {
throwIfCannotDraw(bitmap);
throwIfHasHwBitmapInSwMode(paint);
- nDrawBitmap(mNativeCanvasWrapper, bitmap, left, top,
+ nDrawBitmap(mNativeCanvasWrapper, bitmap.getNativeInstance(), left, top,
paint != null ? paint.getNativeInstance() : 0, mDensity, mScreenDensity,
bitmap.mDensity);
}
public void drawBitmap(@NonNull Bitmap bitmap, @NonNull Matrix matrix, @Nullable Paint paint) {
throwIfHasHwBitmapInSwMode(paint);
- nDrawBitmapMatrix(mNativeCanvasWrapper, bitmap, matrix.ni(),
+ nDrawBitmapMatrix(mNativeCanvasWrapper, bitmap.getNativeInstance(), matrix.ni(),
paint != null ? paint.getNativeInstance() : 0);
}
@@ -140,7 +144,7 @@ public abstract class BaseCanvas {
bottom = src.bottom;
}
- nDrawBitmap(mNativeCanvasWrapper, bitmap, left, top, right, bottom,
+ nDrawBitmap(mNativeCanvasWrapper, bitmap.getNativeInstance(), left, top, right, bottom,
dst.left, dst.top, dst.right, dst.bottom, nativePaint, mScreenDensity,
bitmap.mDensity);
}
@@ -166,7 +170,7 @@ public abstract class BaseCanvas {
bottom = src.bottom;
}
- nDrawBitmap(mNativeCanvasWrapper, bitmap, left, top, right, bottom,
+ nDrawBitmap(mNativeCanvasWrapper, bitmap.getNativeInstance(), left, top, right, bottom,
dst.left, dst.top, dst.right, dst.bottom, nativePaint, mScreenDensity,
bitmap.mDensity);
}
@@ -225,7 +229,7 @@ public abstract class BaseCanvas {
// no mul by 2, since we need only 1 color per vertex
checkRange(colors.length, colorOffset, count);
}
- nDrawBitmapMesh(mNativeCanvasWrapper, bitmap, meshWidth, meshHeight,
+ nDrawBitmapMesh(mNativeCanvasWrapper, bitmap.getNativeInstance(), meshWidth, meshHeight,
verts, vertOffset, colors, colorOffset,
paint != null ? paint.getNativeInstance() : 0);
}
@@ -236,13 +240,31 @@ public abstract class BaseCanvas {
}
public void drawColor(@ColorInt int color) {
- nDrawColor(mNativeCanvasWrapper, color, PorterDuff.Mode.SRC_OVER.nativeInt);
+ nDrawColor(mNativeCanvasWrapper, color, BlendMode.SRC_OVER.getXfermode().porterDuffMode);
}
public void drawColor(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
nDrawColor(mNativeCanvasWrapper, color, mode.nativeInt);
}
+ /**
+ * Make lint happy.
+ * See {@link Canvas#drawColor(int, BlendMode)}
+ */
+ public void drawColor(@ColorInt int color, @NonNull BlendMode mode) {
+ nDrawColor(mNativeCanvasWrapper, color, mode.getXfermode().porterDuffMode);
+ }
+
+ /**
+ * Make lint happy.
+ * See {@link Canvas#drawColor(long, BlendMode)}
+ */
+ public void drawColor(@ColorLong long color, @NonNull BlendMode mode) {
+ ColorSpace cs = Color.colorSpace(color);
+ nDrawColor(mNativeCanvasWrapper, cs.getNativeInstance(), color,
+ mode.getXfermode().porterDuffMode);
+ }
+
public void drawLine(float startX, float startY, float stopX, float stopY,
@NonNull Paint paint) {
throwIfHasHwBitmapInSwMode(paint);
@@ -374,6 +396,53 @@ public abstract class BaseCanvas {
drawRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, paint);
}
+ /**
+ * Make lint happy.
+ * See {@link Canvas#drawDoubleRoundRect(RectF, float, float, RectF, float, float, Paint)}
+ */
+ public void drawDoubleRoundRect(@NonNull RectF outer, float outerRx, float outerRy,
+ @NonNull RectF inner, float innerRx, float innerRy, @NonNull Paint paint) {
+ throwIfHasHwBitmapInSwMode(paint);
+ float outerLeft = outer.left;
+ float outerTop = outer.top;
+ float outerRight = outer.right;
+ float outerBottom = outer.bottom;
+
+ float innerLeft = inner.left;
+ float innerTop = inner.top;
+ float innerRight = inner.right;
+ float innerBottom = inner.bottom;
+ nDrawDoubleRoundRect(mNativeCanvasWrapper, outerLeft, outerTop, outerRight, outerBottom,
+ outerRx, outerRy, innerLeft, innerTop, innerRight, innerBottom, innerRx, innerRy,
+ paint.getNativeInstance());
+ }
+
+ /**
+ * Make lint happy.
+ * See {@link Canvas#drawDoubleRoundRect(RectF, float[], RectF, float[], Paint)}
+ */
+ public void drawDoubleRoundRect(@NonNull RectF outer, @NonNull float[] outerRadii,
+ @NonNull RectF inner, @NonNull float[] innerRadii, @NonNull Paint paint) {
+ throwIfHasHwBitmapInSwMode(paint);
+ if (innerRadii == null || outerRadii == null
+ || innerRadii.length != 8 || outerRadii.length != 8) {
+ throw new IllegalArgumentException("Both inner and outer radii arrays must contain "
+ + "exactly 8 values");
+ }
+ float outerLeft = outer.left;
+ float outerTop = outer.top;
+ float outerRight = outer.right;
+ float outerBottom = outer.bottom;
+
+ float innerLeft = inner.left;
+ float innerTop = inner.top;
+ float innerRight = inner.right;
+ float innerBottom = inner.bottom;
+ nDrawDoubleRoundRect(mNativeCanvasWrapper, outerLeft, outerTop, outerRight,
+ outerBottom, outerRadii, innerLeft, innerTop, innerRight, innerBottom, innerRadii,
+ paint.getNativeInstance());
+ }
+
public void drawText(@NonNull char[] text, int index, int count, float x, float y,
@NonNull Paint paint) {
if ((index | count | (index + count) |
@@ -486,33 +555,46 @@ public abstract class BaseCanvas {
((GraphicsOperations) text).drawTextRun(this, start, end,
contextStart, contextEnd, x, y, isRtl, paint);
} else {
+ if (text instanceof PrecomputedText) {
+ final PrecomputedText pt = (PrecomputedText) text;
+ final int paraIndex = pt.findParaIndex(start);
+ if (end <= pt.getParagraphEnd(paraIndex)) {
+ final int paraStart = pt.getParagraphStart(paraIndex);
+ final MeasuredParagraph mp = pt.getMeasuredParagraph(paraIndex);
+ // Only support the text in the same paragraph.
+ drawTextRun(mp.getMeasuredText(),
+ start - paraStart,
+ end - paraStart,
+ contextStart - paraStart,
+ contextEnd - paraStart,
+ x, y, isRtl, paint);
+ return;
+ }
+ }
int contextLen = contextEnd - contextStart;
int len = end - start;
char[] buf = TemporaryBuffer.obtain(contextLen);
TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
- long measuredTextPtr = 0;
- if (text instanceof PrecomputedText) {
- PrecomputedText mt = (PrecomputedText) text;
- int paraIndex = mt.findParaIndex(start);
- if (end <= mt.getParagraphEnd(paraIndex)) {
- // Only suppor the text in the same paragraph.
- measuredTextPtr = mt.getMeasuredParagraph(paraIndex).getNativePtr();
- }
- }
nDrawTextRun(mNativeCanvasWrapper, buf, start - contextStart, len,
- 0, contextLen, x, y, isRtl, paint.getNativeInstance(), measuredTextPtr);
+ 0, contextLen, x, y, isRtl, paint.getNativeInstance(),
+ 0 /* measured paragraph pointer */);
TemporaryBuffer.recycle(buf);
}
}
+ public void drawTextRun(@NonNull MeasuredText measuredText, int start, int end,
+ int contextStart, int contextEnd, float x, float y, boolean isRtl,
+ @NonNull Paint paint) {
+ nDrawTextRun(mNativeCanvasWrapper, measuredText.getChars(), start, end - start,
+ contextStart, contextEnd - contextStart, x, y, isRtl, paint.getNativeInstance(),
+ measuredText.getNativePtr());
+ }
+
public void drawVertices(@NonNull VertexMode mode, int vertexCount, @NonNull float[] verts,
int vertOffset, @Nullable float[] texs, int texOffset, @Nullable int[] colors,
int colorOffset, @Nullable short[] indices, int indexOffset, int indexCount,
@NonNull Paint paint) {
checkRange(verts.length, vertOffset, vertexCount);
- if (isHardwareAccelerated()) {
- return;
- }
if (texs != null) {
checkRange(texs.length, texOffset, vertexCount);
}
@@ -578,10 +660,11 @@ public abstract class BaseCanvas {
}
}
- private static native void nDrawBitmap(long nativeCanvas, Bitmap bitmap, float left, float top,
- long nativePaintOrZero, int canvasDensity, int screenDensity, int bitmapDensity);
+ private static native void nDrawBitmap(long nativeCanvas, long bitmapHandle, float left,
+ float top, long nativePaintOrZero, int canvasDensity, int screenDensity,
+ int bitmapDensity);
- private static native void nDrawBitmap(long nativeCanvas, Bitmap bitmap, float srcLeft,
+ private static native void nDrawBitmap(long nativeCanvas, long bitmapHandle, float srcLeft,
float srcTop,
float srcRight, float srcBottom, float dstLeft, float dstTop, float dstRight,
float dstBottom, long nativePaintOrZero, int screenDensity, int bitmapDensity);
@@ -591,6 +674,9 @@ public abstract class BaseCanvas {
private static native void nDrawColor(long nativeCanvas, int color, int mode);
+ private static native void nDrawColor(long nativeCanvas, long nativeColorSpace,
+ @ColorLong long color, int mode);
+
private static native void nDrawPaint(long nativeCanvas, long nativePaint);
private static native void nDrawPoint(long canvasHandle, float x, float y, long paintHandle);
@@ -619,6 +705,16 @@ public abstract class BaseCanvas {
private static native void nDrawRoundRect(long nativeCanvas, float left, float top, float right,
float bottom, float rx, float ry, long nativePaint);
+ private static native void nDrawDoubleRoundRect(long nativeCanvas, float outerLeft,
+ float outerTop, float outerRight, float outerBottom, float outerRx, float outerRy,
+ float innerLeft, float innerTop, float innerRight, float innerBottom, float innerRx,
+ float innerRy, long nativePaint);
+
+ private static native void nDrawDoubleRoundRect(long nativeCanvas, float outerLeft,
+ float outerTop, float outerRight, float outerBottom, float[] outerRadii,
+ float innerLeft, float innerTop, float innerRight, float innerBottom,
+ float[] innerRadii, long nativePaint);
+
private static native void nDrawPath(long nativeCanvas, long nativePath, long nativePaint);
private static native void nDrawRegion(long nativeCanvas, long nativeRegion, long nativePaint);
@@ -627,10 +723,10 @@ public abstract class BaseCanvas {
float dstLeft, float dstTop, float dstRight, float dstBottom, long nativePaintOrZero,
int screenDensity, int bitmapDensity);
- private static native void nDrawBitmapMatrix(long nativeCanvas, Bitmap bitmap,
+ private static native void nDrawBitmapMatrix(long nativeCanvas, long bitmapHandle,
long nativeMatrix, long nativePaint);
- private static native void nDrawBitmapMesh(long nativeCanvas, Bitmap bitmap, int meshWidth,
+ private static native void nDrawBitmapMesh(long nativeCanvas, long bitmapHandle, int meshWidth,
int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset,
long nativePaint);
diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java
new file mode 100644
index 000000000000..2f5214cb1df5
--- /dev/null
+++ b/graphics/java/android/graphics/BaseRecordingCanvas.java
@@ -0,0 +1,700 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import android.annotation.ColorInt;
+import android.annotation.ColorLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.Size;
+import android.graphics.text.MeasuredText;
+import android.text.GraphicsOperations;
+import android.text.MeasuredParagraph;
+import android.text.PrecomputedText;
+import android.text.SpannableString;
+import android.text.SpannedString;
+import android.text.TextUtils;
+
+import dalvik.annotation.optimization.FastNative;
+
+/**
+ * This class is a base class for canvases that defer drawing operations, so all
+ * the draw operations can be marked @FastNative. It contains a re-implementation of
+ * all the methods in {@link BaseCanvas}.
+ *
+ * @hide
+ */
+public class BaseRecordingCanvas extends Canvas {
+
+ public BaseRecordingCanvas(long nativeCanvas) {
+ super(nativeCanvas);
+ }
+
+ @Override
+ public final void drawArc(float left, float top, float right, float bottom, float startAngle,
+ float sweepAngle, boolean useCenter, @NonNull Paint paint) {
+ nDrawArc(mNativeCanvasWrapper, left, top, right, bottom, startAngle, sweepAngle,
+ useCenter, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle,
+ boolean useCenter, @NonNull Paint paint) {
+ drawArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, useCenter,
+ paint);
+ }
+
+ @Override
+ public final void drawARGB(int a, int r, int g, int b) {
+ drawColor(Color.argb(a, r, g, b));
+ }
+
+ @Override
+ public final void drawBitmap(@NonNull Bitmap bitmap, float left, float top,
+ @Nullable Paint paint) {
+ throwIfCannotDraw(bitmap);
+ nDrawBitmap(mNativeCanvasWrapper, bitmap.getNativeInstance(), left, top,
+ paint != null ? paint.getNativeInstance() : 0, mDensity, mScreenDensity,
+ bitmap.mDensity);
+ }
+
+ @Override
+ public final void drawBitmap(@NonNull Bitmap bitmap, @NonNull Matrix matrix,
+ @Nullable Paint paint) {
+ nDrawBitmapMatrix(mNativeCanvasWrapper, bitmap.getNativeInstance(), matrix.ni(),
+ paint != null ? paint.getNativeInstance() : 0);
+ }
+
+ @Override
+ public final void drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull Rect dst,
+ @Nullable Paint paint) {
+ if (dst == null) {
+ throw new NullPointerException();
+ }
+ throwIfCannotDraw(bitmap);
+ final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
+
+ int left, top, right, bottom;
+ if (src == null) {
+ left = top = 0;
+ right = bitmap.getWidth();
+ bottom = bitmap.getHeight();
+ } else {
+ left = src.left;
+ right = src.right;
+ top = src.top;
+ bottom = src.bottom;
+ }
+
+ nDrawBitmap(mNativeCanvasWrapper, bitmap.getNativeInstance(), left, top, right, bottom,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint, mScreenDensity,
+ bitmap.mDensity);
+ }
+
+ @Override
+ public final void drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull RectF dst,
+ @Nullable Paint paint) {
+ if (dst == null) {
+ throw new NullPointerException();
+ }
+ throwIfCannotDraw(bitmap);
+ final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
+
+ float left, top, right, bottom;
+ if (src == null) {
+ left = top = 0;
+ right = bitmap.getWidth();
+ bottom = bitmap.getHeight();
+ } else {
+ left = src.left;
+ right = src.right;
+ top = src.top;
+ bottom = src.bottom;
+ }
+
+ nDrawBitmap(mNativeCanvasWrapper, bitmap.getNativeInstance(), left, top, right, bottom,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint, mScreenDensity,
+ bitmap.mDensity);
+ }
+
+ /** @deprecated checkstyle */
+ @Override
+ @Deprecated
+ public final void drawBitmap(@NonNull int[] colors, int offset, int stride, float x, float y,
+ int width, int height, boolean hasAlpha, @Nullable Paint paint) {
+ // check for valid input
+ if (width < 0) {
+ throw new IllegalArgumentException("width must be >= 0");
+ }
+ if (height < 0) {
+ throw new IllegalArgumentException("height must be >= 0");
+ }
+ if (Math.abs(stride) < width) {
+ throw new IllegalArgumentException("abs(stride) must be >= width");
+ }
+ int lastScanline = offset + (height - 1) * stride;
+ int length = colors.length;
+ if (offset < 0 || (offset + width > length) || lastScanline < 0
+ || (lastScanline + width > length)) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ // quick escape if there's nothing to draw
+ if (width == 0 || height == 0) {
+ return;
+ }
+ // punch down to native for the actual draw
+ nDrawBitmap(mNativeCanvasWrapper, colors, offset, stride, x, y, width, height, hasAlpha,
+ paint != null ? paint.getNativeInstance() : 0);
+ }
+
+ /** @deprecated checkstyle */
+ @Override
+ @Deprecated
+ public final void drawBitmap(@NonNull int[] colors, int offset, int stride, int x, int y,
+ int width, int height, boolean hasAlpha, @Nullable Paint paint) {
+ // call through to the common float version
+ drawBitmap(colors, offset, stride, (float) x, (float) y, width, height,
+ hasAlpha, paint);
+ }
+
+ @Override
+ public final void drawBitmapMesh(@NonNull Bitmap bitmap, int meshWidth, int meshHeight,
+ @NonNull float[] verts, int vertOffset, @Nullable int[] colors, int colorOffset,
+ @Nullable Paint paint) {
+ if ((meshWidth | meshHeight | vertOffset | colorOffset) < 0) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ if (meshWidth == 0 || meshHeight == 0) {
+ return;
+ }
+ int count = (meshWidth + 1) * (meshHeight + 1);
+ // we mul by 2 since we need two floats per vertex
+ checkRange(verts.length, vertOffset, count * 2);
+ if (colors != null) {
+ // no mul by 2, since we need only 1 color per vertex
+ checkRange(colors.length, colorOffset, count);
+ }
+ nDrawBitmapMesh(mNativeCanvasWrapper, bitmap.getNativeInstance(), meshWidth, meshHeight,
+ verts, vertOffset, colors, colorOffset,
+ paint != null ? paint.getNativeInstance() : 0);
+ }
+
+ @Override
+ public final void drawCircle(float cx, float cy, float radius, @NonNull Paint paint) {
+ nDrawCircle(mNativeCanvasWrapper, cx, cy, radius, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawColor(@ColorInt int color) {
+ nDrawColor(mNativeCanvasWrapper, color, BlendMode.SRC_OVER.getXfermode().porterDuffMode);
+ }
+
+ @Override
+ public final void drawColor(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
+ nDrawColor(mNativeCanvasWrapper, color, mode.nativeInt);
+ }
+
+ @Override
+ public final void drawColor(@ColorInt int color, @NonNull BlendMode mode) {
+ nDrawColor(mNativeCanvasWrapper, color, mode.getXfermode().porterDuffMode);
+ }
+
+ @Override
+ public final void drawColor(@ColorLong long color, @NonNull BlendMode mode) {
+ ColorSpace cs = Color.colorSpace(color);
+ nDrawColor(mNativeCanvasWrapper, cs.getNativeInstance(), color,
+ mode.getXfermode().porterDuffMode);
+ }
+
+ @Override
+ public final void drawLine(float startX, float startY, float stopX, float stopY,
+ @NonNull Paint paint) {
+ nDrawLine(mNativeCanvasWrapper, startX, startY, stopX, stopY, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawLines(@Size(multiple = 4) @NonNull float[] pts, int offset, int count,
+ @NonNull Paint paint) {
+ nDrawLines(mNativeCanvasWrapper, pts, offset, count, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawLines(@Size(multiple = 4) @NonNull float[] pts, @NonNull Paint paint) {
+ drawLines(pts, 0, pts.length, paint);
+ }
+
+ @Override
+ public final void drawOval(float left, float top, float right, float bottom,
+ @NonNull Paint paint) {
+ nDrawOval(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawOval(@NonNull RectF oval, @NonNull Paint paint) {
+ if (oval == null) {
+ throw new NullPointerException();
+ }
+ drawOval(oval.left, oval.top, oval.right, oval.bottom, paint);
+ }
+
+ @Override
+ public final void drawPaint(@NonNull Paint paint) {
+ nDrawPaint(mNativeCanvasWrapper, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawPatch(@NonNull NinePatch patch, @NonNull Rect dst,
+ @Nullable Paint paint) {
+ Bitmap bitmap = patch.getBitmap();
+ throwIfCannotDraw(bitmap);
+ final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
+ nDrawNinePatch(mNativeCanvasWrapper, bitmap.getNativeInstance(), patch.mNativeChunk,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint,
+ mDensity, patch.getDensity());
+ }
+
+ @Override
+ public final void drawPatch(@NonNull NinePatch patch, @NonNull RectF dst,
+ @Nullable Paint paint) {
+ Bitmap bitmap = patch.getBitmap();
+ throwIfCannotDraw(bitmap);
+ final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
+ nDrawNinePatch(mNativeCanvasWrapper, bitmap.getNativeInstance(), patch.mNativeChunk,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint,
+ mDensity, patch.getDensity());
+ }
+
+ @Override
+ public final void drawPath(@NonNull Path path, @NonNull Paint paint) {
+ if (path.isSimplePath && path.rects != null) {
+ nDrawRegion(mNativeCanvasWrapper, path.rects.mNativeRegion, paint.getNativeInstance());
+ } else {
+ nDrawPath(mNativeCanvasWrapper, path.readOnlyNI(), paint.getNativeInstance());
+ }
+ }
+
+ @Override
+ public final void drawPicture(@NonNull Picture picture) {
+ picture.endRecording();
+ int restoreCount = save();
+ picture.draw(this);
+ restoreToCount(restoreCount);
+ }
+
+ @Override
+ public final void drawPicture(@NonNull Picture picture, @NonNull Rect dst) {
+ save();
+ translate(dst.left, dst.top);
+ if (picture.getWidth() > 0 && picture.getHeight() > 0) {
+ scale((float) dst.width() / picture.getWidth(),
+ (float) dst.height() / picture.getHeight());
+ }
+ drawPicture(picture);
+ restore();
+ }
+
+ @Override
+ public final void drawPicture(@NonNull Picture picture, @NonNull RectF dst) {
+ save();
+ translate(dst.left, dst.top);
+ if (picture.getWidth() > 0 && picture.getHeight() > 0) {
+ scale(dst.width() / picture.getWidth(), dst.height() / picture.getHeight());
+ }
+ drawPicture(picture);
+ restore();
+ }
+
+ @Override
+ public final void drawPoint(float x, float y, @NonNull Paint paint) {
+ nDrawPoint(mNativeCanvasWrapper, x, y, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawPoints(@Size(multiple = 2) float[] pts, int offset, int count,
+ @NonNull Paint paint) {
+ nDrawPoints(mNativeCanvasWrapper, pts, offset, count, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawPoints(@Size(multiple = 2) @NonNull float[] pts, @NonNull Paint paint) {
+ drawPoints(pts, 0, pts.length, paint);
+ }
+
+ /** @deprecated checkstyle */
+ @Override
+ @Deprecated
+ public final void drawPosText(@NonNull char[] text, int index, int count,
+ @NonNull @Size(multiple = 2) float[] pos,
+ @NonNull Paint paint) {
+ if (index < 0 || index + count > text.length || count * 2 > pos.length) {
+ throw new IndexOutOfBoundsException();
+ }
+ for (int i = 0; i < count; i++) {
+ drawText(text, index + i, 1, pos[i * 2], pos[i * 2 + 1], paint);
+ }
+ }
+
+ /** @deprecated checkstyle */
+ @Override
+ @Deprecated
+ public final void drawPosText(@NonNull String text, @NonNull @Size(multiple = 2) float[] pos,
+ @NonNull Paint paint) {
+ drawPosText(text.toCharArray(), 0, text.length(), pos, paint);
+ }
+
+ @Override
+ public final void drawRect(float left, float top, float right, float bottom,
+ @NonNull Paint paint) {
+ nDrawRect(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawRect(@NonNull Rect r, @NonNull Paint paint) {
+ drawRect(r.left, r.top, r.right, r.bottom, paint);
+ }
+
+ @Override
+ public final void drawRect(@NonNull RectF rect, @NonNull Paint paint) {
+ nDrawRect(mNativeCanvasWrapper,
+ rect.left, rect.top, rect.right, rect.bottom, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawRGB(int r, int g, int b) {
+ drawColor(Color.rgb(r, g, b));
+ }
+
+ @Override
+ public final void drawRoundRect(float left, float top, float right, float bottom,
+ float rx, float ry, @NonNull Paint paint) {
+ nDrawRoundRect(mNativeCanvasWrapper, left, top, right, bottom, rx, ry,
+ paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint) {
+ drawRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, paint);
+ }
+
+ @Override
+ public final void drawDoubleRoundRect(@NonNull RectF outer, float outerRx, float outerRy,
+ @NonNull RectF inner, float innerRx, float innerRy, @NonNull Paint paint) {
+ nDrawDoubleRoundRect(mNativeCanvasWrapper,
+ outer.left, outer.top, outer.right, outer.bottom, outerRx, outerRy,
+ inner.left, inner.top, inner.right, inner.bottom, innerRx, innerRy,
+ paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawDoubleRoundRect(@NonNull RectF outer, float[] outerRadii,
+ @NonNull RectF inner, float[] innerRadii, @NonNull Paint paint) {
+ nDrawDoubleRoundRect(mNativeCanvasWrapper,
+ outer.left, outer.top, outer.right, outer.bottom, outerRadii,
+ inner.left, inner.top, inner.right, inner.bottom, innerRadii,
+ paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawText(@NonNull char[] text, int index, int count, float x, float y,
+ @NonNull Paint paint) {
+ if ((index | count | (index + count)
+ | (text.length - index - count)) < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ nDrawText(mNativeCanvasWrapper, text, index, count, x, y, paint.mBidiFlags,
+ paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawText(@NonNull CharSequence text, int start, int end, float x, float y,
+ @NonNull Paint paint) {
+ if ((start | end | (end - start) | (text.length() - end)) < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ if (text instanceof String || text instanceof SpannedString
+ || text instanceof SpannableString) {
+ nDrawText(mNativeCanvasWrapper, text.toString(), start, end, x, y,
+ paint.mBidiFlags, paint.getNativeInstance());
+ } else if (text instanceof GraphicsOperations) {
+ ((GraphicsOperations) text).drawText(this, start, end, x, y,
+ paint);
+ } else {
+ char[] buf = TemporaryBuffer.obtain(end - start);
+ TextUtils.getChars(text, start, end, buf, 0);
+ nDrawText(mNativeCanvasWrapper, buf, 0, end - start, x, y,
+ paint.mBidiFlags, paint.getNativeInstance());
+ TemporaryBuffer.recycle(buf);
+ }
+ }
+
+ @Override
+ public final void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) {
+ nDrawText(mNativeCanvasWrapper, text, 0, text.length(), x, y, paint.mBidiFlags,
+ paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawText(@NonNull String text, int start, int end, float x, float y,
+ @NonNull Paint paint) {
+ if ((start | end | (end - start) | (text.length() - end)) < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ nDrawText(mNativeCanvasWrapper, text, start, end, x, y, paint.mBidiFlags,
+ paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawTextOnPath(@NonNull char[] text, int index, int count, @NonNull Path path,
+ float hOffset, float vOffset, @NonNull Paint paint) {
+ if (index < 0 || index + count > text.length) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ nDrawTextOnPath(mNativeCanvasWrapper, text, index, count,
+ path.readOnlyNI(), hOffset, vOffset,
+ paint.mBidiFlags, paint.getNativeInstance());
+ }
+
+ @Override
+ public final void drawTextOnPath(@NonNull String text, @NonNull Path path, float hOffset,
+ float vOffset, @NonNull Paint paint) {
+ if (text.length() > 0) {
+ nDrawTextOnPath(mNativeCanvasWrapper, text, path.readOnlyNI(), hOffset, vOffset,
+ paint.mBidiFlags, paint.getNativeInstance());
+ }
+ }
+
+ @Override
+ public final void drawTextRun(@NonNull char[] text, int index, int count, int contextIndex,
+ int contextCount, float x, float y, boolean isRtl, @NonNull Paint paint) {
+
+ if (text == null) {
+ throw new NullPointerException("text is null");
+ }
+ if (paint == null) {
+ throw new NullPointerException("paint is null");
+ }
+ if ((index | count | contextIndex | contextCount | index - contextIndex
+ | (contextIndex + contextCount) - (index + count)
+ | text.length - (contextIndex + contextCount)) < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ nDrawTextRun(mNativeCanvasWrapper, text, index, count, contextIndex, contextCount,
+ x, y, isRtl, paint.getNativeInstance(), 0 /* measured text */);
+ }
+
+ @Override
+ public final void drawTextRun(@NonNull CharSequence text, int start, int end, int contextStart,
+ int contextEnd, float x, float y, boolean isRtl, @NonNull Paint paint) {
+
+ if (text == null) {
+ throw new NullPointerException("text is null");
+ }
+ if (paint == null) {
+ throw new NullPointerException("paint is null");
+ }
+ if ((start | end | contextStart | contextEnd | start - contextStart | end - start
+ | contextEnd - end | text.length() - contextEnd) < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ if (text instanceof String || text instanceof SpannedString
+ || text instanceof SpannableString) {
+ nDrawTextRun(mNativeCanvasWrapper, text.toString(), start, end, contextStart,
+ contextEnd, x, y, isRtl, paint.getNativeInstance());
+ } else if (text instanceof GraphicsOperations) {
+ ((GraphicsOperations) text).drawTextRun(this, start, end,
+ contextStart, contextEnd, x, y, isRtl, paint);
+ } else {
+ if (text instanceof PrecomputedText) {
+ final PrecomputedText pt = (PrecomputedText) text;
+ final int paraIndex = pt.findParaIndex(start);
+ if (end <= pt.getParagraphEnd(paraIndex)) {
+ final int paraStart = pt.getParagraphStart(paraIndex);
+ final MeasuredParagraph mp = pt.getMeasuredParagraph(paraIndex);
+ // Only support if the target is in the same paragraph.
+ drawTextRun(mp.getMeasuredText(),
+ start - paraStart,
+ end - paraStart,
+ contextStart - paraStart,
+ contextEnd - paraStart,
+ x, y, isRtl, paint);
+ return;
+ }
+ }
+ int contextLen = contextEnd - contextStart;
+ int len = end - start;
+ char[] buf = TemporaryBuffer.obtain(contextLen);
+ TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
+ nDrawTextRun(mNativeCanvasWrapper, buf, start - contextStart, len,
+ 0, contextLen, x, y, isRtl, paint.getNativeInstance(),
+ 0 /* measured paragraph pointer */);
+ TemporaryBuffer.recycle(buf);
+ }
+ }
+
+ @Override
+ public void drawTextRun(@NonNull MeasuredText measuredText, int start, int end,
+ int contextStart, int contextEnd, float x, float y, boolean isRtl,
+ @NonNull Paint paint) {
+ nDrawTextRun(mNativeCanvasWrapper, measuredText.getChars(), start, end - start,
+ contextStart, contextEnd - contextStart, x, y, isRtl, paint.getNativeInstance(),
+ measuredText.getNativePtr());
+ }
+
+ @Override
+ public final void drawVertices(@NonNull VertexMode mode, int vertexCount,
+ @NonNull float[] verts, int vertOffset, @Nullable float[] texs, int texOffset,
+ @Nullable int[] colors, int colorOffset, @Nullable short[] indices, int indexOffset,
+ int indexCount, @NonNull Paint paint) {
+ checkRange(verts.length, vertOffset, vertexCount);
+ if (texs != null) {
+ checkRange(texs.length, texOffset, vertexCount);
+ }
+ if (colors != null) {
+ checkRange(colors.length, colorOffset, vertexCount / 2);
+ }
+ if (indices != null) {
+ checkRange(indices.length, indexOffset, indexCount);
+ }
+ nDrawVertices(mNativeCanvasWrapper, mode.nativeInt, vertexCount, verts,
+ vertOffset, texs, texOffset, colors, colorOffset,
+ indices, indexOffset, indexCount, paint.getNativeInstance());
+ }
+
+ @FastNative
+ private static native void nDrawBitmap(long nativeCanvas, long bitmapHandle, float left,
+ float top, long nativePaintOrZero, int canvasDensity, int screenDensity,
+ int bitmapDensity);
+
+ @FastNative
+ private static native void nDrawBitmap(long nativeCanvas, long bitmapHandle,
+ float srcLeft, float srcTop, float srcRight, float srcBottom,
+ float dstLeft, float dstTop, float dstRight, float dstBottom,
+ long nativePaintOrZero, int screenDensity, int bitmapDensity);
+
+ @FastNative
+ private static native void nDrawBitmap(long nativeCanvas, int[] colors, int offset, int stride,
+ float x, float y, int width, int height, boolean hasAlpha, long nativePaintOrZero);
+
+ @FastNative
+ private static native void nDrawColor(long nativeCanvas, int color, int mode);
+
+ @FastNative
+ private static native void nDrawColor(long nativeCanvas, long nativeColorSpace,
+ @ColorLong long color, int mode);
+
+ @FastNative
+ private static native void nDrawPaint(long nativeCanvas, long nativePaint);
+
+ @FastNative
+ private static native void nDrawPoint(long canvasHandle, float x, float y, long paintHandle);
+
+ @FastNative
+ private static native void nDrawPoints(long canvasHandle, float[] pts, int offset, int count,
+ long paintHandle);
+
+ @FastNative
+ private static native void nDrawLine(long nativeCanvas, float startX, float startY, float stopX,
+ float stopY, long nativePaint);
+
+ @FastNative
+ private static native void nDrawLines(long canvasHandle, float[] pts, int offset, int count,
+ long paintHandle);
+
+ @FastNative
+ private static native void nDrawRect(long nativeCanvas, float left, float top, float right,
+ float bottom, long nativePaint);
+
+ @FastNative
+ private static native void nDrawOval(long nativeCanvas, float left, float top, float right,
+ float bottom, long nativePaint);
+
+ @FastNative
+ private static native void nDrawCircle(long nativeCanvas, float cx, float cy, float radius,
+ long nativePaint);
+
+ @FastNative
+ private static native void nDrawArc(long nativeCanvas, float left, float top, float right,
+ float bottom, float startAngle, float sweep, boolean useCenter, long nativePaint);
+
+ @FastNative
+ private static native void nDrawRoundRect(long nativeCanvas, float left, float top, float right,
+ float bottom, float rx, float ry, long nativePaint);
+
+ @FastNative
+ private static native void nDrawDoubleRoundRect(long nativeCanvas,
+ float outerLeft, float outerTop, float outerRight, float outerBottom,
+ float outerRx, float outerRy, float innerLeft, float innerTop, float innerRight,
+ float innerBottom, float innerRx, float innerRy, long nativePaint);
+
+ @FastNative
+ private static native void nDrawDoubleRoundRect(long nativeCanvas, float outerLeft,
+ float outerTop, float outerRight, float outerBottom, float[] outerRadii,
+ float innerLeft, float innerTop, float innerRight, float innerBottom,
+ float[] innerRadii, long nativePaint);
+
+ @FastNative
+ private static native void nDrawPath(long nativeCanvas, long nativePath, long nativePaint);
+
+ @FastNative
+ private static native void nDrawRegion(long nativeCanvas, long nativeRegion, long nativePaint);
+
+ @FastNative
+ private static native void nDrawNinePatch(long nativeCanvas, long nativeBitmap, long ninePatch,
+ float dstLeft, float dstTop, float dstRight, float dstBottom, long nativePaintOrZero,
+ int screenDensity, int bitmapDensity);
+
+ @FastNative
+ private static native void nDrawBitmapMatrix(long nativeCanvas, long bitmapHandle,
+ long nativeMatrix, long nativePaint);
+
+ @FastNative
+ private static native void nDrawBitmapMesh(long nativeCanvas, long bitmapHandle, int meshWidth,
+ int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset,
+ long nativePaint);
+
+ @FastNative
+ private static native void nDrawVertices(long nativeCanvas, int mode, int n, float[] verts,
+ int vertOffset, float[] texs, int texOffset, int[] colors, int colorOffset,
+ short[] indices, int indexOffset, int indexCount, long nativePaint);
+
+ @FastNative
+ private static native void nDrawText(long nativeCanvas, char[] text, int index, int count,
+ float x, float y, int flags, long nativePaint);
+
+ @FastNative
+ private static native void nDrawText(long nativeCanvas, String text, int start, int end,
+ float x, float y, int flags, long nativePaint);
+
+ @FastNative
+ private static native void nDrawTextRun(long nativeCanvas, String text, int start, int end,
+ int contextStart, int contextEnd, float x, float y, boolean isRtl, long nativePaint);
+
+ @FastNative
+ private static native void nDrawTextRun(long nativeCanvas, char[] text, int start, int count,
+ int contextStart, int contextCount, float x, float y, boolean isRtl, long nativePaint,
+ long nativePrecomputedText);
+
+ @FastNative
+ private static native void nDrawTextOnPath(long nativeCanvas, char[] text, int index, int count,
+ long nativePath, float hOffset, float vOffset, int bidiFlags, long nativePaint);
+
+ @FastNative
+ private static native void nDrawTextOnPath(long nativeCanvas, String text, long nativePath,
+ float hOffset, float vOffset, int flags, long nativePaint);
+}
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 95a0c56905c0..44710178da5e 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -18,21 +18,25 @@ package android.graphics;
import android.annotation.CheckResult;
import android.annotation.ColorInt;
+import android.annotation.ColorLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.Size;
+import android.annotation.UnsupportedAppUsage;
import android.annotation.WorkerThread;
import android.content.res.ResourcesImpl;
+import android.hardware.HardwareBuffer;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.StrictMode;
import android.os.Trace;
import android.util.DisplayMetrics;
+import android.util.Half;
import android.util.Log;
-import android.view.DisplayListCanvas;
-import android.view.RenderNode;
import android.view.ThreadedRenderer;
+import dalvik.annotation.optimization.CriticalNative;
+
import libcore.util.NativeAllocationRegistry;
import java.io.OutputStream;
@@ -57,10 +61,9 @@ public final class Bitmap implements Parcelable {
private static final long NATIVE_ALLOCATION_SIZE = 32;
// Convenience for JNI access
+ @UnsupportedAppUsage
private final long mNativePtr;
- private final boolean mIsMutable;
-
/**
* Represents whether the Bitmap's content is requested to be pre-multiplied.
* Note that isPremultiplied() does not directly return this value, because
@@ -75,9 +78,13 @@ public final class Bitmap implements Parcelable {
*/
private boolean mRequestPremultiplied;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769491)
private byte[] mNinePatchChunk; // may be null
+ @UnsupportedAppUsage
private NinePatch.InsetStruct mNinePatchInsets; // may be null
+ @UnsupportedAppUsage
private int mWidth;
+ @UnsupportedAppUsage
private int mHeight;
private boolean mRecycled;
@@ -99,11 +106,13 @@ public final class Bitmap implements Parcelable {
* density when running old apps.
* @hide
*/
+ @UnsupportedAppUsage
public static void setDefaultDensity(int density) {
sDefaultDensity = density;
}
@SuppressWarnings("deprecation")
+ @UnsupportedAppUsage
static int getDefaultDensity() {
if (sDefaultDensity >= 0) {
return sDefaultDensity;
@@ -113,20 +122,28 @@ public final class Bitmap implements Parcelable {
}
/**
- * Private constructor that must received an already allocated native bitmap
+ * Private constructor that must receive an already allocated native bitmap
* int (pointer).
*/
- // called from JNI
+ // JNI now calls the version below this one. This is preserved due to UnsupportedAppUsage.
+ @UnsupportedAppUsage(maxTargetSdk = 28)
Bitmap(long nativeBitmap, int width, int height, int density,
- boolean isMutable, boolean requestPremultiplied,
- byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {
+ boolean requestPremultiplied, byte[] ninePatchChunk,
+ NinePatch.InsetStruct ninePatchInsets) {
+ this(nativeBitmap, width, height, density, requestPremultiplied, ninePatchChunk,
+ ninePatchInsets, true);
+ }
+
+ // called from JNI and Bitmap_Delegate.
+ Bitmap(long nativeBitmap, int width, int height, int density,
+ boolean requestPremultiplied, byte[] ninePatchChunk,
+ NinePatch.InsetStruct ninePatchInsets, boolean fromMalloc) {
if (nativeBitmap == 0) {
throw new RuntimeException("internal error: native bitmap is 0");
}
mWidth = width;
mHeight = height;
- mIsMutable = isMutable;
mRequestPremultiplied = requestPremultiplied;
mNinePatchChunk = ninePatchChunk;
@@ -136,13 +153,21 @@ public final class Bitmap implements Parcelable {
}
mNativePtr = nativeBitmap;
- long nativeSize = NATIVE_ALLOCATION_SIZE + getAllocationByteCount();
- NativeAllocationRegistry registry = new NativeAllocationRegistry(
- Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), nativeSize);
+
+ final int allocationByteCount = getAllocationByteCount();
+ NativeAllocationRegistry registry;
+ if (fromMalloc) {
+ registry = NativeAllocationRegistry.createMalloced(
+ Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), allocationByteCount);
+ } else {
+ registry = NativeAllocationRegistry.createNonmalloced(
+ Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), allocationByteCount);
+ }
registry.registerNativeAllocation(this, nativeBitmap);
if (ResourcesImpl.TRACE_FOR_DETAILED_PRELOAD) {
sPreloadTracingNumInstantiatedBitmaps++;
+ long nativeSize = NATIVE_ALLOCATION_SIZE + allocationByteCount;
sPreloadTracingTotalBitmapsSize += nativeSize;
}
}
@@ -160,6 +185,7 @@ public final class Bitmap implements Parcelable {
* width/height values
*/
@SuppressWarnings("unused") // called from JNI
+ @UnsupportedAppUsage
void reinit(int width, int height, boolean requestPremultiplied) {
mWidth = width;
mHeight = height;
@@ -330,6 +356,7 @@ public final class Bitmap implements Parcelable {
*
* @hide
*/
+ @UnsupportedAppUsage
public void setNinePatchChunk(byte[] chunk) {
mNinePatchChunk = chunk;
}
@@ -346,14 +373,9 @@ public final class Bitmap implements Parcelable {
* there are no more references to this bitmap.
*/
public void recycle() {
- if (!mRecycled && mNativePtr != 0) {
- if (nativeRecycle(mNativePtr)) {
- // return value indicates whether native pixel object was actually recycled.
- // false indicates that it is still in use at the native level and these
- // objects should not be collected now. They will be collected later when the
- // Bitmap itself is collected.
- mNinePatchChunk = null;
- }
+ if (!mRecycled) {
+ nativeRecycle(mNativePtr);
+ mNinePatchChunk = null;
mRecycled = true;
}
}
@@ -530,6 +552,7 @@ public final class Bitmap implements Parcelable {
*/
HARDWARE (7);
+ @UnsupportedAppUsage
final int nativeInt;
private static Config sConfigs[] = {
@@ -540,6 +563,7 @@ public final class Bitmap implements Parcelable {
this.nativeInt = ni;
}
+ @UnsupportedAppUsage
static Config nativeToConfig(int ni) {
return sConfigs[ni];
}
@@ -645,7 +669,10 @@ public final class Bitmap implements Parcelable {
* setting the new bitmap's config to the one specified, and then copying
* this bitmap's pixels into the new bitmap. If the conversion is not
* supported, or the allocator fails, then this returns NULL. The returned
- * bitmap has the same density and color space as the original.
+ * bitmap has the same density and color space as the original, except in
+ * the following cases. When copying to {@link Config#ALPHA_8}, the color
+ * space is dropped. When copying to or from {@link Config#RGBA_F16},
+ * EXTENDED or non-EXTENDED variants may be adjusted as appropriate.
*
* @param config The desired config for the resulting bitmap
* @param isMutable True if the resulting bitmap should be mutable (i.e.
@@ -674,6 +701,7 @@ public final class Bitmap implements Parcelable {
*
* @hide
*/
+ @UnsupportedAppUsage
public Bitmap createAshmemBitmap() {
checkRecycled("Can't copy a recycled bitmap");
noteHardwareBitmapSlowCall();
@@ -692,6 +720,7 @@ public final class Bitmap implements Parcelable {
*
* @hide
*/
+ @UnsupportedAppUsage
public Bitmap createAshmemBitmap(Config config) {
checkRecycled("Can't copy a recycled bitmap");
noteHardwareBitmapSlowCall();
@@ -704,14 +733,45 @@ public final class Bitmap implements Parcelable {
}
/**
- * Create hardware bitmap backed GraphicBuffer.
+ * Create a hardware bitmap backed by a {@link HardwareBuffer}.
+ *
+ * <p>The passed HardwareBuffer's usage flags must contain
+ * {@link HardwareBuffer#USAGE_GPU_SAMPLED_IMAGE}.
*
- * @return Bitmap or null if this GraphicBuffer has unsupported PixelFormat.
- * currently PIXEL_FORMAT_RGBA_8888 is the only supported format
+ * <p>The bitmap will keep a reference to the buffer so that callers can safely close the
+ * HardwareBuffer without affecting the Bitmap. However the HardwareBuffer must not be
+ * modified while a wrapped Bitmap is accessing it. Doing so will result in undefined behavior.
+ *
+ * @param hardwareBuffer The HardwareBuffer to wrap.
+ * @param colorSpace The color space of the bitmap. Must be a {@link ColorSpace.Rgb} colorspace.
+ * If null, SRGB is assumed.
+ * @return A bitmap wrapping the buffer, or null if there was a problem creating the bitmap.
+ * @throws IllegalArgumentException if the HardwareBuffer has an invalid usage, or an invalid
+ * colorspace is given.
+ */
+ @Nullable
+ public static Bitmap wrapHardwareBuffer(@NonNull HardwareBuffer hardwareBuffer,
+ @Nullable ColorSpace colorSpace) {
+ if ((hardwareBuffer.getUsage() & HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE) == 0) {
+ throw new IllegalArgumentException("usage flags must contain USAGE_GPU_SAMPLED_IMAGE.");
+ }
+ int format = hardwareBuffer.getFormat();
+ if (colorSpace == null) {
+ colorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
+ }
+ return nativeWrapHardwareBufferBitmap(hardwareBuffer, colorSpace.getNativeInstance());
+ }
+
+ /**
+ * Utility method to create a hardware backed bitmap using the graphics buffer.
* @hide
*/
- public static Bitmap createHardwareBitmap(@NonNull GraphicBuffer graphicBuffer) {
- return nativeCreateHardwareBitmap(graphicBuffer);
+ @Nullable
+ public static Bitmap wrapHardwareBuffer(@NonNull GraphicBuffer graphicBuffer,
+ @Nullable ColorSpace colorSpace) {
+ try (HardwareBuffer hb = HardwareBuffer.createFromGraphicBuffer(graphicBuffer)) {
+ return wrapHardwareBuffer(hb, colorSpace);
+ }
}
/**
@@ -723,7 +783,13 @@ public final class Bitmap implements Parcelable {
* @param src The source bitmap.
* @param dstWidth The new bitmap's desired width.
* @param dstHeight The new bitmap's desired height.
- * @param filter true if the source should be filtered.
+ * @param filter Whether or not bilinear filtering should be used when scaling the
+ * bitmap. If this is true then bilinear filtering will be used when
+ * scaling which has better image quality at the cost of worse performance.
+ * If this is false then nearest-neighbor scaling is used instead which
+ * will have worse image quality but is faster. Recommended default
+ * is to set filter to 'true' as the cost of bilinear filtering is
+ * typically minimal and the improved image quality is significant.
* @return The new scaled bitmap or the source bitmap if no scaling is required.
* @throws IllegalArgumentException if width is <= 0, or height is <= 0
*/
@@ -742,7 +808,7 @@ public final class Bitmap implements Parcelable {
}
/**
- * Returns an immutable bitmap from the source bitmap. The new bitmap may
+ * Returns a bitmap from the source bitmap. The new bitmap may
* be the same object as source, or a copy may have been made. It is
* initialized with the same density and color space as the original bitmap.
*/
@@ -751,7 +817,7 @@ public final class Bitmap implements Parcelable {
}
/**
- * Returns an immutable bitmap from the specified subset of the source
+ * Returns a bitmap from the specified subset of the source
* bitmap. The new bitmap may be the same object as source, or a copy may
* have been made. It is initialized with the same density and color space
* as the original bitmap.
@@ -771,7 +837,7 @@ public final class Bitmap implements Parcelable {
}
/**
- * Returns an immutable bitmap from subset of the source bitmap,
+ * Returns a bitmap from subset of the source bitmap,
* transformed by the optional matrix. The new bitmap may be the
* same object as source, or a copy may have been made. It is
* initialized with the same density and color space as the original
@@ -781,6 +847,12 @@ public final class Bitmap implements Parcelable {
* same as the source bitmap itself, then the source bitmap is
* returned and no new bitmap is created.
*
+ * The returned bitmap will always be mutable except in the following scenarios:
+ * (1) In situations where the source bitmap is returned and the source bitmap is immutable
+ *
+ * (2) The source bitmap is a hardware bitmap. That is {@link #getConfig()} is equivalent to
+ * {@link Config#HARDWARE}
+ *
* @param source The bitmap we are subsetting
* @param x The x coordinate of the first pixel in source
* @param y The y coordinate of the first pixel in source
@@ -793,7 +865,7 @@ public final class Bitmap implements Parcelable {
* @return A bitmap that represents the specified subset of source
* @throws IllegalArgumentException if the x, y, width, height values are
* outside of the dimensions of the source bitmap, or width is <= 0,
- * or height is <= 0
+ * or height is <= 0, or if the source bitmap has already been recycled
*/
public static Bitmap createBitmap(@NonNull Bitmap source, int x, int y, int width, int height,
@Nullable Matrix m, boolean filter) {
@@ -806,6 +878,9 @@ public final class Bitmap implements Parcelable {
if (y + height > source.getHeight()) {
throw new IllegalArgumentException("y + height must be <= bitmap.height()");
}
+ if (source.isRecycled()) {
+ throw new IllegalArgumentException("cannot use a recycled source in createBitmap");
+ }
// check if we can just return our argument unchanged
if (!source.isMutable() && x == 0 && y == 0 && width == source.getWidth() &&
@@ -851,8 +926,10 @@ public final class Bitmap implements Parcelable {
}
}
+ ColorSpace cs = source.getColorSpace();
+
if (m == null || m.isIdentity()) {
- bitmap = createBitmap(neww, newh, newConfig, source.hasAlpha());
+ bitmap = createBitmap(null, neww, newh, newConfig, source.hasAlpha(), cs);
paint = null; // not needed
} else {
final boolean transformed = !m.rectStaysRect();
@@ -866,9 +943,14 @@ public final class Bitmap implements Parcelable {
if (transformed) {
if (transformedConfig != Config.ARGB_8888 && transformedConfig != Config.RGBA_F16) {
transformedConfig = Config.ARGB_8888;
+ if (cs == null) {
+ cs = ColorSpace.get(ColorSpace.Named.SRGB);
+ }
}
}
- bitmap = createBitmap(neww, newh, transformedConfig, transformed || source.hasAlpha());
+
+ bitmap = createBitmap(null, neww, newh, transformedConfig,
+ transformed || source.hasAlpha(), cs);
paint = new Paint();
paint.setFilterBitmap(filter);
@@ -877,8 +959,6 @@ public final class Bitmap implements Parcelable {
}
}
- nativeCopyColorSpace(source.mNativePtr, bitmap.mNativePtr);
-
// The new bitmap was created from a known bitmap source so assume that
// they use the same density
bitmap.mDensity = source.mDensity;
@@ -960,10 +1040,10 @@ public final class Bitmap implements Parcelable {
* @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to
* mark the bitmap as opaque. Doing so will clear the bitmap in black
* instead of transparent.
- * @param colorSpace The color space of the bitmap. If the config is {@link Config#RGBA_F16},
- * {@link ColorSpace.Named#EXTENDED_SRGB scRGB} is assumed, and if the
- * config is not {@link Config#ARGB_8888}, {@link ColorSpace.Named#SRGB sRGB}
- * is assumed.
+ * @param colorSpace The color space of the bitmap. If the config is {@link Config#RGBA_F16}
+ * and {@link ColorSpace.Named#SRGB sRGB} or
+ * {@link ColorSpace.Named#LINEAR_SRGB Linear sRGB} is provided then the
+ * corresponding extended range variant is assumed.
*
* @throws IllegalArgumentException if the width or height are <= 0, if
* Config is Config.HARDWARE (because hardware bitmaps are always
@@ -1015,10 +1095,10 @@ public final class Bitmap implements Parcelable {
* @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to
* mark the bitmap as opaque. Doing so will clear the bitmap in black
* instead of transparent.
- * @param colorSpace The color space of the bitmap. If the config is {@link Config#RGBA_F16},
- * {@link ColorSpace.Named#EXTENDED_SRGB scRGB} is assumed, and if the
- * config is not {@link Config#ARGB_8888}, {@link ColorSpace.Named#SRGB sRGB}
- * is assumed.
+ * @param colorSpace The color space of the bitmap. If the config is {@link Config#RGBA_F16}
+ * and {@link ColorSpace.Named#SRGB sRGB} or
+ * {@link ColorSpace.Named#LINEAR_SRGB Linear sRGB} is provided then the
+ * corresponding extended range variant is assumed.
*
* @throws IllegalArgumentException if the width or height are <= 0, if
* Config is Config.HARDWARE (because hardware bitmaps are always
@@ -1035,30 +1115,12 @@ public final class Bitmap implements Parcelable {
if (config == Config.HARDWARE) {
throw new IllegalArgumentException("can't create mutable bitmap with Config.HARDWARE");
}
- if (colorSpace == null) {
+ if (colorSpace == null && config != Config.ALPHA_8) {
throw new IllegalArgumentException("can't create bitmap without a color space");
}
- Bitmap bm;
- // nullptr color spaces have a particular meaning in native and are interpreted as sRGB
- // (we also avoid the unnecessary extra work of the else branch)
- if (config != Config.ARGB_8888 || colorSpace == ColorSpace.get(ColorSpace.Named.SRGB)) {
- bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true, null, null);
- } else {
- if (!(colorSpace instanceof ColorSpace.Rgb)) {
- throw new IllegalArgumentException("colorSpace must be an RGB color space");
- }
- ColorSpace.Rgb rgb = (ColorSpace.Rgb) colorSpace;
- ColorSpace.Rgb.TransferParameters parameters = rgb.getTransferParameters();
- if (parameters == null) {
- throw new IllegalArgumentException("colorSpace must use an ICC "
- + "parametric transfer function");
- }
-
- ColorSpace.Rgb d50 = (ColorSpace.Rgb) ColorSpace.adapt(rgb, ColorSpace.ILLUMINANT_D50);
- bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true,
- d50.getTransform(), parameters);
- }
+ Bitmap bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true,
+ colorSpace == null ? 0 : colorSpace.getNativeInstance());
if (display != null) {
bm.mDensity = display.densityDpi;
@@ -1136,8 +1198,9 @@ public final class Bitmap implements Parcelable {
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("width and height must be > 0");
}
+ ColorSpace sRGB = ColorSpace.get(ColorSpace.Named.SRGB);
Bitmap bm = nativeCreate(colors, offset, stride, width, height,
- config.nativeInt, false, null, null);
+ config.nativeInt, false, sRGB.getNativeInstance());
if (display != null) {
bm.mDensity = display.densityDpi;
}
@@ -1218,11 +1281,9 @@ public final class Bitmap implements Parcelable {
* scaled to match if necessary.
* @param height The height of the bitmap to create. The picture's height will be
* scaled to match if necessary.
- * @param config The {@link Config} of the created bitmap. If this is null then
- * the bitmap will be {@link Config#HARDWARE}.
+ * @param config The {@link Config} of the created bitmap.
*
- * @return An immutable bitmap with a HARDWARE config whose contents are created
- * from the recorded drawing commands in the Picture source.
+ * @return An immutable bitmap with a configuration specified by the config parameter
*/
public static @NonNull Bitmap createBitmap(@NonNull Picture source, int width, int height,
@NonNull Config config) {
@@ -1240,13 +1301,14 @@ public final class Bitmap implements Parcelable {
final RenderNode node = RenderNode.create("BitmapTemporary", null);
node.setLeftTopRightBottom(0, 0, width, height);
node.setClipToBounds(false);
- final DisplayListCanvas canvas = node.start(width, height);
+ node.setForceDarkAllowed(false);
+ final RecordingCanvas canvas = node.beginRecording(width, height);
if (source.getWidth() != width || source.getHeight() != height) {
canvas.scale(width / (float) source.getWidth(),
height / (float) source.getHeight());
}
canvas.drawPicture(source);
- node.end(canvas);
+ node.endRecording();
Bitmap bitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
if (config != Config.HARDWARE) {
bitmap = bitmap.copy(config, false);
@@ -1261,7 +1323,7 @@ public final class Bitmap implements Parcelable {
}
canvas.drawPicture(source);
canvas.setBitmap(null);
- bitmap.makeImmutable();
+ bitmap.setImmutable();
return bitmap;
}
}
@@ -1352,13 +1414,22 @@ public final class Bitmap implements Parcelable {
* Returns true if the bitmap is marked as mutable (i.e.&nbsp;can be drawn into)
*/
public final boolean isMutable() {
- return mIsMutable;
+ return !nativeIsImmutable(mNativePtr);
}
- /** @hide */
- public final void makeImmutable() {
- // todo mIsMutable = false;
- // todo nMakeImmutable();
+ /**
+ * Marks the Bitmap as immutable. Further modifications to this Bitmap are disallowed.
+ * After this method is called, this Bitmap cannot be made mutable again and subsequent calls
+ * to {@link #reconfigure(int, int, Config)}, {@link #setPixel(int, int, int)},
+ * {@link #setPixels(int[], int, int, int, int, int, int)} and {@link #eraseColor(int)} will
+ * fail and throw an IllegalStateException.
+ *
+ * @hide
+ */
+ public void setImmutable() {
+ if (isMutable()) {
+ nativeSetImmutable(mNativePtr);
+ }
}
/**
@@ -1473,6 +1544,9 @@ public final class Bitmap implements Parcelable {
* Convenience method that returns the width of this bitmap divided
* by the density scale factor.
*
+ * Returns the bitmap's width multiplied by the ratio of the target density to the bitmap's
+ * source density
+ *
* @param targetDensity The density of the target canvas of the bitmap.
* @return The scaled width of this bitmap, according to the density scale factor.
*/
@@ -1484,6 +1558,9 @@ public final class Bitmap implements Parcelable {
* Convenience method that returns the height of this bitmap divided
* by the density scale factor.
*
+ * Returns the bitmap's height multiplied by the ratio of the target density to the bitmap's
+ * source density
+ *
* @param targetDensity The density of the target canvas of the bitmap.
* @return The scaled height of this bitmap, according to the density scale factor.
*/
@@ -1494,6 +1571,7 @@ public final class Bitmap implements Parcelable {
/**
* @hide
*/
+ @UnsupportedAppUsage
static public int scaleFromDensity(int size, int sdensity, int tdensity) {
if (sdensity == DENSITY_NONE || tdensity == DENSITY_NONE || sdensity == tdensity) {
return size;
@@ -1658,41 +1736,79 @@ public final class Bitmap implements Parcelable {
*/
@Nullable
public final ColorSpace getColorSpace() {
- // A reconfigure can change the configuration and rgba16f is
- // always linear scRGB at this time
- if (getConfig() == Config.RGBA_F16) {
- // Reset the color space for potential future reconfigurations
- mColorSpace = null;
- return ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB);
+ checkRecycled("getColorSpace called on a recycled bitmap");
+ if (mColorSpace == null) {
+ mColorSpace = nativeComputeColorSpace(mNativePtr);
}
+ return mColorSpace;
+ }
- // Cache the color space retrieval since it can be fairly expensive
- if (mColorSpace == null) {
- if (nativeIsSRGB(mNativePtr)) {
- mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
- } else if (getConfig() == Config.HARDWARE && nativeIsSRGBLinear(mNativePtr)) {
- mColorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB);
+ /**
+ * <p>Modifies the bitmap to have the specified {@link ColorSpace}, without
+ * affecting the underlying allocation backing the bitmap.</p>
+ *
+ * <p>This affects how the framework will interpret the color at each pixel. A bitmap
+ * with {@link Config#ALPHA_8} never has a color space, since a color space does not
+ * affect the alpha channel. Other {@code Config}s must always have a non-null
+ * {@code ColorSpace}.</p>
+ *
+ * @throws IllegalArgumentException If the specified color space is {@code null}, not
+ * {@link ColorSpace.Model#RGB RGB}, has a transfer function that is not an
+ * {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}, or whose
+ * components min/max values reduce the numerical range compared to the
+ * previously assigned color space.
+ *
+ * @throws IllegalArgumentException If the {@code Config} (returned by {@link #getConfig()})
+ * is {@link Config#ALPHA_8}.
+ *
+ * @param colorSpace to assign to the bitmap
+ */
+ public void setColorSpace(@NonNull ColorSpace colorSpace) {
+ checkRecycled("setColorSpace called on a recycled bitmap");
+ if (colorSpace == null) {
+ throw new IllegalArgumentException("The colorSpace cannot be set to null");
+ }
+
+ if (getConfig() == Config.ALPHA_8) {
+ throw new IllegalArgumentException("Cannot set a ColorSpace on ALPHA_8");
+ }
+
+ // Keep track of the old ColorSpace for comparison, and so we can reset it in case of an
+ // Exception.
+ final ColorSpace oldColorSpace = getColorSpace();
+ nativeSetColorSpace(mNativePtr, colorSpace.getNativeInstance());
+
+ // This will update mColorSpace. It may not be the same as |colorSpace|, e.g. if we
+ // corrected it because the Bitmap is F16.
+ mColorSpace = null;
+ final ColorSpace newColorSpace = getColorSpace();
+
+ try {
+ if (oldColorSpace.getComponentCount() != newColorSpace.getComponentCount()) {
+ throw new IllegalArgumentException("The new ColorSpace must have the same "
+ + "component count as the current ColorSpace");
} else {
- float[] xyz = new float[9];
- float[] params = new float[7];
-
- boolean hasColorSpace = nativeGetColorSpace(mNativePtr, xyz, params);
- if (hasColorSpace) {
- ColorSpace.Rgb.TransferParameters parameters =
- new ColorSpace.Rgb.TransferParameters(
- params[0], params[1], params[2],
- params[3], params[4], params[5], params[6]);
- ColorSpace cs = ColorSpace.match(xyz, parameters);
- if (cs != null) {
- mColorSpace = cs;
- } else {
- mColorSpace = new ColorSpace.Rgb("Unknown", xyz, parameters);
+ for (int i = 0; i < oldColorSpace.getComponentCount(); i++) {
+ if (oldColorSpace.getMinValue(i) < newColorSpace.getMinValue(i)) {
+ throw new IllegalArgumentException("The new ColorSpace cannot increase the "
+ + "minimum value for any of the components compared to the current "
+ + "ColorSpace. To perform this type of conversion create a new "
+ + "Bitmap in the desired ColorSpace and draw this Bitmap into it.");
+ }
+ if (oldColorSpace.getMaxValue(i) > newColorSpace.getMaxValue(i)) {
+ throw new IllegalArgumentException("The new ColorSpace cannot decrease the "
+ + "maximum value for any of the components compared to the current "
+ + "ColorSpace/ To perform this type of conversion create a new "
+ + "Bitmap in the desired ColorSpace and draw this Bitmap into it.");
}
}
}
+ } catch (IllegalArgumentException e) {
+ // Undo the change to the ColorSpace.
+ mColorSpace = oldColorSpace;
+ nativeSetColorSpace(mNativePtr, mColorSpace.getNativeInstance());
+ throw e;
}
-
- return mColorSpace;
}
/**
@@ -1709,6 +1825,25 @@ public final class Bitmap implements Parcelable {
}
/**
+ * Fills the bitmap's pixels with the specified {@code ColorLong}.
+ *
+ * @param color The color to fill as packed by the {@link Color} class.
+ * @throws IllegalStateException if the bitmap is not mutable.
+ * @throws IllegalArgumentException if the color space encoded in the
+ * {@code ColorLong} is invalid or unknown.
+ *
+ */
+ public void eraseColor(@ColorLong long color) {
+ checkRecycled("Can't erase a recycled bitmap");
+ if (!isMutable()) {
+ throw new IllegalStateException("cannot erase immutable bitmaps");
+ }
+
+ ColorSpace cs = Color.colorSpace(color);
+ nativeErase(mNativePtr, cs.getNativeInstance(), color);
+ }
+
+ /**
* Returns the {@link Color} at the specified location. Throws an exception
* if x or y are out of bounds (negative or >= to the width or height
* respectively). The returned color is a non-premultiplied ARGB value in
@@ -1729,6 +1864,46 @@ public final class Bitmap implements Parcelable {
return nativeGetPixel(mNativePtr, x, y);
}
+ private static float clamp(float value, @NonNull ColorSpace cs, int index) {
+ return Math.max(Math.min(value, cs.getMaxValue(index)), cs.getMinValue(index));
+ }
+
+ /**
+ * Returns the {@link Color} at the specified location. Throws an exception
+ * if x or y are out of bounds (negative or >= to the width or height
+ * respectively).
+ *
+ * @param x The x coordinate (0...width-1) of the pixel to return
+ * @param y The y coordinate (0...height-1) of the pixel to return
+ * @return The {@link Color} at the specified coordinate
+ * @throws IllegalArgumentException if x, y exceed the bitmap's bounds
+ * @throws IllegalStateException if the bitmap's config is {@link Config#HARDWARE}
+ *
+ */
+ @NonNull
+ public Color getColor(int x, int y) {
+ checkRecycled("Can't call getColor() on a recycled bitmap");
+ checkHardware("unable to getColor(), "
+ + "pixel access is not supported on Config#HARDWARE bitmaps");
+ checkPixelAccess(x, y);
+
+ final ColorSpace cs = getColorSpace();
+ if (cs.equals(ColorSpace.get(ColorSpace.Named.SRGB))) {
+ return Color.valueOf(nativeGetPixel(mNativePtr, x, y));
+ }
+ // The returned value is in kRGBA_F16_SkColorType, which is packed as
+ // four half-floats, r,g,b,a.
+ long rgba = nativeGetColor(mNativePtr, x, y);
+ float r = Half.toFloat((short) ((rgba >> 0) & 0xffff));
+ float g = Half.toFloat((short) ((rgba >> 16) & 0xffff));
+ float b = Half.toFloat((short) ((rgba >> 32) & 0xffff));
+ float a = Half.toFloat((short) ((rgba >> 48) & 0xffff));
+
+ // Skia may draw outside of the numerical range of the colorSpace.
+ // Clamp to get an expected value.
+ return Color.valueOf(clamp(r, cs, 0), clamp(g, cs, 1), clamp(b, cs, 2), a, cs);
+ }
+
/**
* Returns in pixels[] a copy of the data in the bitmap. Each value is
* a packed int representing a {@link Color}. The stride parameter allows
@@ -1885,7 +2060,7 @@ public final class Bitmap implements Parcelable {
x, y, width, height);
}
- public static final Parcelable.Creator<Bitmap> CREATOR
+ public static final @android.annotation.NonNull Parcelable.Creator<Bitmap> CREATOR
= new Parcelable.Creator<Bitmap>() {
/**
* Rebuilds a bitmap previously stored with writeToParcel().
@@ -1924,7 +2099,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, mIsMutable, mDensity, p)) {
+ if (!nativeWriteToParcel(mNativePtr, isMutable(), mDensity, p)) {
throw new RuntimeException("native writeToParcel failed");
}
}
@@ -2022,10 +2197,15 @@ public final class Bitmap implements Parcelable {
}
/**
- *
* @return {@link GraphicBuffer} which is internally used by hardware bitmap
+ *
+ * Note: the GraphicBuffer 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
*/
+ @UnsupportedAppUsage
public GraphicBuffer createGraphicBufferHandle() {
return nativeCreateGraphicBufferHandle(mNativePtr);
}
@@ -2035,14 +2215,14 @@ public final class Bitmap implements Parcelable {
private static native Bitmap nativeCreate(int[] colors, int offset,
int stride, int width, int height,
int nativeConfig, boolean mutable,
- @Nullable @Size(9) float[] xyzD50,
- @Nullable ColorSpace.Rgb.TransferParameters p);
+ long nativeColorSpace);
private static native Bitmap nativeCopy(long nativeSrcBitmap, int nativeConfig,
boolean isMutable);
private static native Bitmap nativeCopyAshmem(long nativeSrcBitmap);
private static native Bitmap nativeCopyAshmemConfig(long nativeSrcBitmap, int nativeConfig);
private static native long nativeGetNativeFinalizer();
- private static native boolean nativeRecycle(long nativeBitmap);
+ private static native void nativeRecycle(long nativeBitmap);
+ @UnsupportedAppUsage
private static native void nativeReconfigure(long nativeBitmap, int width, int height,
int config, boolean isPremultiplied);
@@ -2050,10 +2230,12 @@ public final class Bitmap implements Parcelable {
int quality, OutputStream stream,
byte[] tempStorage);
private static native void nativeErase(long nativeBitmap, int color);
+ private static native void nativeErase(long nativeBitmap, long colorSpacePtr, long color);
private static native int nativeRowBytes(long nativeBitmap);
private static native int nativeConfig(long nativeBitmap);
private static native int nativeGetPixel(long nativeBitmap, int x, int y);
+ private static native long nativeGetColor(long nativeBitmap, int x, int y);
private static native void nativeGetPixels(long nativeBitmap, int[] pixels,
int offset, int stride, int x, int y,
int width, int height);
@@ -2091,10 +2273,18 @@ public final class Bitmap implements Parcelable {
private static native void nativePrepareToDraw(long nativeBitmap);
private static native int nativeGetAllocationByteCount(long nativeBitmap);
private static native Bitmap nativeCopyPreserveInternalConfig(long nativeBitmap);
- private static native Bitmap nativeCreateHardwareBitmap(GraphicBuffer buffer);
+ private static native Bitmap nativeWrapHardwareBufferBitmap(HardwareBuffer buffer,
+ long nativeColorSpace);
private static native GraphicBuffer nativeCreateGraphicBufferHandle(long nativeBitmap);
- private static native boolean nativeGetColorSpace(long nativePtr, float[] xyz, float[] params);
+ private static native ColorSpace nativeComputeColorSpace(long nativePtr);
+ private static native void nativeSetColorSpace(long nativePtr, long nativeColorSpace);
private static native boolean nativeIsSRGB(long nativePtr);
private static native boolean nativeIsSRGBLinear(long nativePtr);
- private static native void nativeCopyColorSpace(long srcBitmap, long dstBitmap);
+
+ private static native void nativeSetImmutable(long nativePtr);
+
+ // ---------------- @CriticalNative -------------------
+
+ @CriticalNative
+ private static native boolean nativeIsImmutable(long nativePtr);
}
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index 7ea35e73619a..5623a8a49b35 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -20,6 +20,7 @@ import static android.graphics.BitmapFactory.Options.validate;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Trace;
@@ -150,12 +151,9 @@ public class BitmapFactory {
* the decoder will pick either the color space embedded in the image
* or the color space best suited for the requested image configuration
* (for instance {@link ColorSpace.Named#SRGB sRGB} for
- * the {@link Bitmap.Config#ARGB_8888} configuration).</p>
- *
- * <p>{@link Bitmap.Config#RGBA_F16} always uses the
- * {@link ColorSpace.Named#LINEAR_EXTENDED_SRGB scRGB} color space).
- * Bitmaps in other configurations without an embedded color space are
- * assumed to be in the {@link ColorSpace.Named#SRGB sRGB} color space.</p>
+ * {@link Bitmap.Config#ARGB_8888} configuration and
+ * {@link ColorSpace.Named#EXTENDED_SRGB EXTENDED_SRGB} for
+ * {@link Bitmap.Config#RGBA_F16}).</p>
*
* <p class="note">Only {@link ColorSpace.Model#RGB} color spaces are
* currently supported. An <code>IllegalArgumentException</code> will
@@ -438,8 +436,15 @@ public class BitmapFactory {
static void validate(Options opts) {
if (opts == null) return;
- if (opts.inBitmap != null && opts.inBitmap.getConfig() == Bitmap.Config.HARDWARE) {
- throw new IllegalArgumentException("Bitmaps with Config.HARWARE are always immutable");
+ if (opts.inBitmap != null) {
+ if (opts.inBitmap.getConfig() == Bitmap.Config.HARDWARE) {
+ throw new IllegalArgumentException(
+ "Bitmaps with Config.HARDWARE are always immutable");
+ }
+ if (opts.inBitmap.isRecycled()) {
+ throw new IllegalArgumentException(
+ "Cannot reuse a recycled Bitmap");
+ }
}
if (opts.inMutable && opts.inPreferredConfig == Bitmap.Config.HARDWARE) {
@@ -458,6 +463,32 @@ public class BitmapFactory {
}
}
}
+
+ /**
+ * Helper for passing inBitmap's native pointer to native.
+ */
+ static long nativeInBitmap(Options opts) {
+ if (opts == null || opts.inBitmap == null) {
+ return 0;
+ }
+
+ return opts.inBitmap.getNativeInstance();
+ }
+
+ /**
+ * Helper for passing SkColorSpace pointer to native.
+ *
+ * @throws IllegalArgumentException if the ColorSpace is not Rgb or does
+ * not have TransferParameters.
+ */
+ static long nativeColorSpace(Options opts) {
+ if (opts == null || opts.inPreferredColorSpace == null) {
+ return 0;
+ }
+
+ return opts.inPreferredColorSpace.getNativeInstance();
+ }
+
}
/**
@@ -631,7 +662,9 @@ public class BitmapFactory {
Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");
try {
- bm = nativeDecodeByteArray(data, offset, length, opts);
+ bm = nativeDecodeByteArray(data, offset, length, opts,
+ Options.nativeInBitmap(opts),
+ Options.nativeColorSpace(opts));
if (bm == null && opts != null && opts.inBitmap != null) {
throw new IllegalArgumentException("Problem decoding into existing bitmap");
@@ -726,7 +759,8 @@ public class BitmapFactory {
try {
if (is instanceof AssetManager.AssetInputStream) {
final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
- bm = nativeDecodeAsset(asset, outPadding, opts);
+ bm = nativeDecodeAsset(asset, outPadding, opts, Options.nativeInBitmap(opts),
+ Options.nativeColorSpace(opts));
} else {
bm = decodeStreamInternal(is, outPadding, opts);
}
@@ -753,7 +787,9 @@ public class BitmapFactory {
byte [] tempStorage = null;
if (opts != null) tempStorage = opts.inTempStorage;
if (tempStorage == null) tempStorage = new byte[DECODE_BUFFER_SIZE];
- return nativeDecodeStream(is, tempStorage, outPadding, opts);
+ return nativeDecodeStream(is, tempStorage, outPadding, opts,
+ Options.nativeInBitmap(opts),
+ Options.nativeColorSpace(opts));
}
/**
@@ -796,7 +832,9 @@ public class BitmapFactory {
Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeFileDescriptor");
try {
if (nativeIsSeekable(fd)) {
- bm = nativeDecodeFileDescriptor(fd, outPadding, opts);
+ bm = nativeDecodeFileDescriptor(fd, outPadding, opts,
+ Options.nativeInBitmap(opts),
+ Options.nativeColorSpace(opts));
} else {
FileInputStream fis = new FileInputStream(fd);
try {
@@ -831,12 +869,17 @@ public class BitmapFactory {
return decodeFileDescriptor(fd, null, null);
}
+ @UnsupportedAppUsage
private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage,
- Rect padding, Options opts);
+ Rect padding, Options opts, long inBitmapHandle, long colorSpaceHandle);
+ @UnsupportedAppUsage
private static native Bitmap nativeDecodeFileDescriptor(FileDescriptor fd,
- Rect padding, Options opts);
- private static native Bitmap nativeDecodeAsset(long nativeAsset, Rect padding, Options opts);
+ Rect padding, Options opts, long inBitmapHandle, long colorSpaceHandle);
+ @UnsupportedAppUsage
+ private static native Bitmap nativeDecodeAsset(long nativeAsset, Rect padding, Options opts,
+ long inBitmapHandle, long colorSpaceHandle);
+ @UnsupportedAppUsage
private static native Bitmap nativeDecodeByteArray(byte[] data, int offset,
- int length, Options opts);
+ int length, Options opts, long inBitmapHandle, long colorSpaceHandle);
private static native boolean nativeIsSeekable(FileDescriptor fd);
}
diff --git a/graphics/java/android/graphics/BitmapRegionDecoder.java b/graphics/java/android/graphics/BitmapRegionDecoder.java
index 2da27c7dfdbf..629d8c131b68 100644
--- a/graphics/java/android/graphics/BitmapRegionDecoder.java
+++ b/graphics/java/android/graphics/BitmapRegionDecoder.java
@@ -15,7 +15,9 @@
package android.graphics;
+import android.annotation.UnsupportedAppUsage;
import android.content.res.AssetManager;
+import android.os.Build;
import java.io.FileDescriptor;
import java.io.FileInputStream;
@@ -165,6 +167,7 @@ public final class BitmapRegionDecoder {
This can be called from JNI code.
*/
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private BitmapRegionDecoder(long decoder) {
mNativeBitmapRegionDecoder = decoder;
mRecycled = false;
@@ -192,7 +195,9 @@ public final class BitmapRegionDecoder {
|| rect.top >= getHeight())
throw new IllegalArgumentException("rectangle is outside the image");
return nativeDecodeRegion(mNativeBitmapRegionDecoder, rect.left, rect.top,
- rect.right - rect.left, rect.bottom - rect.top, options);
+ rect.right - rect.left, rect.bottom - rect.top, options,
+ BitmapFactory.Options.nativeInBitmap(options),
+ BitmapFactory.Options.nativeColorSpace(options));
}
}
@@ -262,11 +267,13 @@ public final class BitmapRegionDecoder {
private static native Bitmap nativeDecodeRegion(long lbm,
int start_x, int start_y, int width, int height,
- BitmapFactory.Options options);
+ BitmapFactory.Options options, long inBitmapHandle,
+ long colorSpaceHandle);
private static native int nativeGetWidth(long lbm);
private static native int nativeGetHeight(long lbm);
private static native void nativeClean(long lbm);
+ @UnsupportedAppUsage
private static native BitmapRegionDecoder nativeNewInstance(
byte[] data, int offset, int length, boolean isShareable);
private static native BitmapRegionDecoder nativeNewInstance(
diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java
index 5577f53ee28b..198d1e7bc956 100644
--- a/graphics/java/android/graphics/BitmapShader.java
+++ b/graphics/java/android/graphics/BitmapShader.java
@@ -17,6 +17,7 @@
package android.graphics;
import android.annotation.NonNull;
+import android.annotation.UnsupportedAppUsage;
/**
* Shader used to draw a bitmap as a texture. The bitmap can be repeated or
@@ -28,9 +29,12 @@ public class BitmapShader extends Shader {
* @hide
*/
@SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
+ @UnsupportedAppUsage
public Bitmap mBitmap;
+ @UnsupportedAppUsage
private int mTileX;
+ @UnsupportedAppUsage
private int mTileY;
/**
@@ -58,19 +62,9 @@ public class BitmapShader extends Shader {
@Override
long createNativeInstance(long nativeMatrix) {
- return nativeCreate(nativeMatrix, mBitmap, mTileX, mTileY);
+ return nativeCreate(nativeMatrix, mBitmap.getNativeInstance(), mTileX, mTileY);
}
- /**
- * @hide
- */
- @Override
- protected Shader copy() {
- final BitmapShader copy = new BitmapShader(mBitmap, mTileX, mTileY);
- copyLocalMatrix(copy);
- return copy;
- }
-
- private static native long nativeCreate(long nativeMatrix, Bitmap bitmap,
+ private static native long nativeCreate(long nativeMatrix, long bitmapHandle,
int shaderTileModeX, int shaderTileModeY);
}
diff --git a/graphics/java/android/graphics/BlendMode.java b/graphics/java/android/graphics/BlendMode.java
new file mode 100644
index 000000000000..5c294aa72ce8
--- /dev/null
+++ b/graphics/java/android/graphics/BlendMode.java
@@ -0,0 +1,588 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+public enum BlendMode {
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_CLEAR.png" />
+ * <figcaption>Destination pixels covered by the source are cleared to 0.</figcaption>
+ * </p>
+ * <p>\(\alpha_{out} = 0\)</p>
+ * <p>\(C_{out} = 0\)</p>
+ */
+ CLEAR(0),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_SRC.png" />
+ * <figcaption>The source pixels replace the destination pixels.</figcaption>
+ * </p>
+ * <p>\(\alpha_{out} = \alpha_{src}\)</p>
+ * <p>\(C_{out} = C_{src}\)</p>
+ */
+ SRC(1),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_DST.png" />
+ * <figcaption>The source pixels are discarded, leaving the destination intact.</figcaption>
+ * </p>
+ * <p>\(\alpha_{out} = \alpha_{dst}\)</p>
+ * <p>\(C_{out} = C_{dst}\)</p>
+ */
+ DST(2),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_SRC_OVER.png" />
+ * <figcaption>The source pixels are drawn over the destination pixels.</figcaption>
+ * </p>
+ * <p>\(\alpha_{out} = \alpha_{src} + (1 - \alpha_{src}) * \alpha_{dst}\)</p>
+ * <p>\(C_{out} = C_{src} + (1 - \alpha_{src}) * C_{dst}\)</p>
+ */
+ SRC_OVER(3),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_DST_OVER.png" />
+ * <figcaption>The source pixels are drawn behind the destination pixels.</figcaption>
+ * </p>
+ * <p>\(\alpha_{out} = \alpha_{dst} + (1 - \alpha_{dst}) * \alpha_{src}\)</p>
+ * <p>\(C_{out} = C_{dst} + (1 - \alpha_{dst}) * C_{src}\)</p>
+ */
+ DST_OVER(4),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_SRC_IN.png" />
+ * <figcaption>Keeps the source pixels that cover the destination pixels,
+ * discards the remaining source and destination pixels.</figcaption>
+ * </p>
+ * <p>\(\alpha_{out} = \alpha_{src} * \alpha_{dst}\)</p>
+ * <p>\(C_{out} = C_{src} * \alpha_{dst}\)</p>
+ */
+ SRC_IN(5),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_DST_IN.png" />
+ * <figcaption>Keeps the destination pixels that cover source pixels,
+ * discards the remaining source and destination pixels.</figcaption>
+ * </p>
+ * <p>\(\alpha_{out} = \alpha_{src} * \alpha_{dst}\)</p>
+ * <p>\(C_{out} = C_{dst} * \alpha_{src}\)</p>
+ */
+ DST_IN(6),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_SRC_OUT.png" />
+ * <figcaption>Keeps the source pixels that do not cover destination pixels.
+ * Discards source pixels that cover destination pixels. Discards all
+ * destination pixels.</figcaption>
+ * </p>
+ * <p>\(\alpha_{out} = (1 - \alpha_{dst}) * \alpha_{src}\)</p>
+ * <p>\(C_{out} = (1 - \alpha_{dst}) * C_{src}\)</p>
+ */
+ SRC_OUT(7),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_DST_OUT.png" />
+ * <figcaption>Keeps the destination pixels that are not covered by source pixels.
+ * Discards destination pixels that are covered by source pixels. Discards all
+ * source pixels.</figcaption>
+ * </p>
+ * <p>\(\alpha_{out} = (1 - \alpha_{src}) * \alpha_{dst}\)</p>
+ * <p>\(C_{out} = (1 - \alpha_{src}) * C_{dst}\)</p>
+ */
+ DST_OUT(8),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_SRC_ATOP.png" />
+ * <figcaption>Discards the source pixels that do not cover destination pixels.
+ * Draws remaining source pixels over destination pixels.</figcaption>
+ * </p>
+ * <p>\(\alpha_{out} = \alpha_{dst}\)</p>
+ * <p>\(C_{out} = \alpha_{dst} * C_{src} + (1 - \alpha_{src}) * C_{dst}\)</p>
+ */
+ SRC_ATOP(9),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_DST_ATOP.png" />
+ * <figcaption>Discards the destination pixels that are not covered by source pixels.
+ * Draws remaining destination pixels over source pixels.</figcaption>
+ * </p>
+ * <p>\(\alpha_{out} = \alpha_{src}\)</p>
+ * <p>\(C_{out} = \alpha_{src} * C_{dst} + (1 - \alpha_{dst}) * C_{src}\)</p>
+ */
+ DST_ATOP(10),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_XOR.png" />
+ * <figcaption>Discards the source and destination pixels where source pixels
+ * cover destination pixels. Draws remaining source pixels.</figcaption>
+ * </p>
+ * <p>
+ * \(\alpha_{out} = (1 - \alpha_{dst}) * \alpha_{src} + (1 - \alpha_{src}) * \alpha_{dst}\)
+ * </p>
+ * <p>\(C_{out} = (1 - \alpha_{dst}) * C_{src} + (1 - \alpha_{src}) * C_{dst}\)</p>
+ */
+ XOR(11),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_PLUS.png" />
+ * <figcaption>Adds the source pixels to the destination pixels and saturates
+ * the result.</figcaption>
+ * </p>
+ * <p>\(\alpha_{out} = max(0, min(\alpha_{src} + \alpha_{dst}, 1))\)</p>
+ * <p>\(C_{out} = max(0, min(C_{src} + C_{dst}, 1))\)</p>
+ */
+ PLUS(12),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_MODULATE.png" />
+ * <figcaption>Multiplies the source and destination pixels.</figcaption>
+ * </p>
+ * <p>\(\alpha_{out} = \alpha_{src} * \alpha_{dst}\)</p>
+ * <p>\(C_{out} = C_{src} * C_{dst}\)</p>
+ *
+ */
+ MODULATE(13),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_SCREEN.png" />
+ * <figcaption>
+ * Adds the source and destination pixels, then subtracts the
+ * source pixels multiplied by the destination.
+ * </figcaption>
+ * </p>
+ * <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p>
+ * <p>\(C_{out} = C_{src} + C_{dst} - C_{src} * C_{dst}\)</p>
+ */
+ SCREEN(14),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_OVERLAY.png" />
+ * <figcaption>
+ * Multiplies or screens the source and destination depending on the
+ * destination color.
+ * </figcaption>
+ * </p>
+ * <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p>
+ * <p>\(\begin{equation}
+ * C_{out} = \begin{cases} 2 * C_{src} * C_{dst} & 2 * C_{dst} \lt \alpha_{dst} \\
+ * \alpha_{src} * \alpha_{dst} - 2 (\alpha_{dst} - C_{src}) (\alpha_{src} - C_{dst}) &
+ * otherwise \end{cases}
+ * \end{equation}\)</p>
+ */
+ OVERLAY(15),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_DARKEN.png" />
+ * <figcaption>
+ * Retains the smallest component of the source and
+ * destination pixels.
+ * </figcaption>
+ * </p>
+ * <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p>
+ * <p>
+ * \(C_{out} =
+ * (1 - \alpha_{dst}) * C_{src} + (1 - \alpha_{src}) * C_{dst} + min(C_{src}, C_{dst})\)
+ * </p>
+ */
+ DARKEN(16),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_LIGHTEN.png" />
+ * <figcaption>Retains the largest component of the source and
+ * destination pixel.</figcaption>
+ * </p>
+ * <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p>
+ * <p>
+ * \(C_{out} =
+ * (1 - \alpha_{dst}) * C_{src} + (1 - \alpha_{src}) * C_{dst} + max(C_{src}, C_{dst})\)
+ * </p>
+ */
+ LIGHTEN(17),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_COLOR_DODGE.png" />
+ * <figcaption>Makes destination brighter to reflect source.</figcaption>
+ * </p>
+ * <p>
+ * \(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)
+ * </p>
+ * <p>
+ * \begin{equation}
+ * C_{out} =
+ * \begin{cases}
+ * C_{src} * (1 - \alpha_{dst}) & C_{dst} = 0 \\
+ * C_{src} + \alpha_{dst}*(1 - \alpha_{src}) & C_{src} = \alpha_{src} \\
+ * \alpha_{src} * min(\alpha_{dst}, C_{dst} * \alpha_{src}/(\alpha_{src} - C_{src}))
+ * + C_{src} *(1 - \alpha_{dst} + \alpha_{dst}*(1 - \alpha_{src}) & otherwise
+ * \end{cases}
+ * \end{equation}
+ * </p>
+ */
+ COLOR_DODGE(18),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_COLOR_BURN.png" />
+ * <figcaption>Makes destination darker to reflect source.</figcaption>
+ * </p>
+ * <p>
+ * \(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)
+ * </p>
+ * <p>
+ * \begin{equation}
+ * C_{out} =
+ * \begin{cases}
+ * C_{dst} + C_{src}*(1 - \alpha_{dst}) & C_{dst} = \alpha_{dst} \\
+ * \alpha_{dst}*(1 - \alpha_{src}) & C_{src} = 0 \\
+ * \alpha_{src}*(\alpha_{dst} - min(\alpha_{dst}, (\alpha_{dst}
+ * - C_{dst})*\alpha_{src}/C_{src}))
+ * + C_{src} * (1 - \alpha_{dst}) + \alpha_{dst}*(1-\alpha_{src}) & otherwise
+ * \end{cases}
+ * \end{equation}
+ * </p>
+ */
+ COLOR_BURN(19),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_HARD_LIGHT.png" />
+ * <figcaption>Makes destination lighter or darker, depending on source.</figcaption>
+ * </p>
+ * <p>
+ * \(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)
+ * </p>
+ * <p>
+ * \begin{equation}
+ * C_{out} =
+ * \begin{cases}
+ * 2*C_{src}*C_{dst} & C_{src}*(1-\alpha_{dst}) + C_{dst}*(1-\alpha_{src}) + 2*C_{src}
+ * \leq \alpha_{src} \\
+ * \alpha_{src}*\alpha_{dst}- 2*(\alpha_{dst} - C_{dst})*(\alpha_{src} - C_{src})
+ * & otherwise
+ * \end{cases}
+ * \end{equation}
+ * </p>
+ */
+ HARD_LIGHT(20),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_SOFT_LIGHT.png" />
+ * <figcaption>Makes destination lighter or darker, depending on source.</figcaption>
+ * </p>
+ * <p>
+ * Where
+ * \begin{equation}
+ * m =
+ * \begin{cases}
+ * C_{dst} / \alpha_{dst} & \alpha_{dst} \gt 0 \\
+ * 0 & otherwise
+ * \end{cases}
+ * \end{equation}
+ * </p>
+ * <p>
+ * \begin{equation}
+ * g =
+ * \begin{cases}
+ * (16 * m * m + 4 * m) * (m - 1) + 7 * m & 4 * C_{dst} \leq \alpha_{dst} \\
+ * \sqrt m - m & otherwise
+ * \end{cases}
+ * \end{equation}
+ * </p>
+ * <p>
+ * \begin{equation}
+ * f =
+ * \begin{cases}
+ * C_{dst} * (\alpha_{src} + (2 * C_{src} - \alpha_{src}) * (1 - m))
+ * & 2 * C_{src} \leq \alpha_{src} \\
+ * C_{dst} * \alpha_{src} + \alpha_{dst} * (2 * C_{src} - \alpha_{src}) * g
+ * & otherwise
+ * \end{cases}
+ * \end{equation}
+ * </p>
+ * <p>
+ * \begin{equation}
+ * \alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}
+ * \end{equation}
+ * \begin{equation}
+ * C_{out} = C_{src} / \alpha_{dst} + C_{dst} / \alpha_{src} + f
+ * \end{equation}
+ * </p>
+ */
+ SOFT_LIGHT(21),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_DIFFERENCE.png" />
+ * <figcaption>Subtracts darker from lighter with higher contrast.</figcaption>
+ * </p>
+ * <p>
+ * \begin{equation}
+ * \alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}
+ * \end{equation}
+ * </p>
+ * <p>
+ * \begin{equation}
+ * C_{out} = C_{src} + C_{dst} - 2 * min(C_{src}
+ * * \alpha_{dst}, C_{dst} * \alpha_{src})
+ * \end{equation}
+ * </p>
+ */
+ DIFFERENCE(22),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_DIFFERENCE.png" />
+ * <figcaption>Subtracts darker from lighter with lower contrast.</figcaption>
+ * </p>
+ * <p>
+ * \begin{equation}
+ * \alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}
+ * \end{equation}
+ * </p>
+ * <p>
+ * \begin{equation}
+ * C_{out} = C_{src} + C_{dst} - 2 * C_{src} * C_{dst}
+ * \end{equation}
+ * </p>
+ */
+ EXCLUSION(23),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_MODULATE.png" />
+ * <figcaption>Multiplies the source and destination pixels.</figcaption>
+ * </p>
+ * <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p>
+ * <p>\(C_{out} =
+ * C_{src} * (1 - \alpha_{dst}) + C_{dst} * (1 - \alpha_{src}) + (C_{src} * C_{dst})\)
+ * </p>
+ */
+ MULTIPLY(24),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_HUE.png" />
+ * <figcaption>
+ * Replaces hue of destination with hue of source, leaving saturation
+ * and luminosity unchanged.
+ * </figcaption>
+ * </p>
+ */
+ HUE(25),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_SATURATION.png" />
+ * <figcaption>
+ * Replaces saturation of destination saturation hue of source, leaving hue and
+ * luminosity unchanged.
+ * </figcaption>
+ * </p>
+ */
+ SATURATION(26),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_COLOR.png" />
+ * <figcaption>
+ * Replaces hue and saturation of destination with hue and saturation of source,
+ * leaving luminosity unchanged.
+ * </figcaption>
+ * </p>
+ */
+ COLOR(27),
+
+ /**
+ * {@usesMathJax}
+ *
+ * <p>
+ * <img src="{@docRoot}reference/android/images/graphics/blendmode_LUMINOSITY.png" />
+ * <figcaption>
+ * Replaces luminosity of destination with luminosity of source, leaving hue and
+ * saturation unchanged.
+ * </figcaption>
+ * </p>
+ */
+ LUMINOSITY(28);
+
+ private static final BlendMode[] BLEND_MODES = values();
+
+ /**
+ * @hide
+ */
+ public static @Nullable BlendMode fromValue(int value) {
+ for (BlendMode mode : BLEND_MODES) {
+ if (mode.mXfermode.porterDuffMode == value) {
+ return mode;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ public static int toValue(BlendMode mode) {
+ return mode.getXfermode().porterDuffMode;
+ }
+
+ /**
+ * @hide
+ */
+ public static @Nullable PorterDuff.Mode blendModeToPorterDuffMode(@Nullable BlendMode mode) {
+ if (mode != null) {
+ switch (mode) {
+ case CLEAR:
+ return PorterDuff.Mode.CLEAR;
+ case SRC:
+ return PorterDuff.Mode.SRC;
+ case DST:
+ return PorterDuff.Mode.DST;
+ case SRC_OVER:
+ return PorterDuff.Mode.SRC_OVER;
+ case DST_OVER:
+ return PorterDuff.Mode.DST_OVER;
+ case SRC_IN:
+ return PorterDuff.Mode.SRC_IN;
+ case DST_IN:
+ return PorterDuff.Mode.DST_IN;
+ case SRC_OUT:
+ return PorterDuff.Mode.SRC_OUT;
+ case DST_OUT:
+ return PorterDuff.Mode.DST_OUT;
+ case SRC_ATOP:
+ return PorterDuff.Mode.SRC_ATOP;
+ case DST_ATOP:
+ return PorterDuff.Mode.DST_ATOP;
+ case XOR:
+ return PorterDuff.Mode.XOR;
+ case DARKEN:
+ return PorterDuff.Mode.DARKEN;
+ case LIGHTEN:
+ return PorterDuff.Mode.LIGHTEN;
+ // b/73224934 PorterDuff Multiply maps to Skia Modulate
+ case MODULATE:
+ return PorterDuff.Mode.MULTIPLY;
+ case SCREEN:
+ return PorterDuff.Mode.SCREEN;
+ case PLUS:
+ return PorterDuff.Mode.ADD;
+ case OVERLAY:
+ return PorterDuff.Mode.OVERLAY;
+ default:
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ @NonNull
+ private final Xfermode mXfermode;
+
+ BlendMode(int mode) {
+ mXfermode = new Xfermode();
+ mXfermode.porterDuffMode = mode;
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public Xfermode getXfermode() {
+ return mXfermode;
+ }
+}
diff --git a/graphics/java/android/graphics/BlendModeColorFilter.java b/graphics/java/android/graphics/BlendModeColorFilter.java
new file mode 100644
index 000000000000..7caeb4267ad2
--- /dev/null
+++ b/graphics/java/android/graphics/BlendModeColorFilter.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+
+/**
+ * A color filter that can be used to tint the source pixels using a single
+ * color and a specific {@link BlendMode}.
+ */
+public final class BlendModeColorFilter extends ColorFilter {
+
+ @ColorInt final int mColor;
+ private final BlendMode mMode;
+
+ public BlendModeColorFilter(@ColorInt int color, @NonNull BlendMode mode) {
+ mColor = color;
+ mMode = mode;
+ }
+
+
+ /**
+ * Returns the ARGB color used to tint the source pixels when this filter
+ * is applied.
+ *
+ * @see Color
+ *
+ */
+ @ColorInt
+ public int getColor() {
+ return mColor;
+ }
+
+ /**
+ * Returns the Porter-Duff mode used to composite this color filter's
+ * color with the source pixel when this filter is applied.
+ *
+ * @see BlendMode
+ *
+ */
+ public BlendMode getMode() {
+ return mMode;
+ }
+
+ @Override
+ long createNativeInstance() {
+ return native_CreateBlendModeFilter(mColor, mMode.getXfermode().porterDuffMode);
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) {
+ return true;
+ }
+ if (object == null || getClass() != object.getClass()) {
+ return false;
+ }
+ final BlendModeColorFilter other = (BlendModeColorFilter) object;
+ return other.mMode == mMode;
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * mMode.hashCode() + mColor;
+ }
+
+ private static native long native_CreateBlendModeFilter(int srcColor, int blendmode);
+
+}
diff --git a/graphics/java/android/graphics/Camera.java b/graphics/java/android/graphics/Camera.java
index 60588d07196c..cbd4eadca30a 100644
--- a/graphics/java/android/graphics/Camera.java
+++ b/graphics/java/android/graphics/Camera.java
@@ -16,14 +16,14 @@
package android.graphics;
+import android.annotation.UnsupportedAppUsage;
+
/**
* A camera instance can be used to compute 3D transformations and
* generate a matrix that can be applied, for instance, on a
* {@link Canvas}.
*/
public class Camera {
- private Matrix mMatrix;
-
/**
* Creates a new camera, with empty transformations.
*/
@@ -149,13 +149,7 @@ public class Camera {
* @param canvas The Canvas to set the transform matrix onto
*/
public void applyToCanvas(Canvas canvas) {
- if (canvas.isHardwareAccelerated()) {
- if (mMatrix == null) mMatrix = new Matrix();
- getMatrix(mMatrix);
- canvas.concat(mMatrix);
- } else {
- nativeApplyToCanvas(canvas.getNativeCanvasWrapper());
- }
+ nativeApplyToCanvas(canvas.getNativeCanvasWrapper());
}
public native float dotWithNormal(float dx, float dy, float dz);
@@ -174,5 +168,6 @@ public class Camera {
private native void nativeGetMatrix(long native_matrix);
private native void nativeApplyToCanvas(long native_canvas);
+ @UnsupportedAppUsage
long native_instance;
}
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index 22c4e7557b78..a815f20293c5 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -17,10 +17,13 @@
package android.graphics;
import android.annotation.ColorInt;
+import android.annotation.ColorLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
+import android.annotation.UnsupportedAppUsage;
+import android.graphics.text.MeasuredText;
import android.os.Build;
import dalvik.annotation.optimization.CriticalNative;
@@ -54,6 +57,7 @@ public class Canvas extends BaseCanvas {
public static boolean sCompatibilitySetBitmap = false;
/** @hide */
+ @UnsupportedAppUsage
public long getNativeCanvasWrapper() {
return mNativeCanvasWrapper;
}
@@ -62,6 +66,7 @@ public class Canvas extends BaseCanvas {
public boolean isRecordingFor(Object o) { return false; }
// may be null
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 117521088)
private Bitmap mBitmap;
// optional field set by the caller
@@ -71,14 +76,11 @@ public class Canvas extends BaseCanvas {
// (see SkCanvas.cpp, SkDraw.cpp)
private static final int MAXMIMUM_BITMAP_SIZE = 32766;
- // The approximate size of the native allocation associated with
- // a Canvas object.
- private static final long NATIVE_ALLOCATION_SIZE = 525;
-
// Use a Holder to allow static initialization of Canvas in the boot image.
private static class NoImagePreloadHolder {
- public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
- Canvas.class.getClassLoader(), nGetNativeFinalizer(), NATIVE_ALLOCATION_SIZE);
+ public static final NativeAllocationRegistry sRegistry =
+ NativeAllocationRegistry.createMalloced(
+ Canvas.class.getClassLoader(), nGetNativeFinalizer());
}
// This field is used to finalize the native Canvas properly
@@ -93,7 +95,7 @@ public class Canvas extends BaseCanvas {
public Canvas() {
if (!isHardwareAccelerated()) {
// 0 means no native bitmap
- mNativeCanvasWrapper = nInitRaster(null);
+ mNativeCanvasWrapper = nInitRaster(0);
mFinalizer = NoImagePreloadHolder.sRegistry.registerNativeAllocation(
this, mNativeCanvasWrapper);
} else {
@@ -115,7 +117,7 @@ public class Canvas extends BaseCanvas {
throw new IllegalStateException("Immutable bitmap passed to Canvas constructor");
}
throwIfCannotDraw(bitmap);
- mNativeCanvasWrapper = nInitRaster(bitmap);
+ mNativeCanvasWrapper = nInitRaster(bitmap.getNativeInstance());
mFinalizer = NoImagePreloadHolder.sRegistry.registerNativeAllocation(
this, mNativeCanvasWrapper);
mBitmap = bitmap;
@@ -123,6 +125,7 @@ public class Canvas extends BaseCanvas {
}
/** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public Canvas(long nativeCanvas) {
if (nativeCanvas == 0) {
throw new IllegalStateException();
@@ -141,6 +144,7 @@ public class Canvas extends BaseCanvas {
* @hide
*/
@Deprecated
+ @UnsupportedAppUsage
protected GL getGL() {
return null;
}
@@ -181,7 +185,7 @@ public class Canvas extends BaseCanvas {
}
if (bitmap == null) {
- nSetBitmap(mNativeCanvasWrapper, null);
+ nSetBitmap(mNativeCanvasWrapper, 0);
mDensity = Bitmap.DENSITY_NONE;
} else {
if (!bitmap.isMutable()) {
@@ -189,7 +193,7 @@ public class Canvas extends BaseCanvas {
}
throwIfCannotDraw(bitmap);
- nSetBitmap(mNativeCanvasWrapper, bitmap);
+ nSetBitmap(mNativeCanvasWrapper, bitmap.getNativeInstance());
mDensity = bitmap.mDensity;
}
@@ -200,11 +204,70 @@ public class Canvas extends BaseCanvas {
mBitmap = bitmap;
}
- /** @hide */
- public void insertReorderBarrier() {}
+ /**
+ * @deprecated use {@link #enableZ()} instead
+ * @hide */
+ @Deprecated
+ public void insertReorderBarrier() {
+ enableZ();
+ }
- /** @hide */
- public void insertInorderBarrier() {}
+ /**
+ * @deprecated use {@link #disableZ()} instead
+ * @hide */
+ @Deprecated
+ public void insertInorderBarrier() {
+ disableZ();
+ }
+
+ /**
+ * <p>Enables Z support which defaults to disabled. This allows for RenderNodes drawn with
+ * {@link #drawRenderNode(RenderNode)} to be re-arranged based off of their
+ * {@link RenderNode#getElevation()} and {@link RenderNode#getTranslationZ()}
+ * values. It also enables rendering of shadows for RenderNodes with an elevation or
+ * translationZ.</p>
+ *
+ * <p>Any draw reordering will not be moved before this call. A typical usage of this might
+ * look something like:
+ *
+ * <pre class="prettyprint">
+ * void draw(Canvas canvas) {
+ * // Draw any background content
+ * canvas.drawColor(backgroundColor);
+ *
+ * // Begin drawing that may be reordered based off of Z
+ * canvas.enableZ();
+ * for (RenderNode child : children) {
+ * canvas.drawRenderNode(child);
+ * }
+ * // End drawing that may be reordered based off of Z
+ * canvas.disableZ();
+ *
+ * // Draw any overlays
+ * canvas.drawText("I'm on top of everything!", 0, 0, paint);
+ * }
+ * </pre>
+ * </p>
+ *
+ * Note: This is not impacted by any {@link #save()} or {@link #restore()} calls as it is not
+ * considered to be part of the current matrix or clip.
+ *
+ * See {@link #disableZ()}
+ */
+ public void enableZ() {
+ }
+
+ /**
+ * Disables Z support, preventing any RenderNodes drawn after this point from being
+ * visually reordered or having shadows rendered.
+ *
+ * Note: This is not impacted by any {@link #save()} or {@link #restore()} calls as it is not
+ * considered to be part of the current matrix or clip.
+ *
+ * See {@link #enableZ()}
+ */
+ public void disableZ() {
+ }
/**
* Return true if the device that the current layer draws into is opaque
@@ -269,6 +332,7 @@ public class Canvas extends BaseCanvas {
}
/** @hide */
+ @UnsupportedAppUsage
public void setScreenDensity(int density) {
mScreenDensity = density;
}
@@ -491,7 +555,17 @@ public class Canvas extends BaseCanvas {
* @hide
*/
public int saveUnclippedLayer(int left, int top, int right, int bottom) {
- return nSaveLayer(mNativeCanvasWrapper, left, top, right, bottom, 0, 0);
+ return nSaveUnclippedLayer(mNativeCanvasWrapper, left, top, right, bottom);
+ }
+
+ /**
+ * @hide
+ * @param saveCount The save level to restore to.
+ * @param paint This is copied and is applied to the area within the unclipped layer's
+ * bounds (i.e. equivalent to a drawPaint()) before restore() is called.
+ */
+ public void restoreUnclippedLayer(int saveCount, Paint paint) {
+ nRestoreUnclippedLayer(mNativeCanvasWrapper, saveCount, paint.getNativeInstance());
}
/**
@@ -1263,6 +1337,7 @@ public class Canvas extends BaseCanvas {
*
* @hide
*/
+ @UnsupportedAppUsage
public void release() {
mNativeCanvasWrapper = 0;
if (mFinalizer != null) {
@@ -1276,6 +1351,7 @@ public class Canvas extends BaseCanvas {
*
* @hide
*/
+ @UnsupportedAppUsage
public static void freeCaches() {
nFreeCaches();
}
@@ -1285,6 +1361,7 @@ public class Canvas extends BaseCanvas {
*
* @hide
*/
+ @UnsupportedAppUsage
public static void freeTextLayoutCaches() {
nFreeTextLayoutCaches();
}
@@ -1297,14 +1374,16 @@ public class Canvas extends BaseCanvas {
private static native void nFreeCaches();
private static native void nFreeTextLayoutCaches();
- private static native long nInitRaster(Bitmap bitmap);
private static native long nGetNativeFinalizer();
private static native void nSetCompatibilityVersion(int apiLevel);
// ---------------- @FastNative -------------------
@FastNative
- private static native void nSetBitmap(long canvasHandle, Bitmap bitmap);
+ private static native long nInitRaster(long bitmapHandle);
+
+ @FastNative
+ private static native void nSetBitmap(long canvasHandle, long bitmapHandle);
@FastNative
private static native boolean nGetClipBounds(long nativeCanvas, Rect bounds);
@@ -1327,6 +1406,11 @@ public class Canvas extends BaseCanvas {
private static native int nSaveLayerAlpha(long nativeCanvas, float l, float t, float r, float b,
int alpha, int layerFlags);
@CriticalNative
+ private static native int nSaveUnclippedLayer(long nativeCanvas, int l, int t, int r, int b);
+ @CriticalNative
+ private static native void nRestoreUnclippedLayer(long nativeCanvas, int saveCount,
+ long nativePaint);
+ @CriticalNative
private static native boolean nRestore(long canvasHandle);
@CriticalNative
private static native void nRestoreToCount(long canvasHandle, int saveCount);
@@ -1611,10 +1695,23 @@ public class Canvas extends BaseCanvas {
}
/**
+ * Fill the entire canvas' bitmap (restricted to the current clip) with the specified color,
+ * using srcover porterduff mode.
+ *
+ * @param color the {@code ColorLong} to draw onto the canvas. See the {@link Color}
+ * class for details about {@code ColorLong}s.
+ * @throws IllegalArgumentException if the color space encoded in the {@code ColorLong}
+ * is invalid or unknown.
+ */
+ public void drawColor(@ColorLong long color) {
+ super.drawColor(color, BlendMode.SRC_OVER);
+ }
+
+ /**
* Fill the entire canvas' bitmap (restricted to the current clip) with the specified color and
* porter-duff xfermode.
*
- * @param color the color to draw with
+ * @param color the color to draw onto the canvas
* @param mode the porter-duff mode to apply to the color
*/
public void drawColor(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
@@ -1622,6 +1719,31 @@ public class Canvas extends BaseCanvas {
}
/**
+ * Fill the entire canvas' bitmap (restricted to the current clip) with the specified color and
+ * blendmode.
+ *
+ * @param color the color to draw onto the canvas
+ * @param mode the blendmode to apply to the color
+ */
+ public void drawColor(@ColorInt int color, @NonNull BlendMode mode) {
+ super.drawColor(color, mode);
+ }
+
+ /**
+ * Fill the entire canvas' bitmap (restricted to the current clip) with the specified color and
+ * blendmode.
+ *
+ * @param color the {@code ColorLong} to draw onto the canvas. See the {@link Color}
+ * class for details about {@code ColorLong}s.
+ * @param mode the blendmode to apply to the color
+ * @throws IllegalArgumentException if the color space encoded in the {@code ColorLong}
+ * is invalid or unknown.
+ */
+ public void drawColor(@ColorLong long color, @NonNull BlendMode mode) {
+ super.drawColor(color, mode);
+ }
+
+ /**
* Draw a line segment with the specified start and stop x,y coordinates, using the specified
* paint.
* <p>
@@ -1868,6 +1990,51 @@ public class Canvas extends BaseCanvas {
}
/**
+ * Draws a double rounded rectangle using the specified paint. The resultant round rect
+ * will be filled in the area defined between the outer and inner rectangular bounds if
+ * the {@link Paint} configured with {@link Paint.Style#FILL}.
+ * Otherwise if {@link Paint.Style#STROKE} is used, then 2 rounded rect strokes will
+ * be drawn at the outer and inner rounded rectangles
+ *
+ * @param outer The outer rectangular bounds of the roundRect to be drawn
+ * @param outerRx The x-radius of the oval used to round the corners on the outer rectangle
+ * @param outerRy The y-radius of the oval used to round the corners on the outer rectangle
+ * @param inner The inner rectangular bounds of the roundRect to be drawn
+ * @param innerRx The x-radius of the oval used to round the corners on the inner rectangle
+ * @param innerRy The y-radius of the oval used to round the corners on the outer rectangle
+ * @param paint The paint used to draw the double roundRect
+ */
+ @Override
+ public void drawDoubleRoundRect(@NonNull RectF outer, float outerRx, float outerRy,
+ @NonNull RectF inner, float innerRx, float innerRy, @NonNull Paint paint) {
+ super.drawDoubleRoundRect(outer, outerRx, outerRy, inner, innerRx, innerRy, paint);
+ }
+
+ /**
+ * Draws a double rounded rectangle using the specified paint. The resultant round rect
+ * will be filled in the area defined between the outer and inner rectangular bounds if
+ * the {@link Paint} configured with {@link Paint.Style#FILL}.
+ * Otherwise if {@link Paint.Style#STROKE} is used, then 2 rounded rect strokes will
+ * be drawn at the outer and inner rounded rectangles
+ *
+ * @param outer The outer rectangular bounds of the roundRect to be drawn
+ * @param outerRadii Array of 8 float representing the x, y corner radii for top left,
+ * top right, bottom right, bottom left corners respectively on the outer
+ * rounded rectangle
+ *
+ * @param inner The inner rectangular bounds of the roundRect to be drawn
+ * @param innerRadii Array of 8 float representing the x, y corner radii for top left,
+ * top right, bottom right, bottom left corners respectively on the
+ * outer rounded rectangle
+ * @param paint The paint used to draw the double roundRect
+ */
+ @Override
+ public void drawDoubleRoundRect(@NonNull RectF outer, @NonNull float[] outerRadii,
+ @NonNull RectF inner, @NonNull float[] innerRadii, @NonNull Paint paint) {
+ super.drawDoubleRoundRect(outer, outerRadii, inner, innerRadii, paint);
+ }
+
+ /**
* Draw the text, with origin at (x,y), using the specified paint. The origin is interpreted
* based on the Align setting in the paint.
*
@@ -1928,9 +2095,11 @@ public class Canvas extends BaseCanvas {
/**
* Draw the text, with origin at (x,y), using the specified paint, along the specified path. The
- * paint's Align setting determins where along the path to start the text.
+ * paint's Align setting determines where along the path to start the text.
*
* @param text The text to be drawn
+ * @param index The starting index within the text to be drawn
+ * @param count Starting from index, the number of characters to draw
* @param path The path the text should follow for its baseline
* @param hOffset The distance along the path to add to the text's starting position
* @param vOffset The distance above(-) or below(+) the path to position the text
@@ -1943,7 +2112,7 @@ public class Canvas extends BaseCanvas {
/**
* Draw the text, with origin at (x,y), using the specified paint, along the specified path. The
- * paint's Align setting determins where along the path to start the text.
+ * paint's Align setting determines where along the path to start the text.
*
* @param text The text to be drawn
* @param path The path the text should follow for its baseline
@@ -1993,7 +2162,8 @@ public class Canvas extends BaseCanvas {
* the text next to it.
* <p>
* All text outside the range {@code contextStart..contextEnd} is ignored. The text between
- * {@code start} and {@code end} will be laid out and drawn.
+ * {@code start} and {@code end} will be laid out and drawn. The context range is useful for
+ * contextual shaping, e.g. Kerning, Arabic contextural form.
* <p>
* The direction of the run is explicitly specified by {@code isRtl}. Thus, this method is
* suitable only for runs of a single direction. Alignment of the text is as determined by the
@@ -2022,6 +2192,31 @@ public class Canvas extends BaseCanvas {
}
/**
+ * Draw a run of text, all in a single direction, with optional context for complex text
+ * shaping.
+ * <p>
+ * See {@link #drawTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)} for
+ * more details. This method uses a {@link MeasuredText} rather than CharSequence to represent
+ * the string.
+ *
+ * @param text the text to render
+ * @param start the start of the text to render. Data before this position can be used for
+ * shaping context.
+ * @param end the end of the text to render. Data at or after this position can be used for
+ * shaping context.
+ * @param contextStart the index of the start of the shaping context
+ * @param contextEnd the index of the end of the shaping context
+ * @param x the x position at which to draw the text
+ * @param y the y position at which to draw the text
+ * @param isRtl whether the run is in RTL direction
+ * @param paint the paint
+ */
+ public void drawTextRun(@NonNull MeasuredText text, int start, int end, int contextStart,
+ int contextEnd, float x, float y, boolean isRtl, @NonNull Paint paint) {
+ super.drawTextRun(text, start, end, contextStart, contextEnd, x, y, isRtl, paint);
+ }
+
+ /**
* Draw the array of vertices, interpreted as triangles (based on mode). The verts array is
* required, and specifies the x,y pairs for each vertex. If texs is non-null, then it is used
* to specify the coordinate in shader coordinates to use at each vertex (the paint must have a
@@ -2056,4 +2251,17 @@ public class Canvas extends BaseCanvas {
super.drawVertices(mode, vertexCount, verts, vertOffset, texs, texOffset,
colors, colorOffset, indices, indexOffset, indexCount, paint);
}
+
+ /**
+ * Draws the given RenderNode. This is only supported in hardware rendering, which can be
+ * verified by asserting that {@link #isHardwareAccelerated()} is true. If
+ * {@link #isHardwareAccelerated()} is false then this throws an exception.
+ *
+ * See {@link RenderNode} for more information on what a RenderNode is and how to use it.
+ *
+ * @param renderNode The RenderNode to draw, must be non-null.
+ */
+ public void drawRenderNode(@NonNull RenderNode renderNode) {
+ throw new IllegalArgumentException("Software rendering doesn't support drawRenderNode");
+ }
}
diff --git a/graphics/java/android/graphics/CanvasProperty.java b/graphics/java/android/graphics/CanvasProperty.java
index ea3886cde274..1275e0827580 100644
--- a/graphics/java/android/graphics/CanvasProperty.java
+++ b/graphics/java/android/graphics/CanvasProperty.java
@@ -16,6 +16,7 @@
package android.graphics;
+import android.annotation.UnsupportedAppUsage;
import com.android.internal.util.VirtualRefBasePtr;
/**
@@ -26,10 +27,12 @@ public final class CanvasProperty<T> {
private VirtualRefBasePtr mProperty;
+ @UnsupportedAppUsage
public static CanvasProperty<Float> createFloat(float initialValue) {
return new CanvasProperty<Float>(nCreateFloat(initialValue));
}
+ @UnsupportedAppUsage
public static CanvasProperty<Paint> createPaint(Paint initialValue) {
return new CanvasProperty<Paint>(nCreatePaint(initialValue.getNativeInstance()));
}
diff --git a/graphics/java/android/graphics/ColorFilter.java b/graphics/java/android/graphics/ColorFilter.java
index b24b9885d1b0..4c2ef84404e2 100644
--- a/graphics/java/android/graphics/ColorFilter.java
+++ b/graphics/java/android/graphics/ColorFilter.java
@@ -26,8 +26,9 @@ import libcore.util.NativeAllocationRegistry;
public class ColorFilter {
private static class NoImagePreloadHolder {
- public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
- ColorFilter.class.getClassLoader(), nativeGetFinalizer(), 50);
+ public static final NativeAllocationRegistry sRegistry =
+ NativeAllocationRegistry.createMalloced(
+ ColorFilter.class.getClassLoader(), nativeGetFinalizer());
}
/**
diff --git a/graphics/java/android/graphics/ColorMatrixColorFilter.java b/graphics/java/android/graphics/ColorMatrixColorFilter.java
index 9201a2e2310e..0f7980cc32e4 100644
--- a/graphics/java/android/graphics/ColorMatrixColorFilter.java
+++ b/graphics/java/android/graphics/ColorMatrixColorFilter.java
@@ -18,6 +18,7 @@ package android.graphics;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
/**
* A color filter that transforms colors through a 4x5 color matrix. This filter
@@ -26,6 +27,7 @@ import android.annotation.Nullable;
* @see ColorMatrix
*/
public class ColorMatrixColorFilter extends ColorFilter {
+ @UnsupportedAppUsage
private final ColorMatrix mMatrix = new ColorMatrix();
/**
@@ -76,6 +78,7 @@ public class ColorMatrixColorFilter extends ColorFilter {
*
* @hide
*/
+ @UnsupportedAppUsage
public void setColorMatrix(@Nullable ColorMatrix matrix) {
discardNativeInstance();
if (matrix == null) {
@@ -104,6 +107,7 @@ public class ColorMatrixColorFilter extends ColorFilter {
*
* @hide
*/
+ @UnsupportedAppUsage
public void setColorMatrixArray(@Nullable float[] array) {
// called '...Array' so that passing null isn't ambiguous
discardNativeInstance();
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 5814df5b5cc0..9c4b5e8b0165 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -25,6 +25,8 @@ import android.annotation.Size;
import android.annotation.SuppressAutoDoc;
import android.util.Pair;
+import libcore.util.NativeAllocationRegistry;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -199,6 +201,9 @@ public abstract class ColorSpace {
private static final float[] NTSC_1953_PRIMARIES = { 0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f };
private static final float[] ILLUMINANT_D50_XYZ = { 0.964212f, 1.0f, 0.825188f };
+ private static final Rgb.TransferParameters SRGB_TRANSFER_PARAMETERS =
+ new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4);
+
// See static initialization block next to #get(Named)
private static final ColorSpace[] sNamedColorSpaces = new ColorSpace[Named.values().length];
@@ -239,7 +244,7 @@ public abstract class ColorSpace {
* <tr>
* <td>Opto-electronic transfer function (OETF)</td>
* <td colspan="4">\(\begin{equation}
- * C_{sRGB} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0031308 \\
+ * C_{sRGB} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0031308 \\\
* 1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \ge 0.0031308 \end{cases}
* \end{equation}\)
* </td>
@@ -247,7 +252,7 @@ public abstract class ColorSpace {
* <tr>
* <td>Electro-optical transfer function (EOTF)</td>
* <td colspan="4">\(\begin{equation}
- * C_{linear} = \begin{cases}\frac{C_{sRGB}}{12.92} & C_{sRGB} \lt 0.04045 \\
+ * C_{linear} = \begin{cases}\frac{C_{sRGB}}{12.92} & C_{sRGB} \lt 0.04045 \\\
* \left( \frac{C_{sRGB} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \ge 0.04045 \end{cases}
* \end{equation}\)
* </td>
@@ -302,7 +307,7 @@ public abstract class ColorSpace {
* <td>Opto-electronic transfer function (OETF)</td>
* <td colspan="4">\(\begin{equation}
* C_{scRGB} = \begin{cases} sign(C_{linear}) 12.92 \times \left| C_{linear} \right| &
- * \left| C_{linear} \right| \lt 0.0031308 \\
+ * \left| C_{linear} \right| \lt 0.0031308 \\\
* sign(C_{linear}) 1.055 \times \left| C_{linear} \right| ^{\frac{1}{2.4}} - 0.055 &
* \left| C_{linear} \right| \ge 0.0031308 \end{cases}
* \end{equation}\)
@@ -312,7 +317,7 @@ public abstract class ColorSpace {
* <td>Electro-optical transfer function (EOTF)</td>
* <td colspan="4">\(\begin{equation}
* C_{linear} = \begin{cases}sign(C_{scRGB}) \frac{\left| C_{scRGB} \right|}{12.92} &
- * \left| C_{scRGB} \right| \lt 0.04045 \\
+ * \left| C_{scRGB} \right| \lt 0.04045 \\\
* sign(C_{scRGB}) \left( \frac{\left| C_{scRGB} \right| + 0.055}{1.055} \right) ^{2.4} &
* \left| C_{scRGB} \right| \ge 0.04045 \end{cases}
* \end{equation}\)
@@ -367,7 +372,7 @@ public abstract class ColorSpace {
* <tr>
* <td>Opto-electronic transfer function (OETF)</td>
* <td colspan="4">\(\begin{equation}
- * C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\
+ * C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\\
* 1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases}
* \end{equation}\)
* </td>
@@ -375,7 +380,7 @@ public abstract class ColorSpace {
* <tr>
* <td>Electro-optical transfer function (EOTF)</td>
* <td colspan="4">\(\begin{equation}
- * C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\
+ * C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\\
* \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases}
* \end{equation}\)
* </td>
@@ -402,7 +407,7 @@ public abstract class ColorSpace {
* <tr>
* <td>Opto-electronic transfer function (OETF)</td>
* <td colspan="4">\(\begin{equation}
- * C_{BT2020} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.0181 \\
+ * C_{BT2020} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.0181 \\\
* 1.0993 \times C_{linear}^{\frac{1}{2.2}} - 0.0993 & C_{linear} \ge 0.0181 \end{cases}
* \end{equation}\)
* </td>
@@ -410,7 +415,7 @@ public abstract class ColorSpace {
* <tr>
* <td>Electro-optical transfer function (EOTF)</td>
* <td colspan="4">\(\begin{equation}
- * C_{linear} = \begin{cases}\frac{C_{BT2020}}{4.5} & C_{BT2020} \lt 0.08145 \\
+ * C_{linear} = \begin{cases}\frac{C_{BT2020}}{4.5} & C_{BT2020} \lt 0.08145 \\\
* \left( \frac{C_{BT2020} + 0.0993}{1.0993} \right) ^{2.2} & C_{BT2020} \ge 0.08145 \end{cases}
* \end{equation}\)
* </td>
@@ -464,7 +469,7 @@ public abstract class ColorSpace {
* <tr>
* <td>Opto-electronic transfer function (OETF)</td>
* <td colspan="4">\(\begin{equation}
- * C_{DisplayP3} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0030186 \\
+ * C_{DisplayP3} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0030186 \\\
* 1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \ge 0.0030186 \end{cases}
* \end{equation}\)
* </td>
@@ -472,8 +477,8 @@ public abstract class ColorSpace {
* <tr>
* <td>Electro-optical transfer function (EOTF)</td>
* <td colspan="4">\(\begin{equation}
- * C_{linear} = \begin{cases}\frac{C_{DisplayP3}}{12.92} & C_{sRGB} \lt 0.039 \\
- * \left( \frac{C_{DisplayP3} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \ge 0.039 \end{cases}
+ * C_{linear} = \begin{cases}\frac{C_{DisplayP3}}{12.92} & C_{sRGB} \lt 0.04045 \\\
+ * \left( \frac{C_{DisplayP3} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \ge 0.04045 \end{cases}
* \end{equation}\)
* </td>
* </tr>
@@ -499,7 +504,7 @@ public abstract class ColorSpace {
* <tr>
* <td>Opto-electronic transfer function (OETF)</td>
* <td colspan="4">\(\begin{equation}
- * C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\
+ * C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\\
* 1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases}
* \end{equation}\)
* </td>
@@ -507,7 +512,7 @@ public abstract class ColorSpace {
* <tr>
* <td>Electro-optical transfer function (EOTF)</td>
* <td colspan="4">\(\begin{equation}
- * C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\
+ * C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\\
* \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases}
* \end{equation}\)
* </td>
@@ -534,7 +539,7 @@ public abstract class ColorSpace {
* <tr>
* <td>Opto-electronic transfer function (OETF)</td>
* <td colspan="4">\(\begin{equation}
- * C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\
+ * C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\\
* 1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases}
* \end{equation}\)
* </td>
@@ -542,7 +547,7 @@ public abstract class ColorSpace {
* <tr>
* <td>Electro-optical transfer function (EOTF)</td>
* <td colspan="4">\(\begin{equation}
- * C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\
+ * C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\\
* \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases}
* \end{equation}\)
* </td>
@@ -596,7 +601,7 @@ public abstract class ColorSpace {
* <tr>
* <td>Opto-electronic transfer function (OETF)</td>
* <td colspan="4">\(\begin{equation}
- * C_{ROMM} = \begin{cases} 16 \times C_{linear} & C_{linear} \lt 0.001953 \\
+ * C_{ROMM} = \begin{cases} 16 \times C_{linear} & C_{linear} \lt 0.001953 \\\
* C_{linear}^{\frac{1}{1.8}} & C_{linear} \ge 0.001953 \end{cases}
* \end{equation}\)
* </td>
@@ -604,7 +609,7 @@ public abstract class ColorSpace {
* <tr>
* <td>Electro-optical transfer function (EOTF)</td>
* <td colspan="4">\(\begin{equation}
- * C_{linear} = \begin{cases}\frac{C_{ROMM}}{16} & C_{ROMM} \lt 0.031248 \\
+ * C_{linear} = \begin{cases}\frac{C_{ROMM}}{16} & C_{ROMM} \lt 0.031248 \\\
* C_{ROMM}^{1.8} & C_{ROMM} \ge 0.031248 \end{cases}
* \end{equation}\)
* </td>
@@ -759,12 +764,12 @@ public abstract class ColorSpace {
*
* $$\begin{align*}
* \left[ \begin{array}{c} L_1\\ M_1\\ S_1 \end{array} \right] &=
- * A \left[ \begin{array}{c} W1_X\\ W1_Y\\ W1_Z \end{array} \right] \\
+ * A \left[ \begin{array}{c} W1_X\\ W1_Y\\ W1_Z \end{array} \right] \\\
* \left[ \begin{array}{c} L_2\\ M_2\\ S_2 \end{array} \right] &=
- * A \left[ \begin{array}{c} W2_X\\ W2_Y\\ W2_Z \end{array} \right] \\
- * D &= \left[ \begin{matrix} \frac{L_2}{L_1} & 0 & 0 \\
- * 0 & \frac{M_2}{M_1} & 0 \\
- * 0 & 0 & \frac{S_2}{S_1} \end{matrix} \right] \\
+ * A \left[ \begin{array}{c} W2_X\\ W2_Y\\ W2_Z \end{array} \right] \\\
+ * D &= \left[ \begin{matrix} \frac{L_2}{L_1} & 0 & 0 \\\
+ * 0 & \frac{M_2}{M_1} & 0 \\\
+ * 0 & 0 & \frac{S_2}{S_1} \end{matrix} \right] \\\
* T &= A^{-1}.D.A
* \end{align*}$$
*
@@ -984,11 +989,12 @@ public abstract class ColorSpace {
* {@link Named#SRGB sRGB} primaries.
* </li>
* <li>
- * Its white point is withing 1e-3 of the CIE standard
+ * Its white point is within 1e-3 of the CIE standard
* illuminant {@link #ILLUMINANT_D65 D65}.
* </li>
* <li>Its opto-electronic transfer function is not linear.</li>
* <li>Its electro-optical transfer function is not linear.</li>
+ * <li>Its transfer functions yield values within 1e-3 of {@link Named#SRGB}.</li>
* <li>Its range is \([0..1]\).</li>
* </ul>
* <p>This method always returns true for {@link Named#SRGB}.</p>
@@ -1340,6 +1346,26 @@ public abstract class ColorSpace {
}
/**
+ * Helper method for creating native SkColorSpace.
+ *
+ * This essentially calls adapt on a ColorSpace that has not been fully
+ * created. It also does not fully create the adapted ColorSpace, but
+ * just returns the transform.
+ */
+ @NonNull @Size(9)
+ private static float[] adaptToIlluminantD50(
+ @NonNull @Size(2) float[] origWhitePoint,
+ @NonNull @Size(9) float[] origTransform) {
+ float[] desired = ILLUMINANT_D50;
+ if (compare(origWhitePoint, desired)) return origTransform;
+
+ float[] xyz = xyYToXyz(desired);
+ float[] adaptationTransform = chromaticAdaptation(Adaptation.BRADFORD.mTransform,
+ xyYToXyz(origWhitePoint), xyz);
+ return mul3x3(adaptationTransform, origTransform);
+ }
+
+ /**
* <p>Returns an instance of {@link ColorSpace} whose ID matches the
* specified ID.</p>
*
@@ -1354,9 +1380,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 >= Named.values().length) {
throw new IllegalArgumentException("Invalid ID, must be in the range [0.." +
- Named.values().length + "]");
+ Named.values().length + ")");
}
return sNamedColorSpaces[index];
}
@@ -1430,7 +1456,7 @@ public abstract class ColorSpace {
"sRGB IEC61966-2.1",
SRGB_PRIMARIES,
ILLUMINANT_D65,
- new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4),
+ SRGB_TRANSFER_PARAMETERS,
Named.SRGB.ordinal()
);
sNamedColorSpaces[Named.LINEAR_SRGB.ordinal()] = new ColorSpace.Rgb(
@@ -1445,9 +1471,11 @@ public abstract class ColorSpace {
"scRGB-nl IEC 61966-2-2:2003",
SRGB_PRIMARIES,
ILLUMINANT_D65,
+ null,
x -> absRcpResponse(x, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4),
x -> absResponse(x, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4),
-0.799f, 2.399f,
+ SRGB_TRANSFER_PARAMETERS,
Named.EXTENDED_SRGB.ordinal()
);
sNamedColorSpaces[Named.LINEAR_EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb(
@@ -1484,7 +1512,7 @@ public abstract class ColorSpace {
"Display P3",
new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
ILLUMINANT_D65,
- new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.039, 2.4),
+ SRGB_TRANSFER_PARAMETERS,
Named.DISPLAY_P3.ordinal()
);
sNamedColorSpaces[Named.NTSC_1953.ordinal()] = new ColorSpace.Rgb(
@@ -1660,10 +1688,12 @@ public abstract class ColorSpace {
* @param rhs 3x3 matrix, as a non-null array of 9 floats
* @return A new array of 9 floats containing the result of the multiplication
* of rhs by lhs
+ *
+ * @hide
*/
@NonNull
@Size(9)
- private static float[] mul3x3(@NonNull @Size(9) float[] lhs, @NonNull @Size(9) float[] rhs) {
+ public static float[] mul3x3(@NonNull @Size(9) float[] lhs, @NonNull @Size(9) float[] rhs) {
float[] r = new float[9];
r[0] = lhs[0] * rhs[0] + lhs[3] * rhs[1] + lhs[6] * rhs[2];
r[1] = lhs[1] * rhs[0] + lhs[4] * rhs[1] + lhs[7] * rhs[2];
@@ -1779,6 +1809,113 @@ public abstract class ColorSpace {
}
/**
+ * <p>Computes the chromaticity coordinates of a specified correlated color
+ * temperature (CCT) on the Planckian locus. The specified CCT must be
+ * greater than 0. A meaningful CCT range is [1667, 25000].</p>
+ *
+ * <p>The transform is computed using the methods in Kang et
+ * al., <i>Design of Advanced Color - Temperature Control System for HDTV
+ * Applications</i>, Journal of Korean Physical Society 41, 865-871
+ * (2002).</p>
+ *
+ * @param cct The correlated color temperature, in Kelvin
+ * @return Corresponding XYZ values
+ * @throws IllegalArgumentException If cct is invalid
+ *
+ * @hide
+ */
+ @NonNull
+ @Size(3)
+ public static float[] cctToXyz(@IntRange(from = 1) int cct) {
+ if (cct < 1) {
+ throw new IllegalArgumentException("Temperature must be greater than 0");
+ }
+
+ final float icct = 1e3f / cct;
+ final float icct2 = icct * icct;
+ final float x = cct <= 4000.0f ?
+ 0.179910f + 0.8776956f * icct - 0.2343589f * icct2 - 0.2661239f * icct2 * icct :
+ 0.240390f + 0.2226347f * icct + 2.1070379f * icct2 - 3.0258469f * icct2 * icct;
+
+ final float x2 = x * x;
+ final float y = cct <= 2222.0f ?
+ -0.20219683f + 2.18555832f * x - 1.34811020f * x2 - 1.1063814f * x2 * x :
+ cct <= 4000.0f ?
+ -0.16748867f + 2.09137015f * x - 1.37418593f * x2 - 0.9549476f * x2 * x :
+ -0.37001483f + 3.75112997f * x - 5.8733867f * x2 + 3.0817580f * x2 * x;
+
+ return xyYToXyz(new float[] {x, y});
+ }
+
+ /**
+ * <p>Computes the chromaticity coordinates of a CIE series D illuminant
+ * from the specified correlated color temperature (CCT). The specified CCT
+ * must be greater than 0. A meaningful CCT range is [4000, 25000].</p>
+ *
+ * <p>The transform is computed using the methods referred to in Kang et
+ * al., <i>Design of Advanced Color - Temperature Control System for HDTV
+ * Applications</i>, Journal of Korean Physical Society 41, 865-871
+ * (2002).</p>
+ *
+ * @param cct The correlated color temperature, in Kelvin
+ * @return Corresponding XYZ values
+ * @throws IllegalArgumentException If cct is invalid
+ *
+ * @hide
+ */
+ @NonNull
+ @Size(3)
+ public static float[] cctToIlluminantdXyz(@IntRange(from = 1) int cct) {
+ if (cct < 1) {
+ throw new IllegalArgumentException("Temperature must be greater than 0");
+ }
+
+ final float icct = 1.0f / cct;
+ final float icct2 = icct * icct;
+ final float x = cct <= 7000.0f ?
+ 0.244063f + 0.09911e3f * icct + 2.9678e6f * icct2 - 4.6070e9f * icct2 * icct :
+ 0.237040f + 0.24748e3f * icct + 1.9018e6f * icct2 - 2.0064e9f * icct2 * icct;
+ final float y = -3.0f * x * x + 2.87f * x - 0.275f;
+ return xyYToXyz(new float[] {x, y});
+ }
+
+ /**
+ * <p>Computes the chromatic adaptation transform from the specified
+ * source white point to the specified destination white point.</p>
+ *
+ * <p>The transform is computed using the von Kries method, described
+ * in more details in the documentation of {@link Adaptation}. The
+ * {@link Adaptation} enum provides different matrices that can be
+ * used to perform the adaptation.</p>
+ *
+ * @param adaptation The adaptation method
+ * @param srcWhitePoint The white point to adapt from
+ * @param dstWhitePoint The white point to adapt to
+ * @return A 3x3 matrix as a non-null array of 9 floats
+ *
+ * @hide
+ */
+ @NonNull
+ @Size(9)
+ public static float[] chromaticAdaptation(@NonNull Adaptation adaptation,
+ @NonNull @Size(min = 2, max = 3) float[] srcWhitePoint,
+ @NonNull @Size(min = 2, max = 3) float[] dstWhitePoint) {
+ float[] srcXyz = srcWhitePoint.length == 3 ?
+ Arrays.copyOf(srcWhitePoint, 3) : xyYToXyz(srcWhitePoint);
+ float[] dstXyz = dstWhitePoint.length == 3 ?
+ Arrays.copyOf(dstWhitePoint, 3) : xyYToXyz(dstWhitePoint);
+
+ if (compare(srcXyz, dstXyz)) {
+ return new float[] {
+ 1.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f
+ };
+ }
+ return chromaticAdaptation(adaptation.mTransform, srcXyz, dstXyz);
+ }
+
+ /**
* Implementation of the CIE XYZ color space. Assumes the white point is D50.
*/
@AnyThread
@@ -1900,6 +2037,15 @@ public abstract class ColorSpace {
}
/**
+ * Retrieve the native SkColorSpace object for passing to native.
+ *
+ * Only valid on ColorSpace.Rgb.
+ */
+ long getNativeInstance() {
+ throw new IllegalArgumentException("colorSpace must be an RGB color space");
+ }
+
+ /**
* {@usesMathJax}
*
* <p>An RGB color space is an additive color space using the
@@ -2028,7 +2174,7 @@ public abstract class ColorSpace {
* <p>The EOTF is of the form:</p>
*
* \(\begin{equation}
- * Y = \begin{cases}c X + f & X \lt d \\
+ * Y = \begin{cases}c X + f & X \lt d \\\
* \left( a X + b \right) ^{g} + e & X \ge d \end{cases}
* \end{equation}\)
*
@@ -2066,7 +2212,7 @@ public abstract class ColorSpace {
* <p>The EOTF is of the form:</p>
*
* \(\begin{equation}
- * Y = \begin{cases}c X & X \lt d \\
+ * Y = \begin{cases}c X & X \lt d \\\
* \left( a X + b \right) ^{g} & X \ge d \end{cases}
* \end{equation}\)
*
@@ -2202,7 +2348,22 @@ public abstract class ColorSpace {
private final boolean mIsWideGamut;
private final boolean mIsSrgb;
- @Nullable private TransferParameters mTransferParameters;
+ @Nullable private final TransferParameters mTransferParameters;
+ private final long mNativePtr;
+
+ @Override
+ long getNativeInstance() {
+ if (mNativePtr == 0) {
+ // If this object has TransferParameters, it must have a native object.
+ throw new IllegalArgumentException("ColorSpace must use an ICC "
+ + "parametric transfer function! used " + this);
+ }
+ return mNativePtr;
+ }
+
+ private static native long nativeGetNativeFinalizer();
+ private static native long nativeCreate(float a, float b, float c, float d,
+ float e, float f, float g, float[] xyz);
/**
* <p>Creates a new RGB color space using a 3x3 column-major transform matrix.
@@ -2231,8 +2392,8 @@ public abstract class ColorSpace {
@NonNull @Size(9) float[] toXYZ,
@NonNull DoubleUnaryOperator oetf,
@NonNull DoubleUnaryOperator eotf) {
- this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ),
- oetf, eotf, 0.0f, 1.0f, MIN_ID);
+ this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ), null,
+ oetf, eotf, 0.0f, 1.0f, null, MIN_ID);
}
/**
@@ -2282,7 +2443,7 @@ public abstract class ColorSpace {
@NonNull DoubleUnaryOperator eotf,
float min,
float max) {
- this(name, primaries, whitePoint, oetf, eotf, min, max, MIN_ID);
+ this(name, primaries, whitePoint, null, oetf, eotf, min, max, null, MIN_ID);
}
/**
@@ -2392,7 +2553,7 @@ public abstract class ColorSpace {
@NonNull @Size(min = 2, max = 3) float[] whitePoint,
@NonNull TransferParameters function,
@IntRange(from = MIN_ID, to = MAX_ID) int id) {
- this(name, primaries, whitePoint,
+ this(name, primaries, whitePoint, null,
function.e == 0.0 && function.f == 0.0 ?
x -> rcpResponse(x, function.a, function.b,
function.c, function.d, function.g) :
@@ -2403,8 +2564,7 @@ public abstract class ColorSpace {
function.c, function.d, function.g) :
x -> response(x, function.a, function.b, function.c,
function.d, function.e, function.f, function.g),
- 0.0f, 1.0f, id);
- mTransferParameters = function;
+ 0.0f, 1.0f, function, id);
}
/**
@@ -2519,15 +2679,12 @@ public abstract class ColorSpace {
float min,
float max,
@IntRange(from = MIN_ID, to = MAX_ID) int id) {
- this(name, primaries, whitePoint,
+ this(name, primaries, whitePoint, null,
gamma == 1.0 ? DoubleUnaryOperator.identity() :
x -> Math.pow(x < 0.0 ? 0.0 : x, 1 / gamma),
gamma == 1.0 ? DoubleUnaryOperator.identity() :
x -> Math.pow(x < 0.0 ? 0.0 : x, gamma),
- min, max, id);
- mTransferParameters = gamma == 1.0 ?
- new TransferParameters(0.0, 0.0, 1.0, 1.0 + Math.ulp(1.0f), gamma) :
- new TransferParameters(1.0, 0.0, 0.0, 0.0, gamma);
+ min, max, new TransferParameters(1.0, 0.0, 0.0, 0.0, gamma), id);
}
/**
@@ -2550,10 +2707,13 @@ 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 oetf Opto-electronic transfer function, cannot be null
* @param eotf Electro-optical transfer function, cannot be null
* @param min The minimum valid value in this color space's RGB range
* @param max The maximum valid value in this color space's RGB range
+ * @param transferParameters Parameters for the transfer functions
* @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID}
*
* @throws IllegalArgumentException If any of the following conditions is met:
@@ -2572,10 +2732,12 @@ 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 DoubleUnaryOperator oetf,
@NonNull DoubleUnaryOperator eotf,
float min,
float max,
+ @Nullable TransferParameters transferParameters,
@IntRange(from = MIN_ID, to = MAX_ID) int id) {
super(name, Model.RGB, id);
@@ -2603,7 +2765,15 @@ public abstract class ColorSpace {
mWhitePoint = xyWhitePoint(whitePoint);
mPrimaries = xyPrimaries(primaries);
- mTransform = computeXYZMatrix(mPrimaries, mWhitePoint);
+ if (transform == null) {
+ mTransform = computeXYZMatrix(mPrimaries, mWhitePoint);
+ } else {
+ if (transform.length != 9) {
+ throw new IllegalArgumentException("Transform must have 9 entries! Has "
+ + transform.length);
+ }
+ mTransform = transform;
+ }
mInverseTransform = inverse3x3(mTransform);
mOetf = oetf;
@@ -2616,10 +2786,39 @@ public abstract class ColorSpace {
mClampedOetf = oetf.andThen(clamp);
mClampedEotf = clamp.andThen(eotf);
+ mTransferParameters = transferParameters;
+
// A color space is wide-gamut if its area is >90% of NTSC 1953 and
// if it entirely contains the Color space definition in xyY
mIsWideGamut = isWideGamut(mPrimaries, min, max);
mIsSrgb = isSrgb(mPrimaries, mWhitePoint, oetf, eotf, min, max, id);
+
+ if (mTransferParameters != null) {
+ if (mWhitePoint == null || mTransform == null) {
+ throw new IllegalStateException(
+ "ColorSpace (" + this + ") cannot create native object! mWhitePoint: "
+ + mWhitePoint + " mTransform: " + mTransform);
+ }
+
+ // This mimics the old code that was in native.
+ float[] nativeTransform = adaptToIlluminantD50(mWhitePoint, mTransform);
+ mNativePtr = nativeCreate((float) mTransferParameters.a,
+ (float) mTransferParameters.b,
+ (float) mTransferParameters.c,
+ (float) mTransferParameters.d,
+ (float) mTransferParameters.e,
+ (float) mTransferParameters.f,
+ (float) mTransferParameters.g,
+ nativeTransform);
+ NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativePtr);
+ } else {
+ mNativePtr = 0;
+ }
+ }
+
+ private static class NoImagePreloadHolder {
+ public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+ ColorSpace.Rgb.class.getClassLoader(), nativeGetNativeFinalizer(), 0);
}
/**
@@ -2630,27 +2829,9 @@ public abstract class ColorSpace {
private Rgb(Rgb colorSpace,
@NonNull @Size(9) float[] transform,
@NonNull @Size(min = 2, max = 3) float[] whitePoint) {
- super(colorSpace.getName(), Model.RGB, -1);
-
- mWhitePoint = xyWhitePoint(whitePoint);
- mPrimaries = colorSpace.mPrimaries;
-
- mTransform = transform;
- mInverseTransform = inverse3x3(transform);
-
- mMin = colorSpace.mMin;
- mMax = colorSpace.mMax;
-
- mOetf = colorSpace.mOetf;
- mEotf = colorSpace.mEotf;
-
- mClampedOetf = colorSpace.mClampedOetf;
- mClampedEotf = colorSpace.mClampedEotf;
-
- mIsWideGamut = colorSpace.mIsWideGamut;
- mIsSrgb = colorSpace.mIsSrgb;
-
- mTransferParameters = colorSpace.mTransferParameters;
+ this(colorSpace.getName(), colorSpace.mPrimaries, whitePoint, transform,
+ colorSpace.mOetf, colorSpace.mEotf, colorSpace.mMin, colorSpace.mMax,
+ colorSpace.mTransferParameters, MIN_ID);
}
/**
@@ -3083,19 +3264,35 @@ public abstract class ColorSpace {
float max,
@IntRange(from = MIN_ID, to = MAX_ID) int id) {
if (id == 0) return true;
- if (!compare(primaries, SRGB_PRIMARIES)) {
+ if (!ColorSpace.compare(primaries, SRGB_PRIMARIES)) {
return false;
}
- if (!compare(whitePoint, ILLUMINANT_D65)) {
+ if (!ColorSpace.compare(whitePoint, ILLUMINANT_D65)) {
return false;
}
- if (OETF.applyAsDouble(0.5) < 0.5001) return false;
- if (EOTF.applyAsDouble(0.5) > 0.5001) return false;
+
if (min != 0.0f) return false;
if (max != 1.0f) return false;
+
+ // We would have already returned true if this was SRGB itself, so
+ // it is safe to reference it here.
+ ColorSpace.Rgb srgb = (ColorSpace.Rgb) get(Named.SRGB);
+
+ for (double x = 0.0; x <= 1.0; x += 1 / 255.0) {
+ if (!compare(x, OETF, srgb.mOetf)) return false;
+ if (!compare(x, EOTF, srgb.mEotf)) return false;
+ }
+
return true;
}
+ private static boolean compare(double point, @NonNull DoubleUnaryOperator a,
+ @NonNull DoubleUnaryOperator b) {
+ double rA = a.applyAsDouble(point);
+ double rB = b.applyAsDouble(point);
+ return Math.abs(rA - rB) <= 1e-3;
+ }
+
/**
* Computes whether the specified CIE xyY or XYZ primaries (with Y set to 1) form
* a wide color gamut. A color gamut is considered wide if its area is &gt; 90%
diff --git a/graphics/java/android/graphics/ComposeShader.java b/graphics/java/android/graphics/ComposeShader.java
index 70a5f53ae1e0..64ee6bf1f30c 100644
--- a/graphics/java/android/graphics/ComposeShader.java
+++ b/graphics/java/android/graphics/ComposeShader.java
@@ -39,7 +39,8 @@ public class ComposeShader extends Shader {
* @param shaderB The colors from this shader are seen as the "src" by the mode
* @param mode The mode that combines the colors from the two shaders. If mode
* is null, then SRC_OVER is assumed.
- */
+ */
+ @Deprecated
public ComposeShader(@NonNull Shader shaderA, @NonNull Shader shaderB, @NonNull Xfermode mode) {
this(shaderA, shaderB, mode.porterDuffMode);
}
@@ -52,12 +53,27 @@ public class ComposeShader extends Shader {
* @param shaderA The colors from this shader are seen as the "dst" by the mode
* @param shaderB The colors from this shader are seen as the "src" by the mode
* @param mode The PorterDuff mode that combines the colors from the two shaders.
- */
+ *
+ */
public ComposeShader(@NonNull Shader shaderA, @NonNull Shader shaderB,
@NonNull PorterDuff.Mode mode) {
this(shaderA, shaderB, mode.nativeInt);
}
+ /**
+ * Create a new compose shader, given shaders A, B, and a combining PorterDuff mode.
+ * When the mode is applied, it will be given the result from shader A as its
+ * "dst", and the result from shader B as its "src".
+ *
+ * @param shaderA The colors from this shader are seen as the "dst" by the mode
+ * @param shaderB The colors from this shader are seen as the "src" by the mode
+ * @param blendMode The blend mode that combines the colors from the two shaders.
+ */
+ public ComposeShader(@NonNull Shader shaderA, @NonNull Shader shaderB,
+ @NonNull BlendMode blendMode) {
+ this(shaderA, shaderB, blendMode.getXfermode().porterDuffMode);
+ }
+
private ComposeShader(Shader shaderA, Shader shaderB, int nativeMode) {
if (shaderA == null || shaderB == null) {
throw new IllegalArgumentException("Shader parameters must not be null");
@@ -87,17 +103,6 @@ public class ComposeShader extends Shader {
}
}
- /**
- * @hide
- */
- @Override
- protected Shader copy() {
- final ComposeShader copy = new ComposeShader(
- mShaderA.copy(), mShaderB.copy(), mPorterDuffMode);
- copyLocalMatrix(copy);
- return copy;
- }
-
private static native long nativeCreate(long nativeMatrix,
long nativeShaderA, long nativeShaderB, int porterDuffMode);
}
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
index c69eb32148c0..5af0da85bb39 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -17,6 +17,7 @@
package android.graphics;
import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
import android.content.res.AssetManager;
import android.graphics.fonts.FontVariationAxis;
import android.text.TextUtils;
@@ -35,32 +36,48 @@ import java.nio.channels.FileChannel;
* A family of typefaces with different styles.
*
* @hide
+ *
+ * @deprecated Use {@link android.graphics.fonts.FontFamily} instead.
*/
+@Deprecated
public class FontFamily {
private static String TAG = "FontFamily";
- private static final NativeAllocationRegistry sBuilderRegistry = new NativeAllocationRegistry(
- FontFamily.class.getClassLoader(), nGetBuilderReleaseFunc(), 64);
+ private static final NativeAllocationRegistry sBuilderRegistry =
+ NativeAllocationRegistry.createMalloced(
+ FontFamily.class.getClassLoader(), nGetBuilderReleaseFunc());
private @Nullable Runnable mNativeBuilderCleaner;
- private static final NativeAllocationRegistry sFamilyRegistry = new NativeAllocationRegistry(
- FontFamily.class.getClassLoader(), nGetFamilyReleaseFunc(), 64);
+ private static final NativeAllocationRegistry sFamilyRegistry =
+ NativeAllocationRegistry.createMalloced(
+ FontFamily.class.getClassLoader(), nGetFamilyReleaseFunc());
/**
* @hide
+ *
+ * This cannot be deleted because it's in use by AndroidX.
*/
+ @UnsupportedAppUsage(trackingBug = 123768928)
public long mNativePtr;
// Points native font family builder. Must be zero after freezing this family.
private long mBuilderPtr;
+ /**
+ * This cannot be deleted because it's in use by AndroidX.
+ */
+ @UnsupportedAppUsage(trackingBug = 123768928)
public FontFamily() {
mBuilderPtr = nInitBuilder(null, 0);
mNativeBuilderCleaner = sBuilderRegistry.registerNativeAllocation(this, mBuilderPtr);
}
+ /**
+ * This cannot be deleted because it's in use by AndroidX.
+ */
+ @UnsupportedAppUsage(trackingBug = 123768928)
public FontFamily(@Nullable String[] langs, int variant) {
final String langsString;
if (langs == null || langs.length == 0) {
@@ -79,7 +96,10 @@ public class FontFamily {
*
* @return boolean returns false if some error happens in native code, e.g. broken font file is
* passed, etc.
+ *
+ * This cannot be deleted because it's in use by AndroidX.
*/
+ @UnsupportedAppUsage(trackingBug = 123768928)
public boolean freeze() {
if (mBuilderPtr == 0) {
throw new IllegalStateException("This FontFamily is already frozen");
@@ -93,6 +113,10 @@ public class FontFamily {
return mNativePtr != 0;
}
+ /**
+ * This cannot be deleted because it's in use by AndroidX.
+ */
+ @UnsupportedAppUsage(trackingBug = 123768928)
public void abortCreation() {
if (mBuilderPtr == 0) {
throw new IllegalStateException("This FontFamily is already frozen or abandoned");
@@ -101,6 +125,10 @@ public class FontFamily {
mBuilderPtr = 0;
}
+ /**
+ * This cannot be deleted because it's in use by AndroidX.
+ */
+ @UnsupportedAppUsage(trackingBug = 123768928)
public boolean addFont(String path, int ttcIndex, FontVariationAxis[] axes, int weight,
int italic) {
if (mBuilderPtr == 0) {
@@ -122,6 +150,10 @@ public class FontFamily {
}
}
+ /**
+ * This cannot be deleted because it's in use by AndroidX.
+ */
+ @UnsupportedAppUsage(trackingBug = 123768928)
public boolean addFontFromBuffer(ByteBuffer font, int ttcIndex, FontVariationAxis[] axes,
int weight, int italic) {
if (mBuilderPtr == 0) {
@@ -146,7 +178,10 @@ public class FontFamily {
* @param isItalic Whether this font is italic. If the weight is set to 0, this will be resolved
* using the OS/2 table in the font.
* @return
+ *
+ * This cannot be deleted because it's in use by AndroidX.
*/
+ @UnsupportedAppUsage(trackingBug = 123768928)
public boolean addFontFromAssetManager(AssetManager mgr, String path, int cookie,
boolean isAsset, int ttcIndex, int weight, int isItalic,
FontVariationAxis[] axes) {
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 431d0e0eb7b4..21cc3757a40e 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -16,6 +16,7 @@
package android.graphics;
+import android.annotation.UnsupportedAppUsage;
import android.graphics.fonts.FontVariationAxis;
import android.text.FontConfig;
import android.util.Xml;
@@ -37,18 +38,27 @@ import java.util.regex.Pattern;
public class FontListParser {
/* Parse fallback list (no names) */
+ @UnsupportedAppUsage
public static FontConfig parse(InputStream in) throws XmlPullParserException, IOException {
+ return parse(in, "/system/fonts");
+ }
+
+ /**
+ * Parse the fonts.xml
+ */
+ public static FontConfig parse(InputStream in, String fontDir)
+ throws XmlPullParserException, IOException {
try {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(in, null);
parser.nextTag();
- return readFamilies(parser);
+ return readFamilies(parser, fontDir);
} finally {
in.close();
}
}
- private static FontConfig readFamilies(XmlPullParser parser)
+ private static FontConfig readFamilies(XmlPullParser parser, String fontDir)
throws XmlPullParserException, IOException {
List<FontConfig.Family> families = new ArrayList<>();
List<FontConfig.Alias> aliases = new ArrayList<>();
@@ -58,7 +68,7 @@ public class FontListParser {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
String tag = parser.getName();
if (tag.equals("family")) {
- families.add(readFamily(parser));
+ families.add(readFamily(parser, fontDir));
} else if (tag.equals("alias")) {
aliases.add(readAlias(parser));
} else {
@@ -69,18 +79,20 @@ public class FontListParser {
aliases.toArray(new FontConfig.Alias[aliases.size()]));
}
- private static FontConfig.Family readFamily(XmlPullParser parser)
+ /**
+ * Reads a family element
+ */
+ public static FontConfig.Family readFamily(XmlPullParser parser, String fontDir)
throws XmlPullParserException, IOException {
final String name = parser.getAttributeValue(null, "name");
- final String lang = parser.getAttributeValue(null, "lang");
- final String[] langs = lang == null ? null : lang.split("\\s+");
+ final String lang = parser.getAttributeValue("", "lang");
final String variant = parser.getAttributeValue(null, "variant");
final List<FontConfig.Font> fonts = new ArrayList<FontConfig.Font>();
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
final String tag = parser.getName();
if (tag.equals("font")) {
- fonts.add(readFont(parser));
+ fonts.add(readFont(parser, fontDir));
} else {
skip(parser);
}
@@ -93,7 +105,7 @@ public class FontListParser {
intVariant = FontConfig.Family.VARIANT_ELEGANT;
}
}
- return new FontConfig.Family(name, fonts.toArray(new FontConfig.Font[fonts.size()]), langs,
+ return new FontConfig.Family(name, fonts.toArray(new FontConfig.Font[fonts.size()]), lang,
intVariant);
}
@@ -101,7 +113,7 @@ public class FontListParser {
private static final Pattern FILENAME_WHITESPACE_PATTERN =
Pattern.compile("^[ \\n\\r\\t]+|[ \\n\\r\\t]+$");
- private static FontConfig.Font readFont(XmlPullParser parser)
+ private static FontConfig.Font readFont(XmlPullParser parser, String fontDir)
throws XmlPullParserException, IOException {
String indexStr = parser.getAttributeValue(null, "index");
int index = indexStr == null ? 0 : Integer.parseInt(indexStr);
@@ -124,8 +136,8 @@ public class FontListParser {
}
}
String sanitizedName = FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll("");
- return new FontConfig.Font(sanitizedName, index,
- axes.toArray(new FontVariationAxis[axes.size()]), weight, isItalic, fallbackFor);
+ return new FontConfig.Font(fontDir + sanitizedName, index, axes.toArray(
+ new FontVariationAxis[axes.size()]), weight, isItalic, fallbackFor);
}
private static FontVariationAxis readAxis(XmlPullParser parser)
@@ -136,7 +148,10 @@ public class FontListParser {
return new FontVariationAxis(tagStr, Float.parseFloat(styleValueStr));
}
- private static FontConfig.Alias readAlias(XmlPullParser parser)
+ /**
+ * Reads alias elements
+ */
+ public static FontConfig.Alias readAlias(XmlPullParser parser)
throws XmlPullParserException, IOException {
String name = parser.getAttributeValue(null, "name");
String toName = parser.getAttributeValue(null, "to");
@@ -151,7 +166,10 @@ public class FontListParser {
return new FontConfig.Alias(name, toName, weight);
}
- private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
+ /**
+ * Skip until next element
+ */
+ public static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
int depth = 1;
while (depth > 0) {
switch (parser.next()) {
diff --git a/graphics/java/android/graphics/FrameInfo.java b/graphics/java/android/graphics/FrameInfo.java
new file mode 100644
index 000000000000..42a5cc43eaa0
--- /dev/null
+++ b/graphics/java/android/graphics/FrameInfo.java
@@ -0,0 +1,128 @@
+/*
+ * 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.annotation.LongDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class that contains all the timing information for the current frame. This
+ * is used in conjunction with the hardware renderer to provide
+ * continous-monitoring jank events
+ *
+ * All times in nanoseconds from CLOCK_MONOTONIC/System.nanoTime()
+ *
+ * To minimize overhead from System.nanoTime() calls we infer durations of
+ * things by knowing the ordering of the events. For example, to know how
+ * long layout & measure took it's displayListRecordStart - performTraversalsStart.
+ *
+ * These constants must be kept in sync with FrameInfo.h in libhwui and are
+ * used for indexing into AttachInfo's frameInfo long[], which is intended
+ * to be quick to pass down to native via JNI, hence a pre-packed format
+ *
+ * @hide
+ */
+public final class FrameInfo {
+
+ public long[] frameInfo = new long[9];
+
+ // Various flags set to provide extra metadata about the current frame
+ private static final int FLAGS = 0;
+
+ // Is this the first-draw following a window layout?
+ public static final long FLAG_WINDOW_LAYOUT_CHANGED = 1;
+
+ // A renderer associated with just a Surface, not with a ViewRootImpl instance.
+ public static final long FLAG_SURFACE_CANVAS = 1 << 2;
+
+ @LongDef(flag = true, value = {
+ FLAG_WINDOW_LAYOUT_CHANGED, FLAG_SURFACE_CANVAS })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FrameInfoFlags {}
+
+ // The intended vsync time, unadjusted by jitter
+ private static final int INTENDED_VSYNC = 1;
+
+ // Jitter-adjusted vsync time, this is what was used as input into the
+ // animation & drawing system
+ private static final int VSYNC = 2;
+
+ // The time of the oldest input event
+ private static final int OLDEST_INPUT_EVENT = 3;
+
+ // The time of the newest input event
+ private static final int NEWEST_INPUT_EVENT = 4;
+
+ // When input event handling started
+ private static final int HANDLE_INPUT_START = 5;
+
+ // When animation evaluations started
+ private static final int ANIMATION_START = 6;
+
+ // When ViewRootImpl#performTraversals() started
+ private static final int PERFORM_TRAVERSALS_START = 7;
+
+ // When View:draw() started
+ private static final int DRAW_START = 8;
+
+ /** checkstyle */
+ public void setVsync(long intendedVsync, long usedVsync) {
+ frameInfo[INTENDED_VSYNC] = intendedVsync;
+ frameInfo[VSYNC] = usedVsync;
+ frameInfo[OLDEST_INPUT_EVENT] = Long.MAX_VALUE;
+ frameInfo[NEWEST_INPUT_EVENT] = 0;
+ frameInfo[FLAGS] = 0;
+ }
+
+ /** checkstyle */
+ public void updateInputEventTime(long inputEventTime, long inputEventOldestTime) {
+ if (inputEventOldestTime < frameInfo[OLDEST_INPUT_EVENT]) {
+ frameInfo[OLDEST_INPUT_EVENT] = inputEventOldestTime;
+ }
+ if (inputEventTime > frameInfo[NEWEST_INPUT_EVENT]) {
+ frameInfo[NEWEST_INPUT_EVENT] = inputEventTime;
+ }
+ }
+
+ /** checkstyle */
+ public void markInputHandlingStart() {
+ frameInfo[HANDLE_INPUT_START] = System.nanoTime();
+ }
+
+ /** checkstyle */
+ public void markAnimationsStart() {
+ frameInfo[ANIMATION_START] = System.nanoTime();
+ }
+
+ /** checkstyle */
+ public void markPerformTraversalsStart() {
+ frameInfo[PERFORM_TRAVERSALS_START] = System.nanoTime();
+ }
+
+ /** checkstyle */
+ public void markDrawStart() {
+ frameInfo[DRAW_START] = System.nanoTime();
+ }
+
+ /** checkstyle */
+ public void addFlags(@FrameInfoFlags long flags) {
+ frameInfo[FLAGS] |= flags;
+ }
+
+}
diff --git a/graphics/java/android/graphics/GraphicBuffer.java b/graphics/java/android/graphics/GraphicBuffer.java
index 61dd37fb0581..3b1fc70397ea 100644
--- a/graphics/java/android/graphics/GraphicBuffer.java
+++ b/graphics/java/android/graphics/GraphicBuffer.java
@@ -16,6 +16,7 @@
package android.graphics;
+import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
@@ -52,8 +53,8 @@ public class GraphicBuffer implements Parcelable {
private final int mHeight;
private final int mFormat;
private final int mUsage;
- private final boolean mCapturedSecureLayers;
// Note: do not rename, this field is used by native code
+ @UnsupportedAppUsage
private final long mNativeObject;
// These two fields are only used by lock/unlockCanvas()
@@ -83,58 +84,32 @@ public class GraphicBuffer implements Parcelable {
}
/**
- * Private use only. See {@link #create(int, int, int, int, boolean)}.
+ * Private use only. See {@link #create(int, int, int, int)}.
*/
- private GraphicBuffer(int width, int height, int format, int usage, long nativeObject,
- boolean capturedSecureLayers) {
+ @UnsupportedAppUsage
+ private GraphicBuffer(int width, int height, int format, int usage, long nativeObject) {
mWidth = width;
mHeight = height;
mFormat = format;
mUsage = usage;
mNativeObject = nativeObject;
- mCapturedSecureLayers = capturedSecureLayers;
- }
-
- /**
- * Private use only. See {@link #create(int, int, int, int)}.
- */
- private GraphicBuffer(int width, int height, int format, int usage, long nativeObject) {
- this(width, height, format, usage, nativeObject, false);
}
/**
* For SurfaceControl JNI.
* @hide
*/
+ @UnsupportedAppUsage
public static GraphicBuffer createFromExisting(int width, int height,
- int format, int usage, long unwrappedNativeObject,
- boolean capturedSecureLayers) {
+ int format, int usage, long unwrappedNativeObject) {
long nativeObject = nWrapGraphicBuffer(unwrappedNativeObject);
if (nativeObject != 0) {
- return new GraphicBuffer(width, height, format, usage, nativeObject,
- capturedSecureLayers);
+ return new GraphicBuffer(width, height, format, usage, nativeObject);
}
return null;
}
/**
- * For SurfaceControl JNI. Provides and ignored value for capturedSecureLayers for backwards
- * compatibility
- * @hide
- */
- public static GraphicBuffer createFromExisting(int width, int height,
- int format, int usage, long unwrappedNativeObject) {
- return createFromExisting(width, height, format, usage, unwrappedNativeObject, false);
- }
-
- /**
- * Returns true if the buffer contains visible secure layers.
- */
- public boolean doesContainSecureLayers() {
- return mCapturedSecureLayers;
- }
-
- /**
* Returns the width of this buffer in pixels.
*/
public int getWidth() {
@@ -303,7 +278,8 @@ public class GraphicBuffer implements Parcelable {
nWriteGraphicBufferToParcel(mNativeObject, dest);
}
- public static final Parcelable.Creator<GraphicBuffer> CREATOR =
+ @UnsupportedAppUsage
+ public static final @android.annotation.NonNull Parcelable.Creator<GraphicBuffer> CREATOR =
new Parcelable.Creator<GraphicBuffer>() {
public GraphicBuffer createFromParcel(Parcel in) {
int width = in.readInt();
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
new file mode 100644
index 000000000000..b6b2d4e1c46a
--- /dev/null
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -0,0 +1,1183 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+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;
+import android.view.PixelCopy;
+import android.view.Surface;
+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;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+import sun.misc.Cleaner;
+
+/**
+ * <p>Creates an instance of a hardware-accelerated renderer. This is used to render a scene built
+ * from {@link RenderNode}'s to an output {@link android.view.Surface}. There can be as many
+ * HardwareRenderer instances as desired.</p>
+ *
+ * <h3>Resources & lifecycle</h3>
+ *
+ * <p>All HardwareRenderer instances share a common render thread. The render thread contains
+ * the GPU context & resources necessary to do GPU-accelerated rendering. As such, the first
+ * HardwareRenderer created comes with the cost of also creating the associated GPU contexts,
+ * however each incremental HardwareRenderer thereafter is fairly cheap. The expected usage
+ * is to have a HardwareRenderer instance for every active {@link Surface}. For example
+ * when an Activity shows a Dialog the system internally will use 2 hardware renderers, both
+ * of which may be drawing at the same time.</p>
+ *
+ * <p>NOTE: Due to the shared, cooperative nature of the render thread it is critical that
+ * any {@link Surface} used must have a prompt, reliable consuming side. System-provided
+ * consumers such as {@link android.view.SurfaceView},
+ * {@link android.view.Window#takeSurface(SurfaceHolder.Callback2)},
+ * or {@link android.view.TextureView} all fit this requirement. However if custom consumers
+ * are used such as when using {@link SurfaceTexture} or {@link android.media.ImageReader}
+ * it is the app's responsibility to ensure that they consume updates promptly and rapidly.
+ * Failure to do so will cause the render thread to stall on that surface, blocking all
+ * HardwareRenderer instances.</p>
+ */
+public class HardwareRenderer {
+ private static final String LOG_TAG = "HardwareRenderer";
+
+ // Keep in sync with DrawFrameTask.h SYNC_* flags
+ /**
+ * Nothing interesting to report. Sync & draw kicked off
+ */
+ public static final int SYNC_OK = 0;
+
+ /**
+ * The renderer is requesting a redraw. This can occur if there's an animation that's running
+ * in the RenderNode tree and the hardware renderer is unable to self-animate.
+ *
+ * <p>If this is returned from syncAndDraw the expectation is that syncAndDraw
+ * will be called again on the next vsync signal.
+ */
+ public static final int SYNC_REDRAW_REQUESTED = 1 << 0;
+
+ /**
+ * The hardware renderer no longer has a valid {@link android.view.Surface} to render to.
+ * This can happen if {@link Surface#release()} was called. The user should no longer
+ * attempt to call syncAndDraw until a new surface has been provided by calling
+ * setSurface.
+ *
+ * <p>Spoiler: the reward is GPU-accelerated drawing, better find that Surface!
+ */
+ public static final int SYNC_LOST_SURFACE_REWARD_IF_FOUND = 1 << 1;
+
+ /**
+ * The hardware renderer has been set to a "stopped" state. If this is returned then the
+ * rendering content has been synced, however a frame was not produced.
+ */
+ public static final int SYNC_CONTEXT_IS_STOPPED = 1 << 2;
+
+ /**
+ * The content was synced but the renderer has declined to produce a frame in this vsync
+ * interval. This can happen if a frame was already drawn in this vsync or if the renderer
+ * is outrunning the frame consumer. The renderer will internally re-schedule itself
+ * to render a frame in the next vsync signal, so the caller does not need to do anything
+ * in response to this signal.
+ */
+ public static final int SYNC_FRAME_DROPPED = 1 << 3;
+
+ /** @hide */
+ @IntDef(value = {
+ SYNC_OK, SYNC_REDRAW_REQUESTED, SYNC_LOST_SURFACE_REWARD_IF_FOUND,
+ SYNC_CONTEXT_IS_STOPPED, SYNC_FRAME_DROPPED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SyncAndDrawResult {
+ }
+
+ /** @hide */
+ public static final int FLAG_DUMP_FRAMESTATS = 1 << 0;
+ /** @hide */
+ public static final int FLAG_DUMP_RESET = 1 << 1;
+ /** @hide */
+ public static final int FLAG_DUMP_ALL = FLAG_DUMP_FRAMESTATS;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = {"FLAG_DUMP_"}, value = {
+ FLAG_DUMP_FRAMESTATS,
+ FLAG_DUMP_RESET
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DumpFlags {
+ }
+
+ /**
+ * Name of the file that holds the shaders cache.
+ */
+ private static final String CACHE_PATH_SHADERS = "com.android.opengl.shaders_cache";
+ private static final String CACHE_PATH_SKIASHADERS = "com.android.skia.shaders_cache";
+
+ private final long mNativeProxy;
+ /** @hide */
+ protected RenderNode mRootNode;
+ private boolean mOpaque = true;
+ private boolean mForceDark = false;
+ private boolean mIsWideGamut = false;
+
+ /**
+ * Creates a new instance of a HardwareRenderer. The HardwareRenderer will default
+ * to opaque with no light source configured.
+ */
+ public HardwareRenderer() {
+ mRootNode = RenderNode.adopt(nCreateRootRenderNode());
+ mRootNode.setClipToBounds(false);
+ mNativeProxy = nCreateProxy(!mOpaque, mRootNode.mNativeRenderNode);
+ if (mNativeProxy == 0) {
+ throw new OutOfMemoryError("Unable to create hardware renderer");
+ }
+ Cleaner.create(this, new DestroyContextRunnable(mNativeProxy));
+ ProcessInitializer.sInstance.init(mNativeProxy);
+ }
+
+ /**
+ * Destroys the rendering context of this HardwareRenderer. This destroys the resources
+ * associated with this renderer and releases the currently set {@link Surface}. This must
+ * be called when this HardwareRenderer is no longer needed.
+ *
+ * <p>The renderer may be restored from this state by setting a new {@link Surface}, setting
+ * new rendering content with {@link #setContentRoot(RenderNode)}, and resuming
+ * rendering by issuing a new {@link FrameRenderRequest}.
+ *
+ * <p>It is recommended to call this in response to callbacks such as
+ * {@link android.view.SurfaceHolder.Callback#surfaceDestroyed(SurfaceHolder)}.
+ *
+ * <p>Note that if there are any outstanding frame commit callbacks they may never being
+ * invoked if the frame was deferred to a later vsync.
+ */
+ public void destroy() {
+ nDestroy(mNativeProxy, mRootNode.mNativeRenderNode);
+ }
+
+ /**
+ * Sets a name for this renderer. This is used to identify this renderer instance
+ * when reporting debug information such as the per-window frame time metrics
+ * reported by 'adb shell dumpsys gfxinfo [package] framestats'
+ *
+ * @param name The debug name to use for this HardwareRenderer instance
+ */
+ public void setName(@NonNull String name) {
+ nSetName(mNativeProxy, name);
+ }
+
+ /**
+ * Sets the center of the light source. The light source point controls the directionality
+ * and shape of shadows rendered by RenderNode Z & elevation.
+ *
+ * <p>The platform's recommendation is to set lightX to 'displayWidth / 2f - windowLeft', set
+ * lightY to 0 - windowTop, lightZ set to 600dp, and lightRadius to 800dp.
+ *
+ * <p>The light source should be setup both as part of initial configuration, and whenever
+ * the window moves to ensure the light source stays anchored in display space instead
+ * of in window space.
+ *
+ * <p>This must be set at least once along with {@link #setLightSourceAlpha(float, float)}
+ * before shadows will work.
+ *
+ * @param lightX The X position of the light source
+ * @param lightY The Y position of the light source
+ * @param lightZ The Z position of the light source. Must be >= 0.
+ * @param lightRadius The radius of the light source. Smaller radius will have sharper edges,
+ * larger radius will have softer shadows.
+ */
+ public void setLightSourceGeometry(float lightX, float lightY, float lightZ,
+ float lightRadius) {
+ validateFinite(lightX, "lightX");
+ validateFinite(lightY, "lightY");
+ validatePositive(lightZ, "lightZ");
+ validatePositive(lightRadius, "lightRadius");
+ nSetLightGeometry(mNativeProxy, lightX, lightY, lightZ, lightRadius);
+ }
+
+ /**
+ * Configures the ambient & spot shadow alphas. This is the alpha used when the shadow
+ * has max alpha, and ramps down from the values provided to zero.
+ *
+ * <p>These values are typically provided by the current theme, see
+ * {@link android.R.attr#spotShadowAlpha} and {@link android.R.attr#ambientShadowAlpha}.
+ *
+ * <p>This must be set at least once along with
+ * {@link #setLightSourceGeometry(float, float, float, float)} before shadows will work.
+ *
+ * @param ambientShadowAlpha The alpha for the ambient shadow. If unsure, a reasonable default
+ * is 0.039f.
+ * @param spotShadowAlpha The alpha for the spot shadow. If unsure, a reasonable default is
+ * 0.19f.
+ */
+ public void setLightSourceAlpha(@FloatRange(from = 0.0f, to = 1.0f) float ambientShadowAlpha,
+ @FloatRange(from = 0.0f, to = 1.0f) float spotShadowAlpha) {
+ validateAlpha(ambientShadowAlpha, "ambientShadowAlpha");
+ validateAlpha(spotShadowAlpha, "spotShadowAlpha");
+ nSetLightAlpha(mNativeProxy, ambientShadowAlpha, spotShadowAlpha);
+ }
+
+ /**
+ * Sets the content root to render. It is not necessary to call this whenever the content
+ * recording changes. Any mutations to the RenderNode content, or any of the RenderNode's
+ * contained within the content node, will be applied whenever a new {@link FrameRenderRequest}
+ * is issued via {@link #createRenderRequest()} and {@link FrameRenderRequest#syncAndDraw()}.
+ *
+ * @param content The content to set as the root RenderNode. If null the content root is removed
+ * and the renderer will draw nothing.
+ */
+ public void setContentRoot(@Nullable RenderNode content) {
+ RecordingCanvas canvas = mRootNode.beginRecording();
+ if (content != null) {
+ canvas.drawRenderNode(content);
+ }
+ mRootNode.endRecording();
+ }
+
+ /**
+ * <p>The surface to render into. The surface is assumed to be associated with the display and
+ * as such is still driven by vsync signals such as those from
+ * {@link android.view.Choreographer} and that it has a native refresh rate matching that of
+ * the display's (typically 60hz).</p>
+ *
+ * <p>NOTE: Due to the shared, cooperative nature of the render thread it is critical that
+ * any {@link Surface} used must have a prompt, reliable consuming side. System-provided
+ * consumers such as {@link android.view.SurfaceView},
+ * {@link android.view.Window#takeSurface(SurfaceHolder.Callback2)},
+ * or {@link android.view.TextureView} all fit this requirement. However if custom consumers
+ * are used such as when using {@link SurfaceTexture} or {@link android.media.ImageReader}
+ * it is the app's responsibility to ensure that they consume updates promptly and rapidly.
+ * Failure to do so will cause the render thread to stall on that surface, blocking all
+ * HardwareRenderer instances.</p>
+ *
+ * @param surface The surface to render into. If null then rendering will be stopped. If
+ * non-null then {@link Surface#isValid()} must be true.
+ */
+ public void setSurface(@Nullable Surface surface) {
+ if (surface != null && !surface.isValid()) {
+ throw new IllegalArgumentException("Surface is invalid. surface.isValid() == false.");
+ }
+ nSetSurface(mNativeProxy, surface);
+ }
+
+ /**
+ * Sets the parameters that can be used to control a render request for a
+ * {@link HardwareRenderer}. This is not thread-safe and must not be held on to for longer
+ * than a single frame request.
+ */
+ public final class FrameRenderRequest {
+ private FrameInfo mFrameInfo = new FrameInfo();
+ private boolean mWaitForPresent;
+
+ private FrameRenderRequest() { }
+
+ private void reset() {
+ mWaitForPresent = false;
+ // Default to the animation time which, if choreographer is in play, will default to the
+ // current vsync time. Otherwise it will be 'now'.
+ mRenderRequest.setVsyncTime(
+ AnimationUtils.currentAnimationTimeMillis() * TimeUtils.NANOS_PER_MS);
+ }
+
+ /** @hide */
+ public void setFrameInfo(FrameInfo info) {
+ System.arraycopy(info.frameInfo, 0, mFrameInfo.frameInfo, 0, info.frameInfo.length);
+ }
+
+ /**
+ * Sets the vsync time that represents the start point of this frame. Typically this
+ * comes from {@link android.view.Choreographer.FrameCallback}. Other compatible time
+ * sources include {@link System#nanoTime()}, however if the result is being displayed
+ * on-screen then using {@link android.view.Choreographer} is strongly recommended to
+ * ensure smooth animations.
+ *
+ * <p>If the clock source is not from a CLOCK_MONOTONIC source then any animations driven
+ * directly by RenderThread will not be synchronized properly with the current frame.
+ *
+ * @param vsyncTime The vsync timestamp for this frame. The timestamp is in nanoseconds
+ * and should come from a CLOCK_MONOTONIC source.
+ *
+ * @return this instance
+ */
+ public @NonNull FrameRenderRequest setVsyncTime(long vsyncTime) {
+ mFrameInfo.setVsync(vsyncTime, vsyncTime);
+ mFrameInfo.addFlags(FrameInfo.FLAG_SURFACE_CANVAS);
+ return this;
+ }
+
+ /**
+ * Adds a frame commit callback. This callback will be invoked when the current rendering
+ * content has been rendered into a frame and submitted to the swap chain. The frame may
+ * not currently be visible on the display when this is invoked, but it has been submitted.
+ * This callback is useful in combination with {@link PixelCopy} to capture the current
+ * rendered content of the UI reliably.
+ *
+ * @param executor The executor to run the callback on. It is strongly recommended that
+ * this executor post to a different thread, as the calling thread is
+ * highly sensitive to being blocked.
+ * @param frameCommitCallback The callback to invoke when the frame content has been drawn.
+ * Will be invoked on the given {@link Executor}.
+ *
+ * @return this instance
+ */
+ public @NonNull FrameRenderRequest setFrameCommitCallback(@NonNull Executor executor,
+ @NonNull Runnable frameCommitCallback) {
+ setFrameCompleteCallback(frameNr -> executor.execute(frameCommitCallback));
+ return this;
+ }
+
+ /**
+ * Sets whether or not {@link #syncAndDraw()} should block until the frame has been
+ * presented. If this is true and {@link #syncAndDraw()} does not return
+ * {@link #SYNC_FRAME_DROPPED} or an error then when {@link #syncAndDraw()} has returned
+ * the frame has been submitted to the {@link Surface}. The default and typically
+ * recommended value is false, as blocking for present will prevent pipelining from
+ * happening, reducing overall throughput. This is useful for situations such as
+ * {@link SurfaceHolder.Callback2#surfaceRedrawNeeded(SurfaceHolder)} where it is desired
+ * to block until a frame has been presented to ensure first-frame consistency with
+ * other Surfaces.
+ *
+ * @param shouldWait If true the next call to {@link #syncAndDraw()} will block until
+ * completion.
+ * @return this instance
+ */
+ public @NonNull FrameRenderRequest setWaitForPresent(boolean shouldWait) {
+ mWaitForPresent = shouldWait;
+ return this;
+ }
+
+ /**
+ * Syncs the RenderNode tree to the render thread and requests a frame to be drawn. This
+ * {@link FrameRenderRequest} instance should no longer be used after calling this method.
+ * The system internally may reuse instances of {@link FrameRenderRequest} to reduce
+ * allocation churn.
+ *
+ * @return The result of the sync operation.
+ */
+ @SyncAndDrawResult
+ public int syncAndDraw() {
+ int syncResult = syncAndDrawFrame(mFrameInfo);
+ if (mWaitForPresent && (syncResult & SYNC_FRAME_DROPPED) == 0) {
+ fence();
+ }
+ return syncResult;
+ }
+ }
+
+ private FrameRenderRequest mRenderRequest = new FrameRenderRequest();
+
+ /**
+ * Returns a {@link FrameRenderRequest} that can be used to render a new frame. This is used
+ * to synchronize the RenderNode content provided by {@link #setContentRoot(RenderNode)} with
+ * the RenderThread and then renders a single frame to the Surface set with
+ * {@link #setSurface(Surface)}.
+ *
+ * @return An instance of {@link FrameRenderRequest}. The instance may be reused for every
+ * frame, so the caller should not hold onto it for longer than a single render request.
+ */
+ public @NonNull FrameRenderRequest createRenderRequest() {
+ mRenderRequest.reset();
+ return mRenderRequest;
+ }
+
+ /**
+ * Syncs the RenderNode tree to the render thread and requests a frame to be drawn.
+ *
+ * @hide
+ */
+ @SyncAndDrawResult
+ public int syncAndDrawFrame(@NonNull FrameInfo frameInfo) {
+ return nSyncAndDrawFrame(mNativeProxy, frameInfo.frameInfo, frameInfo.frameInfo.length);
+ }
+
+ /**
+ * Suspends any current rendering into the surface but do not do any destruction. This
+ * is useful to temporarily suspend using the active Surface in order to do any Surface
+ * mutations necessary.
+ *
+ * <p>Any subsequent draws will override the pause, resuming normal operation.
+ *
+ * @return true if there was an outstanding render request, false otherwise. If this is true
+ * the caller should ensure that {@link #createRenderRequest()}
+ * and {@link FrameRenderRequest#syncAndDraw()} is called at the soonest
+ * possible time to resume normal operation.
+ *
+ * TODO Should this be exposed? ViewRootImpl needs it because it destroys the old
+ * Surface before getting a new one. However things like SurfaceView will ensure that
+ * the old surface remains un-destroyed until after a new frame has been produced with
+ * the new surface.
+ * @hide
+ */
+ public boolean pause() {
+ return nPause(mNativeProxy);
+ }
+
+ /**
+ * Hard stops rendering into the surface. If the renderer is stopped it will
+ * block any attempt to render. Calls to {@link FrameRenderRequest#syncAndDraw()} will
+ * still sync over the latest rendering content, however they will not render and instead
+ * {@link #SYNC_CONTEXT_IS_STOPPED} will be returned.
+ *
+ * <p>If false is passed then rendering will resume as normal. Any pending rendering requests
+ * will produce a new frame at the next vsync signal.
+ *
+ * <p>This is useful in combination with lifecycle events such as {@link Activity#onStop()}
+ * and {@link Activity#onStart()}.
+ *
+ * @param stopped true to stop all rendering, false to resume
+ * @hide
+ */
+ public void setStopped(boolean stopped) {
+ nSetStopped(mNativeProxy, stopped);
+ }
+
+ /**
+ * Hard stops rendering into the surface. If the renderer is stopped it will
+ * block any attempt to render. Calls to {@link FrameRenderRequest#syncAndDraw()} will
+ * still sync over the latest rendering content, however they will not render and instead
+ * {@link #SYNC_CONTEXT_IS_STOPPED} will be returned.
+ *
+ * <p>This is useful in combination with lifecycle events such as {@link Activity#onStop()}.
+ * See {@link #start()} for resuming rendering.
+ */
+ public void stop() {
+ nSetStopped(mNativeProxy, true);
+ }
+
+ /**
+ * Resumes rendering into the surface. Any pending rendering requests
+ * will produce a new frame at the next vsync signal.
+ *
+ * <p>This is useful in combination with lifecycle events such as {@link Activity#onStart()}.
+ * See {@link #stop()} for stopping rendering.
+ */
+ public void start() {
+ nSetStopped(mNativeProxy, false);
+ }
+
+ /**
+ * Destroys all the display lists associated with the current rendering content.
+ * This includes releasing a reference to the current content root RenderNode. It will
+ * therefore be necessary to call {@link #setContentRoot(RenderNode)} in order to resume
+ * rendering after calling this, along with re-recording the display lists for the
+ * RenderNode tree.
+ *
+ * <p>It is recommended, but not necessary, to use this in combination with lifecycle events
+ * such as {@link Activity#onStop()} and {@link Activity#onStart()} or in response to
+ * {@link android.content.ComponentCallbacks2#onTrimMemory(int)} signals such as
+ * {@link android.content.ComponentCallbacks2#TRIM_MEMORY_UI_HIDDEN}
+ *
+ * See also {@link #stop()}.
+ */
+ public void clearContent() {
+ nDestroyHardwareResources(mNativeProxy);
+ }
+
+ /**
+ * Whether or not the force-dark feature should be used for this renderer.
+ * @hide
+ */
+ public boolean setForceDark(boolean enable) {
+ if (mForceDark != enable) {
+ mForceDark = enable;
+ nSetForceDark(mNativeProxy, enable);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Allocate buffers ahead of time to avoid allocation delays during rendering.
+ *
+ * <p>Typically a Surface will allocate buffers lazily. This is usually fine and reduces the
+ * memory usage of Surfaces that render rarely or never hit triple buffering. However
+ * for UI it can result in a slight bit of jank on first launch. This hint will
+ * tell the HardwareRenderer that now is a good time to allocate the 3 buffers
+ * necessary for typical rendering.
+ *
+ * <p>Must be called after a {@link Surface} has been set.
+ *
+ * TODO: Figure out if we even need/want this. Should HWUI just be doing this in response
+ * to setSurface anyway? Vulkan swapchain makes this murky, so delay making it public
+ * @hide
+ */
+ public void allocateBuffers() {
+ nAllocateBuffers(mNativeProxy);
+ }
+
+ /**
+ * Notifies the hardware renderer that a call to {@link FrameRenderRequest#syncAndDraw()} will
+ * be coming soon. This is used to help schedule when RenderThread-driven animations will
+ * happen as the renderer wants to avoid producing more than one frame per vsync signal.
+ */
+ public void notifyFramePending() {
+ nNotifyFramePending(mNativeProxy);
+ }
+
+ /**
+ * Change the HardwareRenderer's opacity. Will take effect on the next frame produced.
+ *
+ * <p>If the renderer is set to opaque it is the app's responsibility to ensure that the
+ * content renders to every pixel of the Surface, otherwise corruption may result. Note that
+ * this includes ensuring that the first draw of any given pixel does not attempt to blend
+ * against the destination. If this is false then the hardware renderer will clear to
+ * transparent at the start of every frame.
+ *
+ * @param opaque true if the content rendered is opaque, false if the renderer should clear
+ * to transparent before rendering
+ */
+ public void setOpaque(boolean opaque) {
+ if (mOpaque != opaque) {
+ mOpaque = opaque;
+ nSetOpaque(mNativeProxy, mOpaque);
+ }
+ }
+
+ /**
+ * Whether or not the renderer is set to be opaque. See {@link #setOpaque(boolean)}
+ *
+ * @return true if the renderer is opaque, false otherwise
+ */
+ public boolean isOpaque() {
+ return mOpaque;
+ }
+
+ /** @hide */
+ public void setFrameCompleteCallback(FrameCompleteCallback callback) {
+ nSetFrameCompleteCallback(mNativeProxy, callback);
+ }
+
+ /**
+ * TODO: Public API this?
+ *
+ * @hide
+ */
+ public void addFrameMetricsObserver(FrameMetricsObserver observer) {
+ long nativeObserver = nAddFrameMetricsObserver(mNativeProxy, observer);
+ observer.mNative = new VirtualRefBasePtr(nativeObserver);
+ }
+
+ /**
+ * TODO: Public API this?
+ *
+ * @hide
+ */
+ public void removeFrameMetricsObserver(FrameMetricsObserver observer) {
+ nRemoveFrameMetricsObserver(mNativeProxy, observer.mNative.get());
+ observer.mNative = null;
+ }
+
+ /**
+ * Enable/disable wide gamut rendering on this renderer. Whether or not the actual rendering
+ * will be wide gamut depends on the hardware support for such rendering.
+ *
+ * @param wideGamut true if this renderer should render in wide gamut, false if it should
+ * render in sRGB
+ * TODO: Figure out color...
+ * @hide
+ */
+ public void setWideGamut(boolean wideGamut) {
+ mIsWideGamut = wideGamut;
+ nSetWideGamut(mNativeProxy, wideGamut);
+ }
+
+ /**
+ * Blocks until all previously queued work has completed.
+ *
+ * TODO: Only used for draw finished listeners, but the FrameCompleteCallback does that
+ * better
+ *
+ * @hide
+ */
+ public void fence() {
+ nFence(mNativeProxy);
+ }
+
+ /** @hide */
+ public void registerAnimatingRenderNode(RenderNode animator) {
+ nRegisterAnimatingRenderNode(mRootNode.mNativeRenderNode, animator.mNativeRenderNode);
+ }
+
+ /** @hide */
+ public void registerVectorDrawableAnimator(NativeVectorDrawableAnimator animator) {
+ nRegisterVectorDrawableAnimator(mRootNode.mNativeRenderNode,
+ animator.getAnimatorNativePtr());
+ }
+
+ /**
+ * Prevents any further drawing until {@link FrameRenderRequest#syncAndDraw()} is called.
+ * This is a signal that the contents of the RenderNode tree are no longer safe to play back.
+ * In practice this usually means that there are Functor pointers in the
+ * display list that are no longer valid.
+ *
+ * TODO: Can we get webview off of this?
+ *
+ * @hide
+ */
+ public void stopDrawing() {
+ nStopDrawing(mNativeProxy);
+ }
+
+ /**
+ * Creates a new hardware layer. A hardware layer built by calling this
+ * method will be treated as a texture layer, instead of as a render target.
+ *
+ * @return A hardware layer
+ * @hide
+ */
+ public TextureLayer createTextureLayer() {
+ long layer = nCreateTextureLayer(mNativeProxy);
+ return TextureLayer.adoptTextureLayer(this, layer);
+ }
+
+ /**
+ * Detaches the layer's surface texture from the GL context and releases
+ * the texture id
+ *
+ * @hide
+ */
+ public void detachSurfaceTexture(long hardwareLayer) {
+ nDetachSurfaceTexture(mNativeProxy, hardwareLayer);
+ }
+
+
+ /** @hide */
+ public void buildLayer(RenderNode node) {
+ if (node.hasDisplayList()) {
+ nBuildLayer(mNativeProxy, node.mNativeRenderNode);
+ }
+ }
+
+ /** @hide */
+ public boolean copyLayerInto(final TextureLayer layer, final Bitmap bitmap) {
+ return nCopyLayerInto(mNativeProxy, layer.getDeferredLayerUpdater(),
+ bitmap.getNativeInstance());
+ }
+
+ /**
+ * Indicates that the specified hardware layer needs to be updated
+ * as soon as possible.
+ *
+ * @param layer The hardware layer that needs an update
+ * @hide
+ */
+ public void pushLayerUpdate(TextureLayer layer) {
+ nPushLayerUpdate(mNativeProxy, layer.getDeferredLayerUpdater());
+ }
+
+ /**
+ * Tells the HardwareRenderer that the layer is destroyed. The renderer
+ * should remove the layer from any update queues.
+ *
+ * @hide
+ */
+ public void onLayerDestroyed(TextureLayer layer) {
+ nCancelLayerUpdate(mNativeProxy, layer.getDeferredLayerUpdater());
+ }
+
+ /** @hide */
+ public void setFrameCallback(FrameDrawingCallback callback) {
+ nSetFrameCallback(mNativeProxy, callback);
+ }
+
+ /**
+ * Adds a rendernode to the renderer which can be drawn and changed asynchronously to the
+ * rendernode of the UI thread.
+ *
+ * @param node The node to add.
+ * @param placeFront If true, the render node will be placed in front of the content node,
+ * otherwise behind the content node.
+ * @hide
+ */
+ public void addRenderNode(RenderNode node, boolean placeFront) {
+ nAddRenderNode(mNativeProxy, node.mNativeRenderNode, placeFront);
+ }
+
+ /**
+ * Only especially added render nodes can be removed.
+ *
+ * @param node The node which was added via addRenderNode which should get removed again.
+ * @hide
+ */
+ public void removeRenderNode(RenderNode node) {
+ nRemoveRenderNode(mNativeProxy, node.mNativeRenderNode);
+ }
+
+ /**
+ * Draws a particular render node. If the node is not the content node, only the additional
+ * nodes will get drawn and the content remains untouched.
+ *
+ * @param node The node to be drawn.
+ * @hide
+ */
+ public void drawRenderNode(RenderNode node) {
+ nDrawRenderNode(mNativeProxy, node.mNativeRenderNode);
+ }
+
+ /**
+ * Loads system properties used by the renderer. This method is invoked
+ * whenever system properties are modified. Implementations can use this
+ * to trigger live updates of the renderer based on properties.
+ *
+ * @return True if a property has changed.
+ * @hide
+ */
+ public boolean loadSystemProperties() {
+ return nLoadSystemProperties(mNativeProxy);
+ }
+
+ /**
+ * @hide
+ */
+ public void dumpProfileInfo(FileDescriptor fd, @DumpFlags int dumpFlags) {
+ nDumpProfileInfo(mNativeProxy, fd, dumpFlags);
+ }
+
+ /**
+ * To avoid unnecessary overdrawing of the main content all additionally passed render nodes
+ * will be prevented to overdraw this area. It will be synchronized with the draw call.
+ * This should be updated in the content view's draw call.
+ *
+ * @param left The left side of the protected bounds.
+ * @param top The top side of the protected bounds.
+ * @param right The right side of the protected bounds.
+ * @param bottom The bottom side of the protected bounds.
+ * @hide
+ */
+ public void setContentDrawBounds(int left, int top, int right, int bottom) {
+ nSetContentDrawBounds(mNativeProxy, left, top, right, bottom);
+ }
+
+ /** @hide */
+ public void setPictureCaptureCallback(@Nullable PictureCapturedCallback callback) {
+ nSetPictureCaptureCallback(mNativeProxy, callback);
+ }
+
+ /** @hide */
+ public boolean isWideGamut() {
+ return mIsWideGamut;
+ }
+
+ /** called by native */
+ static void invokePictureCapturedCallback(long picturePtr, PictureCapturedCallback callback) {
+ Picture picture = new Picture(picturePtr);
+ callback.onPictureCaptured(picture);
+ }
+
+ /**
+ * Interface used to receive callbacks when a frame is being drawn.
+ *
+ * @hide
+ */
+ public interface FrameDrawingCallback {
+ /**
+ * Invoked during a frame drawing.
+ *
+ * @param frame The id of the frame being drawn.
+ */
+ void onFrameDraw(long frame);
+ }
+
+ /**
+ * Interface used to be notified when a frame has finished rendering
+ *
+ * @hide
+ */
+ public interface FrameCompleteCallback {
+ /**
+ * Invoked after a frame draw
+ *
+ * @param frameNr The id of the frame that was drawn.
+ */
+ void onFrameComplete(long frameNr);
+ }
+
+ /**
+ * Interface for listening to picture captures
+ * @hide
+ */
+ public interface PictureCapturedCallback {
+ /** @hide */
+ void onPictureCaptured(Picture picture);
+ }
+
+ private static void validateAlpha(float alpha, String argumentName) {
+ if (!(alpha >= 0.0f && alpha <= 1.0f)) {
+ throw new IllegalArgumentException(argumentName + " must be a valid alpha, "
+ + alpha + " is not in the range of 0.0f to 1.0f");
+ }
+ }
+
+ private static void validatePositive(float f, String argumentName) {
+ if (!(Float.isFinite(f) && f >= 0.0f)) {
+ throw new IllegalArgumentException(argumentName
+ + " must be a finite positive, given=" + f);
+ }
+ }
+
+ private static void validateFinite(float f, String argumentName) {
+ if (!Float.isFinite(f)) {
+ throw new IllegalArgumentException(argumentName + " must be finite, given=" + f);
+ }
+ }
+
+ /** @hide */
+ public static void invokeFunctor(long functor, boolean waitForCompletion) {
+ nInvokeFunctor(functor, waitForCompletion);
+ }
+
+ /**
+ * b/68769804: For low FPS experiments.
+ *
+ * @hide
+ */
+ public static void setFPSDivisor(int divisor) {
+ nHackySetRTAnimationsEnabled(divisor <= 1);
+ }
+
+ /**
+ * Changes the OpenGL context priority if IMG_context_priority extension is available. Must be
+ * called before any OpenGL context is created.
+ *
+ * @param priority The priority to use. Must be one of EGL_CONTEXT_PRIORITY_* values.
+ * @hide
+ */
+ public static void setContextPriority(int priority) {
+ nSetContextPriority(priority);
+ }
+
+ /**
+ * Sets whether or not high contrast text rendering is enabled. The setting is global
+ * but only affects content rendered after the change is made.
+ *
+ * @hide
+ */
+ public static void setHighContrastText(boolean highContrastText) {
+ nSetHighContrastText(highContrastText);
+ }
+
+ /**
+ * If set RenderThread will avoid doing any IPC using instead a fake vsync & DisplayInfo source
+ *
+ * @hide
+ */
+ public static void setIsolatedProcess(boolean isIsolated) {
+ nSetIsolatedProcess(isIsolated);
+ }
+
+ /**
+ * If set extra graphics debugging abilities will be enabled such as dumping skp
+ *
+ * @hide
+ */
+ public static void setDebuggingEnabled(boolean enable) {
+ nSetDebuggingEnabled(enable);
+ }
+
+ /** @hide */
+ public static int copySurfaceInto(Surface surface, Rect srcRect, Bitmap bitmap) {
+ if (srcRect == null) {
+ // Empty rect means entire surface
+ return nCopySurfaceInto(surface, 0, 0, 0, 0, bitmap.getNativeInstance());
+ } else {
+ return nCopySurfaceInto(surface, srcRect.left, srcRect.top,
+ srcRect.right, srcRect.bottom, bitmap.getNativeInstance());
+ }
+ }
+
+ /**
+ * Creates a {@link android.graphics.Bitmap.Config#HARDWARE} bitmap from the given
+ * RenderNode. Note that the RenderNode should be created as a root node (so x/y of 0,0), and
+ * not the RenderNode from a View.
+ *
+ * @hide
+ **/
+ public static Bitmap createHardwareBitmap(RenderNode node, int width, int height) {
+ return nCreateHardwareBitmap(node.mNativeRenderNode, width, height);
+ }
+
+ /**
+ * Invoke this method when the system is running out of memory. This
+ * method will attempt to recover as much memory as possible, based on
+ * the specified hint.
+ *
+ * @param level Hint about the amount of memory that should be trimmed,
+ * see {@link android.content.ComponentCallbacks}
+ * @hide
+ */
+ public static void trimMemory(int level) {
+ nTrimMemory(level);
+ }
+
+ /** @hide */
+ public static void overrideProperty(@NonNull String name, @NonNull String value) {
+ if (name == null || value == null) {
+ throw new IllegalArgumentException("name and value must be non-null");
+ }
+ nOverrideProperty(name, value);
+ }
+
+ /**
+ * Sets the directory to use as a persistent storage for threaded rendering
+ * resources.
+ *
+ * @param cacheDir A directory the current process can write to
+ * @hide
+ */
+ public static void setupDiskCache(File cacheDir) {
+ setupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath(),
+ new File(cacheDir, CACHE_PATH_SKIASHADERS).getAbsolutePath());
+ }
+
+ /** @hide */
+ public static void setPackageName(String packageName) {
+ ProcessInitializer.sInstance.setPackageName(packageName);
+ }
+
+ private static final class DestroyContextRunnable implements Runnable {
+ private final long mNativeInstance;
+
+ DestroyContextRunnable(long nativeInstance) {
+ mNativeInstance = nativeInstance;
+ }
+
+ @Override
+ public void run() {
+ nDeleteProxy(mNativeInstance);
+ }
+ }
+
+ private static class ProcessInitializer {
+ static ProcessInitializer sInstance = new ProcessInitializer();
+
+ private boolean mInitialized = false;
+
+ private String mPackageName;
+ private IGraphicsStats mGraphicsStatsService;
+ private IGraphicsStatsCallback mGraphicsStatsCallback = new IGraphicsStatsCallback.Stub() {
+ @Override
+ public void onRotateGraphicsStatsBuffer() throws RemoteException {
+ rotateBuffer();
+ }
+ };
+
+ private ProcessInitializer() {
+ }
+
+ synchronized void setPackageName(String name) {
+ if (mInitialized) return;
+ mPackageName = name;
+ }
+
+ synchronized void init(long renderProxy) {
+ if (mInitialized) return;
+ mInitialized = true;
+
+ initSched(renderProxy);
+ initGraphicsStats();
+ }
+
+ private void initSched(long renderProxy) {
+ try {
+ int tid = nGetRenderThreadTid(renderProxy);
+ ActivityManager.getService().setRenderThread(tid);
+ } catch (Throwable t) {
+ Log.w(LOG_TAG, "Failed to set scheduler for RenderThread", t);
+ }
+ }
+
+ private void initGraphicsStats() {
+ if (mPackageName == null) return;
+
+ try {
+ IBinder binder = ServiceManager.getService("graphicsstats");
+ if (binder == null) return;
+ mGraphicsStatsService = IGraphicsStats.Stub.asInterface(binder);
+ requestBuffer();
+ } catch (Throwable t) {
+ Log.w(LOG_TAG, "Could not acquire gfx stats buffer", t);
+ }
+ }
+
+ private void rotateBuffer() {
+ nRotateProcessStatsBuffer();
+ requestBuffer();
+ }
+
+ private void requestBuffer() {
+ try {
+ ParcelFileDescriptor pfd = mGraphicsStatsService
+ .requestBufferForProcess(mPackageName, mGraphicsStatsCallback);
+ nSetProcessStatsBuffer(pfd.getFd());
+ pfd.close();
+ } catch (Throwable t) {
+ Log.w(LOG_TAG, "Could not acquire gfx stats buffer", t);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static native void disableVsync();
+
+ /**
+ * Start render thread and initialize EGL or Vulkan.
+ *
+ * Initializing EGL involves loading and initializing the graphics driver. Some drivers take
+ * several 10s of milliseconds to do this, so doing it on-demand when an app tries to render
+ * its first frame adds directly to user-visible app launch latency.
+ *
+ * Should only be called after GraphicsEnvironment.chooseDriver().
+ * @hide
+ */
+ public static native void preload();
+
+ /** @hide */
+ protected static native void setupShadersDiskCache(String cacheFile, String skiaCacheFile);
+
+ private static native void nRotateProcessStatsBuffer();
+
+ private static native void nSetProcessStatsBuffer(int fd);
+
+ private static native int nGetRenderThreadTid(long nativeProxy);
+
+ private static native long nCreateRootRenderNode();
+
+ private static native long nCreateProxy(boolean translucent, long rootRenderNode);
+
+ private static native void nDeleteProxy(long nativeProxy);
+
+ private static native boolean nLoadSystemProperties(long nativeProxy);
+
+ private static native void nSetName(long nativeProxy, String name);
+
+ private static native void nSetSurface(long nativeProxy, Surface window);
+
+ private static native boolean nPause(long nativeProxy);
+
+ private static native void nSetStopped(long nativeProxy, boolean stopped);
+
+ private static native void nSetLightGeometry(long nativeProxy,
+ float lightX, float lightY, float lightZ, float lightRadius);
+
+ private static native void nSetLightAlpha(long nativeProxy, float ambientShadowAlpha,
+ float spotShadowAlpha);
+
+ private static native void nSetOpaque(long nativeProxy, boolean opaque);
+
+ private static native void nSetWideGamut(long nativeProxy, boolean wideGamut);
+
+ private static native int nSyncAndDrawFrame(long nativeProxy, long[] frameInfo, int size);
+
+ private static native void nDestroy(long nativeProxy, long rootRenderNode);
+
+ private static native void nRegisterAnimatingRenderNode(long rootRenderNode,
+ long animatingNode);
+
+ private static native void nRegisterVectorDrawableAnimator(long rootRenderNode, long animator);
+
+ private static native void nInvokeFunctor(long functor, boolean waitForCompletion);
+
+ private static native long nCreateTextureLayer(long nativeProxy);
+
+ private static native void nBuildLayer(long nativeProxy, long node);
+
+ private static native boolean nCopyLayerInto(long nativeProxy, long layer, long bitmapHandle);
+
+ private static native void nPushLayerUpdate(long nativeProxy, long layer);
+
+ private static native void nCancelLayerUpdate(long nativeProxy, long layer);
+
+ private static native void nDetachSurfaceTexture(long nativeProxy, long layer);
+
+ private static native void nDestroyHardwareResources(long nativeProxy);
+
+ private static native void nTrimMemory(int level);
+
+ private static native void nOverrideProperty(String name, String value);
+
+ private static native void nFence(long nativeProxy);
+
+ private static native void nStopDrawing(long nativeProxy);
+
+ private static native void nNotifyFramePending(long nativeProxy);
+
+ private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd,
+ @DumpFlags int dumpFlags);
+
+ private static native void nAddRenderNode(long nativeProxy, long rootRenderNode,
+ boolean placeFront);
+
+ private static native void nRemoveRenderNode(long nativeProxy, long rootRenderNode);
+
+ private static native void nDrawRenderNode(long nativeProxy, long rootRenderNode);
+
+ private static native void nSetContentDrawBounds(long nativeProxy, int left,
+ int top, int right, int bottom);
+
+ private static native void nSetPictureCaptureCallback(long nativeProxy,
+ PictureCapturedCallback callback);
+
+ private static native void nSetFrameCallback(long nativeProxy, FrameDrawingCallback callback);
+
+ private static native void nSetFrameCompleteCallback(long nativeProxy,
+ FrameCompleteCallback callback);
+
+ private static native long nAddFrameMetricsObserver(long nativeProxy,
+ FrameMetricsObserver observer);
+
+ private static native void nRemoveFrameMetricsObserver(long nativeProxy, long nativeObserver);
+
+ private static native int nCopySurfaceInto(Surface surface,
+ int srcLeft, int srcTop, int srcRight, int srcBottom, long bitmapHandle);
+
+ private static native Bitmap nCreateHardwareBitmap(long renderNode, int width, int height);
+
+ private static native void nSetHighContrastText(boolean enabled);
+
+ // For temporary experimentation b/66945974
+ private static native void nHackySetRTAnimationsEnabled(boolean enabled);
+
+ private static native void nSetDebuggingEnabled(boolean enabled);
+
+ private static native void nSetIsolatedProcess(boolean enabled);
+
+ private static native void nSetContextPriority(int priority);
+
+ private static native void nAllocateBuffers(long nativeProxy);
+
+ private static native void nSetForceDark(long nativeProxy, boolean enabled);
+}
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 098f10003b8a..2d5babc5ebdb 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -28,6 +28,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Px;
import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
import android.annotation.WorkerThread;
import android.content.ContentResolver;
import android.content.res.AssetFileDescriptor;
@@ -58,6 +59,9 @@ import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Retention;
import java.nio.ByteBuffer;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -186,7 +190,7 @@ public final class ImageDecoder implements AutoCloseable {
* {@link OnHeaderDecodedListener OnHeaderDecodedListener} and once with an
* implementation of {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}
* that calls {@link #setTargetSize} with smaller dimensions. One {@code Source}
- * even used simultaneously in multiple threads.</p>
+ * can even be used simultaneously in multiple threads.</p>
*/
public static abstract class Source {
private Source() {}
@@ -282,26 +286,7 @@ public final class ImageDecoder implements AutoCloseable {
return createFromStream(is, true, this);
}
-
- final FileDescriptor fd = assetFd.getFileDescriptor();
- final long offset = assetFd.getStartOffset();
-
- ImageDecoder decoder = null;
- try {
- try {
- Os.lseek(fd, offset, SEEK_SET);
- decoder = nCreate(fd, this);
- } catch (ErrnoException e) {
- decoder = createFromStream(new FileInputStream(fd), true, this);
- }
- } finally {
- if (decoder == null) {
- IoUtils.closeQuietly(assetFd);
- } else {
- decoder.mAssetFd = assetFd;
- }
- }
- return decoder;
+ return createFromAssetFileDescriptor(assetFd, this);
}
}
@@ -353,6 +338,30 @@ public final class ImageDecoder implements AutoCloseable {
return decoder;
}
+ @NonNull
+ private static ImageDecoder createFromAssetFileDescriptor(@NonNull AssetFileDescriptor assetFd,
+ Source source) throws IOException {
+ final FileDescriptor fd = assetFd.getFileDescriptor();
+ final long offset = assetFd.getStartOffset();
+
+ ImageDecoder decoder = null;
+ try {
+ try {
+ Os.lseek(fd, offset, SEEK_SET);
+ decoder = nCreate(fd, source);
+ } catch (ErrnoException e) {
+ decoder = createFromStream(new FileInputStream(fd), true, source);
+ }
+ } finally {
+ if (decoder == null) {
+ IoUtils.closeQuietly(assetFd);
+ } else {
+ decoder.mAssetFd = assetFd;
+ }
+ }
+ return decoder;
+ }
+
/**
* For backwards compatibility, this does *not* close the InputStream.
*
@@ -365,7 +374,7 @@ public final class ImageDecoder implements AutoCloseable {
}
mResources = res;
mInputStream = is;
- mInputDensity = res != null ? inputDensity : Bitmap.DENSITY_NONE;
+ mInputDensity = inputDensity;
}
final Resources mResources;
@@ -527,6 +536,29 @@ public final class ImageDecoder implements AutoCloseable {
}
}
+ private static class CallableSource extends Source {
+ CallableSource(@NonNull Callable<AssetFileDescriptor> callable) {
+ mCallable = callable;
+ }
+
+ private final Callable<AssetFileDescriptor> mCallable;
+
+ @Override
+ public ImageDecoder createImageDecoder() throws IOException {
+ AssetFileDescriptor assetFd = null;
+ try {
+ assetFd = mCallable.call();
+ } catch (Exception e) {
+ if (e instanceof IOException) {
+ throw (IOException) e;
+ } else {
+ throw new IOException(e);
+ }
+ }
+ return createFromAssetFileDescriptor(assetFd, this);
+ }
+ }
+
/**
* Information about an encoded image.
*/
@@ -808,6 +840,40 @@ public final class ImageDecoder implements AutoCloseable {
}
/**
+ * Return if the given MIME type is a supported file format that can be
+ * decoded by this class. This can be useful to determine if a file can be
+ * decoded directly, or if it needs to be converted into a more general
+ * format using an API like {@link ContentResolver#openTypedAssetFile}.
+ */
+ public static boolean isMimeTypeSupported(@NonNull String mimeType) {
+ Objects.requireNonNull(mimeType);
+ switch (mimeType.toLowerCase(Locale.US)) {
+ case "image/png":
+ case "image/jpeg":
+ case "image/webp":
+ case "image/gif":
+ case "image/heif":
+ case "image/heic":
+ case "image/bmp":
+ case "image/x-ico":
+ case "image/vnd.wap.wbmp":
+ case "image/x-sony-arw":
+ case "image/x-canon-cr2":
+ case "image/x-adobe-dng":
+ case "image/x-nikon-nef":
+ case "image/x-nikon-nrw":
+ case "image/x-olympus-orf":
+ case "image/x-fuji-raf":
+ case "image/x-panasonic-rw2":
+ case "image/x-pentax-pef":
+ case "image/x-samsung-srw":
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
* Create a new {@link Source Source} from a resource.
*
* @param res the {@link Resources} object containing the image data.
@@ -970,6 +1036,27 @@ public final class ImageDecoder implements AutoCloseable {
}
/**
+ * Create a new {@link Source Source} from a {@link Callable} that returns a
+ * new {@link AssetFileDescriptor} for each request. This provides control
+ * over how the {@link AssetFileDescriptor} is created, such as passing
+ * options into {@link ContentResolver#openTypedAssetFileDescriptor}, or
+ * enabling use of a {@link android.os.CancellationSignal}.
+ * <p>
+ * It's important for the given {@link Callable} to return a new, unique
+ * {@link AssetFileDescriptor} for each invocation, to support reuse of the
+ * returned {@link Source Source}.
+ *
+ * @return a new Source object, which can be passed to
+ * {@link #decodeDrawable decodeDrawable} or {@link #decodeBitmap
+ * decodeBitmap}.
+ */
+ @AnyThread
+ @NonNull
+ public static Source createSource(@NonNull Callable<AssetFileDescriptor> callable) {
+ return new CallableSource(callable);
+ }
+
+ /**
* Return the width and height of a given sample size.
*
* <p>This takes an input that functions like
@@ -1505,12 +1592,9 @@ public final class ImageDecoder implements AutoCloseable {
* decoder will pick either the color space embedded in the image or the
* {@link ColorSpace} best suited for the requested image configuration
* (for instance {@link ColorSpace.Named#SRGB sRGB} for the
- * {@link Bitmap.Config#ARGB_8888} configuration).</p>
- *
- * <p>{@link Bitmap.Config#RGBA_F16} always uses the
- * {@link ColorSpace.Named#LINEAR_EXTENDED_SRGB scRGB} color space.
- * Bitmaps in other configurations without an embedded color space are
- * assumed to be in the {@link ColorSpace.Named#SRGB sRGB} color space.</p>
+ * {@link Bitmap.Config#ARGB_8888} configuration and
+ * {@link ColorSpace.Named#EXTENDED_SRGB EXTENDED_SRGB} for
+ * {@link Bitmap.Config#RGBA_F16}).</p>
*
* <p class="note">Only {@link ColorSpace.Model#RGB} color spaces are
* currently supported. An <code>IllegalArgumentException</code> will
@@ -1558,14 +1642,16 @@ public final class ImageDecoder implements AutoCloseable {
mTempStorage = null;
}
- private void checkState() {
+ private void checkState(boolean animated) {
if (mNativePtr == 0) {
throw new IllegalStateException("Cannot use closed ImageDecoder!");
}
checkSubset(mDesiredWidth, mDesiredHeight, mCropRect);
- if (mAllocator == ALLOCATOR_HARDWARE) {
+ // animated ignores the allocator, so no need to check for incompatible
+ // fields.
+ if (!animated && mAllocator == ALLOCATOR_HARDWARE) {
if (mMutable) {
throw new IllegalStateException("Cannot make mutable HARDWARE Bitmap!");
}
@@ -1577,17 +1663,6 @@ public final class ImageDecoder implements AutoCloseable {
if (mPostProcessor != null && mUnpremultipliedRequired) {
throw new IllegalStateException("Cannot draw to unpremultiplied pixels!");
}
-
- if (mDesiredColorSpace != null) {
- if (!(mDesiredColorSpace instanceof ColorSpace.Rgb)) {
- throw new IllegalArgumentException("The target color space must use the "
- + "RGB color model - provided: " + mDesiredColorSpace);
- }
- if (((ColorSpace.Rgb) mDesiredColorSpace).getTransferParameters() == null) {
- throw new IllegalArgumentException("The target color space must use an "
- + "ICC parametric transfer function - provided: " + mDesiredColorSpace);
- }
- }
}
private static void checkSubset(int width, int height, Rect r) {
@@ -1600,14 +1675,30 @@ public final class ImageDecoder implements AutoCloseable {
}
}
+ private boolean checkForExtended() {
+ if (mDesiredColorSpace == null) {
+ return false;
+ }
+ return mDesiredColorSpace == ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB)
+ || mDesiredColorSpace == ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB);
+ }
+
+ private long getColorSpacePtr() {
+ if (mDesiredColorSpace == null) {
+ return 0;
+ }
+ return mDesiredColorSpace.getNativeInstance();
+ }
+
@WorkerThread
@NonNull
private Bitmap decodeBitmapInternal() throws IOException {
- checkState();
+ checkState(false);
return nDecodeBitmap(mNativePtr, this, mPostProcessor != null,
mDesiredWidth, mDesiredHeight, mCropRect,
mMutable, mAllocator, mUnpremultipliedRequired,
- mConserveMemory, mDecodeAsAlphaMask, mDesiredColorSpace);
+ mConserveMemory, mDecodeAsAlphaMask, getColorSpacePtr(),
+ checkForExtended());
}
private void callHeaderDecoded(@Nullable OnHeaderDecodedListener listener,
@@ -1673,9 +1764,11 @@ public final class ImageDecoder implements AutoCloseable {
// mPostProcessor exists.
ImageDecoder postProcessPtr = decoder.mPostProcessor == null ?
null : decoder;
+ decoder.checkState(true);
Drawable d = new AnimatedImageDrawable(decoder.mNativePtr,
postProcessPtr, decoder.mDesiredWidth,
- decoder.mDesiredHeight, srcDensity,
+ decoder.mDesiredHeight, decoder.getColorSpacePtr(),
+ decoder.checkForExtended(), srcDensity,
src.computeDstDensity(), decoder.mCropRect,
decoder.mInputStream, decoder.mAssetFd);
// d has taken ownership of these objects.
@@ -1818,8 +1911,8 @@ public final class ImageDecoder implements AutoCloseable {
}
float scale = (float) dstDensity / srcDensity;
- int scaledWidth = (int) (mWidth * scale + 0.5f);
- int scaledHeight = (int) (mHeight * scale + 0.5f);
+ int scaledWidth = Math.max((int) (mWidth * scale + 0.5f), 1);
+ int scaledHeight = Math.max((int) (mHeight * scale + 0.5f), 1);
this.setTargetSize(scaledWidth, scaledHeight);
return dstDensity;
}
@@ -1856,6 +1949,7 @@ public final class ImageDecoder implements AutoCloseable {
* Private method called by JNI.
*/
@SuppressWarnings("unused")
+ @UnsupportedAppUsage
private int postProcessAndRelease(@NonNull Canvas canvas) {
try {
return mPostProcessor.onPostProcess(canvas);
@@ -1894,7 +1988,7 @@ public final class ImageDecoder implements AutoCloseable {
@Nullable Rect cropRect, boolean mutable,
int allocator, boolean unpremulRequired,
boolean conserveMemory, boolean decodeAsAlphaMask,
- @Nullable ColorSpace desiredColorSpace)
+ long desiredColorSpace, boolean extended)
throws IOException;
private static native Size nGetSampledSize(long nativePtr,
int sampleSize);
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index 43fd2708ee3e..15d855e9560c 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -16,7 +16,43 @@
package android.graphics;
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
public class ImageFormat {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ UNKNOWN,
+ RGB_565,
+ YV12,
+ Y8,
+ Y16,
+ NV16,
+ NV21,
+ YUY2,
+ JPEG,
+ DEPTH_JPEG,
+ YUV_420_888,
+ YUV_422_888,
+ YUV_444_888,
+ FLEX_RGB_888,
+ FLEX_RGBA_8888,
+ RAW_SENSOR,
+ RAW_PRIVATE,
+ RAW10,
+ RAW12,
+ DEPTH16,
+ DEPTH_POINT_CLOUD,
+ RAW_DEPTH,
+ PRIVATE,
+ HEIC
+ })
+ public @interface Format {
+ }
+
/*
* these constants are chosen to be binary compatible with their previous
* location in PixelFormat.java
@@ -90,18 +126,20 @@ public class ImageFormat {
* </ul>
* </p>
*
- * <pre> y_size = stride * height </pre>
+ * <pre> size = stride * height </pre>
*
* <p>For example, the {@link android.media.Image} object can provide data
- * in this format from a {@link android.hardware.camera2.CameraDevice}
- * through a {@link android.media.ImageReader} object if this format is
- * supported by {@link android.hardware.camera2.CameraDevice}.</p>
+ * in this format from a {@link android.hardware.camera2.CameraDevice} (if
+ * supported) through a {@link android.media.ImageReader} object. The
+ * {@link android.media.Image#getPlanes() Image#getPlanes()} will return a
+ * single plane containing the pixel data. The pixel stride is always 1 in
+ * {@link android.media.Image.Plane#getPixelStride()}, and the
+ * {@link android.media.Image.Plane#getRowStride()} describes the vertical
+ * neighboring pixel distance (in bytes) between adjacent rows.</p>
*
* @see android.media.Image
* @see android.media.ImageReader
* @see android.hardware.camera2.CameraDevice
- *
- * @hide
*/
public static final int Y8 = 0x20203859;
@@ -181,6 +219,14 @@ public class ImageFormat {
public static final int JPEG = 0x100;
/**
+ * Depth augmented compressed JPEG format.
+ *
+ * <p>JPEG compressed main image along with XMP embedded depth metadata
+ * following ISO 16684-1:2011(E).</p>
+ */
+ public static final int DEPTH_JPEG = 0x69656963;
+
+ /**
* <p>Multi-plane Android YUV 420 format</p>
*
* <p>This format is a generic YCbCr format, capable of describing any 4:2:0
@@ -706,6 +752,14 @@ public class ImageFormat {
public static final int PRIVATE = 0x22;
/**
+ * Compressed HEIC format.
+ *
+ * <p>This format defines the HEIC brand of High Efficiency Image File
+ * Format as described in ISO/IEC 23008-12.</p>
+ */
+ public static final int HEIC = 0x48454946;
+
+ /**
* Use this function to retrieve the number of bits per pixel of an
* ImageFormat.
*
@@ -713,7 +767,7 @@ public class ImageFormat {
* @return the number of bits per pixel of the given format or -1 if the
* format doesn't exist or is not supported.
*/
- public static int getBitsPerPixel(int format) {
+ public static int getBitsPerPixel(@Format int format) {
switch (format) {
case RGB_565:
return 16;
@@ -763,7 +817,7 @@ public class ImageFormat {
*
* @hide
*/
- public static boolean isPublicFormat(int format) {
+ public static boolean isPublicFormat(@Format int format) {
switch (format) {
case RGB_565:
case NV16:
@@ -784,6 +838,9 @@ public class ImageFormat {
case DEPTH_POINT_CLOUD:
case PRIVATE:
case RAW_DEPTH:
+ case Y8:
+ case DEPTH_JPEG:
+ case HEIC:
return true;
}
diff --git a/graphics/java/android/graphics/Insets.aidl b/graphics/java/android/graphics/Insets.aidl
new file mode 100644
index 000000000000..e65e72d27f49
--- /dev/null
+++ b/graphics/java/android/graphics/Insets.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/graphics/Insets.aidl
+**
+** 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;
+
+parcelable Insets;
diff --git a/graphics/java/android/graphics/Insets.java b/graphics/java/android/graphics/Insets.java
index 156f9903a632..1e03c530aed7 100644
--- a/graphics/java/android/graphics/Insets.java
+++ b/graphics/java/android/graphics/Insets.java
@@ -16,6 +16,11 @@
package android.graphics;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
/**
* An Insets instance holds four integer offsets which describe changes to the four
* edges of a Rectangle. By convention, positive values move edges towards the
@@ -23,10 +28,9 @@ package android.graphics;
* <p>
* Insets are immutable so may be treated as values.
*
- * @hide
*/
-public class Insets {
- public static final Insets NONE = new Insets(0, 0, 0, 0);
+public final class Insets implements Parcelable {
+ public static final @NonNull Insets NONE = new Insets(0, 0, 0, 0);
public final int left;
public final int top;
@@ -52,7 +56,7 @@ public class Insets {
*
* @return Insets instance with the appropriate values
*/
- public static Insets of(int left, int top, int right, int bottom) {
+ public static @NonNull Insets of(int left, int top, int right, int bottom) {
if (left == 0 && top == 0 && right == 0 && bottom == 0) {
return NONE;
}
@@ -66,11 +70,66 @@ public class Insets {
*
* @return an Insets instance with the appropriate values
*/
- public static Insets of(Rect r) {
+ public static @NonNull Insets of(@Nullable Rect r) {
return (r == null) ? NONE : of(r.left, r.top, r.right, r.bottom);
}
/**
+ * Returns a Rect instance with the appropriate values.
+ *
+ * @hide
+ */
+ public @NonNull Rect toRect() {
+ return new Rect(left, top, right, bottom);
+ }
+
+ /**
+ * Add two Insets.
+ *
+ * @param a The first Insets to add.
+ * @param b The second Insets to add.
+ * @return a + b, i. e. all insets on every side are added together.
+ */
+ public static @NonNull Insets add(@NonNull Insets a, @NonNull Insets b) {
+ return Insets.of(a.left + b.left, a.top + b.top, a.right + b.right, a.bottom + b.bottom);
+ }
+
+ /**
+ * Subtract two Insets.
+ *
+ * @param a The minuend.
+ * @param b The subtrahend.
+ * @return a - b, i. e. all insets on every side are subtracted from each other.
+ */
+ public static @NonNull Insets subtract(@NonNull Insets a, @NonNull Insets b) {
+ return Insets.of(a.left - b.left, a.top - b.top, a.right - b.right, a.bottom - b.bottom);
+ }
+
+ /**
+ * Retrieves the maximum of two Insets.
+ *
+ * @param a The first Insets.
+ * @param b The second Insets.
+ * @return max(a, b), i. e. the larger of every inset on every side is taken for the result.
+ */
+ public static @NonNull Insets max(@NonNull Insets a, @NonNull Insets b) {
+ return Insets.of(Math.max(a.left, b.left), Math.max(a.top, b.top),
+ Math.max(a.right, b.right), Math.max(a.bottom, b.bottom));
+ }
+
+ /**
+ * Retrieves the minimum of two Insets.
+ *
+ * @param a The first Insets.
+ * @param b The second Insets.
+ * @return min(a, b), i. e. the smaller of every inset on every side is taken for the result.
+ */
+ public static @NonNull Insets min(@NonNull Insets a, @NonNull Insets b) {
+ return Insets.of(Math.min(a.left, b.left), Math.min(a.top, b.top),
+ Math.min(a.right, b.right), Math.min(a.bottom, b.bottom));
+ }
+
+ /**
* Two Insets instances are equal iff they belong to the same class and their fields are
* pairwise equal.
*
@@ -111,4 +170,29 @@ public class Insets {
", bottom=" + bottom +
'}';
}
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(left);
+ out.writeInt(top);
+ out.writeInt(right);
+ out.writeInt(bottom);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<Insets> CREATOR = new Parcelable.Creator<Insets>() {
+ @Override
+ public Insets createFromParcel(Parcel in) {
+ return new Insets(in.readInt(), in.readInt(), in.readInt(), in.readInt());
+ }
+
+ @Override
+ public Insets[] newArray(int size) {
+ return new Insets[size];
+ }
+ };
}
diff --git a/graphics/java/android/graphics/LightingColorFilter.java b/graphics/java/android/graphics/LightingColorFilter.java
index 1578ffb873f0..62a890ff4f0b 100644
--- a/graphics/java/android/graphics/LightingColorFilter.java
+++ b/graphics/java/android/graphics/LightingColorFilter.java
@@ -22,6 +22,7 @@
package android.graphics;
import android.annotation.ColorInt;
+import android.annotation.UnsupportedAppUsage;
/**
* A color filter that can be used to simulate simple lighting effects.
@@ -72,6 +73,7 @@ public class LightingColorFilter extends ColorFilter {
*
* @hide
*/
+ @UnsupportedAppUsage
public void setColorMultiply(@ColorInt int mul) {
if (mMul != mul) {
mMul = mul;
@@ -97,6 +99,7 @@ public class LightingColorFilter extends ColorFilter {
*
* @hide
*/
+ @UnsupportedAppUsage
public void setColorAdd(@ColorInt int add) {
if (mAdd != add) {
mAdd = add;
diff --git a/graphics/java/android/graphics/LinearGradient.java b/graphics/java/android/graphics/LinearGradient.java
index 7139efec9337..12e63c09d76b 100644
--- a/graphics/java/android/graphics/LinearGradient.java
+++ b/graphics/java/android/graphics/LinearGradient.java
@@ -17,30 +17,59 @@
package android.graphics;
import android.annotation.ColorInt;
+import android.annotation.ColorLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
-public class LinearGradient extends Shader {
-
- private static final int TYPE_COLORS_AND_POSITIONS = 1;
- private static final int TYPE_COLOR_START_AND_COLOR_END = 2;
-
- /**
- * Type of the LinearGradient: can be either TYPE_COLORS_AND_POSITIONS or
- * TYPE_COLOR_START_AND_COLOR_END.
- */
- private int mType;
+public class LinearGradient extends Shader {
+ @UnsupportedAppUsage
private float mX0;
+ @UnsupportedAppUsage
private float mY0;
+ @UnsupportedAppUsage
private float mX1;
+ @UnsupportedAppUsage
private float mY1;
- private int[] mColors;
+ @UnsupportedAppUsage
private float[] mPositions;
+ @UnsupportedAppUsage
+ private TileMode mTileMode;
+
+ // @ColorInts are replaced by @ColorLongs, but these remain due to @UnsupportedAppUsage.
+ @UnsupportedAppUsage
+ @ColorInt
+ private int[] mColors;
+ @UnsupportedAppUsage
+ @ColorInt
private int mColor0;
+ @UnsupportedAppUsage
+ @ColorInt
private int mColor1;
- private TileMode mTileMode;
+ @ColorLong
+ private final long[] mColorLongs;
+
+
+ /**
+ * Create a shader that draws a linear gradient along a line.
+ *
+ * @param x0 The x-coordinate for the start of the gradient line
+ * @param y0 The y-coordinate for the start of the gradient line
+ * @param x1 The x-coordinate for the end of the gradient line
+ * @param y1 The y-coordinate for the end of the gradient line
+ * @param colors The sRGB colors to be distributed along the gradient line
+ * @param positions May be null. The relative positions [0..1] of
+ * each corresponding color in the colors array. If this is null,
+ * the the colors are distributed evenly along the gradient line.
+ * @param tile The Shader tiling mode
+ */
+ public LinearGradient(float x0, float y0, float x1, float y1, @NonNull @ColorInt int[] colors,
+ @Nullable float[] positions, @NonNull TileMode tile) {
+ this(x0, y0, x1, y1, convertColors(colors), positions, tile,
+ ColorSpace.get(ColorSpace.Named.SRGB));
+ }
/**
* Create a shader that draws a linear gradient along a line.
@@ -54,21 +83,33 @@ public class LinearGradient extends Shader {
* each corresponding color in the colors array. If this is null,
* the the colors are distributed evenly along the gradient line.
* @param tile The Shader tiling mode
- */
- public LinearGradient(float x0, float y0, float x1, float y1, @NonNull @ColorInt int colors[],
- @Nullable float positions[], @NonNull TileMode tile) {
- if (colors.length < 2) {
- throw new IllegalArgumentException("needs >= 2 number of colors");
- }
+ *
+ * @throws IllegalArgumentException if there are less than two colors, the colors do
+ * not share the same {@link ColorSpace} or do not use a valid one, or {@code positions}
+ * is not {@code null} and has a different length from {@code colors}.
+ */
+ public LinearGradient(float x0, float y0, float x1, float y1, @NonNull @ColorLong long[] colors,
+ @Nullable float[] positions, @NonNull TileMode tile) {
+ this(x0, y0, x1, y1, colors.clone(), positions, tile, detectColorSpace(colors));
+ }
+
+ /**
+ * Base constructor. Assumes @param colors is a copy that this object can hold onto,
+ * and all colors share @param colorSpace.
+ */
+ private LinearGradient(float x0, float y0, float x1, float y1,
+ @NonNull @ColorLong long[] colors, @Nullable float[] positions, @NonNull TileMode tile,
+ @NonNull ColorSpace colorSpace) {
+ super(colorSpace);
+
if (positions != null && colors.length != positions.length) {
throw new IllegalArgumentException("color and position arrays must be of equal length");
}
- mType = TYPE_COLORS_AND_POSITIONS;
mX0 = x0;
mY0 = y0;
mX1 = x1;
mY1 = y1;
- mColors = colors.clone();
+ mColorLongs = colors;
mPositions = positions != null ? positions.clone() : null;
mTileMode = tile;
}
@@ -80,54 +121,43 @@ public class LinearGradient extends Shader {
* @param y0 The y-coordinate for the start of the gradient line
* @param x1 The x-coordinate for the end of the gradient line
* @param y1 The y-coordinate for the end of the gradient line
- * @param color0 The color at the start of the gradient line.
- * @param color1 The color at the end of the gradient line.
+ * @param color0 The sRGB color at the start of the gradient line.
+ * @param color1 The sRGB color at the end of the gradient line.
* @param tile The Shader tiling mode
- */
+ */
public LinearGradient(float x0, float y0, float x1, float y1,
@ColorInt int color0, @ColorInt int color1,
@NonNull TileMode tile) {
- mType = TYPE_COLOR_START_AND_COLOR_END;
- mX0 = x0;
- mY0 = y0;
- mX1 = x1;
- mY1 = y1;
- mColor0 = color0;
- mColor1 = color1;
- mColors = null;
- mPositions = null;
- mTileMode = tile;
- }
-
- @Override
- long createNativeInstance(long nativeMatrix) {
- if (mType == TYPE_COLORS_AND_POSITIONS) {
- return nativeCreate1(nativeMatrix, mX0, mY0, mX1, mY1,
- mColors, mPositions, mTileMode.nativeInt);
- } else { // TYPE_COLOR_START_AND_COLOR_END
- return nativeCreate2(nativeMatrix, mX0, mY0, mX1, mY1,
- mColor0, mColor1, mTileMode.nativeInt);
- }
+ this(x0, y0, x1, y1, Color.pack(color0), Color.pack(color1), tile);
}
/**
- * @hide
+ * Create a shader that draws a linear gradient along a line.
+ *
+ * @param x0 The x-coordinate for the start of the gradient line
+ * @param y0 The y-coordinate for the start of the gradient line
+ * @param x1 The x-coordinate for the end of the gradient line
+ * @param y1 The y-coordinate for the end of the gradient line
+ * @param color0 The color at the start of the gradient line.
+ * @param color1 The color at the end of the gradient line.
+ * @param tile The Shader tiling mode
+ *
+ * @throws IllegalArgumentException if the colors do
+ * not share the same {@link ColorSpace} or do not use a valid one.
*/
+ public LinearGradient(float x0, float y0, float x1, float y1,
+ @ColorLong long color0, @ColorLong long color1,
+ @NonNull TileMode tile) {
+ this(x0, y0, x1, y1, new long[] {color0, color1}, null, tile);
+ }
+
@Override
- protected Shader copy() {
- final LinearGradient copy;
- if (mType == TYPE_COLORS_AND_POSITIONS) {
- copy = new LinearGradient(mX0, mY0, mX1, mY1, mColors.clone(),
- mPositions != null ? mPositions.clone() : null, mTileMode);
- } else { // TYPE_COLOR_START_AND_COLOR_END
- copy = new LinearGradient(mX0, mY0, mX1, mY1, mColor0, mColor1, mTileMode);
- }
- copyLocalMatrix(copy);
- return copy;
+ long createNativeInstance(long nativeMatrix) {
+ return nativeCreate(nativeMatrix, mX0, mY0, mX1, mY1,
+ mColorLongs, mPositions, mTileMode.nativeInt,
+ colorSpace().getNativeInstance());
}
- private native long nativeCreate1(long matrix, float x0, float y0, float x1, float y1,
- int colors[], float positions[], int tileMode);
- private native long nativeCreate2(long matrix, float x0, float y0, float x1, float y1,
- int color0, int color1, int tileMode);
+ private native long nativeCreate(long matrix, float x0, float y0, float x1, float y1,
+ long[] colors, float[] positions, int tileMode, long colorSpaceHandle);
}
diff --git a/graphics/java/android/graphics/Matrix.java b/graphics/java/android/graphics/Matrix.java
index 486070c99e3f..22b6401fdc2e 100644
--- a/graphics/java/android/graphics/Matrix.java
+++ b/graphics/java/android/graphics/Matrix.java
@@ -16,6 +16,8 @@
package android.graphics;
+import android.annotation.UnsupportedAppUsage;
+
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
@@ -39,6 +41,7 @@ public class Matrix {
public static final int MPERSP_2 = 8; //!< use with getValues/setValues
/** @hide */
+ @UnsupportedAppUsage
public final static Matrix IDENTITY_MATRIX = new Matrix() {
void oops() {
throw new IllegalStateException("Matrix can not be modified");
@@ -220,17 +223,16 @@ public class Matrix {
}
};
- // sizeof(SkMatrix) is 9 * sizeof(float) + uint32_t
- private static final long NATIVE_ALLOCATION_SIZE = 40;
-
private static class NoImagePreloadHolder {
- public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
- Matrix.class.getClassLoader(), nGetNativeFinalizer(), NATIVE_ALLOCATION_SIZE);
+ public static final NativeAllocationRegistry sRegistry =
+ NativeAllocationRegistry.createMalloced(
+ Matrix.class.getClassLoader(), nGetNativeFinalizer());
}
/**
* @hide
*/
+ @UnsupportedAppUsage
public final long native_instance;
/**
diff --git a/graphics/java/android/graphics/Movie.java b/graphics/java/android/graphics/Movie.java
index 83857bece930..6f030ffac2df 100644
--- a/graphics/java/android/graphics/Movie.java
+++ b/graphics/java/android/graphics/Movie.java
@@ -16,7 +16,9 @@
package android.graphics;
+import android.annotation.UnsupportedAppUsage;
import android.content.res.AssetManager;
+import android.os.Build;
import java.io.FileInputStream;
import java.io.InputStream;
@@ -24,9 +26,12 @@ import java.io.InputStream;
/**
* @deprecated Prefer {@link android.graphics.drawable.AnimatedImageDrawable}.
*/
+@Deprecated
public class Movie {
+ @UnsupportedAppUsage
private long mNativeMovie;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private Movie(long nativeMovie) {
if (nativeMovie == 0) {
throw new RuntimeException("native movie creation failed");
diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java
index b6a209f25df9..c4c1eaceb4fc 100644
--- a/graphics/java/android/graphics/NinePatch.java
+++ b/graphics/java/android/graphics/NinePatch.java
@@ -16,6 +16,8 @@
package android.graphics;
+import android.annotation.UnsupportedAppUsage;
+
/**
* The NinePatch class permits drawing a bitmap in nine or more sections.
* Essentially, it allows the creation of custom graphics that will scale the
@@ -41,6 +43,7 @@ public class NinePatch {
*/
public static class InsetStruct {
@SuppressWarnings({"UnusedDeclaration"}) // called from JNI
+ @UnsupportedAppUsage
InsetStruct(int opticalLeft, int opticalTop, int opticalRight, int opticalBottom,
int outlineLeft, int outlineTop, int outlineRight, int outlineBottom,
float outlineRadius, int outlineAlpha, float decodeScale) {
@@ -77,6 +80,7 @@ public class NinePatch {
}
}
+ @UnsupportedAppUsage
private final Bitmap mBitmap;
/**
@@ -84,6 +88,7 @@ public class NinePatch {
*
* @hide
*/
+ @UnsupportedAppUsage
public long mNativeChunk;
private Paint mPaint;
@@ -256,7 +261,8 @@ public class NinePatch {
* that are transparent.
*/
public final Region getTransparentRegion(Rect bounds) {
- long r = nativeGetTransparentRegion(mBitmap, mNativeChunk, bounds);
+ long r = nativeGetTransparentRegion(mBitmap.getNativeInstance(),
+ mNativeChunk, bounds);
return r != 0 ? new Region(r) : null;
}
@@ -277,5 +283,6 @@ public class NinePatch {
*/
private static native long validateNinePatchChunk(byte[] chunk);
private static native void nativeFinalize(long chunk);
- private static native long nativeGetTransparentRegion(Bitmap bitmap, long chunk, Rect location);
+ private static native long nativeGetTransparentRegion(long bitmapHandle, long chunk,
+ Rect location);
}
diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java
index 1c85df0de590..1fc056c3652f 100644
--- a/graphics/java/android/graphics/Outline.java
+++ b/graphics/java/android/graphics/Outline.java
@@ -19,6 +19,7 @@ package android.graphics;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.UnsupportedAppUsage;
import android.graphics.drawable.Drawable;
import java.lang.annotation.Retention;
@@ -66,6 +67,7 @@ public final class Outline {
public Path mPath;
/** @hide */
+ @UnsupportedAppUsage
public final Rect mRect = new Rect();
/** @hide */
public float mRadius = RADIUS_UNDEFINED;
@@ -271,8 +273,12 @@ public final class Outline {
}
/**
- * Sets the Constructs an Outline from a
+ * Sets the Outline to a
* {@link android.graphics.Path#isConvex() convex path}.
+ *
+ * @param convexPath used to construct the Outline. As of
+ * {@link android.os.Build.VERSION_CODES#Q}, it is no longer required to be
+ * convex.
*/
public void setConvexPath(@NonNull Path convexPath) {
if (convexPath.isEmpty()) {
@@ -280,10 +286,6 @@ public final class Outline {
return;
}
- if (!convexPath.isConvex()) {
- throw new IllegalArgumentException("path must be convex");
- }
-
if (mPath == null) {
mPath = new Path();
}
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 42dac38affba..109d8631284d 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -17,9 +17,16 @@
package android.graphics;
import android.annotation.ColorInt;
+import android.annotation.ColorLong;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.Px;
import android.annotation.Size;
+import android.annotation.UnsupportedAppUsage;
import android.graphics.fonts.FontVariationAxis;
+import android.os.Build;
import android.os.LocaleList;
import android.text.GraphicsOperations;
import android.text.SpannableString;
@@ -33,6 +40,8 @@ import dalvik.annotation.optimization.FastNative;
import libcore.util.NativeAllocationRegistry;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -44,38 +53,39 @@ import java.util.Locale;
*/
public class Paint {
+ @UnsupportedAppUsage
private long mNativePaint;
private long mNativeShader;
private long mNativeColorFilter;
- // The approximate size of a native paint object.
- private static final long NATIVE_PAINT_SIZE = 98;
-
// Use a Holder to allow static initialization of Paint in the boot image.
private static class NoImagePreloadHolder {
- public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
- Paint.class.getClassLoader(), nGetNativeFinalizer(), NATIVE_PAINT_SIZE);
+ public static final NativeAllocationRegistry sRegistry =
+ NativeAllocationRegistry.createMalloced(
+ Paint.class.getClassLoader(), nGetNativeFinalizer());
}
- private ColorFilter mColorFilter;
- private MaskFilter mMaskFilter;
- private PathEffect mPathEffect;
- private Shader mShader;
- private Typeface mTypeface;
- private Xfermode mXfermode;
+ @ColorLong private long mColor;
+ private ColorFilter mColorFilter;
+ private MaskFilter mMaskFilter;
+ private PathEffect mPathEffect;
+ private Shader mShader;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private Typeface mTypeface;
+ private Xfermode mXfermode;
- private boolean mHasCompatScaling;
- private float mCompatScaling;
- private float mInvCompatScaling;
+ private boolean mHasCompatScaling;
+ private float mCompatScaling;
+ private float mInvCompatScaling;
- private LocaleList mLocales;
- private String mFontFeatureSettings;
- private String mFontVariationSettings;
+ private LocaleList mLocales;
+ private String mFontFeatureSettings;
+ private String mFontVariationSettings;
- private float mShadowLayerRadius;
- private float mShadowLayerDx;
- private float mShadowLayerDy;
- private int mShadowLayerColor;
+ private float mShadowLayerRadius;
+ private float mShadowLayerDx;
+ private float mShadowLayerDy;
+ @ColorLong private long mShadowLayerColor;
private static final Object sCacheLock = new Object();
@@ -219,7 +229,8 @@ public class Paint {
public static final int VERTICAL_TEXT_FLAG = 0x1000;
// These flags are always set on a new/reset paint, even if flags 0 is passed.
- static final int HIDDEN_DEFAULT_PAINT_FLAGS = DEV_KERN_TEXT_FLAG | EMBEDDED_BITMAP_TEXT_FLAG;
+ static final int HIDDEN_DEFAULT_PAINT_FLAGS = DEV_KERN_TEXT_FLAG | EMBEDDED_BITMAP_TEXT_FLAG
+ | FILTER_BITMAP_FLAG;
/**
* Font hinter option that disables font hinting.
@@ -303,38 +314,47 @@ public class Paint {
*/
public static final int DIRECTION_RTL = 1;
+ /** @hide */
+ @IntDef(prefix = { "CURSOR_" }, value = {
+ CURSOR_AFTER, CURSOR_AT_OR_AFTER, CURSOR_BEFORE, CURSOR_AT_OR_BEFORE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CursorOption {}
+
/**
- * Option for getTextRunCursor to compute the valid cursor after
- * offset or the limit of the context, whichever is less.
- * @hide
+ * Option for getTextRunCursor.
+ *
+ * Compute the valid cursor after offset or the limit of the context, whichever is less.
*/
public static final int CURSOR_AFTER = 0;
/**
- * Option for getTextRunCursor to compute the valid cursor at or after
- * the offset or the limit of the context, whichever is less.
- * @hide
+ * Option for getTextRunCursor.
+ *
+ * Compute the valid cursor at or after the offset or the limit of the context, whichever is
+ * less.
*/
public static final int CURSOR_AT_OR_AFTER = 1;
/**
- * Option for getTextRunCursor to compute the valid cursor before
- * offset or the start of the context, whichever is greater.
- * @hide
+ * Option for getTextRunCursor.
+ *
+ * Compute the valid cursor before offset or the start of the context, whichever is greater.
*/
public static final int CURSOR_BEFORE = 2;
/**
- * Option for getTextRunCursor to compute the valid cursor at or before
- * offset or the start of the context, whichever is greater.
- * @hide
+ * Option for getTextRunCursor.
+ *
+ * Compute the valid cursor at or before offset or the start of the context, whichever is
+ * greater.
*/
public static final int CURSOR_AT_OR_BEFORE = 3;
/**
- * Option for getTextRunCursor to return offset if the cursor at offset
- * is valid, or -1 if it isn't.
- * @hide
+ * Option for getTextRunCursor.
+ *
+ * Return offset if the cursor at offset is valid, or -1 if it isn't.
*/
public static final int CURSOR_AT = 4;
@@ -343,19 +363,79 @@ public class Paint {
*/
private static final int CURSOR_OPT_MAX_VALUE = CURSOR_AT;
+ /** @hide */
+ @IntDef(prefix = { "START_HYPHEN_EDIT_" }, value = {
+ START_HYPHEN_EDIT_NO_EDIT,
+ START_HYPHEN_EDIT_INSERT_HYPHEN,
+ START_HYPHEN_EDIT_INSERT_ZWJ
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StartHyphenEdit {}
+
/**
- * Mask for hyphen edits that happen at the end of a line. Keep in sync with the definition in
- * Minikin's Hyphenator.h.
- * @hide
+ * An integer representing the starting of the line has no modification for hyphenation.
*/
- public static final int HYPHENEDIT_MASK_END_OF_LINE = 0x07;
+ public static final int START_HYPHEN_EDIT_NO_EDIT = 0x00;
/**
- * Mask for hyphen edits that happen at the start of a line. Keep in sync with the definition in
- * Minikin's Hyphenator.h.
- * @hide
+ * An integer representing the starting of the line has normal hyphen character (U+002D).
+ */
+ public static final int START_HYPHEN_EDIT_INSERT_HYPHEN = 0x01;
+
+ /**
+ * An integer representing the starting of the line has Zero-Width-Joiner (U+200D).
+ */
+ public static final int START_HYPHEN_EDIT_INSERT_ZWJ = 0x02;
+
+ /** @hide */
+ @IntDef(prefix = { "END_HYPHEN_EDIT_" }, value = {
+ END_HYPHEN_EDIT_NO_EDIT,
+ END_HYPHEN_EDIT_REPLACE_WITH_HYPHEN,
+ END_HYPHEN_EDIT_INSERT_HYPHEN,
+ END_HYPHEN_EDIT_INSERT_ARMENIAN_HYPHEN,
+ END_HYPHEN_EDIT_INSERT_MAQAF,
+ END_HYPHEN_EDIT_INSERT_UCAS_HYPHEN,
+ END_HYPHEN_EDIT_INSERT_ZWJ_AND_HYPHEN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EndHyphenEdit {}
+
+ /**
+ * An integer representing the end of the line has no modification for hyphenation.
+ */
+ public static final int END_HYPHEN_EDIT_NO_EDIT = 0x00;
+
+ /**
+ * An integer representing the character at the end of the line is replaced with hyphen
+ * character (U+002D).
+ */
+ public static final int END_HYPHEN_EDIT_REPLACE_WITH_HYPHEN = 0x01;
+
+ /**
+ * An integer representing the end of the line has normal hyphen character (U+002D).
+ */
+ public static final int END_HYPHEN_EDIT_INSERT_HYPHEN = 0x02;
+
+ /**
+ * An integer representing the end of the line has Armentian hyphen (U+058A).
+ */
+ public static final int END_HYPHEN_EDIT_INSERT_ARMENIAN_HYPHEN = 0x03;
+
+ /**
+ * An integer representing the end of the line has maqaf (Hebrew hyphen, U+05BE).
+ */
+ public static final int END_HYPHEN_EDIT_INSERT_MAQAF = 0x04;
+
+ /**
+ * An integer representing the end of the line has Canadian Syllabics hyphen (U+1400).
*/
- public static final int HYPHENEDIT_MASK_START_OF_LINE = 0x03 << 3;
+ public static final int END_HYPHEN_EDIT_INSERT_UCAS_HYPHEN = 0x05;
+
+ /**
+ * An integer representing the end of the line has Zero-Width-Joiner (U+200D) followed by normal
+ * hyphen character (U+002D).
+ */
+ public static final int END_HYPHEN_EDIT_INSERT_ZWJ_AND_HYPHEN = 0x06;
/**
* The Style specifies if the primitive being drawn is filled, stroked, or
@@ -484,6 +564,7 @@ public class Paint {
// ? HINTING_OFF : HINTING_ON);
mCompatScaling = mInvCompatScaling = 1;
setTextLocales(LocaleList.getAdjustedDefault());
+ mColor = Color.pack(Color.BLACK);
}
/**
@@ -509,6 +590,7 @@ public class Paint {
// setHinting(DisplayMetrics.DENSITY_DEVICE >= DisplayMetrics.DENSITY_TV
// ? HINTING_OFF : HINTING_ON);
+ mColor = Color.pack(Color.BLACK);
mColorFilter = null;
mMaskFilter = null;
mPathEffect = null;
@@ -530,7 +612,7 @@ public class Paint {
mShadowLayerRadius = 0.0f;
mShadowLayerDx = 0.0f;
mShadowLayerDy = 0.0f;
- mShadowLayerColor = 0;
+ mShadowLayerColor = Color.pack(0);
}
/**
@@ -551,6 +633,7 @@ public class Paint {
* {@link Paint}.
*/
private void setClassVariablesFrom(Paint paint) {
+ mColor = paint.mColor;
mColorFilter = paint.mColorFilter;
mMaskFilter = paint.mMaskFilter;
mPathEffect = paint.mPathEffect;
@@ -574,50 +657,8 @@ public class Paint {
mShadowLayerColor = paint.mShadowLayerColor;
}
- /**
- * Returns true if all attributes are equal.
- *
- * The caller is expected to have checked the trivial cases, like the pointers being equal,
- * the objects having different classes, or the parameter being null.
- * @hide
- */
- public boolean hasEqualAttributes(@NonNull Paint other) {
- return mColorFilter == other.mColorFilter
- && mMaskFilter == other.mMaskFilter
- && mPathEffect == other.mPathEffect
- && mShader == other.mShader
- && mTypeface == other.mTypeface
- && mXfermode == other.mXfermode
- && mHasCompatScaling == other.mHasCompatScaling
- && mCompatScaling == other.mCompatScaling
- && mInvCompatScaling == other.mInvCompatScaling
- && mBidiFlags == other.mBidiFlags
- && mLocales.equals(other.mLocales)
- && TextUtils.equals(mFontFeatureSettings, other.mFontFeatureSettings)
- && TextUtils.equals(mFontVariationSettings, other.mFontVariationSettings)
- && mShadowLayerRadius == other.mShadowLayerRadius
- && mShadowLayerDx == other.mShadowLayerDx
- && mShadowLayerDy == other.mShadowLayerDy
- && mShadowLayerColor == other.mShadowLayerColor
- && getFlags() == other.getFlags()
- && getHinting() == other.getHinting()
- && getStyle() == other.getStyle()
- && getColor() == other.getColor()
- && getStrokeWidth() == other.getStrokeWidth()
- && getStrokeMiter() == other.getStrokeMiter()
- && getStrokeCap() == other.getStrokeCap()
- && getStrokeJoin() == other.getStrokeJoin()
- && getTextAlign() == other.getTextAlign()
- && isElegantTextHeight() == other.isElegantTextHeight()
- && getTextSize() == other.getTextSize()
- && getTextScaleX() == other.getTextScaleX()
- && getTextSkewX() == other.getTextSkewX()
- && getLetterSpacing() == other.getLetterSpacing()
- && getWordSpacing() == other.getWordSpacing()
- && getHyphenEdit() == other.getHyphenEdit();
- }
-
/** @hide */
+ @UnsupportedAppUsage
public void setCompatibilityScaling(float factor) {
if (factor == 1.0) {
mHasCompatScaling = false;
@@ -635,6 +676,7 @@ public class Paint {
*
* @hide
*/
+ @UnsupportedAppUsage
public long getNativeInstance() {
long newNativeShader = mShader == null ? 0 : mShader.getNativeInstance();
if (newNativeShader != mNativeShader) {
@@ -800,25 +842,39 @@ public class Paint {
* Helper for getFlags(), returning true if UNDERLINE_TEXT_FLAG bit is set
*
* @return true if the underlineText bit is set in the paint's flags.
+ * @see #getUnderlinePosition()
+ * @see #getUnderlineThickness()
+ * @see #setUnderlineText(boolean)
*/
public final boolean isUnderlineText() {
return (getFlags() & UNDERLINE_TEXT_FLAG) != 0;
}
/**
- * Distance from top of the underline to the baseline. Positive values mean below the baseline.
- * This method returns where the underline should be drawn independent of if the underlineText
- * bit is set at the moment.
- * @hide
+ * Returns the distance from top of the underline to the baseline in pixels.
+ *
+ * The result is positive for positions that are below the baseline.
+ * This method returns where the underline should be drawn independent of if the {@link
+ * #UNDERLINE_TEXT_FLAG} bit is set.
+ *
+ * @return the position of the underline in pixels
+ * @see #isUnderlineText()
+ * @see #getUnderlineThickness()
+ * @see #setUnderlineText(boolean)
*/
- public float getUnderlinePosition() {
+ public @Px float getUnderlinePosition() {
return nGetUnderlinePosition(mNativePaint);
}
/**
- * @hide
+ * Returns the thickness of the underline in pixels.
+ *
+ * @return the thickness of the underline in pixels
+ * @see #isUnderlineText()
+ * @see #getUnderlinePosition()
+ * @see #setUnderlineText(boolean)
*/
- public float getUnderlineThickness() {
+ public @Px float getUnderlineThickness() {
return nGetUnderlineThickness(mNativePaint);
}
@@ -827,6 +883,9 @@ public class Paint {
*
* @param underlineText true to set the underlineText bit in the paint's
* flags, false to clear it.
+ * @see #isUnderlineText()
+ * @see #getUnderlinePosition()
+ * @see #getUnderlineThickness()
*/
public void setUnderlineText(boolean underlineText) {
nSetUnderlineText(mNativePaint, underlineText);
@@ -835,26 +894,40 @@ public class Paint {
/**
* Helper for getFlags(), returning true if STRIKE_THRU_TEXT_FLAG bit is set
*
- * @return true if the strikeThruText bit is set in the paint's flags.
+ * @return true if the {@link #STRIKE_THRU_TEXT_FLAG} bit is set in the paint's flags.
+ * @see #getStrikeThruPosition()
+ * @see #getStrikeThruThickness()
+ * @see #setStrikeThruText(boolean)
*/
public final boolean isStrikeThruText() {
return (getFlags() & STRIKE_THRU_TEXT_FLAG) != 0;
}
/**
- * Distance from top of the strike-through line to the baseline. Negative values mean above the
- * baseline. This method returns where the strike-through line should be drawn independent of if
- * the strikeThruText bit is set at the moment.
- * @hide
+ * Distance from top of the strike-through line to the baseline in pixels.
+ *
+ * The result is negative for positions that are above the baseline.
+ * This method returns where the strike-through line should be drawn independent of if the
+ * {@link #STRIKE_THRU_TEXT_FLAG} bit is set.
+ *
+ * @return the position of the strike-through line in pixels
+ * @see #getStrikeThruThickness()
+ * @see #setStrikeThruText(boolean)
+ * @see #isStrikeThruText()
*/
- public float getStrikeThruPosition() {
+ public @Px float getStrikeThruPosition() {
return nGetStrikeThruPosition(mNativePaint);
}
/**
- * @hide
+ * Returns the thickness of the strike-through line in pixels.
+ *
+ * @return the position of the strike-through line in pixels
+ * @see #getStrikeThruPosition()
+ * @see #setStrikeThruText(boolean)
+ * @see #isStrikeThruText()
*/
- public float getStrikeThruThickness() {
+ public @Px float getStrikeThruThickness() {
return nGetStrikeThruThickness(mNativePaint);
}
@@ -863,6 +936,9 @@ public class Paint {
*
* @param strikeThruText true to set the strikeThruText bit in the paint's
* flags, false to clear it.
+ * @see #getStrikeThruPosition()
+ * @see #getStrikeThruThickness()
+ * @see #isStrikeThruText()
*/
public void setStrikeThruText(boolean strikeThruText) {
nSetStrikeThruText(mNativePaint, strikeThruText);
@@ -935,7 +1011,7 @@ public class Paint {
}
/**
- * Return the paint's color. Note that the color is a 32bit value
+ * Return the paint's color in sRGB. Note that the color is a 32bit value
* containing alpha as well as r,g,b. This 32bit value is not premultiplied,
* meaning that its alpha can be any value, regardless of the values of
* r,g,b. See the Color class for more details.
@@ -944,7 +1020,21 @@ public class Paint {
*/
@ColorInt
public int getColor() {
- return nGetColor(mNativePaint);
+ return Color.toArgb(mColor);
+ }
+
+ /**
+ * Return the paint's color. Note that the color is a long with an encoded
+ * {@link ColorSpace} as well as alpha and r,g,b. These values are not
+ * premultiplied, meaning that alpha can be any value, regardless of the
+ * values of r,g,b. See the {@link Color} class for more details.
+ *
+ * @return the paint's color, alpha, and {@code ColorSpace} encoded as a
+ * {@code ColorLong}
+ */
+ @ColorLong
+ public long getColorLong() {
+ return mColor;
}
/**
@@ -957,6 +1047,26 @@ public class Paint {
*/
public void setColor(@ColorInt int color) {
nSetColor(mNativePaint, color);
+ mColor = Color.pack(color);
+ }
+
+ /**
+ * Set the paint's color with a {@code ColorLong}. Note that the color is
+ * a long with an encoded {@link ColorSpace} as well as alpha and r,g,b.
+ * These values are not premultiplied, meaning that alpha can be any value,
+ * regardless of the values of r,g,b. See the {@link Color} class for more
+ * details.
+ *
+ * @param color The new color (including alpha and {@link ColorSpace})
+ * to set in the paint.
+ * @throws IllegalArgumentException if the color space encoded in the
+ * {@code ColorLong} is invalid or unknown.
+ */
+ public void setColor(@ColorLong long color) {
+ ColorSpace cs = Color.colorSpace(color);
+
+ nSetColor(mNativePaint, cs.getNativeInstance(), color);
+ mColor = color;
}
/**
@@ -967,7 +1077,7 @@ public class Paint {
* @return the alpha component of the paint's color.
*/
public int getAlpha() {
- return nGetAlpha(mNativePaint);
+ return Math.round(Color.alpha(mColor) * 255.0f);
}
/**
@@ -978,6 +1088,13 @@ public class Paint {
* @param a set the alpha component [0..255] of the paint's color.
*/
public void setAlpha(int a) {
+ // FIXME: No need to unpack this. Instead, just update the alpha bits.
+ // b/122959599
+ ColorSpace cs = Color.colorSpace(mColor);
+ float r = Color.red(mColor);
+ float g = Color.green(mColor);
+ float b = Color.blue(mColor);
+ mColor = Color.pack(r, g, b, a * (1.0f / 255), cs);
nSetAlpha(mNativePaint, a);
}
@@ -997,7 +1114,7 @@ public class Paint {
* Return the width for stroking.
* <p />
* A value of 0 strokes in hairline mode.
- * Hairlines always draws a single pixel independent of the canva's matrix.
+ * Hairlines always draws a single pixel independent of the canvas's matrix.
*
* @return the paint's stroke width, used whenever the paint's style is
* Stroke or StrokeAndFill.
@@ -1009,7 +1126,7 @@ public class Paint {
/**
* Set the width for stroking.
* Pass 0 to stroke in hairline mode.
- * Hairlines always draws a single pixel independent of the canva's matrix.
+ * Hairlines always draws a single pixel independent of the canvas's matrix.
*
* @param width set the paint's stroke width, used whenever the paint's
* style is Stroke or StrokeAndFill.
@@ -1163,6 +1280,20 @@ public class Paint {
}
/**
+ * Get the paint's blend mode object.
+ *
+ * @return the paint's blend mode (or null)
+ */
+ @Nullable
+ public BlendMode getBlendMode() {
+ if (mXfermode == null) {
+ return null;
+ } else {
+ return BlendMode.fromValue(mXfermode.porterDuffMode);
+ }
+ }
+
+ /**
* Set or clear the transfer mode object. A transfer mode defines how
* source pixels (generate by a drawing command) are composited with
* the destination pixels (content of the render target).
@@ -1176,6 +1307,11 @@ public class Paint {
* @return xfermode
*/
public Xfermode setXfermode(Xfermode xfermode) {
+ return installXfermode(xfermode);
+ }
+
+ @Nullable
+ private Xfermode installXfermode(Xfermode xfermode) {
int newMode = xfermode != null ? xfermode.porterDuffMode : Xfermode.DEFAULT;
int curMode = mXfermode != null ? mXfermode.porterDuffMode : Xfermode.DEFAULT;
if (newMode != curMode) {
@@ -1186,6 +1322,22 @@ public class Paint {
}
/**
+ * Set or clear the blend mode. A blend mode defines how source pixels
+ * (generated by a drawing command) are composited with the destination pixels
+ * (content of the render target).
+ * <p />
+ * Pass null to clear any previous blend mode.
+ * <p />
+ *
+ * @see BlendMode
+ *
+ * @param blendmode May be null. The blend mode to be installed in the paint
+ */
+ public void setBlendMode(@Nullable BlendMode blendmode) {
+ installXfermode(blendmode != null ? blendmode.getXfermode() : null);
+ }
+
+ /**
* Get the paint's patheffect object.
*
* @return the paint's patheffect (or null)
@@ -1315,12 +1467,33 @@ public class Paint {
* The alpha of the shadow will be the paint's alpha if the shadow color is
* opaque, or the alpha from the shadow color if not.
*/
- public void setShadowLayer(float radius, float dx, float dy, int shadowColor) {
- mShadowLayerRadius = radius;
- mShadowLayerDx = dx;
- mShadowLayerDy = dy;
- mShadowLayerColor = shadowColor;
- nSetShadowLayer(mNativePaint, radius, dx, dy, shadowColor);
+ public void setShadowLayer(float radius, float dx, float dy, @ColorInt int shadowColor) {
+ setShadowLayer(radius, dx, dy, Color.pack(shadowColor));
+ }
+
+ /**
+ * This draws a shadow layer below the main layer, with the specified
+ * offset and color, and blur radius. If radius is 0, then the shadow
+ * layer is removed.
+ * <p>
+ * Can be used to create a blurred shadow underneath text. Support for use
+ * with other drawing operations is constrained to the software rendering
+ * pipeline.
+ * <p>
+ * The alpha of the shadow will be the paint's alpha if the shadow color is
+ * opaque, or the alpha from the shadow color if not.
+ *
+ * @throws IllegalArgumentException if the color space encoded in the
+ * {@code ColorLong} is invalid or unknown.
+ */
+ public void setShadowLayer(float radius, float dx, float dy, @ColorLong long shadowColor) {
+ ColorSpace cs = Color.colorSpace(shadowColor);
+ nSetShadowLayer(mNativePaint, radius, dx, dy, cs.getNativeInstance(), shadowColor);
+
+ mShadowLayerRadius = radius;
+ mShadowLayerDx = dx;
+ mShadowLayerDy = dy;
+ mShadowLayerColor = shadowColor;
}
/**
@@ -1341,6 +1514,54 @@ public class Paint {
}
/**
+ * Returns the blur radius of the shadow layer.
+ * @see #setShadowLayer(float,float,float,int)
+ * @see #setShadowLayer(float,float,float,long)
+ */
+ public float getShadowLayerRadius() {
+ return mShadowLayerRadius;
+ }
+
+ /**
+ * Returns the x offset of the shadow layer.
+ * @see #setShadowLayer(float,float,float,int)
+ * @see #setShadowLayer(float,float,float,long)
+ */
+ public float getShadowLayerDx() {
+ return mShadowLayerDx;
+ }
+
+ /**
+ * Returns the y offset of the shadow layer.
+ * @see #setShadowLayer(float,float,float,int)
+ * @see #setShadowLayer(float,float,float,long)
+ */
+ public float getShadowLayerDy() {
+ return mShadowLayerDy;
+ }
+
+ /**
+ * Returns the color of the shadow layer.
+ * @see #setShadowLayer(float,float,float,int)
+ * @see #setShadowLayer(float,float,float,long)
+ */
+ public @ColorInt int getShadowLayerColor() {
+ return Color.toArgb(mShadowLayerColor);
+ }
+
+ /**
+ * Returns the color of the shadow layer.
+ *
+ * @return the shadow layer's color encoded as a {@link ColorLong}.
+ * @see #setShadowLayer(float,float,float,int)
+ * @see #setShadowLayer(float,float,float,long)
+ * @see Color
+ */
+ public @ColorLong long getShadowLayerColorLong() {
+ return mShadowLayerColor;
+ }
+
+ /**
* Return the paint's Align value for drawing text. This controls how the
* text is positioned relative to its origin. LEFT align means that all of
* the text will be drawn to the right of its origin (i.e. the origin
@@ -1555,24 +1776,27 @@ public class Paint {
}
/**
- * Return the paint's word-spacing for text. The default value is 0.
+ * Return the paint's extra word-spacing for text.
*
- * @return the paint's word-spacing for drawing text.
- * @hide
+ * The default value is 0.
+ *
+ * @return the paint's extra word-spacing for drawing text in pixels.
+ * @see #setWordSpacing(float)
*/
- public float getWordSpacing() {
+ public @Px float getWordSpacing() {
return nGetWordSpacing(mNativePaint);
}
/**
- * Set the paint's word-spacing for text. The default value is 0.
- * The value is in pixels (note the units are not the same as for
- * letter-spacing).
+ * Set the paint's extra word-spacing for text.
*
- * @param wordSpacing set the paint's word-spacing for drawing text.
- * @hide
+ * Increases the white space width between words with the given amount of pixels.
+ * The default value is 0.
+ *
+ * @param wordSpacing set the paint's extra word-spacing for drawing text in pixels.
+ * @see #getWordSpacing()
*/
- public void setWordSpacing(float wordSpacing) {
+ public void setWordSpacing(@Px float wordSpacing) {
nSetWordSpacing(mNativePaint, wordSpacing);
}
@@ -1699,27 +1923,80 @@ public class Paint {
}
/**
- * Get the current value of hyphen edit.
+ * Get the current value of start hyphen edit.
*
- * @return the current hyphen edit value
+ * The default value is 0 which is equivalent to {@link #START_HYPHEN_EDIT_NO_EDIT}.
*
- * @hide
+ * @return the current starting hyphen edit value
+ * @see #setStartHyphenEdit(int)
*/
- public int getHyphenEdit() {
- return nGetHyphenEdit(mNativePaint);
+ public @StartHyphenEdit int getStartHyphenEdit() {
+ return nGetStartHyphenEdit(mNativePaint);
}
/**
- * Set a hyphen edit on the paint (causes a hyphen to be added to text when
- * measured or drawn).
+ * Get the current value of end hyphen edit.
*
- * @param hyphen 0 for no edit, 1 for adding a hyphen at the end, etc.
- * Definition of various values are in the HyphenEdit class in Minikin's Hyphenator.h.
+ * The default value is 0 which is equivalent to {@link #END_HYPHEN_EDIT_NO_EDIT}.
*
- * @hide
+ * @return the current starting hyphen edit value
+ * @see #setStartHyphenEdit(int)
+ */
+ public @EndHyphenEdit int getEndHyphenEdit() {
+ return nGetEndHyphenEdit(mNativePaint);
+ }
+
+ /**
+ * Set a start hyphen edit on the paint.
+ *
+ * By setting start hyphen edit, the measurement and drawing is performed with modifying
+ * hyphenation at the start of line. For example, by passing
+ * {@link #START_HYPHEN_EDIT_INSERT_HYPHEN} like as follows, HYPHEN(U+2010)
+ * character is appended at the start of line.
+ *
+ * <pre>
+ * <code>
+ * Paint paint = new Paint();
+ * paint.setStartHyphenEdit(Paint.START_HYPHEN_EDIT_INSERT_HYPHEN);
+ * paint.measureText("abc", 0, 3); // Returns the width of "-abc"
+ * Canvas.drawText("abc", 0, 3, 0f, 0f, paint); // Draws "-abc"
+ * </code>
+ * </pre>
+ *
+ * The default value is 0 which is equivalent to
+ * {@link #START_HYPHEN_EDIT_NO_EDIT}.
+ *
+ * @param startHyphen a start hyphen edit value.
+ * @see #getStartHyphenEdit()
+ */
+ public void setStartHyphenEdit(@StartHyphenEdit int startHyphen) {
+ nSetStartHyphenEdit(mNativePaint, startHyphen);
+ }
+
+ /**
+ * Set a end hyphen edit on the paint.
+ *
+ * By setting end hyphen edit, the measurement and drawing is performed with modifying
+ * hyphenation at the end of line. For example, by passing
+ * {@link #END_HYPHEN_EDIT_INSERT_HYPHEN} like as follows, HYPHEN(U+2010)
+ * character is appended at the end of line.
+ *
+ * <pre>
+ * <code>
+ * Paint paint = new Paint();
+ * paint.setEndHyphenEdit(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN);
+ * paint.measureText("abc", 0, 3); // Returns the width of "abc-"
+ * Canvas.drawText("abc", 0, 3, 0f, 0f, paint); // Draws "abc-"
+ * </code>
+ * </pre>
+ *
+ * The default value is 0 which is equivalent to {@link #END_HYPHEN_EDIT_NO_EDIT}.
+ *
+ * @param endHyphen a end hyphen edit value.
+ * @see #getEndHyphenEdit()
*/
- public void setHyphenEdit(int hyphen) {
- nSetHyphenEdit(mNativePaint, hyphen);
+ public void setEndHyphenEdit(@EndHyphenEdit int endHyphen) {
+ nSetEndHyphenEdit(mNativePaint, endHyphen);
}
/**
@@ -2255,16 +2532,53 @@ public class Paint {
}
/**
- * Convenience overload that takes a char array instead of a
- * String.
+ * Retrieve the character advances of the text.
*
- * @see #getTextRunAdvances(String, int, int, int, int, boolean, float[], int)
- * @hide
- */
- public float getTextRunAdvances(char[] chars, int index, int count,
- int contextIndex, int contextCount, boolean isRtl, float[] advances,
- int advancesIndex) {
-
+ * Returns the total advance width for the characters in the run from {@code index} for
+ * {@code count} of chars, and if {@code advances} is not null, the advance assigned to each of
+ * these characters (java chars).
+ *
+ * <p>
+ * The trailing surrogate in a valid surrogate pair is assigned an advance of 0. Thus the
+ * number of returned advances is always equal to count, not to the number of unicode codepoints
+ * represented by the run.
+ * </p>
+ *
+ * <p>
+ * In the case of conjuncts or combining marks, the total advance is assigned to the first
+ * logical character, and the following characters are assigned an advance of 0.
+ * </p>
+ *
+ * <p>
+ * This generates the sum of the advances of glyphs for characters in a reordered cluster as the
+ * width of the first logical character in the cluster, and 0 for the widths of all other
+ * characters in the cluster. In effect, such clusters are treated like conjuncts.
+ * </p>
+ *
+ * <p>
+ * The shaping bounds limit the amount of context available outside start and end that can be
+ * used for shaping analysis. These bounds typically reflect changes in bidi level or font
+ * metrics across which shaping does not occur.
+ * </p>
+ *
+ * @param chars the text to measure.
+ * @param index the index of the first character to measure
+ * @param count the number of characters to measure
+ * @param contextIndex the index of the first character to use for shaping context.
+ * Context must cover the measureing target.
+ * @param contextCount the number of character to use for shaping context.
+ * Context must cover the measureing target.
+ * @param isRtl whether the run is in RTL direction
+ * @param advances array to receive the advances, must have room for all advances.
+ * This can be null if only total advance is needed
+ * @param advancesIndex the position in advances at which to put the advance corresponding to
+ * the character at start
+ * @return the total advance in pixels
+ */
+ public float getTextRunAdvances(@NonNull char[] chars, @IntRange(from = 0) int index,
+ @IntRange(from = 0) int count, @IntRange(from = 0) int contextIndex,
+ @IntRange(from = 0) int contextCount, boolean isRtl, @Nullable float[] advances,
+ @IntRange(from = 0) int advancesIndex) {
if (chars == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -2301,158 +2615,32 @@ public class Paint {
}
/**
- * Convenience overload that takes a CharSequence instead of a
- * String.
- *
- * @see #getTextRunAdvances(String, int, int, int, int, boolean, float[], int)
- * @hide
- */
- public float getTextRunAdvances(CharSequence text, int start, int end,
- int contextStart, int contextEnd, boolean isRtl, float[] advances,
- int advancesIndex) {
- if (text == null) {
- throw new IllegalArgumentException("text cannot be null");
- }
- if ((start | end | contextStart | contextEnd | advancesIndex | (end - start)
- | (start - contextStart) | (contextEnd - end)
- | (text.length() - contextEnd)
- | (advances == null ? 0 :
- (advances.length - advancesIndex - (end - start)))) < 0) {
- throw new IndexOutOfBoundsException();
- }
-
- if (text instanceof String) {
- return getTextRunAdvances((String) text, start, end,
- contextStart, contextEnd, isRtl, advances, advancesIndex);
- }
- if (text instanceof SpannedString ||
- text instanceof SpannableString) {
- return getTextRunAdvances(text.toString(), start, end,
- contextStart, contextEnd, isRtl, advances, advancesIndex);
- }
- if (text instanceof GraphicsOperations) {
- return ((GraphicsOperations) text).getTextRunAdvances(start, end,
- contextStart, contextEnd, isRtl, advances, advancesIndex, this);
- }
- if (text.length() == 0 || end == start) {
- return 0f;
- }
-
- int contextLen = contextEnd - contextStart;
- int len = end - start;
- char[] buf = TemporaryBuffer.obtain(contextLen);
- TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
- float result = getTextRunAdvances(buf, start - contextStart, len,
- 0, contextLen, isRtl, advances, advancesIndex);
- TemporaryBuffer.recycle(buf);
- return result;
- }
-
- /**
- * Returns the total advance width for the characters in the run
- * between start and end, and if advances is not null, the advance
- * assigned to each of these characters (java chars).
- *
- * <p>The trailing surrogate in a valid surrogate pair is assigned
- * an advance of 0. Thus the number of returned advances is
- * always equal to count, not to the number of unicode codepoints
- * represented by the run.
+ * Returns the next cursor position in the run.
*
- * <p>In the case of conjuncts or combining marks, the total
- * advance is assigned to the first logical character, and the
- * following characters are assigned an advance of 0.
- *
- * <p>This generates the sum of the advances of glyphs for
- * characters in a reordered cluster as the width of the first
- * logical character in the cluster, and 0 for the widths of all
- * other characters in the cluster. In effect, such clusters are
- * treated like conjuncts.
- *
- * <p>The shaping bounds limit the amount of context available
- * outside start and end that can be used for shaping analysis.
- * These bounds typically reflect changes in bidi level or font
- * metrics across which shaping does not occur.
- *
- * @param text the text to measure. Cannot be null.
- * @param start the index of the first character to measure
- * @param end the index past the last character to measure
- * @param contextStart the index of the first character to use for shaping context,
- * must be <= start
- * @param contextEnd the index past the last character to use for shaping context,
- * must be >= end
- * @param isRtl whether the run is in RTL direction
- * @param advances array to receive the advances, must have room for all advances,
- * can be null if only total advance is needed
- * @param advancesIndex the position in advances at which to put the
- * advance corresponding to the character at start
- * @return the total advance
- *
- * @hide
- */
- public float getTextRunAdvances(String text, int start, int end, int contextStart,
- int contextEnd, boolean isRtl, float[] advances, int advancesIndex) {
- if (text == null) {
- throw new IllegalArgumentException("text cannot be null");
- }
- if ((start | end | contextStart | contextEnd | advancesIndex | (end - start)
- | (start - contextStart) | (contextEnd - end)
- | (text.length() - contextEnd)
- | (advances == null ? 0 :
- (advances.length - advancesIndex - (end - start)))) < 0) {
- throw new IndexOutOfBoundsException();
- }
-
- if (text.length() == 0 || start == end) {
- return 0f;
- }
-
- if (!mHasCompatScaling) {
- return nGetTextAdvances(mNativePaint, text, start, end, contextStart, contextEnd,
- isRtl ? BIDI_FORCE_RTL : BIDI_FORCE_LTR, advances, advancesIndex);
- }
-
- final float oldSize = getTextSize();
- setTextSize(oldSize * mCompatScaling);
- final float totalAdvance = nGetTextAdvances(mNativePaint, text, start, end, contextStart,
- contextEnd, isRtl ? BIDI_FORCE_RTL : BIDI_FORCE_LTR, advances, advancesIndex);
- setTextSize(oldSize);
-
- if (advances != null) {
- for (int i = advancesIndex, e = i + (end - start); i < e; i++) {
- advances[i] *= mInvCompatScaling;
- }
- }
- return totalAdvance * mInvCompatScaling; // assume errors are insignificant
- }
-
- /**
- * Returns the next cursor position in the run. This avoids placing the
- * cursor between surrogates, between characters that form conjuncts,
- * between base characters and combining marks, or within a reordering
- * cluster.
+ * This avoids placing the cursor between surrogates, between characters that form conjuncts,
+ * between base characters and combining marks, or within a reordering cluster.
*
- * <p>ContextStart and offset are relative to the start of text.
- * The context is the shaping context for cursor movement, generally
- * the bounds of the metric span enclosing the cursor in the direction of
- * movement.
+ * <p>
+ * ContextStart and offset are relative to the start of text.
+ * The context is the shaping context for cursor movement, generally the bounds of the metric
+ * span enclosing the cursor in the direction of movement.
*
- * <p>If cursorOpt is {@link #CURSOR_AT} and the offset is not a valid
- * cursor position, this returns -1. Otherwise this will never return a
- * value before contextStart or after contextStart + contextLength.
+ * <p>
+ * If cursorOpt is {@link #CURSOR_AT} and the offset is not a valid cursor position, this
+ * returns -1. Otherwise this will never return a value before contextStart or after
+ * contextStart + contextLength.
*
* @param text the text
* @param contextStart the start of the context
* @param contextLength the length of the context
- * @param dir either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR}
+ * @param isRtl true if the paragraph context is RTL, otherwise false
* @param offset the cursor position to move from
- * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER},
- * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE},
- * {@link #CURSOR_AT_OR_BEFORE}, or {@link #CURSOR_AT}
+ * @param cursorOpt how to move the cursor
* @return the offset of the next position, or -1
- * @hide
*/
- public int getTextRunCursor(char[] text, int contextStart, int contextLength,
- int dir, int offset, int cursorOpt) {
+ public int getTextRunCursor(@NonNull char[] text, @IntRange(from = 0) int contextStart,
+ @IntRange(from = 0) int contextLength, boolean isRtl, @IntRange(from = 0) int offset,
+ @CursorOption int cursorOpt) {
int contextEnd = contextStart + contextLength;
if (((contextStart | contextEnd | offset | (contextEnd - contextStart)
| (offset - contextStart) | (contextEnd - offset)
@@ -2461,85 +2649,87 @@ public class Paint {
throw new IndexOutOfBoundsException();
}
- return nGetTextRunCursor(mNativePaint, text, contextStart, contextLength, dir, offset,
- cursorOpt);
+ return nGetTextRunCursor(mNativePaint, text, contextStart, contextLength,
+ isRtl ? DIRECTION_RTL : DIRECTION_LTR, offset, cursorOpt);
}
/**
- * Returns the next cursor position in the run. This avoids placing the
- * cursor between surrogates, between characters that form conjuncts,
- * between base characters and combining marks, or within a reordering
- * cluster.
+ * Returns the next cursor position in the run.
+ *
+ * This avoids placing the cursor between surrogates, between characters that form conjuncts,
+ * between base characters and combining marks, or within a reordering cluster.
*
- * <p>ContextStart, contextEnd, and offset are relative to the start of
+ * <p>
+ * ContextStart, contextEnd, and offset are relative to the start of
* text. The context is the shaping context for cursor movement, generally
* the bounds of the metric span enclosing the cursor in the direction of
* movement.
*
- * <p>If cursorOpt is {@link #CURSOR_AT} and the offset is not a valid
- * cursor position, this returns -1. Otherwise this will never return a
- * value before contextStart or after contextEnd.
+ * <p>
+ * If cursorOpt is {@link #CURSOR_AT} and the offset is not a valid cursor position, this
+ * returns -1. Otherwise this will never return a value before contextStart or after
+ * contextEnd.
*
* @param text the text
* @param contextStart the start of the context
* @param contextEnd the end of the context
- * @param dir either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR}
+ * @param isRtl true if the paragraph context is RTL, otherwise false
* @param offset the cursor position to move from
- * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER},
- * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE},
- * {@link #CURSOR_AT_OR_BEFORE}, or {@link #CURSOR_AT}
+ * @param cursorOpt how to move the cursor
* @return the offset of the next position, or -1
- * @hide
*/
- public int getTextRunCursor(CharSequence text, int contextStart,
- int contextEnd, int dir, int offset, int cursorOpt) {
+ public int getTextRunCursor(@NonNull CharSequence text, @IntRange(from = 0) int contextStart,
+ @IntRange(from = 0) int contextEnd, boolean isRtl, @IntRange(from = 0) int offset,
+ @CursorOption int cursorOpt) {
if (text instanceof String || text instanceof SpannedString ||
text instanceof SpannableString) {
return getTextRunCursor(text.toString(), contextStart, contextEnd,
- dir, offset, cursorOpt);
+ isRtl, offset, cursorOpt);
}
if (text instanceof GraphicsOperations) {
return ((GraphicsOperations) text).getTextRunCursor(
- contextStart, contextEnd, dir, offset, cursorOpt, this);
+ contextStart, contextEnd, isRtl, offset, cursorOpt, this);
}
int contextLen = contextEnd - contextStart;
char[] buf = TemporaryBuffer.obtain(contextLen);
TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
- int relPos = getTextRunCursor(buf, 0, contextLen, dir, offset - contextStart, cursorOpt);
+ int relPos = getTextRunCursor(buf, 0, contextLen, isRtl, offset - contextStart, cursorOpt);
TemporaryBuffer.recycle(buf);
return (relPos == -1) ? -1 : relPos + contextStart;
}
/**
- * Returns the next cursor position in the run. This avoids placing the
- * cursor between surrogates, between characters that form conjuncts,
- * between base characters and combining marks, or within a reordering
- * cluster.
+ * Returns the next cursor position in the run.
*
- * <p>ContextStart, contextEnd, and offset are relative to the start of
- * text. The context is the shaping context for cursor movement, generally
- * the bounds of the metric span enclosing the cursor in the direction of
- * movement.
+ * This avoids placing the cursor between surrogates, between characters that form conjuncts,
+ * between base characters and combining marks, or within a reordering cluster.
+ *
+ * <p>
+ * ContextStart, contextEnd, and offset are relative to the start of text. The context is the
+ * shaping context for cursor movement, generally the bounds of the metric span enclosing the
+ * cursor in the direction of movement.
+ * </p>
*
- * <p>If cursorOpt is {@link #CURSOR_AT} and the offset is not a valid
- * cursor position, this returns -1. Otherwise this will never return a
- * value before contextStart or after contextEnd.
+ * <p>
+ * If cursorOpt is {@link #CURSOR_AT} and the offset is not a valid cursor position, this
+ * returns -1. Otherwise this will never return a value before contextStart or after
+ * contextEnd.
+ * </p>
*
* @param text the text
* @param contextStart the start of the context
* @param contextEnd the end of the context
- * @param dir either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR}
+ * @param isRtl true if the paragraph context is RTL, otherwise false.
* @param offset the cursor position to move from
- * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER},
- * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE},
- * {@link #CURSOR_AT_OR_BEFORE}, or {@link #CURSOR_AT}
+ * @param cursorOpt how to move the cursor
* @return the offset of the next position, or -1
* @hide
*/
- public int getTextRunCursor(String text, int contextStart, int contextEnd,
- int dir, int offset, int cursorOpt) {
+ public int getTextRunCursor(@NonNull String text, @IntRange(from = 0) int contextStart,
+ @IntRange(from = 0) int contextEnd, boolean isRtl, @IntRange(from = 0) int offset,
+ @CursorOption int cursorOpt) {
if (((contextStart | contextEnd | offset | (contextEnd - contextStart)
| (offset - contextStart) | (contextEnd - offset)
| (text.length() - contextEnd) | cursorOpt) < 0)
@@ -2547,8 +2737,8 @@ public class Paint {
throw new IndexOutOfBoundsException();
}
- return nGetTextRunCursor(mNativePaint, text, contextStart, contextEnd, dir, offset,
- cursorOpt);
+ return nGetTextRunCursor(mNativePaint, text, contextStart, contextEnd,
+ isRtl ? DIRECTION_RTL : DIRECTION_LTR, offset, cursorOpt);
}
/**
@@ -2592,6 +2782,8 @@ public class Paint {
}
/**
+ * Retrieve the text boundary box and store to bounds.
+ *
* Return in bounds (allocated by the caller) the smallest rectangle that
* encloses all of the characters, with an implied origin at (0,0).
*
@@ -2611,16 +2803,21 @@ public class Paint {
}
/**
+ * Retrieve the text boundary box and store to bounds.
+ *
* Return in bounds (allocated by the caller) the smallest rectangle that
* encloses all of the characters, with an implied origin at (0,0).
*
+ * Note that styles are ignored even if you pass {@link android.text.Spanned} instance.
+ * Use {@link android.text.StaticLayout} for measuring bounds of {@link android.text.Spanned}.
+ *
* @param text text to measure and return its bounds
* @param start index of the first char in the text to measure
* @param end 1 past the last char in the text to measure
* @param bounds returns the unioned bounds of all the text. Must be allocated by the caller
- * @hide
*/
- public void getTextBounds(CharSequence text, int start, int end, Rect bounds) {
+ public void getTextBounds(@NonNull CharSequence text, int start, int end,
+ @NonNull Rect bounds) {
if ((start | end | (end - start) | (text.length() - end)) < 0) {
throw new IndexOutOfBoundsException();
}
@@ -2929,7 +3126,8 @@ public class Paint {
int mMinikinLocaleListId);
@CriticalNative
private static native void nSetShadowLayer(long paintPtr,
- float radius, float dx, float dy, int color);
+ float radius, float dx, float dy, long colorSpaceHandle,
+ @ColorLong long shadowColor);
@CriticalNative
private static native boolean nHasShadowLayer(long paintPtr);
@CriticalNative
@@ -2941,9 +3139,13 @@ public class Paint {
@CriticalNative
private static native void nSetWordSpacing(long paintPtr, float wordSpacing);
@CriticalNative
- private static native int nGetHyphenEdit(long paintPtr);
+ private static native int nGetStartHyphenEdit(long paintPtr);
+ @CriticalNative
+ private static native int nGetEndHyphenEdit(long paintPtr);
@CriticalNative
- private static native void nSetHyphenEdit(long paintPtr, int hyphen);
+ private static native void nSetStartHyphenEdit(long paintPtr, int hyphen);
+ @CriticalNative
+ private static native void nSetEndHyphenEdit(long paintPtr, int hyphen);
@CriticalNative
private static native void nSetStrokeMiter(long paintPtr, float miter);
@CriticalNative
@@ -2977,12 +3179,11 @@ public class Paint {
@CriticalNative
private static native void nSetFilterBitmap(long paintPtr, boolean filter);
@CriticalNative
- private static native int nGetColor(long paintPtr);
+ private static native void nSetColor(long paintPtr, long colorSpaceHandle,
+ @ColorLong long color);
@CriticalNative
private static native void nSetColor(long paintPtr, @ColorInt int color);
@CriticalNative
- private static native int nGetAlpha(long paintPtr);
- @CriticalNative
private static native void nSetStrikeThruText(long paintPtr, boolean strikeThruText);
@CriticalNative
private static native boolean nIsElegantTextHeight(long paintPtr);
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index cd0862cd13fe..7282d52d6e23 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -20,6 +20,7 @@ import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
+import android.annotation.UnsupportedAppUsage;
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
@@ -35,8 +36,9 @@ import libcore.util.NativeAllocationRegistry;
*/
public class Path {
- private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
- Path.class.getClassLoader(), nGetFinalizer(), 48 /* dummy size */);
+ private static final NativeAllocationRegistry sRegistry =
+ NativeAllocationRegistry.createMalloced(
+ Path.class.getClassLoader(), nGetFinalizer());
/**
* @hide
@@ -46,10 +48,12 @@ public class Path {
/**
* @hide
*/
+ @UnsupportedAppUsage
public boolean isSimplePath = true;
/**
* @hide
*/
+ @UnsupportedAppUsage
public Region rects;
private Direction mLastDirection = null;
@@ -66,7 +70,7 @@ public class Path {
*
* @param src The path to copy from when initializing the new path
*/
- public Path(Path src) {
+ public Path(@Nullable Path src) {
long valNative = 0;
if (src != null) {
valNative = src.mNativePath;
@@ -168,7 +172,7 @@ public class Path {
* @see Op
* @see #op(Path, Path, android.graphics.Path.Op)
*/
- public boolean op(Path path, Op op) {
+ public boolean op(@NonNull Path path, @NonNull Op op) {
return op(this, path, op);
}
@@ -186,7 +190,7 @@ public class Path {
* @see Op
* @see #op(Path, android.graphics.Path.Op)
*/
- public boolean op(Path path1, Path path2, Op op) {
+ public boolean op(@NonNull Path path1, @NonNull Path path2, @NonNull Op op) {
if (nOp(path1.mNativePath, path2.mNativePath, op.ordinal(), this.mNativePath)) {
isSimplePath = false;
rects = null;
@@ -255,6 +259,7 @@ public class Path {
*
* @return the path's fill type
*/
+ @NonNull
public FillType getFillType() {
return sFillTypeArray[nGetFillType(mNativePath)];
}
@@ -264,7 +269,7 @@ public class Path {
*
* @param ft The new fill type for this path
*/
- public void setFillType(FillType ft) {
+ public void setFillType(@NonNull FillType ft) {
nSetFillType(mNativePath, ft.nativeInt);
}
@@ -318,7 +323,7 @@ public class Path {
* @param exact This parameter is no longer used.
*/
@SuppressWarnings({"UnusedDeclaration"})
- public void computeBounds(RectF bounds, boolean exact) {
+ public void computeBounds(@NonNull RectF bounds, boolean exact) {
nComputeBounds(mNativePath, bounds);
}
@@ -461,7 +466,7 @@ public class Path {
* mod 360.
* @param forceMoveTo If true, always begin a new contour with the arc
*/
- public void arcTo(RectF oval, float startAngle, float sweepAngle,
+ public void arcTo(@NonNull RectF oval, float startAngle, float sweepAngle,
boolean forceMoveTo) {
arcTo(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, forceMoveTo);
}
@@ -477,7 +482,7 @@ public class Path {
* @param startAngle Starting angle (in degrees) where the arc begins
* @param sweepAngle Sweep angle (in degrees) measured clockwise
*/
- public void arcTo(RectF oval, float startAngle, float sweepAngle) {
+ public void arcTo(@NonNull RectF oval, float startAngle, float sweepAngle) {
arcTo(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, false);
}
@@ -542,7 +547,7 @@ public class Path {
* @param rect The rectangle to add as a closed contour to the path
* @param dir The direction to wind the rectangle's contour
*/
- public void addRect(RectF rect, Direction dir) {
+ public void addRect(@NonNull RectF rect, @NonNull Direction dir) {
addRect(rect.left, rect.top, rect.right, rect.bottom, dir);
}
@@ -555,7 +560,7 @@ public class Path {
* @param bottom The bottom of a rectangle to add to the path
* @param dir The direction to wind the rectangle's contour
*/
- public void addRect(float left, float top, float right, float bottom, Direction dir) {
+ public void addRect(float left, float top, float right, float bottom, @NonNull Direction dir) {
detectSimplePath(left, top, right, bottom, dir);
nAddRect(mNativePath, left, top, right, bottom, dir.nativeInt);
}
@@ -566,7 +571,7 @@ public class Path {
* @param oval The bounds of the oval to add as a closed contour to the path
* @param dir The direction to wind the oval's contour
*/
- public void addOval(RectF oval, Direction dir) {
+ public void addOval(@NonNull RectF oval, @NonNull Direction dir) {
addOval(oval.left, oval.top, oval.right, oval.bottom, dir);
}
@@ -575,7 +580,7 @@ public class Path {
*
* @param dir The direction to wind the oval's contour
*/
- public void addOval(float left, float top, float right, float bottom, Direction dir) {
+ public void addOval(float left, float top, float right, float bottom, @NonNull Direction dir) {
isSimplePath = false;
nAddOval(mNativePath, left, top, right, bottom, dir.nativeInt);
}
@@ -588,7 +593,7 @@ public class Path {
* @param radius The radius of a circle to add to the path
* @param dir The direction to wind the circle's contour
*/
- public void addCircle(float x, float y, float radius, Direction dir) {
+ public void addCircle(float x, float y, float radius, @NonNull Direction dir) {
isSimplePath = false;
nAddCircle(mNativePath, x, y, radius, dir.nativeInt);
}
@@ -600,7 +605,7 @@ public class Path {
* @param startAngle Starting angle (in degrees) where the arc begins
* @param sweepAngle Sweep angle (in degrees) measured clockwise
*/
- public void addArc(RectF oval, float startAngle, float sweepAngle) {
+ public void addArc(@NonNull RectF oval, float startAngle, float sweepAngle) {
addArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle);
}
@@ -624,7 +629,7 @@ public class Path {
* @param ry The y-radius of the rounded corners on the round-rectangle
* @param dir The direction to wind the round-rectangle's contour
*/
- public void addRoundRect(RectF rect, float rx, float ry, Direction dir) {
+ public void addRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Direction dir) {
addRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, dir);
}
@@ -636,7 +641,7 @@ public class Path {
* @param dir The direction to wind the round-rectangle's contour
*/
public void addRoundRect(float left, float top, float right, float bottom, float rx, float ry,
- Direction dir) {
+ @NonNull Direction dir) {
isSimplePath = false;
nAddRoundRect(mNativePath, left, top, right, bottom, rx, ry, dir.nativeInt);
}
@@ -650,7 +655,7 @@ public class Path {
* @param radii Array of 8 values, 4 pairs of [X,Y] radii
* @param dir The direction to wind the round-rectangle's contour
*/
- public void addRoundRect(RectF rect, float[] radii, Direction dir) {
+ public void addRoundRect(@NonNull RectF rect, @NonNull float[] radii, @NonNull Direction dir) {
if (rect == null) {
throw new NullPointerException("need rect parameter");
}
@@ -665,8 +670,8 @@ public class Path {
* @param radii Array of 8 values, 4 pairs of [X,Y] radii
* @param dir The direction to wind the round-rectangle's contour
*/
- public void addRoundRect(float left, float top, float right, float bottom, float[] radii,
- Direction dir) {
+ public void addRoundRect(float left, float top, float right, float bottom,
+ @NonNull float[] radii, @NonNull Direction dir) {
if (radii.length < 8) {
throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values");
}
@@ -680,7 +685,7 @@ public class Path {
* @param src The path to add as a new contour
* @param dx The amount to translate the path in X as it is added
*/
- public void addPath(Path src, float dx, float dy) {
+ public void addPath(@NonNull Path src, float dx, float dy) {
isSimplePath = false;
nAddPath(mNativePath, src.mNativePath, dx, dy);
}
@@ -690,7 +695,7 @@ public class Path {
*
* @param src The path that is appended to the current path
*/
- public void addPath(Path src) {
+ public void addPath(@NonNull Path src) {
isSimplePath = false;
nAddPath(mNativePath, src.mNativePath);
}
@@ -700,7 +705,7 @@ public class Path {
*
* @param src The path to add as a new contour
*/
- public void addPath(Path src, Matrix matrix) {
+ public void addPath(@NonNull Path src, @NonNull Matrix matrix) {
if (!src.isSimplePath) isSimplePath = false;
nAddPath(mNativePath, src.mNativePath, matrix.native_instance);
}
@@ -760,7 +765,7 @@ public class Path {
* @param dst The transformed path is written here. If dst is null,
* then the the original path is modified
*/
- public void transform(Matrix matrix, Path dst) {
+ public void transform(@NonNull Matrix matrix, @Nullable Path dst) {
long dstNative = 0;
if (dst != null) {
dst.isSimplePath = false;
@@ -774,7 +779,7 @@ public class Path {
*
* @param matrix The matrix to apply to the path
*/
- public void transform(Matrix matrix) {
+ public void transform(@NonNull Matrix matrix) {
isSimplePath = false;
nTransform(mNativePath, matrix.native_instance);
}
diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java
index ac386979ff5b..8d12cbffc793 100644
--- a/graphics/java/android/graphics/Picture.java
+++ b/graphics/java/android/graphics/Picture.java
@@ -16,6 +16,9 @@
package android.graphics;
+import android.annotation.NonNull;
+import android.annotation.UnsupportedAppUsage;
+
import java.io.InputStream;
import java.io.OutputStream;
@@ -32,6 +35,8 @@ import java.io.OutputStream;
*/
public class Picture {
private PictureCanvas mRecordingCanvas;
+ // TODO: Figure out if this was a false-positive
+ @UnsupportedAppUsage(maxTargetSdk = 28)
private long mNativePicture;
private boolean mRequiresHwAcceleration;
@@ -53,23 +58,43 @@ public class Picture {
this(nativeConstructor(src != null ? src.mNativePicture : 0));
}
- private Picture(long nativePicture) {
+ /** @hide */
+ public Picture(long nativePicture) {
if (nativePicture == 0) {
- throw new RuntimeException();
+ throw new IllegalArgumentException();
}
mNativePicture = nativePicture;
}
+ /**
+ * Immediately releases the backing data of the Picture. This object will no longer
+ * be usable after calling this, and any further calls on the Picture will throw an
+ * IllegalStateException.
+ * // TODO: Support?
+ * @hide
+ */
+ public void close() {
+ if (mNativePicture != 0) {
+ nativeDestructor(mNativePicture);
+ mNativePicture = 0;
+ }
+ }
+
@Override
protected void finalize() throws Throwable {
try {
- nativeDestructor(mNativePicture);
- mNativePicture = 0;
+ close();
} finally {
super.finalize();
}
}
+ private void verifyValid() {
+ if (mNativePicture == 0) {
+ throw new IllegalStateException("Picture is destroyed");
+ }
+ }
+
/**
* To record a picture, call beginRecording() and then draw into the Canvas
* that is returned. Nothing we appear on screen, but all of the draw
@@ -78,7 +103,9 @@ public class Picture {
* that was returned must no longer be used, and nothing should be drawn
* into it.
*/
+ @NonNull
public Canvas beginRecording(int width, int height) {
+ verifyValid();
if (mRecordingCanvas != null) {
throw new IllegalStateException("Picture already recording, must call #endRecording()");
}
@@ -95,6 +122,7 @@ public class Picture {
* or {@link Canvas#drawPicture(Picture)} is called.
*/
public void endRecording() {
+ verifyValid();
if (mRecordingCanvas != null) {
mRequiresHwAcceleration = mRecordingCanvas.mHoldsHwBitmap;
mRecordingCanvas = null;
@@ -107,7 +135,8 @@ public class Picture {
* does not reflect (per se) the content of the picture.
*/
public int getWidth() {
- return nativeGetWidth(mNativePicture);
+ verifyValid();
+ return nativeGetWidth(mNativePicture);
}
/**
@@ -115,7 +144,8 @@ public class Picture {
* does not reflect (per se) the content of the picture.
*/
public int getHeight() {
- return nativeGetHeight(mNativePicture);
+ verifyValid();
+ return nativeGetHeight(mNativePicture);
}
/**
@@ -130,6 +160,7 @@ public class Picture {
* false otherwise.
*/
public boolean requiresHardwareAcceleration() {
+ verifyValid();
return mRequiresHwAcceleration;
}
@@ -146,7 +177,8 @@ public class Picture {
*
* @param canvas The picture is drawn to this canvas
*/
- public void draw(Canvas canvas) {
+ public void draw(@NonNull Canvas canvas) {
+ verifyValid();
if (mRecordingCanvas != null) {
endRecording();
}
@@ -163,12 +195,13 @@ public class Picture {
* properly and are highly discouraged.
*
* @see #writeToStream(java.io.OutputStream)
+ * @removed
* @deprecated The recommended alternative is to not use writeToStream and
* instead draw the picture into a Bitmap from which you can persist it as
* raw or compressed pixels.
*/
@Deprecated
- public static Picture createFromStream(InputStream stream) {
+ public static Picture createFromStream(@NonNull InputStream stream) {
return new Picture(nativeCreateFromStream(stream, new byte[WORKING_STREAM_STORAGE]));
}
@@ -179,14 +212,16 @@ public class Picture {
* there is no guarantee that the Picture can be successfully reconstructed.
*
* @see #createFromStream(java.io.InputStream)
+ * @removed
* @deprecated The recommended alternative is to draw the picture into a
* Bitmap from which you can persist it as raw or compressed pixels.
*/
@Deprecated
- public void writeToStream(OutputStream stream) {
+ public void writeToStream(@NonNull OutputStream stream) {
+ verifyValid();
// do explicit check before calling the native method
if (stream == null) {
- throw new NullPointerException();
+ throw new IllegalArgumentException("stream cannot be null");
}
if (!nativeWriteToStream(mNativePicture, stream, new byte[WORKING_STREAM_STORAGE])) {
throw new RuntimeException();
@@ -212,7 +247,7 @@ public class Picture {
public PictureCanvas(Picture pict, long nativeCanvas) {
super(nativeCanvas);
mPicture = pict;
- // Disable bitmap density scaling. This matches DisplayListCanvas.
+ // Disable bitmap density scaling. This matches RecordingCanvas.
mDensity = 0;
}
diff --git a/graphics/java/android/graphics/PixelFormat.java b/graphics/java/android/graphics/PixelFormat.java
index 96d6eeece0a3..dde757b4eb01 100644
--- a/graphics/java/android/graphics/PixelFormat.java
+++ b/graphics/java/android/graphics/PixelFormat.java
@@ -90,6 +90,9 @@ public class PixelFormat {
public static final int RGBA_F16 = 0x16;
public static final int RGBA_1010102 = 0x2B;
+ /** @hide */
+ public static final int HSV_888 = 0x37;
+
/**
* @deprecated use {@link android.graphics.ImageFormat#JPEG
* ImageFormat.JPEG} instead.
@@ -109,6 +112,7 @@ public class PixelFormat {
info.bytesPerPixel = 4;
break;
case RGB_888:
+ case HSV_888:
info.bitsPerPixel = 24;
info.bytesPerPixel = 3;
break;
@@ -227,6 +231,8 @@ public class PixelFormat {
return "RGBA_F16";
case RGBA_1010102:
return "RGBA_1010102";
+ case HSV_888:
+ return "HSV_888";
case JPEG:
return "JPEG";
default:
diff --git a/graphics/java/android/graphics/Point.java b/graphics/java/android/graphics/Point.java
index c6b6c668f03a..3614f3bcb3be 100644
--- a/graphics/java/android/graphics/Point.java
+++ b/graphics/java/android/graphics/Point.java
@@ -16,8 +16,10 @@
package android.graphics;
+import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Size;
import android.util.proto.ProtoOutputStream;
import java.io.PrintWriter;
@@ -37,7 +39,7 @@ public class Point implements Parcelable {
this.y = y;
}
- public Point(Point src) {
+ public Point(@NonNull Point src) {
this.x = src.x;
this.y = src.y;
}
@@ -99,7 +101,7 @@ public class Point implements Parcelable {
}
/** @hide */
- public void printShortString(PrintWriter pw) {
+ public void printShortString(@NonNull PrintWriter pw) {
pw.print("["); pw.print(x); pw.print(","); pw.print(y); pw.print("]");
}
@@ -130,17 +132,18 @@ public class Point implements Parcelable {
* @param fieldId Field Id of the Rect as defined in the parent message
* @hide
*/
- public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
+ public void writeToProto(@NonNull ProtoOutputStream protoOutputStream, long fieldId) {
final long token = protoOutputStream.start(fieldId);
protoOutputStream.write(PointProto.X, x);
protoOutputStream.write(PointProto.Y, y);
protoOutputStream.end(token);
}
- public static final Parcelable.Creator<Point> CREATOR = new Parcelable.Creator<Point>() {
+ public static final @android.annotation.NonNull Parcelable.Creator<Point> CREATOR = new Parcelable.Creator<Point>() {
/**
* Return a new point from the data in the specified parcel.
*/
+ @Override
public Point createFromParcel(Parcel in) {
Point r = new Point();
r.readFromParcel(in);
@@ -150,6 +153,7 @@ public class Point implements Parcelable {
/**
* Return an array of rectangles of the specified size.
*/
+ @Override
public Point[] newArray(int size) {
return new Point[size];
}
@@ -161,8 +165,18 @@ public class Point implements Parcelable {
*
* @param in The parcel to read the point's coordinates from
*/
- public void readFromParcel(Parcel in) {
+ public void readFromParcel(@NonNull Parcel in) {
x = in.readInt();
y = in.readInt();
}
+
+ /** {@hide} */
+ public static @NonNull Point convert(@NonNull Size size) {
+ return new Point(size.getWidth(), size.getHeight());
+ }
+
+ /** {@hide} */
+ public static @NonNull Size convert(@NonNull Point point) {
+ return new Size(point.x, point.y);
+ }
}
diff --git a/graphics/java/android/graphics/PointF.java b/graphics/java/android/graphics/PointF.java
index 8e4288e35c42..f1f48adc7018 100644
--- a/graphics/java/android/graphics/PointF.java
+++ b/graphics/java/android/graphics/PointF.java
@@ -16,10 +16,10 @@
package android.graphics;
+import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
-
/**
* PointF holds two float coordinates
*/
@@ -34,7 +34,7 @@ public class PointF implements Parcelable {
this.y = y;
}
- public PointF(Point p) {
+ public PointF(@NonNull Point p) {
this.x = p.x;
this.y = p.y;
}
@@ -50,7 +50,7 @@ public class PointF implements Parcelable {
/**
* Set the point's x and y coordinates to the coordinates of p
*/
- public final void set(PointF p) {
+ public final void set(@NonNull PointF p) {
this.x = p.x;
this.y = p.y;
}
@@ -130,10 +130,11 @@ public class PointF implements Parcelable {
out.writeFloat(y);
}
- public static final Parcelable.Creator<PointF> CREATOR = new Parcelable.Creator<PointF>() {
+ public static final @android.annotation.NonNull Parcelable.Creator<PointF> CREATOR = new Parcelable.Creator<PointF>() {
/**
* Return a new point from the data in the specified parcel.
*/
+ @Override
public PointF createFromParcel(Parcel in) {
PointF r = new PointF();
r.readFromParcel(in);
@@ -143,6 +144,7 @@ public class PointF implements Parcelable {
/**
* Return an array of rectangles of the specified size.
*/
+ @Override
public PointF[] newArray(int size) {
return new PointF[size];
}
@@ -154,7 +156,7 @@ public class PointF implements Parcelable {
*
* @param in The parcel to read the point's coordinates from
*/
- public void readFromParcel(Parcel in) {
+ public void readFromParcel(@NonNull Parcel in) {
x = in.readFloat();
y = in.readFloat();
}
diff --git a/graphics/java/android/graphics/PorterDuff.java b/graphics/java/android/graphics/PorterDuff.java
index d7d3049b0efa..bc1f66fdd5c0 100644
--- a/graphics/java/android/graphics/PorterDuff.java
+++ b/graphics/java/android/graphics/PorterDuff.java
@@ -16,11 +16,15 @@
package android.graphics;
+import android.annotation.UnsupportedAppUsage;
+
/**
* <p>This class contains the list of alpha compositing and blending modes
* that can be passed to {@link PorterDuffXfermode}, a specialized implementation
* of {@link Paint}'s {@link Paint#setXfermode(Xfermode) transfer mode}.
* All the available modes can be found in the {@link Mode} enum.</p>
+ *
+ * Consider using {@link BlendMode} instead as it provides a wider variety of tinting options
*/
public class PorterDuff {
/**
@@ -364,6 +368,7 @@ public class PorterDuff {
/**
* @hide
*/
+ @UnsupportedAppUsage
public final int nativeInt;
}
diff --git a/graphics/java/android/graphics/PorterDuffColorFilter.java b/graphics/java/android/graphics/PorterDuffColorFilter.java
index 01d5825dd1e0..cc2d3a8969fc 100644
--- a/graphics/java/android/graphics/PorterDuffColorFilter.java
+++ b/graphics/java/android/graphics/PorterDuffColorFilter.java
@@ -18,6 +18,7 @@ package android.graphics;
import android.annotation.ColorInt;
import android.annotation.NonNull;
+import android.annotation.UnsupportedAppUsage;
/**
* A color filter that can be used to tint the source pixels using a single
@@ -35,8 +36,6 @@ public class PorterDuffColorFilter extends ColorFilter {
* @param mode The porter-duff mode that is applied
*
* @see Color
- * @see #setColor(int)
- * @see #setMode(android.graphics.PorterDuff.Mode)
*/
public PorterDuffColorFilter(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
mColor = color;
@@ -48,68 +47,31 @@ public class PorterDuffColorFilter extends ColorFilter {
* is applied.
*
* @see Color
- * @see #setColor(int)
*
* @hide
*/
@ColorInt
+ @UnsupportedAppUsage
public int getColor() {
return mColor;
}
/**
- * Specifies the color to tint the source pixels with when this color
- * filter is applied.
- *
- * @param color An ARGB {@link Color color}
- *
- * @see Color
- * @see #getColor()
- * @see #getMode()
- *
- * @hide
- */
- public void setColor(@ColorInt int color) {
- if (mColor != color) {
- mColor = color;
- discardNativeInstance();
- }
- }
-
- /**
* Returns the Porter-Duff mode used to composite this color filter's
* color with the source pixel when this filter is applied.
*
* @see PorterDuff
- * @see #setMode(android.graphics.PorterDuff.Mode)
*
* @hide
*/
+ @UnsupportedAppUsage
public PorterDuff.Mode getMode() {
return mMode;
}
- /**
- * Specifies the Porter-Duff mode to use when compositing this color
- * filter's color with the source pixel at draw time.
- *
- * @see PorterDuff
- * @see #getMode()
- * @see #getColor()
- *
- * @hide
- */
- public void setMode(@NonNull PorterDuff.Mode mode) {
- if (mode == null) {
- throw new IllegalArgumentException("mode must be non-null");
- }
- mMode = mode;
- discardNativeInstance();
- }
-
@Override
long createNativeInstance() {
- return native_CreatePorterDuffFilter(mColor, mMode.nativeInt);
+ return native_CreateBlendModeFilter(mColor, mMode.nativeInt);
}
@Override
@@ -129,5 +91,5 @@ public class PorterDuffColorFilter extends ColorFilter {
return 31 * mMode.hashCode() + mColor;
}
- private static native long native_CreatePorterDuffFilter(int srcColor, int porterDuffMode);
+ private static native long native_CreateBlendModeFilter(int srcColor, int blendmode);
}
diff --git a/graphics/java/android/graphics/PorterDuffXfermode.java b/graphics/java/android/graphics/PorterDuffXfermode.java
index 84d953de91db..ff9ff8b0069d 100644
--- a/graphics/java/android/graphics/PorterDuffXfermode.java
+++ b/graphics/java/android/graphics/PorterDuffXfermode.java
@@ -21,6 +21,7 @@ package android.graphics;
* {@link Paint#setXfermode(Xfermode) transfer mode}. Refer to the
* documentation of the {@link PorterDuff.Mode} enum for more
* information on the available alpha compositing and blending modes.</p>
+ *
*/
public class PorterDuffXfermode extends Xfermode {
/**
diff --git a/graphics/java/android/graphics/RadialGradient.java b/graphics/java/android/graphics/RadialGradient.java
index f4b11917a415..acbe3da75247 100644
--- a/graphics/java/android/graphics/RadialGradient.java
+++ b/graphics/java/android/graphics/RadialGradient.java
@@ -16,30 +16,57 @@
package android.graphics;
+import android.annotation.ColorInt;
+import android.annotation.ColorLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.ColorInt;
+import android.annotation.UnsupportedAppUsage;
public class RadialGradient extends Shader {
-
- private static final int TYPE_COLORS_AND_POSITIONS = 1;
- private static final int TYPE_COLOR_CENTER_AND_COLOR_EDGE = 2;
-
- /**
- * Type of the RadialGradient: can be either TYPE_COLORS_AND_POSITIONS or
- * TYPE_COLOR_CENTER_AND_COLOR_EDGE.
- */
- private int mType;
-
+ @UnsupportedAppUsage
private float mX;
+ @UnsupportedAppUsage
private float mY;
+ @UnsupportedAppUsage
private float mRadius;
- private int[] mColors;
+ @UnsupportedAppUsage
private float[] mPositions;
+ @UnsupportedAppUsage
+ private TileMode mTileMode;
+
+ // @ColorInts are replaced by @ColorLongs, but these remain due to @UnsupportedAppUsage.
+ @UnsupportedAppUsage
+ @ColorInt
+ private int[] mColors;
+ @UnsupportedAppUsage
+ @ColorInt
private int mCenterColor;
+ @UnsupportedAppUsage
+ @ColorInt
private int mEdgeColor;
- private TileMode mTileMode;
+ @ColorLong
+ private final long[] mColorLongs;
+
+ /**
+ * Create a shader that draws a radial gradient given the center and radius.
+ *
+ * @param centerX The x-coordinate of the center of the radius
+ * @param centerY The y-coordinate of the center of the radius
+ * @param radius Must be positive. The radius of the circle for this gradient.
+ * @param colors The sRGB colors to be distributed between the center and edge of the circle
+ * @param stops May be <code>null</code>. Valid values are between <code>0.0f</code> and
+ * <code>1.0f</code>. The relative position of each corresponding color in
+ * the colors array. If <code>null</code>, colors are distributed evenly
+ * between the center and edge of the circle.
+ * @param tileMode The Shader tiling mode
+ */
+ public RadialGradient(float centerX, float centerY, float radius,
+ @NonNull @ColorInt int[] colors, @Nullable float[] stops,
+ @NonNull TileMode tileMode) {
+ this(centerX, centerY, radius, convertColors(colors), stops, tileMode,
+ ColorSpace.get(ColorSpace.Named.SRGB));
+ }
/**
* Create a shader that draws a radial gradient given the center and radius.
@@ -53,24 +80,36 @@ public class RadialGradient extends Shader {
* the colors array. If <code>null</code>, colors are distributed evenly
* between the center and edge of the circle.
* @param tileMode The Shader tiling mode
+ *
+ * @throws IllegalArgumentException if there are less than two colors, the colors do
+ * not share the same {@link ColorSpace} or do not use a valid one, or {@code stops}
+ * is not {@code null} and has a different length from {@code colors}.
*/
public RadialGradient(float centerX, float centerY, float radius,
- @NonNull @ColorInt int colors[], @Nullable float stops[],
+ @NonNull @ColorLong long[] colors, @Nullable float[] stops,
@NonNull TileMode tileMode) {
+ this(centerX, centerY, radius, colors.clone(), stops, tileMode, detectColorSpace(colors));
+ }
+
+ /**
+ * Base constructor. Assumes @param colors is a copy that this object can hold onto,
+ * and all colors share @param colorSpace.
+ */
+ private RadialGradient(float centerX, float centerY, float radius,
+ @NonNull @ColorLong long[] colors, @Nullable float[] stops,
+ @NonNull TileMode tileMode, ColorSpace colorSpace) {
+ super(colorSpace);
+
if (radius <= 0) {
throw new IllegalArgumentException("radius must be > 0");
}
- if (colors.length < 2) {
- throw new IllegalArgumentException("needs >= 2 number of colors");
- }
if (stops != null && colors.length != stops.length) {
throw new IllegalArgumentException("color and position arrays must be of equal length");
}
- mType = TYPE_COLORS_AND_POSITIONS;
mX = centerX;
mY = centerY;
mRadius = radius;
- mColors = colors.clone();
+ mColorLongs = colors;
mPositions = stops != null ? stops.clone() : null;
mTileMode = tileMode;
}
@@ -81,54 +120,41 @@ public class RadialGradient extends Shader {
* @param centerX The x-coordinate of the center of the radius
* @param centerY The y-coordinate of the center of the radius
* @param radius Must be positive. The radius of the circle for this gradient
- * @param centerColor The color at the center of the circle.
- * @param edgeColor The color at the edge of the circle.
+ * @param centerColor The sRGB color at the center of the circle.
+ * @param edgeColor The sRGB color at the edge of the circle.
* @param tileMode The Shader tiling mode
*/
public RadialGradient(float centerX, float centerY, float radius,
@ColorInt int centerColor, @ColorInt int edgeColor, @NonNull TileMode tileMode) {
- if (radius <= 0) {
- throw new IllegalArgumentException("radius must be > 0");
- }
- mType = TYPE_COLOR_CENTER_AND_COLOR_EDGE;
- mX = centerX;
- mY = centerY;
- mRadius = radius;
- mCenterColor = centerColor;
- mEdgeColor = edgeColor;
- mTileMode = tileMode;
- }
-
- @Override
- long createNativeInstance(long nativeMatrix) {
- if (mType == TYPE_COLORS_AND_POSITIONS) {
- return nativeCreate1(nativeMatrix, mX, mY, mRadius,
- mColors, mPositions, mTileMode.nativeInt);
- } else { // TYPE_COLOR_CENTER_AND_COLOR_EDGE
- return nativeCreate2(nativeMatrix, mX, mY, mRadius,
- mCenterColor, mEdgeColor, mTileMode.nativeInt);
- }
+ this(centerX, centerY, radius, Color.pack(centerColor), Color.pack(edgeColor), tileMode);
}
/**
- * @hide
+ * Create a shader that draws a radial gradient given the center and radius.
+ *
+ * @param centerX The x-coordinate of the center of the radius
+ * @param centerY The y-coordinate of the center of the radius
+ * @param radius Must be positive. The radius of the circle for this gradient
+ * @param centerColor The color at the center of the circle.
+ * @param edgeColor The color at the edge of the circle.
+ * @param tileMode The Shader tiling mode
+ *
+ * @throws IllegalArgumentException if the colors do
+ * not share the same {@link ColorSpace} or do not use a valid one.
*/
+ public RadialGradient(float centerX, float centerY, float radius,
+ @ColorLong long centerColor, @ColorLong long edgeColor, @NonNull TileMode tileMode) {
+ this(centerX, centerY, radius, new long[] {centerColor, edgeColor}, null, tileMode);
+ }
+
@Override
- protected Shader copy() {
- final RadialGradient copy;
- if (mType == TYPE_COLORS_AND_POSITIONS) {
- copy = new RadialGradient(mX, mY, mRadius, mColors.clone(),
- mPositions != null ? mPositions.clone() : null, mTileMode);
- } else { // TYPE_COLOR_CENTER_AND_COLOR_EDGE
- copy = new RadialGradient(mX, mY, mRadius, mCenterColor, mEdgeColor, mTileMode);
- }
- copyLocalMatrix(copy);
- return copy;
+ long createNativeInstance(long nativeMatrix) {
+ return nativeCreate(nativeMatrix, mX, mY, mRadius,
+ mColorLongs, mPositions, mTileMode.nativeInt,
+ colorSpace().getNativeInstance());
}
- private static native long nativeCreate1(long matrix, float x, float y, float radius,
- int colors[], float positions[], int tileMode);
- private static native long nativeCreate2(long matrix, float x, float y, float radius,
- int color0, int color1, int tileMode);
+ private static native long nativeCreate(long matrix, float x, float y, float radius,
+ @ColorLong long[] colors, float[] positions, int tileMode, long colorSpaceHandle);
}
diff --git a/graphics/java/android/graphics/RecordingCanvas.java b/graphics/java/android/graphics/RecordingCanvas.java
new file mode 100644
index 000000000000..c0e0a24583ec
--- /dev/null
+++ b/graphics/java/android/graphics/RecordingCanvas.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Pools.SynchronizedPool;
+import android.view.DisplayListCanvas;
+import android.view.TextureLayer;
+
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
+/**
+ * A Canvas implementation that records view system drawing operations for deferred rendering.
+ * This is used in combination with RenderNode. This class keeps a list of all the Paint and
+ * Bitmap objects that it draws, preventing the backing memory of Bitmaps from being released while
+ * the RecordingCanvas is still holding a native reference to the memory.
+ *
+ * This is obtained by calling {@link RenderNode#beginRecording()} and is valid until the matching
+ * {@link RenderNode#endRecording()} is called. It must not be retained beyond that as it is
+ * internally reused.
+ */
+public final class RecordingCanvas extends DisplayListCanvas {
+ // The recording canvas pool should be large enough to handle a deeply nested
+ // view hierarchy because display lists are generated recursively.
+ private static final int POOL_LIMIT = 25;
+
+ /** @hide */
+ public static final int MAX_BITMAP_SIZE = 100 * 1024 * 1024; // 100 MB
+
+ private static final SynchronizedPool<RecordingCanvas> sPool =
+ new SynchronizedPool<>(POOL_LIMIT);
+
+ /**
+ * TODO: Temporarily exposed for RenderNodeAnimator(Set)
+ * @hide */
+ public RenderNode mNode;
+ private int mWidth;
+ private int mHeight;
+
+ /** @hide */
+ static RecordingCanvas obtain(@NonNull RenderNode node, int width, int height) {
+ if (node == null) throw new IllegalArgumentException("node cannot be null");
+ RecordingCanvas canvas = sPool.acquire();
+ if (canvas == null) {
+ canvas = new RecordingCanvas(node, width, height);
+ } else {
+ nResetDisplayListCanvas(canvas.mNativeCanvasWrapper, node.mNativeRenderNode,
+ width, height);
+ }
+ canvas.mNode = node;
+ canvas.mWidth = width;
+ canvas.mHeight = height;
+ return canvas;
+ }
+
+ /** @hide */
+ void recycle() {
+ mNode = null;
+ sPool.release(this);
+ }
+
+ /** @hide */
+ long finishRecording() {
+ return nFinishRecording(mNativeCanvasWrapper);
+ }
+
+ /** @hide */
+ @Override
+ public boolean isRecordingFor(Object o) {
+ return o == mNode;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Constructors
+ ///////////////////////////////////////////////////////////////////////////
+
+ /** @hide */
+ protected RecordingCanvas(@NonNull RenderNode node, int width, int height) {
+ super(nCreateDisplayListCanvas(node.mNativeRenderNode, width, height));
+ mDensity = 0; // disable bitmap density scaling
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Canvas management
+ ///////////////////////////////////////////////////////////////////////////
+
+
+ @Override
+ public void setDensity(int density) {
+ // drop silently, since RecordingCanvas doesn't perform density scaling
+ }
+
+ @Override
+ public boolean isHardwareAccelerated() {
+ return true;
+ }
+
+ @Override
+ public void setBitmap(Bitmap bitmap) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isOpaque() {
+ return false;
+ }
+
+ @Override
+ public int getWidth() {
+ return mWidth;
+ }
+
+ @Override
+ public int getHeight() {
+ return mHeight;
+ }
+
+ @Override
+ public int getMaximumBitmapWidth() {
+ return nGetMaximumTextureWidth();
+ }
+
+ @Override
+ public int getMaximumBitmapHeight() {
+ return nGetMaximumTextureHeight();
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Setup
+ ///////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public void enableZ() {
+ nInsertReorderBarrier(mNativeCanvasWrapper, true);
+ }
+
+ @Override
+ public void disableZ() {
+ nInsertReorderBarrier(mNativeCanvasWrapper, false);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Functor
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Records the functor specified with the drawGLFunction function pointer. This is
+ * functionality used by webview for calling into their renderer from our display lists.
+ *
+ * @param drawGLFunction A native function pointer
+ *
+ * @hide
+ * @deprecated Use {@link #drawWebViewFunctor(int)}
+ */
+ @Deprecated
+ public void callDrawGLFunction2(long drawGLFunction) {
+ nCallDrawGLFunction(mNativeCanvasWrapper, drawGLFunction, null);
+ }
+
+ /**
+ * Records the functor specified with the drawGLFunction function pointer. This is
+ * functionality used by webview for calling into their renderer from our display lists.
+ *
+ * @param drawGLFunctor A native function pointer
+ * @param releasedCallback Called when the display list is destroyed, and thus
+ * the functor is no longer referenced by this canvas's display list.
+ *
+ * NOTE: The callback does *not* necessarily mean that there are no longer
+ * any references to the functor, just that the reference from this specific
+ * canvas's display list has been released.
+ *
+ * @hide
+ * @deprecated Use {@link #drawWebViewFunctor(int)}
+ */
+ @Deprecated
+ public void drawGLFunctor2(long drawGLFunctor, @Nullable Runnable releasedCallback) {
+ nCallDrawGLFunction(mNativeCanvasWrapper, drawGLFunctor, releasedCallback);
+ }
+
+ /**
+ * Calls the provided functor that was created via WebViewFunctor_create()
+ * @hide
+ */
+ public void drawWebViewFunctor(int functor) {
+ nDrawWebViewFunctor(mNativeCanvasWrapper, functor);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Display list
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Draws the specified display list onto this canvas.
+ *
+ * @param renderNode The RenderNode to draw.
+ */
+ @Override
+ public void drawRenderNode(@NonNull RenderNode renderNode) {
+ nDrawRenderNode(mNativeCanvasWrapper, renderNode.mNativeRenderNode);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Hardware layer
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Draws the specified layer onto this canvas.
+ *
+ * @param layer The layer to composite on this canvas
+ * @hide
+ */
+ public void drawTextureLayer(TextureLayer layer) {
+ nDrawTextureLayer(mNativeCanvasWrapper, layer.getLayerHandle());
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Drawing
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Draws a circle
+ *
+ * @param cx
+ * @param cy
+ * @param radius
+ * @param paint
+ *
+ * @hide
+ */
+ public void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy,
+ CanvasProperty<Float> radius, CanvasProperty<Paint> paint) {
+ nDrawCircle(mNativeCanvasWrapper, cx.getNativeContainer(), cy.getNativeContainer(),
+ radius.getNativeContainer(), paint.getNativeContainer());
+ }
+
+ /**
+ * Draws a round rect
+ *
+ * @param left
+ * @param top
+ * @param right
+ * @param bottom
+ * @param rx
+ * @param ry
+ * @param paint
+ *
+ * @hide
+ */
+ public void drawRoundRect(CanvasProperty<Float> left, CanvasProperty<Float> top,
+ CanvasProperty<Float> right, CanvasProperty<Float> bottom, CanvasProperty<Float> rx,
+ CanvasProperty<Float> ry, CanvasProperty<Paint> paint) {
+ nDrawRoundRect(mNativeCanvasWrapper, left.getNativeContainer(), top.getNativeContainer(),
+ right.getNativeContainer(), bottom.getNativeContainer(),
+ rx.getNativeContainer(), ry.getNativeContainer(),
+ paint.getNativeContainer());
+ }
+
+ /** @hide */
+ @Override
+ protected void throwIfCannotDraw(Bitmap bitmap) {
+ super.throwIfCannotDraw(bitmap);
+ int bitmapSize = bitmap.getByteCount();
+ if (bitmapSize > MAX_BITMAP_SIZE) {
+ throw new RuntimeException(
+ "Canvas: trying to draw too large(" + bitmapSize + "bytes) bitmap.");
+ }
+ }
+
+
+ // ------------------ Fast JNI ------------------------
+
+ @FastNative
+ private static native void nCallDrawGLFunction(long renderer,
+ long drawGLFunction, Runnable releasedCallback);
+
+
+ // ------------------ Critical JNI ------------------------
+
+ @CriticalNative
+ private static native long nCreateDisplayListCanvas(long node, int width, int height);
+ @CriticalNative
+ private static native void nResetDisplayListCanvas(long canvas, long node,
+ int width, int height);
+ @CriticalNative
+ private static native int nGetMaximumTextureWidth();
+ @CriticalNative
+ private static native int nGetMaximumTextureHeight();
+ @CriticalNative
+ private static native void nInsertReorderBarrier(long renderer, boolean enableReorder);
+ @CriticalNative
+ private static native long nFinishRecording(long renderer);
+ @CriticalNative
+ private static native void nDrawRenderNode(long renderer, long renderNode);
+ @CriticalNative
+ private static native void nDrawTextureLayer(long renderer, long layer);
+ @CriticalNative
+ private static native void nDrawCircle(long renderer, long propCx,
+ long propCy, long propRadius, long propPaint);
+ @CriticalNative
+ private static native void nDrawRoundRect(long renderer, long propLeft, long propTop,
+ long propRight, long propBottom, long propRx, long propRy, long propPaint);
+ @CriticalNative
+ private static native void nDrawWebViewFunctor(long canvas, int functor);
+}
diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java
index 3843cb91154c..d47f682ec2d3 100644
--- a/graphics/java/android/graphics/Rect.java
+++ b/graphics/java/android/graphics/Rect.java
@@ -17,12 +17,17 @@
package android.graphics;
import android.annotation.CheckResult;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
-
import android.text.TextUtils;
+import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
+import android.util.proto.WireTypeMismatchException;
+
+import java.io.IOException;
import java.io.PrintWriter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -89,7 +94,21 @@ public final class Rect implements Parcelable {
* @param r The rectangle whose coordinates are copied into the new
* rectangle.
*/
- public Rect(Rect r) {
+ public Rect(@Nullable Rect r) {
+ if (r == null) {
+ left = top = right = bottom = 0;
+ } else {
+ left = r.left;
+ top = r.top;
+ right = r.right;
+ bottom = r.bottom;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public Rect(@Nullable Insets r) {
if (r == null) {
left = top = right = bottom = 0;
} else {
@@ -140,6 +159,7 @@ public final class Rect implements Parcelable {
/**
* Return a string representation of the rectangle in a compact form.
*/
+ @NonNull
public String toShortString() {
return toShortString(new StringBuilder(32));
}
@@ -148,7 +168,8 @@ public final class Rect implements Parcelable {
* Return a string representation of the rectangle in a compact form.
* @hide
*/
- public String toShortString(StringBuilder sb) {
+ @NonNull
+ public String toShortString(@NonNull StringBuilder sb) {
sb.setLength(0);
sb.append('['); sb.append(left); sb.append(',');
sb.append(top); sb.append("]["); sb.append(right);
@@ -164,6 +185,7 @@ public final class Rect implements Parcelable {
*
* @return Returns a new String of the form "left top right bottom"
*/
+ @NonNull
public String flattenToString() {
StringBuilder sb = new StringBuilder(32);
// WARNING: Do not change the format of this string, it must be
@@ -182,7 +204,8 @@ public final class Rect implements Parcelable {
* Returns a Rect from a string of the form returned by {@link #flattenToString},
* or null if the string is not of that form.
*/
- public static Rect unflattenFromString(String str) {
+ @Nullable
+ public static Rect unflattenFromString(@Nullable String str) {
if (TextUtils.isEmpty(str)) {
return null;
}
@@ -201,7 +224,8 @@ public final class Rect implements Parcelable {
* Print short representation to given writer.
* @hide
*/
- public void printShortString(PrintWriter pw) {
+ @UnsupportedAppUsage
+ public void printShortString(@NonNull PrintWriter pw) {
pw.print('['); pw.print(left); pw.print(',');
pw.print(top); pw.print("]["); pw.print(right);
pw.print(','); pw.print(bottom); pw.print(']');
@@ -215,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(ProtoOutputStream protoOutputStream, long fieldId) {
+ public void writeToProto(@NonNull ProtoOutputStream protoOutputStream, long fieldId) {
final long token = protoOutputStream.start(fieldId);
protoOutputStream.write(RectProto.LEFT, left);
protoOutputStream.write(RectProto.TOP, top);
@@ -225,6 +249,40 @@ public final class Rect implements Parcelable {
}
/**
+ * Read from a protocol buffer input stream.
+ * Protocol buffer message definition at {@link android.graphics.RectProto}
+ *
+ * @param proto Stream to read the Rect object from.
+ * @param fieldId Field Id of the Rect as defined in the parent message
+ * @hide
+ */
+ public void readFromProto(@NonNull ProtoInputStream proto, long fieldId) throws IOException,
+ WireTypeMismatchException {
+ final long token = proto.start(fieldId);
+ try {
+ while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (proto.getFieldNumber()) {
+ case (int) RectProto.LEFT:
+ left = proto.readInt(RectProto.LEFT);
+ break;
+ case (int) RectProto.TOP:
+ top = proto.readInt(RectProto.TOP);
+ break;
+ case (int) RectProto.RIGHT:
+ right = proto.readInt(RectProto.RIGHT);
+ break;
+ case (int) RectProto.BOTTOM:
+ bottom = proto.readInt(RectProto.BOTTOM);
+ break;
+ }
+ }
+ } finally {
+ // Let caller handle any exceptions
+ proto.end(token);
+ }
+ }
+
+ /**
* Returns true if the rectangle is empty (left >= right or top >= bottom)
*/
public final boolean isEmpty() {
@@ -309,7 +367,7 @@ public final class Rect implements Parcelable {
* @param src The rectangle whose coordinates are copied into this
* rectangle.
*/
- public void set(Rect src) {
+ public void set(@NonNull Rect src) {
this.left = src.left;
this.top = src.top;
this.right = src.right;
@@ -366,7 +424,19 @@ public final class Rect implements Parcelable {
* @hide
* @param insets The rectangle specifying the insets on all side.
*/
- public void inset(Rect insets) {
+ public void inset(@NonNull Rect insets) {
+ left += insets.left;
+ top += insets.top;
+ right -= insets.right;
+ bottom -= insets.bottom;
+ }
+
+ /**
+ * Insets the rectangle on all sides specified by the dimensions of {@code insets}.
+ * @hide
+ * @param insets The insets to inset the rect by.
+ */
+ public void inset(Insets insets) {
left += insets.left;
top += insets.top;
right -= insets.right;
@@ -432,7 +502,7 @@ public final class Rect implements Parcelable {
* @return true iff the specified rectangle r is inside or equal to this
* rectangle
*/
- public boolean contains(Rect r) {
+ public boolean contains(@NonNull Rect r) {
// check for empty first
return this.left < this.right && this.top < this.bottom
// now check for containment
@@ -481,7 +551,7 @@ public final class Rect implements Parcelable {
* return false and do not change this rectangle.
*/
@CheckResult
- public boolean intersect(Rect r) {
+ public boolean intersect(@NonNull Rect r) {
return intersect(r.left, r.top, r.right, r.bottom);
}
@@ -491,7 +561,7 @@ public final class Rect implements Parcelable {
* @see #inset(int, int, int, int) but without checking if the rects overlap.
* @hide
*/
- public void intersectUnchecked(Rect other) {
+ public void intersectUnchecked(@NonNull Rect other) {
left = Math.max(left, other.left);
top = Math.max(top, other.top);
right = Math.min(right, other.right);
@@ -511,7 +581,7 @@ public final class Rect implements Parcelable {
* false and do not change this rectangle.
*/
@CheckResult
- public boolean setIntersect(Rect a, Rect b) {
+ public boolean setIntersect(@NonNull Rect a, @NonNull Rect b) {
if (a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom) {
left = Math.max(a.left, b.left);
top = Math.max(a.top, b.top);
@@ -550,7 +620,7 @@ public final class Rect implements Parcelable {
* @return true iff the two specified rectangles intersect. In no event are
* either of the rectangles modified.
*/
- public static boolean intersects(Rect a, Rect b) {
+ public static boolean intersects(@NonNull Rect a, @NonNull Rect b) {
return a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom;
}
@@ -587,7 +657,7 @@ public final class Rect implements Parcelable {
*
* @param r The rectangle being unioned with this rectangle
*/
- public void union(Rect r) {
+ public void union(@NonNull Rect r) {
union(r.left, r.top, r.right, r.bottom);
}
@@ -634,6 +704,7 @@ public final class Rect implements Parcelable {
/**
* Parcelable interface methods
*/
+ @Override
public int describeContents() {
return 0;
}
@@ -643,6 +714,7 @@ public final class Rect implements Parcelable {
* a parcel, use readFromParcel()
* @param out The parcel to write the rectangle's coordinates into
*/
+ @Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(left);
out.writeInt(top);
@@ -650,10 +722,11 @@ public final class Rect implements Parcelable {
out.writeInt(bottom);
}
- public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() {
+ public static final @android.annotation.NonNull Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() {
/**
* Return a new rectangle from the data in the specified parcel.
*/
+ @Override
public Rect createFromParcel(Parcel in) {
Rect r = new Rect();
r.readFromParcel(in);
@@ -663,6 +736,7 @@ public final class Rect implements Parcelable {
/**
* Return an array of rectangles of the specified size.
*/
+ @Override
public Rect[] newArray(int size) {
return new Rect[size];
}
@@ -674,7 +748,7 @@ public final class Rect implements Parcelable {
*
* @param in The parcel to read the rectangle's coordinates from
*/
- public void readFromParcel(Parcel in) {
+ public void readFromParcel(@NonNull Parcel in) {
left = in.readInt();
top = in.readInt();
right = in.readInt();
@@ -685,6 +759,7 @@ public final class Rect implements Parcelable {
* Scales up the rect by the given scale.
* @hide
*/
+ @UnsupportedAppUsage
public void scale(float scale) {
if (scale != 1.0f) {
left = (int) (left * scale + 0.5f);
diff --git a/graphics/java/android/graphics/RectF.java b/graphics/java/android/graphics/RectF.java
index b49054550956..1d294d51a235 100644
--- a/graphics/java/android/graphics/RectF.java
+++ b/graphics/java/android/graphics/RectF.java
@@ -16,15 +16,18 @@
package android.graphics;
-import java.io.PrintWriter;
-
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
+
import com.android.internal.util.FastMath;
+import java.io.PrintWriter;
+
/**
* RectF holds four float coordinates for a rectangle. The rectangle is
- * represented by the coordinates of its 4 edges (left, top, right bottom).
+ * represented by the coordinates of its 4 edges (left, top, right, bottom).
* These fields can be accessed directly. Use width() and height() to retrieve
* the rectangle's width and height. Note: most methods do not check to see that
* the coordinates are sorted correctly (i.e. left <= right and top <= bottom).
@@ -64,7 +67,7 @@ public class RectF implements Parcelable {
* @param r The rectangle whose coordinates are copied into the new
* rectangle.
*/
- public RectF(RectF r) {
+ public RectF(@Nullable RectF r) {
if (r == null) {
left = top = right = bottom = 0.0f;
} else {
@@ -75,7 +78,7 @@ public class RectF implements Parcelable {
}
}
- public RectF(Rect r) {
+ public RectF(@Nullable Rect r) {
if (r == null) {
left = top = right = bottom = 0.0f;
} else {
@@ -104,6 +107,7 @@ public class RectF implements Parcelable {
return result;
}
+ @Override
public String toString() {
return "RectF(" + left + ", " + top + ", "
+ right + ", " + bottom + ")";
@@ -112,6 +116,7 @@ public class RectF implements Parcelable {
/**
* Return a string representation of the rectangle in a compact form.
*/
+ @NonNull
public String toShortString() {
return toShortString(new StringBuilder(32));
}
@@ -120,7 +125,8 @@ public class RectF implements Parcelable {
* Return a string representation of the rectangle in a compact form.
* @hide
*/
- public String toShortString(StringBuilder sb) {
+ @NonNull
+ public String toShortString(@NonNull StringBuilder sb) {
sb.setLength(0);
sb.append('['); sb.append(left); sb.append(',');
sb.append(top); sb.append("]["); sb.append(right);
@@ -132,7 +138,7 @@ public class RectF implements Parcelable {
* Print short representation to given writer.
* @hide
*/
- public void printShortString(PrintWriter pw) {
+ public void printShortString(@NonNull PrintWriter pw) {
pw.print('['); pw.print(left); pw.print(',');
pw.print(top); pw.print("]["); pw.print(right);
pw.print(','); pw.print(bottom); pw.print(']');
@@ -207,7 +213,7 @@ public class RectF implements Parcelable {
* @param src The rectangle whose coordinates are copied into this
* rectangle.
*/
- public void set(RectF src) {
+ public void set(@NonNull RectF src) {
this.left = src.left;
this.top = src.top;
this.right = src.right;
@@ -220,7 +226,7 @@ public class RectF implements Parcelable {
* @param src The rectangle whose coordinates are copied into this
* rectangle.
*/
- public void set(Rect src) {
+ public void set(@NonNull Rect src) {
this.left = src.left;
this.top = src.top;
this.right = src.right;
@@ -315,7 +321,7 @@ public class RectF implements Parcelable {
* @return true iff the specified rectangle r is inside or equal to this
* rectangle
*/
- public boolean contains(RectF r) {
+ public boolean contains(@NonNull RectF r) {
// check for empty first
return this.left < this.right && this.top < this.bottom
// now check for containment
@@ -372,7 +378,7 @@ public class RectF implements Parcelable {
* (and this rectangle is then set to that intersection) else
* return false and do not change this rectangle.
*/
- public boolean intersect(RectF r) {
+ public boolean intersect(@NonNull RectF r) {
return intersect(r.left, r.top, r.right, r.bottom);
}
@@ -388,7 +394,7 @@ public class RectF implements Parcelable {
* this rectangle to that intersection. If they do not, return
* false and do not change this rectangle.
*/
- public boolean setIntersect(RectF a, RectF b) {
+ public boolean setIntersect(@NonNull RectF a, @NonNull RectF b) {
if (a.left < b.right && b.left < a.right
&& a.top < b.bottom && b.top < a.bottom) {
left = Math.max(a.left, b.left);
@@ -430,7 +436,7 @@ public class RectF implements Parcelable {
* @return true iff the two specified rectangles intersect. In no event are
* either of the rectangles modified.
*/
- public static boolean intersects(RectF a, RectF b) {
+ public static boolean intersects(@NonNull RectF a, @NonNull RectF b) {
return a.left < b.right && b.left < a.right
&& a.top < b.bottom && b.top < a.bottom;
}
@@ -439,7 +445,7 @@ public class RectF implements Parcelable {
* Set the dst integer Rect by rounding this rectangle's coordinates
* to their nearest integer values.
*/
- public void round(Rect dst) {
+ public void round(@NonNull Rect dst) {
dst.set(FastMath.round(left), FastMath.round(top),
FastMath.round(right), FastMath.round(bottom));
}
@@ -448,7 +454,7 @@ public class RectF implements Parcelable {
* Set the dst integer Rect by rounding "out" this rectangle, choosing the
* floor of top and left, and the ceiling of right and bottom.
*/
- public void roundOut(Rect dst) {
+ public void roundOut(@NonNull Rect dst) {
dst.set((int) Math.floor(left), (int) Math.floor(top),
(int) Math.ceil(right), (int) Math.ceil(bottom));
}
@@ -490,7 +496,7 @@ public class RectF implements Parcelable {
*
* @param r The rectangle being unioned with this rectangle
*/
- public void union(RectF r) {
+ public void union(@NonNull RectF r) {
union(r.left, r.top, r.right, r.bottom);
}
@@ -537,6 +543,7 @@ public class RectF implements Parcelable {
/**
* Parcelable interface methods
*/
+ @Override
public int describeContents() {
return 0;
}
@@ -546,6 +553,7 @@ public class RectF implements Parcelable {
* a parcel, use readFromParcel()
* @param out The parcel to write the rectangle's coordinates into
*/
+ @Override
public void writeToParcel(Parcel out, int flags) {
out.writeFloat(left);
out.writeFloat(top);
@@ -553,10 +561,11 @@ public class RectF implements Parcelable {
out.writeFloat(bottom);
}
- public static final Parcelable.Creator<RectF> CREATOR = new Parcelable.Creator<RectF>() {
+ public static final @android.annotation.NonNull Parcelable.Creator<RectF> CREATOR = new Parcelable.Creator<RectF>() {
/**
* Return a new rectangle from the data in the specified parcel.
*/
+ @Override
public RectF createFromParcel(Parcel in) {
RectF r = new RectF();
r.readFromParcel(in);
@@ -566,6 +575,7 @@ public class RectF implements Parcelable {
/**
* Return an array of rectangles of the specified size.
*/
+ @Override
public RectF[] newArray(int size) {
return new RectF[size];
}
@@ -577,7 +587,7 @@ public class RectF implements Parcelable {
*
* @param in The parcel to read the rectangle's coordinates from
*/
- public void readFromParcel(Parcel in) {
+ public void readFromParcel(@NonNull Parcel in) {
left = in.readFloat();
top = in.readFloat();
right = in.readFloat();
diff --git a/graphics/java/android/graphics/Region.java b/graphics/java/android/graphics/Region.java
index dca6d9ed3b2f..ec7f7a05b685 100644
--- a/graphics/java/android/graphics/Region.java
+++ b/graphics/java/android/graphics/Region.java
@@ -16,6 +16,8 @@
package android.graphics;
+import android.annotation.NonNull;
+import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Pools.SynchronizedPool;
@@ -30,6 +32,7 @@ public class Region implements Parcelable {
/**
* @hide
*/
+ @UnsupportedAppUsage
public long mNativeRegion;
// the native values for these must match up with the enum in SkRegion.h
@@ -48,6 +51,7 @@ public class Region implements Parcelable {
/**
* @hide
*/
+ @UnsupportedAppUsage
public final int nativeInt;
}
@@ -59,14 +63,14 @@ public class Region implements Parcelable {
/** Return a copy of the specified region
*/
- public Region(Region region) {
+ public Region(@NonNull Region region) {
this(nativeConstructor());
nativeSetRegion(mNativeRegion, region.mNativeRegion);
}
/** Return a region set to the specified rectangle
*/
- public Region(Rect r) {
+ public Region(@NonNull Rect r) {
mNativeRegion = nativeConstructor();
nativeSetRect(mNativeRegion, r.left, r.top, r.right, r.bottom);
}
@@ -86,14 +90,14 @@ public class Region implements Parcelable {
/** Set the region to the specified region.
*/
- public boolean set(Region region) {
+ public boolean set(@NonNull Region region) {
nativeSetRegion(mNativeRegion, region.mNativeRegion);
return true;
}
/** Set the region to the specified rectangle
*/
- public boolean set(Rect r) {
+ public boolean set(@NonNull Rect r) {
return nativeSetRect(mNativeRegion, r.left, r.top, r.right, r.bottom);
}
@@ -109,7 +113,7 @@ public class Region implements Parcelable {
* that is identical to the pixels that would be drawn by the path
* (with no antialiasing).
*/
- public boolean setPath(Path path, Region clip) {
+ public boolean setPath(@NonNull Path path, @NonNull Region clip) {
return nativeSetPath(mNativeRegion, path.readOnlyNI(), clip.mNativeRegion);
}
@@ -132,6 +136,7 @@ public class Region implements Parcelable {
* Return a new Rect set to the bounds of the region. If the region is
* empty, the Rect will be set to [0, 0, 0, 0]
*/
+ @NonNull
public Rect getBounds() {
Rect r = new Rect();
nativeGetBounds(mNativeRegion, r);
@@ -142,7 +147,7 @@ public class Region implements Parcelable {
* Set the Rect to the bounds of the region. If the region is empty, the
* Rect will be set to [0, 0, 0, 0]
*/
- public boolean getBounds(Rect r) {
+ public boolean getBounds(@NonNull Rect r) {
if (r == null) {
throw new NullPointerException();
}
@@ -153,6 +158,7 @@ public class Region implements Parcelable {
* Return the boundary of the region as a new Path. If the region is empty,
* the path will also be empty.
*/
+ @NonNull
public Path getBoundaryPath() {
Path path = new Path();
nativeGetBoundaryPath(mNativeRegion, path.mutateNI());
@@ -163,7 +169,7 @@ public class Region implements Parcelable {
* Set the path to the boundary of the region. If the region is empty, the
* path will also be empty.
*/
- public boolean getBoundaryPath(Path path) {
+ public boolean getBoundaryPath(@NonNull Path path) {
return nativeGetBoundaryPath(mNativeRegion, path.mutateNI());
}
@@ -178,7 +184,7 @@ public class Region implements Parcelable {
* that the rectangle is not contained by this region, but return true is a
* guarantee that the rectangle is contained by this region.
*/
- public boolean quickContains(Rect r) {
+ public boolean quickContains(@NonNull Rect r) {
return quickContains(r.left, r.top, r.right, r.bottom);
}
@@ -196,7 +202,7 @@ public class Region implements Parcelable {
* not intersect the region. Returning false is not a guarantee that they
* intersect, but returning true is a guarantee that they do not.
*/
- public boolean quickReject(Rect r) {
+ public boolean quickReject(@NonNull Rect r) {
return quickReject(r.left, r.top, r.right, r.bottom);
}
@@ -236,6 +242,7 @@ public class Region implements Parcelable {
*
* @hide
*/
+ @UnsupportedAppUsage
public void scale(float scale) {
scale(scale, null);
}
@@ -247,7 +254,7 @@ public class Region implements Parcelable {
*/
public native void scale(float scale, Region dst);
- public final boolean union(Rect r) {
+ public final boolean union(@NonNull Rect r) {
return op(r, Op.UNION);
}
@@ -255,7 +262,7 @@ public class Region implements Parcelable {
* Perform the specified Op on this region and the specified rect. Return
* true if the result of the op is not empty.
*/
- public boolean op(Rect r, Op op) {
+ public boolean op(@NonNull Rect r, @NonNull Op op) {
return nativeOp(mNativeRegion, r.left, r.top, r.right, r.bottom,
op.nativeInt);
}
@@ -264,7 +271,7 @@ public class Region implements Parcelable {
* Perform the specified Op on this region and the specified rect. Return
* true if the result of the op is not empty.
*/
- public boolean op(int left, int top, int right, int bottom, Op op) {
+ public boolean op(int left, int top, int right, int bottom, @NonNull Op op) {
return nativeOp(mNativeRegion, left, top, right, bottom,
op.nativeInt);
}
@@ -273,7 +280,7 @@ public class Region implements Parcelable {
* Perform the specified Op on this region and the specified region. Return
* true if the result of the op is not empty.
*/
- public boolean op(Region region, Op op) {
+ public boolean op(@NonNull Region region, @NonNull Op op) {
return op(this, region, op);
}
@@ -281,7 +288,7 @@ public class Region implements Parcelable {
* Set this region to the result of performing the Op on the specified rect
* and region. Return true if the result is not empty.
*/
- public boolean op(Rect rect, Region region, Op op) {
+ public boolean op(@NonNull Rect rect, @NonNull Region region, @NonNull Op op) {
return nativeOp(mNativeRegion, rect, region.mNativeRegion,
op.nativeInt);
}
@@ -290,11 +297,12 @@ public class Region implements Parcelable {
* Set this region to the result of performing the Op on the specified
* regions. Return true if the result is not empty.
*/
- public boolean op(Region region1, Region region2, Op op) {
+ public boolean op(@NonNull Region region1, @NonNull Region region2, @NonNull Op op) {
return nativeOp(mNativeRegion, region1.mNativeRegion,
region2.mNativeRegion, op.nativeInt);
}
+ @Override
public String toString() {
return nativeToString(mNativeRegion);
}
@@ -304,6 +312,7 @@ public class Region implements Parcelable {
*
* @hide
*/
+ @NonNull
public static Region obtain() {
Region region = sPool.acquire();
return (region != null) ? region : new Region();
@@ -316,7 +325,8 @@ public class Region implements Parcelable {
*
* @hide
*/
- public static Region obtain(Region other) {
+ @NonNull
+ public static Region obtain(@NonNull Region other) {
Region region = obtain();
region.set(other);
return region;
@@ -327,6 +337,7 @@ public class Region implements Parcelable {
*
* @hide
*/
+ @UnsupportedAppUsage
public void recycle() {
setEmpty();
sPool.release(this);
@@ -334,13 +345,14 @@ public class Region implements Parcelable {
//////////////////////////////////////////////////////////////////////////
- public static final Parcelable.Creator<Region> CREATOR
+ public static final @android.annotation.NonNull Parcelable.Creator<Region> CREATOR
= new Parcelable.Creator<Region>() {
/**
* Rebuild a Region previously stored with writeToParcel().
* @param p Parcel object to read the region from
* @return a new region created from the data in the parcel
*/
+ @Override
public Region createFromParcel(Parcel p) {
long ni = nativeCreateFromParcel(p);
if (ni == 0) {
@@ -348,11 +360,13 @@ public class Region implements Parcelable {
}
return new Region(ni);
}
+ @Override
public Region[] newArray(int size) {
return new Region[size];
}
};
+ @Override
public int describeContents() {
return 0;
}
@@ -362,6 +376,7 @@ public class Region implements Parcelable {
* rebuilt from the parcel by calling CREATOR.createFromParcel().
* @param p Parcel object to write the region data into
*/
+ @Override
public void writeToParcel(Parcel p, int flags) {
if (!nativeWriteToParcel(mNativeRegion, p)) {
throw new RuntimeException();
@@ -377,6 +392,7 @@ public class Region implements Parcelable {
return nativeEquals(mNativeRegion, peer.mNativeRegion);
}
+ @Override
protected void finalize() throws Throwable {
try {
nativeDestructor(mNativeRegion);
@@ -395,6 +411,7 @@ public class Region implements Parcelable {
/* add dummy parameter so constructor can be called from jni without
triggering 'not cloneable' exception */
+ @UnsupportedAppUsage
private Region(long ni, int dummy) {
this(ni);
}
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
new file mode 100644
index 000000000000..ae7fe6c46f2f
--- /dev/null
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -0,0 +1,1746 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import android.annotation.BytesLong;
+import android.annotation.ColorInt;
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.view.NativeVectorDrawableAnimator;
+import android.view.RenderNodeAnimator;
+import android.view.Surface;
+import android.view.View;
+
+import com.android.internal.util.ArrayUtils;
+
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * <p>RenderNode is used to build hardware accelerated rendering hierarchies. Each RenderNode
+ * contains both a display list as well as a set of properties that affect the rendering of the
+ * display list. RenderNodes are used internally for all Views by default and are not typically
+ * used directly.</p>
+ *
+ * <p>RenderNodes are used to divide up the rendering content of a complex scene into smaller
+ * pieces that can then be updated individually more cheaply. Updating part of the scene only needs
+ * to update the display list or properties of a small number of RenderNode instead of redrawing
+ * everything from scratch. A RenderNode only needs its display list re-recorded when its content
+ * alone should be changed. RenderNodes can also be transformed without re-recording the display
+ * list through the transform properties.</p>
+ *
+ * <p>A text editor might for instance store each paragraph into its own RenderNode.
+ * Thus when the user inserts or removes characters, only the display list of the
+ * affected paragraph needs to be recorded again.</p>
+ *
+ * <h3>Hardware acceleration</h3>
+ * <p>RenderNodes can be drawn using a {@link RecordingCanvas}. They are not
+ * supported in software. Always make sure that the {@link android.graphics.Canvas}
+ * you are using to render a display list is hardware accelerated using
+ * {@link android.graphics.Canvas#isHardwareAccelerated()}.</p>
+ *
+ * <h3>Creating a RenderNode</h3>
+ * <pre class="prettyprint">
+ * RenderNode renderNode = new RenderNode("myRenderNode");
+ * renderNode.setLeftTopRightBottom(0, 0, 50, 50); // Set the size to 50x50
+ * RecordingCanvas canvas = renderNode.beginRecording();
+ * try {
+ * // Draw with the canvas
+ * canvas.drawRect(...);
+ * } finally {
+ * renderNode.endRecording();
+ * }</pre>
+ *
+ * <h3>Drawing a RenderNode in a View</h3>
+ * <pre class="prettyprint">
+ * protected void onDraw(Canvas canvas) {
+ * if (canvas.isHardwareAccelerated()) {
+ * // Check that the RenderNode has a display list, re-recording it if it does not.
+ * if (!myRenderNode.hasDisplayList()) {
+ * updateDisplayList(myRenderNode);
+ * }
+ * // Draw the RenderNode into this canvas.
+ * canvas.drawRenderNode(myRenderNode);
+ * }
+ * }</pre>
+ *
+ * <h3>Releasing resources</h3>
+ * <p>This step is not mandatory but recommended if you want to release resources
+ * held by a display list as soon as possible. Most significantly any bitmaps it may contain.</p>
+ * <pre class="prettyprint">
+ * // Discards the display list content allowing for any held resources to be released.
+ * // After calling this
+ * renderNode.discardDisplayList();</pre>
+ *
+ *
+ * <h3>Properties</h3>
+ * <p>In addition, a RenderNode offers several properties, such as
+ * {@link #setScaleX(float)} or {@link #setTranslationX(float)}, that can be used to affect all
+ * the drawing commands recorded within. For instance, these properties can be used
+ * to move around a large number of images without re-issuing all the individual
+ * <code>canvas.drawBitmap()</code> calls.</p>
+ *
+ * <pre class="prettyprint">
+ * private void createDisplayList() {
+ * mRenderNode = new RenderNode("MyRenderNode");
+ * mRenderNode.setLeftTopRightBottom(0, 0, width, height);
+ * RecordingCanvas canvas = mRenderNode.beginRecording();
+ * try {
+ * for (Bitmap b : mBitmaps) {
+ * canvas.drawBitmap(b, 0.0f, 0.0f, null);
+ * canvas.translate(0.0f, b.getHeight());
+ * }
+ * } finally {
+ * mRenderNode.endRecording();
+ * }
+ * }
+ *
+ * protected void onDraw(Canvas canvas) {
+ * if (canvas.isHardwareAccelerated())
+ * canvas.drawRenderNode(mRenderNode);
+ * }
+ * }
+ *
+ * private void moveContentBy(int x) {
+ * // This will move all the bitmaps recorded inside the display list
+ * // by x pixels to the right and redraw this view. All the commands
+ * // recorded in createDisplayList() won't be re-issued, only onDraw()
+ * // will be invoked and will execute very quickly
+ * mRenderNode.offsetLeftAndRight(x);
+ * invalidate();
+ * }</pre>
+ *
+ * <p>A few of the properties may at first appear redundant, such as {@link #setElevation(float)}
+ * and {@link #setTranslationZ(float)}. The reason for these duplicates are to allow for a
+ * separation between static & transient usages. For example consider a button that raises from 2dp
+ * to 8dp when pressed. To achieve that an application may decide to setElevation(2dip), and then
+ * on press to animate setTranslationZ to 6dip. Combined this achieves the final desired 8dip
+ * value, but the animation need only concern itself with animating the lift from press without
+ * needing to know the initial starting value. {@link #setTranslationX(float)} and
+ * {@link #setTranslationY(float)} are similarly provided for animation uses despite the functional
+ * overlap with {@link #setPosition(Rect)}.
+ *
+ * <p>The RenderNode's transform matrix is computed at render time as follows:
+ * <pre class="prettyprint">
+ * Matrix transform = new Matrix();
+ * transform.setTranslate(renderNode.getTranslationX(), renderNode.getTranslationY());
+ * transform.preRotate(renderNode.getRotationZ(),
+ * renderNode.getPivotX(), renderNode.getPivotY());
+ * transform.preScale(renderNode.getScaleX(), renderNode.getScaleY(),
+ * renderNode.getPivotX(), renderNode.getPivotY());</pre>
+ * The current canvas transform matrix, which is translated to the RenderNode's position,
+ * is then multiplied by the RenderNode's transform matrix. Therefore the ordering of calling
+ * property setters does not affect the result. That is to say that:
+ *
+ * <pre class="prettyprint">
+ * renderNode.setTranslationX(100);
+ * renderNode.setScaleX(100);</pre>
+ *
+ * is equivalent to:
+ *
+ * <pre class="prettyprint">
+ * renderNode.setScaleX(100);
+ * renderNode.setTranslationX(100);</pre>
+ *
+ * <h3>Threading</h3>
+ * <p>RenderNode may be created and used on any thread but they are not thread-safe. Only
+ * a single thread may interact with a RenderNode at any given time. It is critical
+ * that the RenderNode is only used on the same thread it is drawn with. For example when using
+ * RenderNode with a custom View, then that RenderNode must only be used from the UI thread.</p>
+ *
+ * <h3>When to re-render</h3>
+ * <p>Many of the RenderNode mutation methods, such as {@link #setTranslationX(float)}, return
+ * a boolean indicating if the value actually changed or not. This is useful in detecting
+ * if a new frame should be rendered or not. A typical usage would look like:
+ * <pre class="prettyprint">
+ * public void translateTo(int x, int y) {
+ * boolean needsUpdate = myRenderNode.setTranslationX(x);
+ * needsUpdate |= myRenderNode.setTranslationY(y);
+ * if (needsUpdate) {
+ * myOwningView.invalidate();
+ * }
+ * }</pre>
+ * This is marginally faster than doing a more explicit up-front check if the value changed by
+ * comparing the desired value against {@link #getTranslationX()} as it minimizes JNI transitions.
+ * The actual mechanism of requesting a new frame to be rendered will depend on how this
+ * RenderNode is being drawn. If it's drawn to a containing View, as in the above snippet,
+ * then simply invalidating that View works. If instead the RenderNode is being drawn to a Canvas
+ * directly such as with {@link Surface#lockHardwareCanvas()} then a new frame needs to be drawn
+ * by calling {@link Surface#lockHardwareCanvas()}, re-drawing the root RenderNode or whatever
+ * top-level content is desired, and finally calling {@link Surface#unlockCanvasAndPost(Canvas)}.
+ * </p>
+ */
+public final class RenderNode {
+
+ // Use a Holder to allow static initialization in the boot image.
+ private static class NoImagePreloadHolder {
+ public static final NativeAllocationRegistry sRegistry =
+ NativeAllocationRegistry.createMalloced(
+ RenderNode.class.getClassLoader(), nGetNativeFinalizer());
+ }
+
+ /**
+ * Not for general use; use only if you are ThreadedRenderer or RecordingCanvas.
+ *
+ * @hide
+ */
+ public final long mNativeRenderNode;
+ private final AnimationHost mAnimationHost;
+ private RecordingCanvas mCurrentRecordingCanvas;
+
+ // Will be null if not currently registered
+ @Nullable
+ private CompositePositionUpdateListener mCompositePositionUpdateListener;
+
+ /**
+ * Creates a new RenderNode that can be used to record batches of
+ * drawing operations, and store / apply render properties when drawn.
+ *
+ * @param name The name of the RenderNode, used for debugging purpose. May be null.
+ */
+ public RenderNode(@Nullable String name) {
+ this(name, null);
+ }
+
+ private RenderNode(String name, AnimationHost animationHost) {
+ mNativeRenderNode = nCreate(name);
+ NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativeRenderNode);
+ mAnimationHost = animationHost;
+ }
+
+ /**
+ * @see RenderNode#adopt(long)
+ */
+ private RenderNode(long nativePtr) {
+ mNativeRenderNode = nativePtr;
+ NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativeRenderNode);
+ mAnimationHost = null;
+ }
+
+ /** @hide */
+ public static RenderNode create(String name, @Nullable AnimationHost animationHost) {
+ return new RenderNode(name, animationHost);
+ }
+
+ /**
+ * Adopts an existing native render node.
+ *
+ * Note: This will *NOT* incRef() on the native object, however it will
+ * decRef() when it is destroyed. The caller should have already incRef'd it
+ *
+ * @hide
+ */
+ public static RenderNode adopt(long nativePtr) {
+ return new RenderNode(nativePtr);
+ }
+
+ /**
+ * Listens for RenderNode position updates for synchronous window movement.
+ *
+ * This is not suitable for generic position listening, it is only designed & intended
+ * for use by things which require external position events like SurfaceView, PopupWindow, etc..
+ *
+ * @hide
+ */
+ public interface PositionUpdateListener {
+
+ /**
+ * Called by native by a Rendering Worker thread to update window position
+ *
+ * @hide
+ */
+ void positionChanged(long frameNumber, int left, int top, int right, int bottom);
+
+ /**
+ * Called by native on RenderThread to notify that the view is no longer in the
+ * draw tree. UI thread is blocked at this point.
+ *
+ * @hide
+ */
+ void positionLost(long frameNumber);
+
+ }
+
+ private static final class CompositePositionUpdateListener implements PositionUpdateListener {
+ private final PositionUpdateListener[] mListeners;
+ private static final PositionUpdateListener[] sEmpty = new PositionUpdateListener[0];
+
+ CompositePositionUpdateListener(PositionUpdateListener... listeners) {
+ mListeners = listeners != null ? listeners : sEmpty;
+ }
+
+ public CompositePositionUpdateListener with(PositionUpdateListener listener) {
+ return new CompositePositionUpdateListener(
+ ArrayUtils.appendElement(PositionUpdateListener.class, mListeners, listener));
+ }
+
+ public CompositePositionUpdateListener without(PositionUpdateListener listener) {
+ return new CompositePositionUpdateListener(
+ ArrayUtils.removeElement(PositionUpdateListener.class, mListeners, listener));
+ }
+
+ @Override
+ public void positionChanged(long frameNumber, int left, int top, int right, int bottom) {
+ for (PositionUpdateListener pul : mListeners) {
+ pul.positionChanged(frameNumber, left, top, right, bottom);
+ }
+ }
+
+ @Override
+ public void positionLost(long frameNumber) {
+ for (PositionUpdateListener pul : mListeners) {
+ pul.positionLost(frameNumber);
+ }
+ }
+ }
+
+ /**
+ * Enable callbacks for position changes. Call only from the UI thread or with
+ * external synchronization.
+ *
+ * @hide
+ */
+ public void addPositionUpdateListener(@NonNull PositionUpdateListener listener) {
+ CompositePositionUpdateListener comp = mCompositePositionUpdateListener;
+ if (comp == null) {
+ comp = new CompositePositionUpdateListener(listener);
+ } else {
+ comp = comp.with(listener);
+ }
+ mCompositePositionUpdateListener = comp;
+ nRequestPositionUpdates(mNativeRenderNode, comp);
+ }
+
+ /**
+ * Disable a callback for position changes. Call only from the UI thread or with
+ * external synchronization.
+ *
+ * @param listener Callback to remove
+ * @hide
+ */
+ public void removePositionUpdateListener(@NonNull PositionUpdateListener listener) {
+ CompositePositionUpdateListener comp = mCompositePositionUpdateListener;
+ if (comp != null) {
+ comp = comp.without(listener);
+ mCompositePositionUpdateListener = comp;
+ nRequestPositionUpdates(mNativeRenderNode, comp);
+ }
+ }
+
+ /**
+ * Starts recording a display list for the render node. All
+ * operations performed on the returned canvas are recorded and
+ * stored in this display list.
+ *
+ * {@link #endRecording()} must be called when the recording is finished in order to apply
+ * the updated display list. Failing to call {@link #endRecording()} will result in an
+ * {@link IllegalStateException} if {@link #beginRecording(int, int)} is called again.
+ *
+ * @param width The width of the recording viewport. This will not alter the width of the
+ * RenderNode itself, that must be set with {@link #setPosition(Rect)}.
+ * @param height The height of the recording viewport. This will not alter the height of the
+ * RenderNode itself, that must be set with {@link #setPosition(Rect)}.
+ * @return A canvas to record drawing operations.
+ * @throws IllegalStateException If a recording is already in progress. That is, the previous
+ * call to {@link #beginRecording(int, int)} did not call {@link #endRecording()}.
+ * @see #endRecording()
+ * @see #hasDisplayList()
+ */
+ public @NonNull RecordingCanvas beginRecording(int width, int height) {
+ if (mCurrentRecordingCanvas != null) {
+ throw new IllegalStateException(
+ "Recording currently in progress - missing #endRecording() call?");
+ }
+ mCurrentRecordingCanvas = RecordingCanvas.obtain(this, width, height);
+ return mCurrentRecordingCanvas;
+ }
+
+ /**
+ * Same as {@link #beginRecording(int, int)} with the width & height set
+ * to the RenderNode's own width & height. The RenderNode's width & height may be set
+ * with {@link #setPosition(int, int, int, int)}.
+ *
+ * @return A canvas to record drawing operations.
+ * @throws IllegalStateException If a recording is already in progress. That is, the previous
+ * call to {@link #beginRecording(int, int)} did not call {@link #endRecording()}.
+ * @see #endRecording()
+ * @see #hasDisplayList()
+ */
+ public @NonNull RecordingCanvas beginRecording() {
+ return beginRecording(nGetWidth(mNativeRenderNode), nGetHeight(mNativeRenderNode));
+ }
+
+ /**
+ * `
+ * Ends the recording for this display list. Calling this method marks
+ * the display list valid and {@link #hasDisplayList()} will return true.
+ *
+ * @see #beginRecording(int, int)
+ * @see #hasDisplayList()
+ */
+ public void endRecording() {
+ if (mCurrentRecordingCanvas == null) {
+ throw new IllegalStateException(
+ "No recording in progress, forgot to call #beginRecording()?");
+ }
+ RecordingCanvas canvas = mCurrentRecordingCanvas;
+ mCurrentRecordingCanvas = null;
+ long displayList = canvas.finishRecording();
+ nSetDisplayList(mNativeRenderNode, displayList);
+ canvas.recycle();
+ }
+
+ /**
+ * @hide
+ * @deprecated use {@link #beginRecording(int, int)} instead
+ */
+ @Deprecated
+ public RecordingCanvas start(int width, int height) {
+ return beginRecording(width, height);
+ }
+
+ /**
+ * @hide
+ * @deprecated use {@link #endRecording()} instead
+ */
+ @Deprecated
+ public void end(RecordingCanvas canvas) {
+ if (canvas != mCurrentRecordingCanvas) {
+ throw new IllegalArgumentException("Wrong canvas");
+ }
+ endRecording();
+ }
+
+ /**
+ * Reset native resources. This is called when cleaning up the state of display lists
+ * during destruction of hardware resources, to ensure that we do not hold onto
+ * obsolete resources after related resources are gone.
+ */
+ public void discardDisplayList() {
+ nSetDisplayList(mNativeRenderNode, 0);
+ }
+
+ /**
+ * Returns whether the RenderNode has a display list. If this returns false, the RenderNode
+ * should be re-recorded with {@link #beginRecording()} and {@link #endRecording()}.
+ *
+ * A RenderNode without a display list may still be drawn, however it will have no impact
+ * on the rendering content until its display list is updated.
+ *
+ * When a RenderNode is no longer drawn by anything the system may automatically
+ * invoke {@link #discardDisplayList()}. It is therefore important to ensure that
+ * {@link #hasDisplayList()} is true on a RenderNode prior to drawing it.
+ *
+ * See {@link #discardDisplayList()}
+ *
+ * @return boolean true if this RenderNode has a display list, false otherwise.
+ */
+ public boolean hasDisplayList() {
+ return nIsValid(mNativeRenderNode);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Matrix manipulation
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Whether or not the RenderNode has an identity transform. This is a faster
+ * way to do the otherwise equivalent {@link #getMatrix(Matrix)} {@link Matrix#isIdentity()}
+ * as it doesn't require copying the Matrix first, thus minimizing overhead.
+ *
+ * @return true if the RenderNode has an identity transform, false otherwise
+ */
+ public boolean hasIdentityMatrix() {
+ return nHasIdentityMatrix(mNativeRenderNode);
+ }
+
+ /**
+ * Gets the current transform matrix
+ *
+ * @param outMatrix The matrix to store the transform of the RenderNode
+ */
+ public void getMatrix(@NonNull Matrix outMatrix) {
+ nGetTransformMatrix(mNativeRenderNode, outMatrix.native_instance);
+ }
+
+ /**
+ * Gets the current transform inverted. This is a faster way to do the otherwise
+ * equivalent {@link #getMatrix(Matrix)} followed by {@link Matrix#invert(Matrix)}
+ *
+ * @param outMatrix The matrix to store the inverse transform of the RenderNode
+ */
+ public void getInverseMatrix(@NonNull Matrix outMatrix) {
+ nGetInverseTransformMatrix(mNativeRenderNode, outMatrix.native_instance);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // RenderProperty Setters
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * @hide
+ * @deprecated use {@link #setUseCompositingLayer(boolean, Paint)} instead
+ */
+ @Deprecated
+ public boolean setLayerType(int layerType) {
+ return nSetLayerType(mNativeRenderNode, layerType);
+ }
+
+ /**
+ * @hide
+ * @deprecated use {@link #setUseCompositingLayer(boolean, Paint)} instead
+ */
+ @Deprecated
+ public boolean setLayerPaint(@Nullable Paint paint) {
+ return nSetLayerPaint(mNativeRenderNode, paint != null ? paint.getNativeInstance() : 0);
+ }
+
+ /**
+ * Controls whether or not to force this RenderNode to render to an intermediate buffer.
+ * Internally RenderNode will already promote itself to a composition layer if it's useful
+ * for performance or required for the current combination of {@link #setAlpha(float)} and
+ * {@link #setHasOverlappingRendering(boolean)}.
+ *
+ * <p>The usage of this is instead to allow for either overriding of the internal behavior
+ * if it's measured to be necessary for the particular rendering content in question or, more
+ * usefully, to add a composition effect to the RenderNode via the optional paint parameter.
+ *
+ * <p>Note: When a RenderNode is using a compositing layer it will also result in
+ * clipToBounds=true behavior.
+ *
+ * @param forceToLayer if true this forces the RenderNode to use an intermediate buffer.
+ * Default & generally recommended value is false.
+ * @param paint The blend mode, alpha, and ColorFilter to apply to the compositing layer.
+ * Only applies if forceToLayer is true. The paint's alpha is multiplied
+ * with {@link #getAlpha()} to resolve the final alpha of the RenderNode.
+ * If null then no additional composition effects are applied on top of the
+ * composition layer.
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ */
+ public boolean setUseCompositingLayer(boolean forceToLayer, @Nullable Paint paint) {
+ boolean didChange = nSetLayerType(mNativeRenderNode, forceToLayer ? 2 : 0);
+ didChange |= nSetLayerPaint(mNativeRenderNode,
+ paint != null ? paint.getNativeInstance() : 0);
+ return didChange;
+ }
+
+ /**
+ * Gets whether or not a compositing layer is forced to be used. The default & recommended
+ * is false, as it is typically faster to avoid using compositing layers.
+ * See {@link #setUseCompositingLayer(boolean, Paint)}.
+ *
+ * @return true if a compositing layer is forced, false otherwise
+ */
+ public boolean getUseCompositingLayer() {
+ return nGetLayerType(mNativeRenderNode) != 0;
+ }
+
+ /**
+ * Sets an additional clip on the RenderNode. If null, the extra clip is removed from the
+ * RenderNode. If non-null, the RenderNode will be clipped to this rect. In addition if
+ * {@link #setClipToBounds(boolean)} is true, then the RenderNode will be clipped to the
+ * intersection of this rectangle and the bounds of the render node, which is set with
+ * {@link #setPosition(Rect)}.
+ *
+ * <p>This is equivalent to do a {@link Canvas#clipRect(Rect)} at the start of this
+ * RenderNode's display list. However, as this is a property of the RenderNode instead
+ * of part of the display list it can be more easily animated for transient additional
+ * clipping. An example usage of this would be the {@link android.transition.ChangeBounds}
+ * transition animation with the resizeClip=true option.
+ *
+ * @param rect the bounds to clip to. If null, the additional clip is removed.
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ */
+ public boolean setClipRect(@Nullable Rect rect) {
+ if (rect == null) {
+ return nSetClipBoundsEmpty(mNativeRenderNode);
+ } else {
+ return nSetClipBounds(mNativeRenderNode, rect.left, rect.top, rect.right, rect.bottom);
+ }
+ }
+
+ /**
+ * Set whether the Render node should clip itself to its bounds. This defaults to true,
+ * and is useful to the renderer in enable quick-rejection of chunks of the tree as well as
+ * better partial invalidation support. Clipping can be further restricted or controlled
+ * through the combination of this property as well as {@link #setClipRect(Rect)}, which
+ * allows for a different clipping rectangle to be used in addition to or instead of the
+ * {@link #setPosition(int, int, int, int)} or the RenderNode.
+ *
+ * @param clipToBounds true if the display list should clip to its bounds, false otherwise.
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ */
+ public boolean setClipToBounds(boolean clipToBounds) {
+ return nSetClipToBounds(mNativeRenderNode, clipToBounds);
+ }
+
+ /**
+ * Returns whether or not the RenderNode is clipping to its bounds. See
+ * {@link #setClipToBounds(boolean)} and {@link #setPosition(int, int, int, int)}
+ *
+ * @return true if the render node clips to its bounds, false otherwise.
+ */
+ public boolean getClipToBounds() {
+ return nGetClipToBounds(mNativeRenderNode);
+ }
+
+ /**
+ * <p>Sets whether the RenderNode should be drawn immediately after the
+ * closest ancestor RenderNode containing a projection receiver.
+ *
+ * <p>The default is false, and the rendering of this node happens in the typical draw order.
+ *
+ * <p>If true, then at rendering time this rendernode will not be drawn in order with the
+ * {@link Canvas#drawRenderNode(RenderNode)} command that drew this RenderNode, but instead
+ * it will be re-positioned in the RenderNode tree to be drawn on the closet ancestor with a
+ * child rendernode that has {@link #setProjectionReceiver(boolean)} as true.
+ *
+ * <p>The typical usage of this is to allow a child RenderNode to draw on a parent's background,
+ * such as the platform's usage with {@link android.graphics.drawable.RippleDrawable}. Consider
+ * the following structure, built out of which RenderNode called drawRenderNode on a different
+ * RenderNode:
+ *
+ * <pre>
+ * +-------------+
+ * |RenderNode: P|
+ * +-+----------++
+ * | |
+ * v v
+ * +-------+-----+ +-+--------------+
+ * |RenderNode: C| |RenderNode: P'BG|
+ * +-------+-----+ +----------------+
+ * |
+ * |
+ * +--------+-------+
+ * |RenderNode: C'BG|
+ * +----------------+
+ * </pre>
+ *
+ * If P'BG is a projection receiver, and C'BG is set to project backwards then C'BG will
+ * behave as if it was drawn directly by P'BG instead of by C. This includes inheriting P'BG's
+ * clip instead of C's clip.
+ *
+ * @param shouldProject true if the display list should be projected onto a
+ * containing volume. Default is false.
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ */
+ public boolean setProjectBackwards(boolean shouldProject) {
+ return nSetProjectBackwards(mNativeRenderNode, shouldProject);
+ }
+
+ /**
+ * Sets whether the RenderNode is a projection receiver. If true then this RenderNode's parent
+ * should draw any descendant RenderNodes with ProjectBackwards=true directly on top of it.
+ * Default value is false. See
+ * {@link #setProjectBackwards(boolean)} for a description of what this entails.
+ *
+ * @param shouldRecieve True if this RenderNode is a projection receiver, false otherwise.
+ * Default is false.
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ */
+ public boolean setProjectionReceiver(boolean shouldRecieve) {
+ return nSetProjectionReceiver(mNativeRenderNode, shouldRecieve);
+ }
+
+ /**
+ * Sets the outline, defining the shape that casts a shadow, and the path to
+ * be clipped if setClipToOutline is set.
+ *
+ * This will make a copy of the provided {@link Outline}, so any future modifications
+ * to the outline will need to call {@link #setOutline(Outline)} with the modified
+ * outline for those changes to be applied.
+ *
+ * @param outline The outline to use for this RenderNode.
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ */
+ public boolean setOutline(@Nullable Outline outline) {
+ if (outline == null) {
+ return nSetOutlineNone(mNativeRenderNode);
+ }
+
+ switch (outline.mMode) {
+ case Outline.MODE_EMPTY:
+ return nSetOutlineEmpty(mNativeRenderNode);
+ case Outline.MODE_ROUND_RECT:
+ return nSetOutlineRoundRect(mNativeRenderNode,
+ 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,
+ outline.mAlpha);
+ }
+
+ throw new IllegalArgumentException("Unrecognized outline?");
+ }
+
+ /**
+ * Checks if the RenderNode has a shadow. That is, if the combination of {@link #getElevation()}
+ * and {@link #getTranslationZ()} is greater than zero, there is an {@link Outline} set with
+ * a valid shadow caster path, and the provided outline has a non-zero
+ * {@link Outline#getAlpha()}.
+ *
+ * @return True if this RenderNode has a shadow, false otherwise
+ */
+ public boolean hasShadow() {
+ return nHasShadow(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the color of the spot shadow that is drawn when the RenderNode has a positive Z or
+ * elevation value and is drawn inside of a {@link Canvas#enableZ()} section.
+ * <p>
+ * By default the shadow color is black. Generally, this color will be opaque so the intensity
+ * of the shadow is consistent between different RenderNodes with different colors.
+ * <p>
+ * The opacity of the final spot shadow is a function of the shadow caster height, the
+ * alpha channel of the outlineSpotShadowColor (typically opaque), and the
+ * {@link android.R.attr#spotShadowAlpha} theme attribute
+ *
+ * @param color The color this RenderNode will cast for its elevation spot shadow.
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ */
+ public boolean setSpotShadowColor(@ColorInt int color) {
+ return nSetSpotShadowColor(mNativeRenderNode, color);
+ }
+
+ /**
+ * @return The shadow color set by {@link #setSpotShadowColor(int)}, or black if nothing
+ * was set
+ */
+ public @ColorInt int getSpotShadowColor() {
+ return nGetSpotShadowColor(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the color of the ambient shadow that is drawn when the RenderNode has a positive Z or
+ * elevation value and is drawn inside of a {@link Canvas#enableZ()} section.
+ * <p>
+ * By default the shadow color is black. Generally, this color will be opaque so the intensity
+ * of the shadow is consistent between different RenderNodes with different colors.
+ * <p>
+ * The opacity of the final ambient shadow is a function of the shadow caster height, the
+ * alpha channel of the outlineAmbientShadowColor (typically opaque), and the
+ * {@link android.R.attr#ambientShadowAlpha} theme attribute.
+ *
+ * @param color The color this RenderNode will cast for its elevation shadow.
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ */
+ public boolean setAmbientShadowColor(@ColorInt int color) {
+ return nSetAmbientShadowColor(mNativeRenderNode, color);
+ }
+
+ /**
+ * @return The shadow color set by {@link #setAmbientShadowColor(int)}, or black if
+ * nothing was set
+ */
+ public @ColorInt int getAmbientShadowColor() {
+ return nGetAmbientShadowColor(mNativeRenderNode);
+ }
+
+ /**
+ * Enables or disables clipping to the outline.
+ *
+ * @param clipToOutline true if clipping to the outline.
+ * @return True if the clipToOutline value changed, false if previous value matched the new
+ * value.
+ */
+ public boolean setClipToOutline(boolean clipToOutline) {
+ return nSetClipToOutline(mNativeRenderNode, clipToOutline);
+ }
+
+ /**
+ * See {@link #setClipToOutline(boolean)}
+ *
+ * @return True if this RenderNode clips to its outline, false otherwise
+ */
+ public boolean getClipToOutline() {
+ return nGetClipToOutline(mNativeRenderNode);
+ }
+
+ /**
+ * Controls the RenderNode's circular reveal clip.
+ *
+ * @hide
+ */
+ public boolean setRevealClip(boolean shouldClip,
+ float x, float y, float radius) {
+ return nSetRevealClip(mNativeRenderNode, shouldClip, x, y, radius);
+ }
+
+ /**
+ * Set the static matrix on the display list. The specified matrix is combined with other
+ * transforms (such as {@link #setScaleX(float)}, {@link #setRotationZ(float)}, etc.)
+ *
+ * @param matrix A transform matrix to apply to this display list
+ * @hide TODO Do we want this?
+ */
+ public boolean setStaticMatrix(Matrix matrix) {
+ return nSetStaticMatrix(mNativeRenderNode, matrix.native_instance);
+ }
+
+ /**
+ * Set the Animation matrix on the display list. This matrix exists if an Animation is
+ * currently playing on a View, and is set on the display list during at draw() time. When
+ * the Animation finishes, the matrix should be cleared by sending <code>null</code>
+ * for the matrix parameter.
+ *
+ * @param matrix The matrix, null indicates that the matrix should be cleared.
+ * @see #getAnimationMatrix()
+ *
+ * @hide TODO Do we want this?
+ */
+ public boolean setAnimationMatrix(@Nullable Matrix matrix) {
+ return nSetAnimationMatrix(mNativeRenderNode,
+ (matrix != null) ? matrix.native_instance : 0);
+ }
+
+ /**
+ * Returns the previously set Animation matrix. This matrix exists if an Animation is
+ * currently playing on a View, and is set on the display list during at draw() time.
+ * Returns <code>null</code> when there is no transformation provided by
+ * {@link #setAnimationMatrix(Matrix)}.
+ *
+ * @return the current Animation matrix.
+ * @see #setAnimationMatrix(Matrix)
+ *
+ * @hide
+ */
+ @Nullable
+ public Matrix getAnimationMatrix() {
+ Matrix output = new Matrix();
+ if (nGetAnimationMatrix(mNativeRenderNode, output.native_instance)) {
+ return output;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Sets the translucency level for the display list.
+ *
+ * @param alpha The translucency of the display list, must be a value between 0.0f and 1.0f
+ * @see View#setAlpha(float)
+ * @see #getAlpha()
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ */
+ public boolean setAlpha(float alpha) {
+ return nSetAlpha(mNativeRenderNode, alpha);
+ }
+
+ /**
+ * Returns the translucency level of this display list.
+ *
+ * @return A value between 0.0f and 1.0f
+ * @see #setAlpha(float)
+ */
+ public float getAlpha() {
+ return nGetAlpha(mNativeRenderNode);
+ }
+
+ /**
+ * Sets whether the display list renders content which overlaps. Non-overlapping rendering
+ * can use a fast path for alpha that avoids rendering to an offscreen buffer. By default
+ * display lists consider they do not have overlapping content.
+ *
+ * @param hasOverlappingRendering False if the content is guaranteed to be non-overlapping,
+ * true otherwise.
+ * @see android.view.View#hasOverlappingRendering()
+ * @see #hasOverlappingRendering()
+ */
+ public boolean setHasOverlappingRendering(boolean hasOverlappingRendering) {
+ return nSetHasOverlappingRendering(mNativeRenderNode, hasOverlappingRendering);
+ }
+
+ /** @hide */
+ @IntDef({USAGE_BACKGROUND})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UsageHint {
+ }
+
+ /**
+ * The default usage hint
+ *
+ * @hide
+ */
+ public static final int USAGE_UNKNOWN = 0;
+
+ /**
+ * Usage is background content
+ *
+ * @hide
+ */
+ public static final int USAGE_BACKGROUND = 1;
+
+ /**
+ * Provides a hint on what this RenderNode's display list content contains. This hint is used
+ * for automatic content transforms to improve accessibility or similar.
+ *
+ * @hide
+ */
+ public void setUsageHint(@UsageHint int usageHint) {
+ nSetUsageHint(mNativeRenderNode, usageHint);
+ }
+
+ /**
+ * Indicates whether the content of this display list overlaps.
+ *
+ * @return True if this display list renders content which overlaps, false otherwise.
+ * @see #setHasOverlappingRendering(boolean)
+ */
+ public boolean hasOverlappingRendering() {
+ return nHasOverlappingRendering(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the base elevation of this RenderNode in pixels
+ *
+ * @param lift the elevation in pixels
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ */
+ public boolean setElevation(float lift) {
+ return nSetElevation(mNativeRenderNode, lift);
+ }
+
+ /**
+ * See {@link #setElevation(float)}
+ *
+ * @return The RenderNode's current elevation
+ */
+ public float getElevation() {
+ return nGetElevation(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the translation value for the display list on the X axis.
+ *
+ * @param translationX The X axis translation value of the display list, in pixels
+ * @see View#setTranslationX(float)
+ * @see #getTranslationX()
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ */
+ public boolean setTranslationX(float translationX) {
+ return nSetTranslationX(mNativeRenderNode, translationX);
+ }
+
+ /**
+ * Returns the translation value for this display list on the X axis, in pixels.
+ *
+ * @see #setTranslationX(float)
+ */
+ public float getTranslationX() {
+ return nGetTranslationX(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the translation value for the display list on the Y axis.
+ *
+ * @param translationY The Y axis translation value of the display list, in pixels
+ * @see View#setTranslationY(float)
+ * @see #getTranslationY()
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ */
+ public boolean setTranslationY(float translationY) {
+ return nSetTranslationY(mNativeRenderNode, translationY);
+ }
+
+ /**
+ * Returns the translation value for this display list on the Y axis, in pixels.
+ *
+ * @see #setTranslationY(float)
+ */
+ public float getTranslationY() {
+ return nGetTranslationY(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the translation value for the display list on the Z axis.
+ *
+ * @see View#setTranslationZ(float)
+ * @see #getTranslationZ()
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ */
+ public boolean setTranslationZ(float translationZ) {
+ return nSetTranslationZ(mNativeRenderNode, translationZ);
+ }
+
+ /**
+ * Returns the translation value for this display list on the Z axis.
+ *
+ * @see #setTranslationZ(float)
+ */
+ public float getTranslationZ() {
+ return nGetTranslationZ(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the rotation value for the display list around the Z axis.
+ *
+ * @param rotation The rotation value of the display list, in degrees
+ * @see View#setRotationZ(float)
+ * @see #getRotationZ()
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ */
+ public boolean setRotationZ(float rotation) {
+ return nSetRotation(mNativeRenderNode, rotation);
+ }
+
+ /**
+ * Returns the rotation value for this display list around the Z axis, in degrees.
+ *
+ * @see #setRotationZ(float)
+ */
+ public float getRotationZ() {
+ return nGetRotation(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the rotation value for the display list around the X axis.
+ *
+ * @param rotationX The rotation value of the display list, in degrees
+ * @see View#setRotationX(float)
+ * @see #getRotationX()
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ */
+ public boolean setRotationX(float rotationX) {
+ return nSetRotationX(mNativeRenderNode, rotationX);
+ }
+
+ /**
+ * Returns the rotation value for this display list around the X axis, in degrees.
+ *
+ * @see #setRotationX(float)
+ */
+ public float getRotationX() {
+ return nGetRotationX(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the rotation value for the display list around the Y axis.
+ *
+ * @param rotationY The rotation value of the display list, in degrees
+ * @see View#setRotationY(float)
+ * @see #getRotationY()
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ */
+ public boolean setRotationY(float rotationY) {
+ return nSetRotationY(mNativeRenderNode, rotationY);
+ }
+
+ /**
+ * Returns the rotation value for this display list around the Y axis, in degrees.
+ *
+ * @see #setRotationY(float)
+ */
+ public float getRotationY() {
+ return nGetRotationY(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the scale value for the display list on the X axis.
+ *
+ * @param scaleX The scale value of the display list
+ * @see View#setScaleX(float)
+ * @see #getScaleX()
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ */
+ public boolean setScaleX(float scaleX) {
+ return nSetScaleX(mNativeRenderNode, scaleX);
+ }
+
+ /**
+ * Returns the scale value for this display list on the X axis.
+ *
+ * @see #setScaleX(float)
+ */
+ public float getScaleX() {
+ return nGetScaleX(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the scale value for the display list on the Y axis.
+ *
+ * @param scaleY The scale value of the display list
+ * @see View#setScaleY(float)
+ * @see #getScaleY()
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ */
+ public boolean setScaleY(float scaleY) {
+ return nSetScaleY(mNativeRenderNode, scaleY);
+ }
+
+ /**
+ * Returns the scale value for this display list on the Y axis.
+ *
+ * @see #setScaleY(float)
+ */
+ public float getScaleY() {
+ return nGetScaleY(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the pivot value for the display list on the X axis
+ *
+ * @param pivotX The pivot value of the display list on the X axis, in pixels
+ * @see View#setPivotX(float)
+ * @see #getPivotX()
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ */
+ public boolean setPivotX(float pivotX) {
+ return nSetPivotX(mNativeRenderNode, pivotX);
+ }
+
+ /**
+ * Returns the pivot value for this display list on the X axis, in pixels.
+ *
+ * @see #setPivotX(float)
+ */
+ public float getPivotX() {
+ return nGetPivotX(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the pivot value for the display list on the Y axis
+ *
+ * @param pivotY The pivot value of the display list on the Y axis, in pixels
+ * @see View#setPivotY(float)
+ * @see #getPivotY()
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ */
+ public boolean setPivotY(float pivotY) {
+ return nSetPivotY(mNativeRenderNode, pivotY);
+ }
+
+ /**
+ * Returns the pivot value for this display list on the Y axis, in pixels.
+ *
+ * @see #setPivotY(float)
+ */
+ public float getPivotY() {
+ return nGetPivotY(mNativeRenderNode);
+ }
+
+ /**
+ * @return Whether or not a pivot was explicitly set with {@link #setPivotX(float)} or
+ * {@link #setPivotY(float)}. If no pivot has been set then the pivot will be the center
+ * of the RenderNode.
+ */
+ public boolean isPivotExplicitlySet() {
+ return nIsPivotExplicitlySet(mNativeRenderNode);
+ }
+
+ /**
+ * Clears any pivot previously set by a call to {@link #setPivotX(float)} or
+ * {@link #setPivotY(float)}. After calling this {@link #isPivotExplicitlySet()} will be false
+ * and the pivot used for rotation will return to default of being centered on the view.
+ *
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ */
+ public boolean resetPivot() {
+ return nResetPivot(mNativeRenderNode);
+ }
+
+ /**
+ * <p>Sets the distance along the Z axis (orthogonal to the X/Y plane on which
+ * RenderNodes are drawn) from the camera to this RenderNode. The camera's distance
+ * affects 3D transformations, for instance rotations around the X and Y
+ * axis. If the rotationX or rotationY properties are changed and this view is
+ * large (more than half the size of the screen), it is recommended to always
+ * use a camera distance that's greater than the height (X axis rotation) or
+ * the width (Y axis rotation) of this view.</p>
+ *
+ * <p>The distance of the camera from the drawing plane can have an affect on the
+ * perspective distortion of the RenderNode when it is rotated around the x or y axis.
+ * For example, a large distance will result in a large viewing angle, and there
+ * will not be much perspective distortion of the view as it rotates. A short
+ * distance may cause much more perspective distortion upon rotation, and can
+ * also result in some drawing artifacts if the rotated view ends up partially
+ * behind the camera (which is why the recommendation is to use a distance at
+ * least as far as the size of the view, if the view is to be rotated.)</p>
+ *
+ * <p>The distance is expressed in pixels and must always be positive</p>
+ *
+ * @param distance The distance in pixels, must always be positive
+ * @see #setRotationX(float)
+ * @see #setRotationY(float)
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ */
+ public boolean setCameraDistance(
+ @FloatRange(from = 0.0f, to = Float.MAX_VALUE) float distance) {
+ if (!Float.isFinite(distance) || distance < 0.0f) {
+ throw new IllegalArgumentException("distance must be finite & positive, given="
+ + distance);
+ }
+ // Native actually wants this to be negative not positive, so we flip it.
+ return nSetCameraDistance(mNativeRenderNode, -distance);
+ }
+
+ /**
+ * Returns the distance in Z of the camera for this RenderNode
+ *
+ * @return the distance along the Z axis in pixels.
+ * @see #setCameraDistance(float)
+ */
+ public @FloatRange(from = 0.0f, to = Float.MAX_VALUE) float getCameraDistance() {
+ return -nGetCameraDistance(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the left position for the RenderNode.
+ *
+ * @param left The left position, in pixels, of the RenderNode
+ * @return true if the value changed, false otherwise
+ * @hide
+ */
+ public boolean setLeft(int left) {
+ return nSetLeft(mNativeRenderNode, left);
+ }
+
+ /**
+ * Sets the top position for the RenderNode.
+ *
+ * @param top The top position, in pixels, of the RenderNode
+ * @return true if the value changed, false otherwise.
+ * @hide
+ */
+ public boolean setTop(int top) {
+ return nSetTop(mNativeRenderNode, top);
+ }
+
+ /**
+ * Sets the right position for the RenderNode.
+ *
+ * @param right The right position, in pixels, of the RenderNode
+ * @return true if the value changed, false otherwise.
+ * @hide
+ */
+ public boolean setRight(int right) {
+ return nSetRight(mNativeRenderNode, right);
+ }
+
+ /**
+ * Sets the bottom position for the RenderNode.
+ *
+ * @param bottom The bottom position, in pixels, of the RenderNode
+ * @return true if the value changed, false otherwise.
+ * @hide
+ */
+ public boolean setBottom(int bottom) {
+ return nSetBottom(mNativeRenderNode, bottom);
+ }
+
+ /**
+ * Gets the left position for the RenderNode.
+ *
+ * @return the left position in pixels
+ */
+ public int getLeft() {
+ return nGetLeft(mNativeRenderNode);
+ }
+
+ /**
+ * Gets the top position for the RenderNode.
+ *
+ * @return the top position in pixels
+ */
+ public int getTop() {
+ return nGetTop(mNativeRenderNode);
+ }
+
+ /**
+ * Gets the right position for the RenderNode.
+ *
+ * @return the right position in pixels
+ */
+ public int getRight() {
+ return nGetRight(mNativeRenderNode);
+ }
+
+ /**
+ * Gets the bottom position for the RenderNode.
+ *
+ * @return the bottom position in pixels
+ */
+ public int getBottom() {
+ return nGetBottom(mNativeRenderNode);
+ }
+
+ /**
+ * Gets the width of the RenderNode, which is the right - left.
+ *
+ * @return the width of the RenderNode
+ */
+ public int getWidth() {
+ return nGetWidth(mNativeRenderNode);
+ }
+
+ /**
+ * Gets the height of the RenderNode, which is the bottom - top.
+ *
+ * @return the height of the RenderNode
+ */
+ public int getHeight() {
+ return nGetHeight(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the left, top, right, and bottom of the RenderNode.
+ *
+ * @param left The left position of the RenderNode, in pixels
+ * @param top The top position of the RenderNode, in pixels
+ * @param right The right position of the RenderNode, in pixels
+ * @param bottom The bottom position of the RenderNode, in pixels
+ * @return true if any values changed, false otherwise.
+ * @hide
+ */
+ public boolean setLeftTopRightBottom(int left, int top, int right, int bottom) {
+ return nSetLeftTopRightBottom(mNativeRenderNode, left, top, right, bottom);
+ }
+
+ /**
+ * Sets the position of the RenderNode.
+ *
+ * @param left The left position of the RenderNode, in pixels
+ * @param top The top position of the RenderNode, in pixels
+ * @param right The right position of the RenderNode, in pixels
+ * @param bottom The bottom position of the RenderNode, in pixels
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ */
+ public boolean setPosition(int left, int top, int right, int bottom) {
+ return nSetLeftTopRightBottom(mNativeRenderNode, left, top, right, bottom);
+ }
+
+ /**
+ * Sets the position of the RenderNode.
+ *
+ * @param position The position rectangle in pixels
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ */
+ public boolean setPosition(@NonNull Rect position) {
+ return nSetLeftTopRightBottom(mNativeRenderNode,
+ position.left, position.top, position.right, position.bottom);
+ }
+
+ /**
+ * Offsets the left and right positions for the RenderNode
+ *
+ * @param offset The amount that the left and right positions are offset in pixels
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ */
+ public boolean offsetLeftAndRight(int offset) {
+ return nOffsetLeftAndRight(mNativeRenderNode, offset);
+ }
+
+ /**
+ * Offsets the top and bottom values for the RenderNode
+ *
+ * @param offset The amount that the left and right positions are offset in pixels
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ */
+ public boolean offsetTopAndBottom(int offset) {
+ return nOffsetTopAndBottom(mNativeRenderNode, offset);
+ }
+
+ /**
+ * Outputs the RenderNode to the log. This method exists for use by
+ * tools to output display lists for selected nodes to the log.
+ *
+ * @hide TODO: Expose? Should the shape of this be different than forced dump to logcat?
+ */
+ public void output() {
+ nOutput(mNativeRenderNode);
+ }
+
+ /**
+ * Gets the approximate memory usage of the RenderNode for debug purposes. Does not include
+ * the memory usage of any child RenderNodes nor any bitmaps, only the memory usage of
+ * this RenderNode and any data it owns.
+ *
+ * @return Approximate memory usage in bytes.
+ */
+ public @BytesLong long computeApproximateMemoryUsage() {
+ return nGetDebugSize(mNativeRenderNode);
+ }
+
+ /**
+ * Sets whether or not to allow force dark to apply to this RenderNode.
+ *
+ * Setting this to false will disable the auto-dark feature on everything this RenderNode
+ * draws, including any descendants.
+ *
+ * Setting this to true will allow this RenderNode to be automatically made dark, however
+ * a value of 'true' will not override any 'false' value in its parent chain nor will
+ * it prevent any 'false' in any of its children.
+ *
+ * @param allow Whether or not to allow force dark.
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ */
+ public boolean setForceDarkAllowed(boolean allow) {
+ return nSetAllowForceDark(mNativeRenderNode, allow);
+ }
+
+ /**
+ * See {@link #setForceDarkAllowed(boolean)}
+ *
+ * @return true if force dark is allowed (default), false if it is disabled
+ */
+ public boolean isForceDarkAllowed() {
+ return nGetAllowForceDark(mNativeRenderNode);
+ }
+
+ /**
+ * Returns the unique ID that identifies this RenderNode. This ID is unique for the
+ * lifetime of the process. IDs are reset on process death, and are unique only within
+ * the process.
+ *
+ * This ID is intended to be used with debugging tools to associate a particular
+ * RenderNode across different debug dumping & inspection tools. For example
+ * a View layout inspector should include the unique ID for any RenderNodes that it owns
+ * to associate the drawing content with the layout content.
+ *
+ * @return the unique ID for this RenderNode
+ */
+ public long getUniqueId() {
+ return nGetUniqueId(mNativeRenderNode);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Animations
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * TODO: Figure out if this can be eliminated/refactored away
+ *
+ * For now this interface exists to de-couple RenderNode from anything View-specific in a
+ * bit of a kludge.
+ *
+ * @hide
+ */
+ public interface AnimationHost {
+ /** @hide */
+ void registerAnimatingRenderNode(RenderNode animator);
+
+ /** @hide */
+ void registerVectorDrawableAnimator(NativeVectorDrawableAnimator animator);
+
+ /** @hide */
+ boolean isAttached();
+ }
+
+ /** @hide */
+ public void addAnimator(RenderNodeAnimator animator) {
+ if (!isAttached()) {
+ throw new IllegalStateException("Cannot start this animator on a detached view!");
+ }
+ nAddAnimator(mNativeRenderNode, animator.getNativeAnimator());
+ mAnimationHost.registerAnimatingRenderNode(this);
+ }
+
+ /** @hide */
+ public boolean isAttached() {
+ return mAnimationHost != null && mAnimationHost.isAttached();
+ }
+
+ /** @hide */
+ public void registerVectorDrawableAnimator(NativeVectorDrawableAnimator animatorSet) {
+ if (!isAttached()) {
+ throw new IllegalStateException("Cannot start this animator on a detached view!");
+ }
+ mAnimationHost.registerVectorDrawableAnimator(animatorSet);
+ }
+
+ /** @hide */
+ public void endAllAnimators() {
+ nEndAllAnimators(mNativeRenderNode);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Regular JNI methods
+ ///////////////////////////////////////////////////////////////////////////
+
+ private static native long nCreate(String name);
+
+ private static native long nGetNativeFinalizer();
+
+ private static native void nOutput(long renderNode);
+
+ private static native int nGetDebugSize(long renderNode);
+
+ private static native void nRequestPositionUpdates(long renderNode,
+ PositionUpdateListener callback);
+
+ // Animations
+
+ private static native void nAddAnimator(long renderNode, long animatorPtr);
+
+ private static native void nEndAllAnimators(long renderNode);
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // @FastNative methods
+ ///////////////////////////////////////////////////////////////////////////
+
+ @FastNative
+ private static native void nSetDisplayList(long renderNode, long newData);
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // @CriticalNative methods
+ ///////////////////////////////////////////////////////////////////////////
+
+ @CriticalNative
+ private static native boolean nIsValid(long renderNode);
+
+ // Matrix
+
+ @CriticalNative
+ private static native void nGetTransformMatrix(long renderNode, long nativeMatrix);
+
+ @CriticalNative
+ private static native void nGetInverseTransformMatrix(long renderNode, long nativeMatrix);
+
+ @CriticalNative
+ private static native boolean nHasIdentityMatrix(long renderNode);
+
+ // Properties
+
+ @CriticalNative
+ private static native boolean nOffsetTopAndBottom(long renderNode, int offset);
+
+ @CriticalNative
+ private static native boolean nOffsetLeftAndRight(long renderNode, int offset);
+
+ @CriticalNative
+ private static native boolean nSetLeftTopRightBottom(long renderNode, int left, int top,
+ int right, int bottom);
+
+ @CriticalNative
+ private static native boolean nSetLeft(long renderNode, int left);
+
+ @CriticalNative
+ private static native boolean nSetTop(long renderNode, int top);
+
+ @CriticalNative
+ private static native boolean nSetRight(long renderNode, int right);
+
+ @CriticalNative
+ private static native boolean nSetBottom(long renderNode, int bottom);
+
+ @CriticalNative
+ private static native int nGetLeft(long renderNode);
+
+ @CriticalNative
+ private static native int nGetTop(long renderNode);
+
+ @CriticalNative
+ private static native int nGetRight(long renderNode);
+
+ @CriticalNative
+ private static native int nGetBottom(long renderNode);
+
+ @CriticalNative
+ private static native boolean nSetCameraDistance(long renderNode, float distance);
+
+ @CriticalNative
+ private static native boolean nSetPivotY(long renderNode, float pivotY);
+
+ @CriticalNative
+ private static native boolean nSetPivotX(long renderNode, float pivotX);
+
+ @CriticalNative
+ private static native boolean nResetPivot(long renderNode);
+
+ @CriticalNative
+ private static native boolean nSetLayerType(long renderNode, int layerType);
+
+ @CriticalNative
+ private static native int nGetLayerType(long renderNode);
+
+ @CriticalNative
+ private static native boolean nSetLayerPaint(long renderNode, long paint);
+
+ @CriticalNative
+ private static native boolean nSetClipToBounds(long renderNode, boolean clipToBounds);
+
+ @CriticalNative
+ private static native boolean nGetClipToBounds(long renderNode);
+
+ @CriticalNative
+ private static native boolean nSetClipBounds(long renderNode, int left, int top,
+ int right, int bottom);
+
+ @CriticalNative
+ private static native boolean nSetClipBoundsEmpty(long renderNode);
+
+ @CriticalNative
+ private static native boolean nSetProjectBackwards(long renderNode, boolean shouldProject);
+
+ @CriticalNative
+ private static native boolean nSetProjectionReceiver(long renderNode, boolean shouldRecieve);
+
+ @CriticalNative
+ private static native boolean nSetOutlineRoundRect(long renderNode, int left, int top,
+ int right, int bottom, float radius, float alpha);
+
+ @CriticalNative
+ private static native boolean nSetOutlineConvexPath(long renderNode, long nativePath,
+ float alpha);
+
+ @CriticalNative
+ private static native boolean nSetOutlineEmpty(long renderNode);
+
+ @CriticalNative
+ private static native boolean nSetOutlineNone(long renderNode);
+
+ @CriticalNative
+ private static native boolean nHasShadow(long renderNode);
+
+ @CriticalNative
+ private static native boolean nSetSpotShadowColor(long renderNode, int color);
+
+ @CriticalNative
+ private static native boolean nSetAmbientShadowColor(long renderNode, int color);
+
+ @CriticalNative
+ private static native int nGetSpotShadowColor(long renderNode);
+
+ @CriticalNative
+ private static native int nGetAmbientShadowColor(long renderNode);
+
+ @CriticalNative
+ private static native boolean nSetClipToOutline(long renderNode, boolean clipToOutline);
+
+ @CriticalNative
+ private static native boolean nSetRevealClip(long renderNode,
+ boolean shouldClip, float x, float y, float radius);
+
+ @CriticalNative
+ private static native boolean nSetAlpha(long renderNode, float alpha);
+
+ @CriticalNative
+ private static native boolean nSetHasOverlappingRendering(long renderNode,
+ boolean hasOverlappingRendering);
+
+ @CriticalNative
+ private static native void nSetUsageHint(long renderNode, int usageHint);
+
+ @CriticalNative
+ private static native boolean nSetElevation(long renderNode, float lift);
+
+ @CriticalNative
+ private static native boolean nSetTranslationX(long renderNode, float translationX);
+
+ @CriticalNative
+ private static native boolean nSetTranslationY(long renderNode, float translationY);
+
+ @CriticalNative
+ private static native boolean nSetTranslationZ(long renderNode, float translationZ);
+
+ @CriticalNative
+ private static native boolean nSetRotation(long renderNode, float rotation);
+
+ @CriticalNative
+ private static native boolean nSetRotationX(long renderNode, float rotationX);
+
+ @CriticalNative
+ private static native boolean nSetRotationY(long renderNode, float rotationY);
+
+ @CriticalNative
+ private static native boolean nSetScaleX(long renderNode, float scaleX);
+
+ @CriticalNative
+ private static native boolean nSetScaleY(long renderNode, float scaleY);
+
+ @CriticalNative
+ private static native boolean nSetStaticMatrix(long renderNode, long nativeMatrix);
+
+ @CriticalNative
+ private static native boolean nSetAnimationMatrix(long renderNode, long animationMatrix);
+
+ @CriticalNative
+ private static native boolean nHasOverlappingRendering(long renderNode);
+
+ @CriticalNative
+ private static native boolean nGetAnimationMatrix(long renderNode, long animationMatrix);
+
+ @CriticalNative
+ private static native boolean nGetClipToOutline(long renderNode);
+
+ @CriticalNative
+ private static native float nGetAlpha(long renderNode);
+
+ @CriticalNative
+ private static native float nGetCameraDistance(long renderNode);
+
+ @CriticalNative
+ private static native float nGetScaleX(long renderNode);
+
+ @CriticalNative
+ private static native float nGetScaleY(long renderNode);
+
+ @CriticalNative
+ private static native float nGetElevation(long renderNode);
+
+ @CriticalNative
+ private static native float nGetTranslationX(long renderNode);
+
+ @CriticalNative
+ private static native float nGetTranslationY(long renderNode);
+
+ @CriticalNative
+ private static native float nGetTranslationZ(long renderNode);
+
+ @CriticalNative
+ private static native float nGetRotation(long renderNode);
+
+ @CriticalNative
+ private static native float nGetRotationX(long renderNode);
+
+ @CriticalNative
+ private static native float nGetRotationY(long renderNode);
+
+ @CriticalNative
+ private static native boolean nIsPivotExplicitlySet(long renderNode);
+
+ @CriticalNative
+ private static native float nGetPivotX(long renderNode);
+
+ @CriticalNative
+ private static native float nGetPivotY(long renderNode);
+
+ @CriticalNative
+ private static native int nGetWidth(long renderNode);
+
+ @CriticalNative
+ private static native int nGetHeight(long renderNode);
+
+ @CriticalNative
+ private static native boolean nSetAllowForceDark(long renderNode, boolean allowForceDark);
+
+ @CriticalNative
+ private static native boolean nGetAllowForceDark(long renderNode);
+
+ @CriticalNative
+ private static native long nGetUniqueId(long renderNode);
+}
diff --git a/graphics/java/android/graphics/Shader.java b/graphics/java/android/graphics/Shader.java
index 40288f5ec8af..3050d1dae5e4 100644
--- a/graphics/java/android/graphics/Shader.java
+++ b/graphics/java/android/graphics/Shader.java
@@ -16,8 +16,11 @@
package android.graphics;
+import android.annotation.ColorInt;
+import android.annotation.ColorLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
import libcore.util.NativeAllocationRegistry;
@@ -30,15 +33,41 @@ import libcore.util.NativeAllocationRegistry;
public class Shader {
private static class NoImagePreloadHolder {
- public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
- Shader.class.getClassLoader(), nativeGetFinalizer(), 50);
+ public static final NativeAllocationRegistry sRegistry =
+ NativeAllocationRegistry.createMalloced(
+ Shader.class.getClassLoader(), nativeGetFinalizer());
}
/**
* @deprecated Use subclass constructors directly instead.
*/
@Deprecated
- public Shader() {}
+ public Shader() {
+ mColorSpace = null;
+ }
+
+ /**
+ * @hide
+ */
+ public Shader(ColorSpace colorSpace) {
+ mColorSpace = colorSpace;
+ if (colorSpace == null) {
+ throw new IllegalArgumentException(
+ "Use Shader() to create a Shader with no ColorSpace");
+ }
+
+ // This just ensures that if the ColorSpace is invalid, the Exception will be thrown now.
+ mColorSpace.getNativeInstance();
+ }
+
+ private final ColorSpace mColorSpace;
+
+ /**
+ * @hide
+ */
+ protected ColorSpace colorSpace() {
+ return mColorSpace;
+ }
/**
* Current native shader instance. Created and updated lazily when {@link #getNativeInstance()}
@@ -72,6 +101,7 @@ public class Shader {
TileMode(int nativeInt) {
this.nativeInt = nativeInt;
}
+ @UnsupportedAppUsage
final int nativeInt;
}
@@ -133,21 +163,6 @@ public class Shader {
protected void verifyNativeInstance() {
}
- /**
- * @hide
- */
- protected Shader copy() {
- final Shader copy = new Shader();
- copyLocalMatrix(copy);
- return copy;
- }
-
- /**
- * @hide
- */
- protected void copyLocalMatrix(Shader dest) {
- dest.mLocalMatrix.set(mLocalMatrix);
- }
/**
* @hide
@@ -167,6 +182,43 @@ public class Shader {
return mNativeInstance;
}
+ /**
+ * @hide
+ */
+ public static @ColorLong long[] convertColors(@NonNull @ColorInt int[] colors) {
+ if (colors.length < 2) {
+ throw new IllegalArgumentException("needs >= 2 number of colors");
+ }
+
+ long[] colorLongs = new long[colors.length];
+ for (int i = 0; i < colors.length; ++i) {
+ colorLongs[i] = Color.pack(colors[i]);
+ }
+
+ return colorLongs;
+ }
+
+ /**
+ * Detect the ColorSpace that the {@code colors} share.
+ *
+ * @throws IllegalArgumentException if the colors do not all share the same,
+ * valid ColorSpace, or if there are less than 2 colors.
+ *
+ * @hide
+ */
+ public static ColorSpace detectColorSpace(@NonNull @ColorLong long[] colors) {
+ if (colors.length < 2) {
+ throw new IllegalArgumentException("needs >= 2 number of colors");
+ }
+ final ColorSpace colorSpace = Color.colorSpace(colors[0]);
+ for (int i = 1; i < colors.length; ++i) {
+ if (Color.colorSpace(colors[i]) != colorSpace) {
+ throw new IllegalArgumentException("All colors must be in the same ColorSpace!");
+ }
+ }
+ return colorSpace;
+ }
+
private static native long nativeGetFinalizer();
}
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index 1eebd2647753..99f440d599cb 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -17,6 +17,7 @@
package android.graphics;
import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -68,13 +69,17 @@ import java.lang.ref.WeakReference;
*/
public class SurfaceTexture {
private final Looper mCreatorLooper;
+ @UnsupportedAppUsage
private Handler mOnFrameAvailableHandler;
/**
* These fields are used by native code, do not access or modify.
*/
+ @UnsupportedAppUsage
private long mSurfaceTexture;
+ @UnsupportedAppUsage
private long mProducer;
+ @UnsupportedAppUsage
private long mFrameAvailableListener;
private boolean mIsSingleBuffered;
@@ -378,6 +383,7 @@ public class SurfaceTexture {
* This method is invoked from native code only.
*/
@SuppressWarnings({"UnusedDeclaration"})
+ @UnsupportedAppUsage
private static void postEventFromNative(WeakReference<SurfaceTexture> weakSelf) {
SurfaceTexture st = weakSelf.get();
if (st != null) {
@@ -405,6 +411,7 @@ public class SurfaceTexture {
private native void nativeSetDefaultBufferSize(int width, int height);
private native void nativeUpdateTexImage();
private native void nativeReleaseTexImage();
+ @UnsupportedAppUsage
private native int nativeDetachFromGLContext();
private native int nativeAttachToGLContext(int texName);
private native void nativeRelease();
diff --git a/graphics/java/android/graphics/SweepGradient.java b/graphics/java/android/graphics/SweepGradient.java
index b6b80b4f57dc..667f45afe500 100644
--- a/graphics/java/android/graphics/SweepGradient.java
+++ b/graphics/java/android/graphics/SweepGradient.java
@@ -17,27 +17,52 @@
package android.graphics;
import android.annotation.ColorInt;
+import android.annotation.ColorLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
public class SweepGradient extends Shader {
-
- private static final int TYPE_COLORS_AND_POSITIONS = 1;
- private static final int TYPE_COLOR_START_AND_COLOR_END = 2;
-
- /**
- * Type of the LinearGradient: can be either TYPE_COLORS_AND_POSITIONS or
- * TYPE_COLOR_START_AND_COLOR_END.
- */
- private int mType;
-
+ @UnsupportedAppUsage
private float mCx;
+ @UnsupportedAppUsage
private float mCy;
- private int[] mColors;
+ @UnsupportedAppUsage
private float[] mPositions;
+
+ // @ColorInts are replaced by @ColorLongs, but these remain due to @UnsupportedAppUsage.
+ @UnsupportedAppUsage
+ @ColorInt
+ private int[] mColors;
+ @UnsupportedAppUsage
+ @ColorInt
private int mColor0;
+ @UnsupportedAppUsage
+ @ColorInt
private int mColor1;
+ @ColorLong
+ private final long[] mColorLongs;
+
+ /**
+ * A Shader that draws a sweep gradient around a center point.
+ *
+ * @param cx The x-coordinate of the center
+ * @param cy The y-coordinate of the center
+ * @param colors The sRGB colors to be distributed between around the center.
+ * There must be at least 2 colors in the array.
+ * @param positions May be NULL. The relative position of
+ * each corresponding color in the colors array, beginning
+ * with 0 and ending with 1.0. If the values are not
+ * monotonic, the drawing may produce unexpected results.
+ * If positions is NULL, then the colors are automatically
+ * spaced evenly.
+ */
+ public SweepGradient(float cx, float cy, @NonNull @ColorInt int[] colors,
+ @Nullable float[] positions) {
+ this(cx, cy, convertColors(colors), positions, ColorSpace.get(ColorSpace.Named.SRGB));
+ }
+
/**
* A Shader that draws a sweep gradient around a center point.
*
@@ -51,20 +76,30 @@ public class SweepGradient extends Shader {
* monotonic, the drawing may produce unexpected results.
* If positions is NULL, then the colors are automatically
* spaced evenly.
+ * @throws IllegalArgumentException if there are less than two colors, the colors do
+ * not share the same {@link ColorSpace} or do not use a valid one, or {@code positions}
+ * is not {@code null} and has a different length from {@code colors}.
*/
- public SweepGradient(float cx, float cy,
- @NonNull @ColorInt int colors[], @Nullable float positions[]) {
- if (colors.length < 2) {
- throw new IllegalArgumentException("needs >= 2 number of colors");
- }
+ public SweepGradient(float cx, float cy, @NonNull @ColorLong long[] colors,
+ @Nullable float[] positions) {
+ this(cx, cy, colors.clone(), positions, detectColorSpace(colors));
+ }
+
+ /**
+ * Base constructor. Assumes @param colors is a copy that this object can hold onto,
+ * and all colors share @param colorSpace.
+ */
+ private SweepGradient(float cx, float cy, @NonNull @ColorLong long[] colors,
+ @Nullable float[] positions, ColorSpace colorSpace) {
+ super(colorSpace);
+
if (positions != null && colors.length != positions.length) {
throw new IllegalArgumentException(
"color and position arrays must be of equal length");
}
- mType = TYPE_COLORS_AND_POSITIONS;
mCx = cx;
mCy = cy;
- mColors = colors.clone();
+ mColorLongs = colors;
mPositions = positions != null ? positions.clone() : null;
}
@@ -73,47 +108,35 @@ public class SweepGradient extends Shader {
*
* @param cx The x-coordinate of the center
* @param cy The y-coordinate of the center
- * @param color0 The color to use at the start of the sweep
- * @param color1 The color to use at the end of the sweep
+ * @param color0 The sRGB color to use at the start of the sweep
+ * @param color1 The sRGB color to use at the end of the sweep
*/
public SweepGradient(float cx, float cy, @ColorInt int color0, @ColorInt int color1) {
- mType = TYPE_COLOR_START_AND_COLOR_END;
- mCx = cx;
- mCy = cy;
- mColor0 = color0;
- mColor1 = color1;
- mColors = null;
- mPositions = null;
- }
-
- @Override
- long createNativeInstance(long nativeMatrix) {
- if (mType == TYPE_COLORS_AND_POSITIONS) {
- return nativeCreate1(nativeMatrix, mCx, mCy, mColors, mPositions);
- } else { // TYPE_COLOR_START_AND_COLOR_END
- return nativeCreate2(nativeMatrix, mCx, mCy, mColor0, mColor1);
- }
+ this(cx, cy, Color.pack(color0), Color.pack(color1));
}
/**
- * @hide
+ * A Shader that draws a sweep gradient around a center point.
+ *
+ * @param cx The x-coordinate of the center
+ * @param cy The y-coordinate of the center
+ * @param color0 The color to use at the start of the sweep
+ * @param color1 The color to use at the end of the sweep
+ *
+ * @throws IllegalArgumentException if the colors do
+ * not share the same {@link ColorSpace} or do not use a valid one.
*/
+ public SweepGradient(float cx, float cy, @ColorLong long color0, @ColorLong long color1) {
+ this(cx, cy, new long[] {color0, color1}, null);
+ }
+
@Override
- protected Shader copy() {
- final SweepGradient copy;
- if (mType == TYPE_COLORS_AND_POSITIONS) {
- copy = new SweepGradient(mCx, mCy, mColors.clone(),
- mPositions != null ? mPositions.clone() : null);
- } else { // TYPE_COLOR_START_AND_COLOR_END
- copy = new SweepGradient(mCx, mCy, mColor0, mColor1);
- }
- copyLocalMatrix(copy);
- return copy;
+ long createNativeInstance(long nativeMatrix) {
+ return nativeCreate(nativeMatrix, mCx, mCy, mColorLongs, mPositions,
+ colorSpace().getNativeInstance());
}
- private static native long nativeCreate1(long matrix, float x, float y,
- int colors[], float positions[]);
- private static native long nativeCreate2(long matrix, float x, float y,
- int color0, int color1);
+ private static native long nativeCreate(long matrix, float x, float y,
+ long[] colors, float[] positions, long colorSpaceHandle);
}
diff --git a/graphics/java/android/graphics/TableMaskFilter.java b/graphics/java/android/graphics/TableMaskFilter.java
index d0c1438285a8..d81c491e07e0 100644
--- a/graphics/java/android/graphics/TableMaskFilter.java
+++ b/graphics/java/android/graphics/TableMaskFilter.java
@@ -16,6 +16,8 @@
package android.graphics;
+import android.annotation.UnsupportedAppUsage;
+
/**
* @hide
*/
@@ -32,6 +34,7 @@ public class TableMaskFilter extends MaskFilter {
native_instance = ni;
}
+ @UnsupportedAppUsage
public static TableMaskFilter CreateClipTable(int min, int max) {
return new TableMaskFilter(nativeNewClip(min, max));
}
diff --git a/graphics/java/android/graphics/TemporaryBuffer.java b/graphics/java/android/graphics/TemporaryBuffer.java
index 36a2275738c2..0ae2c703c21c 100644
--- a/graphics/java/android/graphics/TemporaryBuffer.java
+++ b/graphics/java/android/graphics/TemporaryBuffer.java
@@ -16,12 +16,14 @@
package android.graphics;
+import android.annotation.UnsupportedAppUsage;
import com.android.internal.util.ArrayUtils;
/**
* @hide
*/
public class TemporaryBuffer {
+ @UnsupportedAppUsage
public static char[] obtain(int len) {
char[] buf;
@@ -37,6 +39,7 @@ public class TemporaryBuffer {
return buf;
}
+ @UnsupportedAppUsage
public static void recycle(char[] temp) {
if (temp.length > 1000) return;
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 18dd97f8acef..6d20ec32cdc4 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -25,15 +25,19 @@ import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
import android.content.res.AssetManager;
+import android.graphics.fonts.Font;
+import android.graphics.fonts.FontFamily;
+import android.graphics.fonts.FontStyle;
import android.graphics.fonts.FontVariationAxis;
-import android.net.Uri;
+import android.graphics.fonts.SystemFonts;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
import android.provider.FontRequest;
import android.provider.FontsContract;
import android.text.FontConfig;
-import android.util.ArrayMap;
import android.util.Base64;
-import android.util.Log;
import android.util.LongSparseArray;
import android.util.LruCache;
import android.util.SparseArray;
@@ -46,18 +50,12 @@ import dalvik.annotation.optimization.CriticalNative;
import libcore.util.NativeAllocationRegistry;
-import org.xmlpull.v1.XmlPullParserException;
-
import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -75,8 +73,9 @@ public class Typeface {
private static String TAG = "Typeface";
- private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
- Typeface.class.getClassLoader(), nativeGetReleaseFunc(), 64);
+ private static final NativeAllocationRegistry sRegistry =
+ NativeAllocationRegistry.createMalloced(
+ Typeface.class.getClassLoader(), nativeGetReleaseFunc());
/** The default NORMAL typeface object */
public static final Typeface DEFAULT;
@@ -93,6 +92,13 @@ public class Typeface {
/** The NORMAL style of the default monospace typeface. */
public static final Typeface MONOSPACE;
+ /**
+ * The default {@link Typeface}s for different text styles.
+ * Call {@link #defaultFromStyle(int)} to get the default typeface for the given text style.
+ * It shouldn't be changed for app wide typeface settings. Please use theme and font XML for
+ * the same purpose.
+ */
+ @UnsupportedAppUsage(trackingBug = 123769446)
static Typeface[] sDefaults;
/**
@@ -119,12 +125,28 @@ public class Typeface {
private static final Object sDynamicCacheLock = new Object();
static Typeface sDefaultTypeface;
+
+ // Following two fields are not used but left for hiddenapi private list
+ /**
+ * sSystemFontMap is read only and unmodifiable.
+ * Use public API {@link #create(String, int)} to get the typeface for given familyName.
+ */
+ @UnsupportedAppUsage(trackingBug = 123769347)
static final Map<String, Typeface> sSystemFontMap;
- static final Map<String, FontFamily[]> sSystemFallbackMap;
+
+ // We cannot support sSystemFallbackMap since we will migrate to public FontFamily API.
+ /**
+ * @deprecated Use {@link android.graphics.fonts.FontFamily} instead.
+ */
+ @UnsupportedAppUsage(trackingBug = 123768928)
+ @Deprecated
+ static final Map<String, android.graphics.FontFamily[]> sSystemFallbackMap =
+ Collections.emptyMap();
/**
* @hide
*/
+ @UnsupportedAppUsage
public long native_instance;
/** @hide */
@@ -139,15 +161,10 @@ public class Typeface {
public static final int BOLD_ITALIC = 3;
/** @hide */ public static final int STYLE_MASK = 0x03;
+ @UnsupportedAppUsage
private @Style int mStyle = 0;
- /**
- * A maximum value for the weight value.
- * @hide
- */
- public static final int MAX_WEIGHT = 1000;
-
- private @IntRange(from = 0, to = MAX_WEIGHT) int mWeight = 0;
+ private @IntRange(from = 0, to = FontStyle.FONT_WEIGHT_MAX) int mWeight = 0;
// Value for weight and italic. Indicates the value is resolved by font metadata.
// Must be the same as the C++ constant in core/jni/android/graphics/FontFamily.cpp
@@ -162,6 +179,12 @@ public class Typeface {
private int[] mSupportedAxes;
private static final int[] EMPTY_AXES = {};
+ /**
+ * Please use font in xml and also your application global theme to change the default Typeface.
+ * android:textViewStyle and its attribute android:textAppearance can be used in order to change
+ * typeface and other text related properties.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
private static void setDefault(Typeface t) {
sDefaultTypeface = t;
nativeSetDefault(t.native_instance);
@@ -189,38 +212,6 @@ public class Typeface {
/**
* @hide
- * Used by Resources to load a font resource of type font file.
- */
- @Nullable
- public static Typeface createFromResources(AssetManager mgr, String path, int cookie) {
- synchronized (sDynamicCacheLock) {
- final String key = Builder.createAssetUid(
- mgr, path, 0 /* ttcIndex */, null /* axes */,
- RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */,
- DEFAULT_FAMILY);
- Typeface typeface = sDynamicTypefaceCache.get(key);
- if (typeface != null) return typeface;
-
- FontFamily fontFamily = new FontFamily();
- // TODO: introduce ttc index and variation settings to resource type font.
- if (fontFamily.addFontFromAssetManager(mgr, path, cookie, false /* isAsset */,
- 0 /* ttcIndex */, RESOLVE_BY_FONT_TABLE /* weight */,
- RESOLVE_BY_FONT_TABLE /* italic */, null /* axes */)) {
- if (!fontFamily.freeze()) {
- return null;
- }
- FontFamily[] families = {fontFamily};
- typeface = createFromFamiliesWithDefault(families, DEFAULT_FAMILY,
- RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
- sDynamicTypefaceCache.put(key, typeface);
- return typeface;
- }
- }
- return null;
- }
-
- /**
- * @hide
* Used by Resources to load a font resource of type xml.
*/
@Nullable
@@ -255,21 +246,53 @@ public class Typeface {
// family is FontFamilyFilesResourceEntry
final FontFamilyFilesResourceEntry filesEntry = (FontFamilyFilesResourceEntry) entry;
- FontFamily fontFamily = new FontFamily();
- for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) {
- if (!fontFamily.addFontFromAssetManager(mgr, fontFile.getFileName(),
- 0 /* resourceCookie */, false /* isAsset */, fontFile.getTtcIndex(),
- fontFile.getWeight(), fontFile.getItalic(),
- FontVariationAxis.fromFontVariationSettings(fontFile.getVariationSettings()))) {
- return null;
+ try {
+ FontFamily.Builder familyBuilder = null;
+ for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) {
+ final Font.Builder fontBuilder = new Font.Builder(mgr, fontFile.getFileName(),
+ false /* isAsset */, 0 /* cookie */)
+ .setTtcIndex(fontFile.getTtcIndex())
+ .setFontVariationSettings(fontFile.getVariationSettings());
+ if (fontFile.getWeight() != Typeface.RESOLVE_BY_FONT_TABLE) {
+ fontBuilder.setWeight(fontFile.getWeight());
+ }
+ if (fontFile.getItalic() != Typeface.RESOLVE_BY_FONT_TABLE) {
+ fontBuilder.setSlant(fontFile.getItalic() == FontFileResourceEntry.ITALIC
+ ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT);
+ }
+
+ if (familyBuilder == null) {
+ familyBuilder = new FontFamily.Builder(fontBuilder.build());
+ } else {
+ familyBuilder.addFont(fontBuilder.build());
+ }
}
- }
- if (!fontFamily.freeze()) {
+ if (familyBuilder == null) {
+ return Typeface.DEFAULT;
+ }
+ final FontFamily family = familyBuilder.build();
+ final FontStyle normal = new FontStyle(FontStyle.FONT_WEIGHT_NORMAL,
+ FontStyle.FONT_SLANT_UPRIGHT);
+ Font bestFont = family.getFont(0);
+ int bestScore = normal.getMatchScore(bestFont.getStyle());
+ for (int i = 1; i < family.getSize(); ++i) {
+ final Font candidate = family.getFont(i);
+ final int score = normal.getMatchScore(candidate.getStyle());
+ if (score < bestScore) {
+ bestFont = candidate;
+ bestScore = score;
+ }
+ }
+ typeface = new Typeface.CustomFallbackBuilder(family)
+ .setStyle(bestFont.getStyle())
+ .build();
+ } catch (IllegalArgumentException e) {
+ // To be a compatible behavior with API28 or before, catch IllegalArgumentExcetpion
+ // thrown by native code and returns null.
return null;
+ } catch (IOException e) {
+ typeface = Typeface.DEFAULT;
}
- FontFamily[] familyChain = { fontFamily };
- typeface = createFromFamiliesWithDefault(familyChain, DEFAULT_FAMILY,
- RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
synchronized (sDynamicCacheLock) {
final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */,
null /* axes */, RESOLVE_BY_FONT_TABLE /* weight */,
@@ -336,15 +359,11 @@ public class Typeface {
/** @hide */
public static final int BOLD_WEIGHT = 700;
- private int mTtcIndex;
- private FontVariationAxis[] mAxes;
-
- private AssetManager mAssetManager;
- private String mPath;
- private FileDescriptor mFd;
+ // Kept for generating asset cache key.
+ private final AssetManager mAssetManager;
+ private final String mPath;
- private FontsContract.FontInfo[] mFonts;
- private Map<Uri, ByteBuffer> mFontBuffers;
+ private final @Nullable Font.Builder mFontBuilder;
private String mFallbackFamilyName;
@@ -357,7 +376,9 @@ public class Typeface {
* @param path The file object refers to the font file.
*/
public Builder(@NonNull File path) {
- mPath = path.getAbsolutePath();
+ mFontBuilder = new Font.Builder(path);
+ mAssetManager = null;
+ mPath = null;
}
/**
@@ -369,7 +390,18 @@ public class Typeface {
* @param fd The file descriptor. The passed fd must be mmap-able.
*/
public Builder(@NonNull FileDescriptor fd) {
- mFd = fd;
+ Font.Builder builder;
+ try {
+ builder = new Font.Builder(ParcelFileDescriptor.dup(fd));
+ } catch (IOException e) {
+ // We cannot tell the error to developer at this moment since we cannot change the
+ // public API signature. Instead, silently fallbacks to system fallback in the build
+ // method as the same as other error cases.
+ builder = null;
+ }
+ mFontBuilder = builder;
+ mAssetManager = null;
+ mPath = null;
}
/**
@@ -378,7 +410,9 @@ public class Typeface {
* @param path The full path to the font file.
*/
public Builder(@NonNull String path) {
- mPath = path;
+ mFontBuilder = new Font.Builder(new File(path));
+ mAssetManager = null;
+ mPath = null;
}
/**
@@ -388,27 +422,22 @@ public class Typeface {
* @param path The file name of the font data in the asset directory
*/
public Builder(@NonNull AssetManager assetManager, @NonNull String path) {
- mAssetManager = Preconditions.checkNotNull(assetManager);
- mPath = Preconditions.checkStringNotEmpty(path);
+ this(assetManager, path, true /* is asset */, 0 /* cookie */);
}
/**
- * Constracts a builder from an array of FontsContract.FontInfo.
- *
- * Since {@link FontsContract.FontInfo} holds information about TTC indices and
- * variation settings, there is no need to call {@link #setTtcIndex} or
- * {@link #setFontVariationSettings}. Similary, {@link FontsContract.FontInfo} holds
- * weight and italic information, so {@link #setWeight} and {@link #setItalic} are used
- * for style matching during font selection.
+ * Constructs a builder from an asset manager and a file path in an asset directory.
*
- * @param fonts The array of {@link FontsContract.FontInfo}
- * @param buffers The mapping from URI to buffers to be used during building.
+ * @param assetManager The application's asset manager
+ * @param path The file name of the font data in the asset directory
+ * @param cookie a cookie for the asset
* @hide
*/
- public Builder(@NonNull FontsContract.FontInfo[] fonts,
- @NonNull Map<Uri, ByteBuffer> buffers) {
- mFonts = fonts;
- mFontBuffers = buffers;
+ public Builder(@NonNull AssetManager assetManager, @NonNull String path, boolean isAsset,
+ int cookie) {
+ mFontBuilder = new Font.Builder(assetManager, path, isAsset, cookie);
+ mAssetManager = assetManager;
+ mPath = path;
}
/**
@@ -420,6 +449,7 @@ public class Typeface {
*/
public Builder setWeight(@IntRange(from = 1, to = 1000) int weight) {
mWeight = weight;
+ mFontBuilder.setWeight(weight);
return this;
}
@@ -431,7 +461,8 @@ public class Typeface {
* @param italic {@code true} if the font is italic. Otherwise {@code false}.
*/
public Builder setItalic(boolean italic) {
- mItalic = italic ? STYLE_ITALIC : STYLE_NORMAL;
+ mItalic = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+ mFontBuilder.setSlant(mItalic);
return this;
}
@@ -443,11 +474,7 @@ public class Typeface {
* collection, do not call this method or specify 0.
*/
public Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) {
- if (mFonts != null) {
- throw new IllegalArgumentException(
- "TTC index can not be specified for FontResult source.");
- }
- mTtcIndex = ttcIndex;
+ mFontBuilder.setTtcIndex(ttcIndex);
return this;
}
@@ -459,14 +486,7 @@ public class Typeface {
* format.
*/
public Builder setFontVariationSettings(@Nullable String variationSettings) {
- if (mFonts != null) {
- throw new IllegalArgumentException(
- "Font variation settings can not be specified for FontResult source.");
- }
- if (mAxes != null) {
- throw new IllegalStateException("Font variation settings are already set.");
- }
- mAxes = FontVariationAxis.fromFontVariationSettings(variationSettings);
+ mFontBuilder.setFontVariationSettings(variationSettings);
return this;
}
@@ -476,14 +496,7 @@ public class Typeface {
* @param axes An array of font variation axis tag-value pairs.
*/
public Builder setFontVariationSettings(@Nullable FontVariationAxis[] axes) {
- if (mFonts != null) {
- throw new IllegalArgumentException(
- "Font variation settings can not be specified for FontResult source.");
- }
- if (mAxes != null) {
- throw new IllegalStateException("Font variation settings are already set.");
- }
- mAxes = axes;
+ mFontBuilder.setFontVariationSettings(axes);
return this;
}
@@ -559,11 +572,7 @@ public class Typeface {
return null;
}
- Typeface base = sSystemFontMap.get(mFallbackFamilyName);
- if (base == null) {
- base = sDefaultTypeface;
- }
-
+ final Typeface base = getSystemDefaultTypeface(mFallbackFamilyName);
if (mWeight == RESOLVE_BY_FONT_TABLE && mItalic == RESOLVE_BY_FONT_TABLE) {
return base;
}
@@ -580,91 +589,209 @@ public class Typeface {
* @return Newly created Typeface. May return null if some parameters are invalid.
*/
public Typeface build() {
- if (mFd != null) { // Builder is created with file descriptor.
- try (FileInputStream fis = new FileInputStream(mFd)) {
- FileChannel channel = fis.getChannel();
- long size = channel.size();
- ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, size);
-
- final FontFamily fontFamily = new FontFamily();
- if (!fontFamily.addFontFromBuffer(buffer, mTtcIndex, mAxes, mWeight, mItalic)) {
- fontFamily.abortCreation();
- return resolveFallbackTypeface();
- }
- if (!fontFamily.freeze()) {
- return resolveFallbackTypeface();
- }
- FontFamily[] families = { fontFamily };
- return createFromFamiliesWithDefault(families, mFallbackFamilyName, mWeight,
- mItalic);
- } catch (IOException e) {
- return resolveFallbackTypeface();
- }
- } else if (mAssetManager != null) { // Builder is created with asset manager.
- final String key = createAssetUid(
- mAssetManager, mPath, mTtcIndex, mAxes, mWeight, mItalic,
- mFallbackFamilyName);
- synchronized (sDynamicCacheLock) {
- Typeface typeface = sDynamicTypefaceCache.get(key);
- if (typeface != null) return typeface;
- final FontFamily fontFamily = new FontFamily();
- if (!fontFamily.addFontFromAssetManager(mAssetManager, mPath, mTtcIndex,
- true /* isAsset */, mTtcIndex, mWeight, mItalic, mAxes)) {
- fontFamily.abortCreation();
- return resolveFallbackTypeface();
- }
- if (!fontFamily.freeze()) {
- return resolveFallbackTypeface();
+ if (mFontBuilder == null) {
+ return resolveFallbackTypeface();
+ }
+ try {
+ final Font font = mFontBuilder.build();
+ final String key = mAssetManager == null ? null : createAssetUid(
+ mAssetManager, mPath, font.getTtcIndex(), font.getAxes(),
+ mWeight, mItalic,
+ mFallbackFamilyName == null ? DEFAULT_FAMILY : mFallbackFamilyName);
+ if (key != null) {
+ // Dynamic cache lookup is only for assets.
+ synchronized (sDynamicCacheLock) {
+ final Typeface typeface = sDynamicTypefaceCache.get(key);
+ if (typeface != null) {
+ return typeface;
+ }
}
- FontFamily[] families = { fontFamily };
- typeface = createFromFamiliesWithDefault(families, mFallbackFamilyName,
- mWeight, mItalic);
- sDynamicTypefaceCache.put(key, typeface);
- return typeface;
- }
- } else if (mPath != null) { // Builder is created with file path.
- final FontFamily fontFamily = new FontFamily();
- if (!fontFamily.addFont(mPath, mTtcIndex, mAxes, mWeight, mItalic)) {
- fontFamily.abortCreation();
- return resolveFallbackTypeface();
}
- if (!fontFamily.freeze()) {
- return resolveFallbackTypeface();
+ final FontFamily family = new FontFamily.Builder(font).build();
+ final int weight = mWeight == RESOLVE_BY_FONT_TABLE
+ ? font.getStyle().getWeight() : mWeight;
+ final int slant = mItalic == RESOLVE_BY_FONT_TABLE
+ ? font.getStyle().getSlant() : mItalic;
+ final CustomFallbackBuilder builder = new CustomFallbackBuilder(family)
+ .setStyle(new FontStyle(weight, slant));
+ if (mFallbackFamilyName != null) {
+ builder.setSystemFallback(mFallbackFamilyName);
}
- FontFamily[] families = { fontFamily };
- return createFromFamiliesWithDefault(families, mFallbackFamilyName, mWeight,
- mItalic);
- } else if (mFonts != null) {
- final FontFamily fontFamily = new FontFamily();
- boolean atLeastOneFont = false;
- for (FontsContract.FontInfo font : mFonts) {
- final ByteBuffer fontBuffer = mFontBuffers.get(font.getUri());
- if (fontBuffer == null) {
- continue; // skip
+ final Typeface typeface = builder.build();
+ if (key != null) {
+ synchronized (sDynamicCacheLock) {
+ sDynamicTypefaceCache.put(key, typeface);
}
- final boolean success = fontFamily.addFontFromBuffer(fontBuffer,
- font.getTtcIndex(), font.getAxes(), font.getWeight(),
- font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL);
- if (!success) {
- fontFamily.abortCreation();
- return null;
- }
- atLeastOneFont = true;
- }
- if (!atLeastOneFont) {
- // No fonts are avaialble. No need to create new Typeface and returns fallback
- // Typeface instead.
- fontFamily.abortCreation();
- return null;
}
- fontFamily.freeze();
- FontFamily[] families = { fontFamily };
- return createFromFamiliesWithDefault(families, mFallbackFamilyName, mWeight,
- mItalic);
+ return typeface;
+ } catch (IOException | IllegalArgumentException e) {
+ return resolveFallbackTypeface();
}
+ }
+ }
+
+ /**
+ * A builder class for creating new Typeface instance.
+ *
+ * There are two font fallback mechanisms, custom font fallback and system font fallback.
+ * The custom font fallback is a simple ordered list. The text renderer tries to see if it can
+ * render a character with the first font and if that font does not support the character, try
+ * next one and so on. It will keep trying until end of the custom fallback chain. The maximum
+ * length of the custom fallback chain is 64.
+ * The system font fallback is a system pre-defined fallback chain. The system fallback is
+ * processed only when no matching font is found in the custom font fallback.
+ *
+ * <p>
+ * Examples,
+ * 1) Create Typeface from single ttf file.
+ * <pre>
+ * <code>
+ * Font font = new Font.Builder("your_font_file.ttf").build();
+ * FontFamily family = new FontFamily.Builder(font).build();
+ * Typeface typeface = new Typeface.CustomFallbackBuilder(family).build();
+ * </code>
+ * </pre>
+ *
+ * 2) Create Typeface from multiple font files and select bold style by default.
+ * <pre>
+ * <code>
+ * Font regularFont = new Font.Builder("regular.ttf").build();
+ * Font boldFont = new Font.Builder("bold.ttf").build();
+ * FontFamily family = new FontFamily.Builder(regularFont)
+ * .addFont(boldFont).build();
+ * Typeface typeface = new Typeface.CustomFallbackBuilder(family)
+ * .setWeight(Font.FONT_WEIGHT_BOLD) // Set bold style as the default style.
+ * // If the font family doesn't have bold style font,
+ * // system will select the closest font.
+ * .build();
+ * </code>
+ * </pre>
+ *
+ * 3) Create Typeface from single ttf file and if that font does not have glyph for the
+ * characters, use "serif" font family instead.
+ * <pre>
+ * <code>
+ * Font font = new Font.Builder("your_font_file.ttf").build();
+ * FontFamily family = new FontFamily.Builder(font).build();
+ * Typeface typeface = new Typeface.CustomFallbackBuilder(family)
+ * .setSystemFallback("serif") // Set serif font family as the fallback.
+ * .build();
+ * </code>
+ * </pre>
+ * 4) Create Typeface from single ttf file and set another ttf file for the fallback.
+ * <pre>
+ * <code>
+ * Font font = new Font.Builder("English.ttf").build();
+ * FontFamily family = new FontFamily.Builder(font).build();
+ *
+ * Font fallbackFont = new Font.Builder("Arabic.ttf").build();
+ * FontFamily fallbackFamily = new FontFamily.Builder(fallbackFont).build();
+ * Typeface typeface = new Typeface.CustomFallbackBuilder(family)
+ * .addCustomFallback(fallbackFamily) // Specify fallback family.
+ * .setSystemFallback("serif") // Set serif font family as the fallback.
+ * .build();
+ * </code>
+ * </pre>
+ * </p>
+ */
+ public static final class CustomFallbackBuilder {
+ private static final int MAX_CUSTOM_FALLBACK = 64;
+ private final ArrayList<FontFamily> mFamilies = new ArrayList<>();
+ private String mFallbackName = null;
+ private @Nullable FontStyle mStyle;
- // Must not reach here.
- throw new IllegalArgumentException("No source was set.");
+ /**
+ * Returns the maximum capacity of custom fallback families.
+ *
+ * This includes the the first font family passed to the constructor.
+ * It is guaranteed that the value will be greater than or equal to 64.
+ *
+ * @return the maximum number of font families for the custom fallback
+ */
+ public static @IntRange(from = 64) int getMaxCustomFallbackCount() {
+ return MAX_CUSTOM_FALLBACK;
+ }
+
+ /**
+ * Constructs a builder with a font family.
+ *
+ * @param family a family object
+ */
+ public CustomFallbackBuilder(@NonNull FontFamily family) {
+ Preconditions.checkNotNull(family);
+ mFamilies.add(family);
+ }
+
+ /**
+ * Sets a system fallback by name.
+ *
+ * You can specify generic font familiy names or OEM specific family names. If the system
+ * don't have a specified fallback, the default fallback is used instead.
+ * For more information about generic font families, see <a
+ * href="https://www.w3.org/TR/css-fonts-4/#generic-font-families">CSS specification</a>
+ *
+ * For more information about fallback, see class description.
+ *
+ * @param familyName a family name to be used for fallback if the provided fonts can not be
+ * used
+ */
+ public @NonNull CustomFallbackBuilder setSystemFallback(@NonNull String familyName) {
+ Preconditions.checkNotNull(familyName);
+ mFallbackName = familyName;
+ return this;
+ }
+
+ /**
+ * Sets a font style of the Typeface.
+ *
+ * If the font family doesn't have a font of given style, system will select the closest
+ * font from font family. For example, if a font family has fonts of 300 weight and 700
+ * weight then setWeight(400) is called, system will select the font of 300 weight.
+ *
+ * @param style a font style
+ */
+ public @NonNull CustomFallbackBuilder setStyle(@NonNull FontStyle style) {
+ mStyle = style;
+ return this;
+ }
+
+ /**
+ * Append a font family to the end of the custom font fallback.
+ *
+ * You can set up to 64 custom fallback families including the first font family you passed
+ * to the constructor.
+ * For more information about fallback, see class description.
+ *
+ * @param family a fallback family
+ * @throws IllegalArgumentException if you give more than 64 custom fallback families
+ */
+ public @NonNull CustomFallbackBuilder addCustomFallback(@NonNull FontFamily family) {
+ Preconditions.checkNotNull(family);
+ Preconditions.checkArgument(mFamilies.size() < getMaxCustomFallbackCount(),
+ "Custom fallback limit exceeded(" + getMaxCustomFallbackCount() + ")");
+ mFamilies.add(family);
+ return this;
+ }
+
+ /**
+ * Create the Typeface based on the configured values.
+ *
+ * @return the Typeface object
+ */
+ public @NonNull Typeface build() {
+ final int userFallbackSize = mFamilies.size();
+ final FontFamily[] fallback = SystemFonts.getSystemFallback(mFallbackName);
+ final long[] ptrArray = new long[fallback.length + userFallbackSize];
+ for (int i = 0; i < userFallbackSize; ++i) {
+ ptrArray[i] = mFamilies.get(i).getNativePtr();
+ }
+ for (int i = 0; i < fallback.length; ++i) {
+ ptrArray[i + userFallbackSize] = fallback[i].getNativePtr();
+ }
+ final int weight = mStyle == null ? 400 : mStyle.getWeight();
+ final int italic =
+ (mStyle == null || mStyle.getSlant() == FontStyle.FONT_SLANT_UPRIGHT) ? 0 : 1;
+ return new Typeface(nativeCreateFromArray(ptrArray, weight, italic));
}
}
@@ -680,7 +807,7 @@ public class Typeface {
* @return The best matching typeface.
*/
public static Typeface create(String familyName, @Style int style) {
- return create(sSystemFontMap.get(familyName), style);
+ return create(getSystemDefaultTypeface(familyName), style);
}
/**
@@ -797,8 +924,7 @@ public class Typeface {
}
typeface = new Typeface(
- nativeCreateFromTypefaceWithExactStyle(
- base.native_instance, weight, italic));
+ nativeCreateFromTypefaceWithExactStyle(base.native_instance, weight, italic));
innerCache.put(key, typeface);
}
return typeface;
@@ -807,8 +933,8 @@ public class Typeface {
/** @hide */
public static Typeface createFromTypefaceWithVariation(@Nullable Typeface family,
@NonNull List<FontVariationAxis> axes) {
- final long ni = family == null ? 0 : family.native_instance;
- return new Typeface(nativeCreateFromTypefaceWithVariation(ni, axes));
+ final Typeface base = family == null ? Typeface.DEFAULT : family;
+ return new Typeface(nativeCreateFromTypefaceWithVariation(base.native_instance, axes));
}
/**
@@ -890,8 +1016,11 @@ public class Typeface {
* Create a new typeface from an array of font families.
*
* @param families array of font families
+ * @deprecated
*/
- private static Typeface createFromFamilies(FontFamily[] families) {
+ @Deprecated
+ @UnsupportedAppUsage(trackingBug = 123768928)
+ private static Typeface createFromFamilies(android.graphics.FontFamily[] families) {
long[] ptrArray = new long[families.length];
for (int i = 0; i < families.length; i++) {
ptrArray[i] = families[i].mNativePtr;
@@ -901,11 +1030,28 @@ public class Typeface {
}
/**
+ * Create a new typeface from an array of android.graphics.fonts.FontFamily.
+ *
+ * @param families array of font families
+ */
+ private static Typeface createFromFamilies(@Nullable FontFamily[] families) {
+ final long[] ptrArray = new long[families.length];
+ for (int i = 0; i < families.length; ++i) {
+ ptrArray[i] = families[i].getNativePtr();
+ }
+ return new Typeface(nativeCreateFromArray(ptrArray,
+ RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ }
+
+ /**
* This method is used by supportlib-v27.
- * TODO: Remove private API use in supportlib: http://b/72665240
+ *
+ * @deprecated Use {@link android.graphics.fonts.FontFamily} instead.
*/
- private static Typeface createFromFamiliesWithDefault(FontFamily[] families, int weight,
- int italic) {
+ @UnsupportedAppUsage(trackingBug = 123768395)
+ @Deprecated
+ private static Typeface createFromFamiliesWithDefault(
+ android.graphics.FontFamily[] families, int weight, int italic) {
return createFromFamiliesWithDefault(families, DEFAULT_FAMILY, weight, italic);
}
@@ -921,24 +1067,26 @@ public class Typeface {
* the first family's font is used. If the first family has multiple fonts, the
* closest to the regular weight and upright font is used.
* @param families array of font families
+ *
+ * @deprecated Use {@link android.graphics.fonts.FontFamily} instead.
*/
- private static Typeface createFromFamiliesWithDefault(FontFamily[] families,
+ @UnsupportedAppUsage(trackingBug = 123768928)
+ @Deprecated
+ private static Typeface createFromFamiliesWithDefault(android.graphics.FontFamily[] families,
String fallbackName, int weight, int italic) {
- FontFamily[] fallback = sSystemFallbackMap.get(fallbackName);
- if (fallback == null) {
- fallback = sSystemFallbackMap.get(DEFAULT_FAMILY);
- }
+ android.graphics.fonts.FontFamily[] fallback = SystemFonts.getSystemFallback(fallbackName);
long[] ptrArray = new long[families.length + fallback.length];
for (int i = 0; i < families.length; i++) {
ptrArray[i] = families[i].mNativePtr;
}
for (int i = 0; i < fallback.length; i++) {
- ptrArray[i + families.length] = fallback[i].mNativePtr;
+ ptrArray[i + families.length] = fallback[i].getNativePtr();
}
return new Typeface(nativeCreateFromArray(ptrArray, weight, italic));
}
// don't allow clients to call this directly
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private Typeface(long ni) {
if (ni == 0) {
throw new RuntimeException("native typeface cannot be made");
@@ -950,193 +1098,54 @@ public class Typeface {
mWeight = nativeGetWeight(ni);
}
- private static @Nullable ByteBuffer mmap(String fullPath) {
- try (FileInputStream file = new FileInputStream(fullPath)) {
- final FileChannel fileChannel = file.getChannel();
- final long fontSize = fileChannel.size();
- return fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
- } catch (IOException e) {
- Log.e(TAG, "Error mapping font file " + fullPath);
- return null;
- }
+ private static Typeface getSystemDefaultTypeface(@NonNull String familyName) {
+ Typeface tf = sSystemFontMap.get(familyName);
+ return tf == null ? Typeface.DEFAULT : tf;
}
- private static @Nullable FontFamily createFontFamily(
- String familyName, List<FontConfig.Font> fonts, String[] languageTags, int variant,
- Map<String, ByteBuffer> cache, String fontDir) {
- final FontFamily family = new FontFamily(languageTags, variant);
- for (int i = 0; i < fonts.size(); i++) {
- final FontConfig.Font font = fonts.get(i);
- final String fullPath = fontDir + font.getFontName();
- ByteBuffer buffer = cache.get(fullPath);
- if (buffer == null) {
- if (cache.containsKey(fullPath)) {
- continue; // Already failed to mmap. Skip it.
- }
- buffer = mmap(fullPath);
- cache.put(fullPath, buffer);
- if (buffer == null) {
- continue;
- }
- }
- if (!family.addFontFromBuffer(buffer, font.getTtcIndex(), font.getAxes(),
- font.getWeight(), font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL)) {
- Log.e(TAG, "Error creating font " + fullPath + "#" + font.getTtcIndex());
- }
- }
- if (!family.freeze()) {
- Log.e(TAG, "Unable to load Family: " + familyName + " : "
- + Arrays.toString(languageTags));
- return null;
+ /** @hide */
+ @VisibleForTesting
+ public static void initSystemDefaultTypefaces(Map<String, Typeface> systemFontMap,
+ Map<String, FontFamily[]> fallbacks,
+ FontConfig.Alias[] aliases) {
+ for (Map.Entry<String, FontFamily[]> entry : fallbacks.entrySet()) {
+ systemFontMap.put(entry.getKey(), createFromFamilies(entry.getValue()));
}
- return family;
- }
-
- private static void pushFamilyToFallback(FontConfig.Family xmlFamily,
- ArrayMap<String, ArrayList<FontFamily>> fallbackMap,
- Map<String, ByteBuffer> cache,
- String fontDir) {
-
- final String[] languageTags = xmlFamily.getLanguages();
- final int variant = xmlFamily.getVariant();
- final ArrayList<FontConfig.Font> defaultFonts = new ArrayList<>();
- final ArrayMap<String, ArrayList<FontConfig.Font>> specificFallbackFonts = new ArrayMap<>();
-
- // Collect default fallback and specific fallback fonts.
- for (final FontConfig.Font font : xmlFamily.getFonts()) {
- final String fallbackName = font.getFallbackFor();
- if (fallbackName == null) {
- defaultFonts.add(font);
- } else {
- ArrayList<FontConfig.Font> fallback = specificFallbackFonts.get(fallbackName);
- if (fallback == null) {
- fallback = new ArrayList<>();
- specificFallbackFonts.put(fallbackName, fallback);
- }
- fallback.add(font);
+ for (FontConfig.Alias alias : aliases) {
+ if (systemFontMap.containsKey(alias.getName())) {
+ continue; // If alias and named family are conflict, use named family.
}
- }
-
- final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily(
- xmlFamily.getName(), defaultFonts, languageTags, variant, cache, fontDir);
-
- // Insert family into fallback map.
- for (int i = 0; i < fallbackMap.size(); i++) {
- final ArrayList<FontConfig.Font> fallback =
- specificFallbackFonts.get(fallbackMap.keyAt(i));
- if (fallback == null) {
- if (defaultFamily != null) {
- fallbackMap.valueAt(i).add(defaultFamily);
- }
- } else {
- final FontFamily family = createFontFamily(
- xmlFamily.getName(), fallback, languageTags, variant, cache, fontDir);
- if (family != null) {
- fallbackMap.valueAt(i).add(family);
- } else if (defaultFamily != null) {
- fallbackMap.valueAt(i).add(defaultFamily);
- } else {
- // There is no valid for for default fallback. Ignore.
- }
+ final Typeface base = systemFontMap.get(alias.getToName());
+ if (base == null) {
+ // The missing target is a valid thing, some configuration don't have font files,
+ // e.g. wear devices. Just skip this alias.
+ continue;
}
+ final int weight = alias.getWeight();
+ final Typeface newFace = weight == 400 ? base :
+ new Typeface(nativeCreateWeightAlias(base.native_instance, weight));
+ systemFontMap.put(alias.getName(), newFace);
}
}
- /**
- * Build the system fallback from xml file.
- *
- * @param xmlPath A full path string to the fonts.xml file.
- * @param fontDir A full path string to the system font directory. This must end with
- * slash('/').
- * @param fontMap An output system font map. Caller must pass empty map.
- * @param fallbackMap An output system fallback map. Caller must pass empty map.
- * @hide
- */
- @VisibleForTesting
- public static void buildSystemFallback(String xmlPath, String fontDir,
- ArrayMap<String, Typeface> fontMap, ArrayMap<String, FontFamily[]> fallbackMap) {
- try {
- final FileInputStream fontsIn = new FileInputStream(xmlPath);
- final FontConfig fontConfig = FontListParser.parse(fontsIn);
-
- final HashMap<String, ByteBuffer> bufferCache = new HashMap<String, ByteBuffer>();
- final FontConfig.Family[] xmlFamilies = fontConfig.getFamilies();
-
- final ArrayMap<String, ArrayList<FontFamily>> fallbackListMap = new ArrayMap<>();
- // First traverse families which have a 'name' attribute to create fallback map.
- for (final FontConfig.Family xmlFamily : xmlFamilies) {
- final String familyName = xmlFamily.getName();
- if (familyName == null) {
- continue;
- }
- final FontFamily family = createFontFamily(
- xmlFamily.getName(), Arrays.asList(xmlFamily.getFonts()),
- xmlFamily.getLanguages(), xmlFamily.getVariant(), bufferCache, fontDir);
- if (family == null) {
- continue;
- }
- final ArrayList<FontFamily> fallback = new ArrayList<>();
- fallback.add(family);
- fallbackListMap.put(familyName, fallback);
- }
-
- // Then, add fallback fonts to the each fallback map.
- for (int i = 0; i < xmlFamilies.length; i++) {
- final FontConfig.Family xmlFamily = xmlFamilies[i];
- // The first family (usually the sans-serif family) is always placed immediately
- // after the primary family in the fallback.
- if (i == 0 || xmlFamily.getName() == null) {
- pushFamilyToFallback(xmlFamily, fallbackListMap, bufferCache, fontDir);
- }
- }
-
- // Build the font map and fallback map.
- for (int i = 0; i < fallbackListMap.size(); i++) {
- final String fallbackName = fallbackListMap.keyAt(i);
- final List<FontFamily> familyList = fallbackListMap.valueAt(i);
- final FontFamily[] families = familyList.toArray(new FontFamily[familyList.size()]);
-
- fallbackMap.put(fallbackName, families);
- final long[] ptrArray = new long[families.length];
- for (int j = 0; j < families.length; j++) {
- ptrArray[j] = families[j].mNativePtr;
- }
- fontMap.put(fallbackName, new Typeface(nativeCreateFromArray(
- ptrArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)));
- }
-
- // Insert alias to font maps.
- for (final FontConfig.Alias alias : fontConfig.getAliases()) {
- Typeface base = fontMap.get(alias.getToName());
- Typeface newFace = base;
- int weight = alias.getWeight();
- if (weight != 400) {
- newFace = new Typeface(nativeCreateWeightAlias(base.native_instance, weight));
- }
- fontMap.put(alias.getName(), newFace);
- }
- } catch (RuntimeException e) {
- Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)", e);
- // TODO: normal in non-Minikin case, remove or make error when Minikin-only
- } catch (FileNotFoundException e) {
- Log.e(TAG, "Error opening " + xmlPath, e);
- } catch (IOException e) {
- Log.e(TAG, "Error reading " + xmlPath, e);
- } catch (XmlPullParserException e) {
- Log.e(TAG, "XML parse exception for " + xmlPath, e);
+ private static void registerGenericFamilyNative(@NonNull String familyName,
+ @Nullable Typeface typeface) {
+ if (typeface != null) {
+ nativeRegisterGenericFamily(familyName, typeface.native_instance);
}
}
static {
- final ArrayMap<String, Typeface> systemFontMap = new ArrayMap<>();
- final ArrayMap<String, FontFamily[]> systemFallbackMap = new ArrayMap<>();
- buildSystemFallback("/system/etc/fonts.xml", "/system/fonts/", systemFontMap,
- systemFallbackMap);
+ final HashMap<String, Typeface> systemFontMap = new HashMap<>();
+ initSystemDefaultTypefaces(systemFontMap, SystemFonts.getRawSystemFallbackMap(),
+ SystemFonts.getAliases());
sSystemFontMap = Collections.unmodifiableMap(systemFontMap);
- sSystemFallbackMap = Collections.unmodifiableMap(systemFallbackMap);
- setDefault(sSystemFontMap.get(DEFAULT_FAMILY));
+ // We can't assume DEFAULT_FAMILY available on Roboletric.
+ if (sSystemFontMap.containsKey(DEFAULT_FAMILY)) {
+ setDefault(sSystemFontMap.get(DEFAULT_FAMILY));
+ }
// Set up defaults and typefaces exposed in public API
DEFAULT = create((String) null, 0);
@@ -1152,6 +1161,15 @@ public class Typeface {
create((String) null, Typeface.BOLD_ITALIC),
};
+ // A list of generic families to be registered in native.
+ // https://www.w3.org/TR/css-fonts-4/#generic-font-families
+ String[] genericFamilies = {
+ "serif", "sans-serif", "cursive", "fantasy", "monospace", "system-ui"
+ };
+
+ for (String genericFamily : genericFamilies) {
+ registerGenericFamilyNative(genericFamily, systemFontMap.get(genericFamily));
+ }
}
@Override
@@ -1197,7 +1215,9 @@ public class Typeface {
// TODO: clean up: change List<FontVariationAxis> to FontVariationAxis[]
private static native long nativeCreateFromTypefaceWithVariation(
long native_instance, List<FontVariationAxis> axes);
+ @UnsupportedAppUsage
private static native long nativeCreateWeightAlias(long native_instance, int weight);
+ @UnsupportedAppUsage
private static native long nativeCreateFromArray(long[] familyArray, int weight, int italic);
private static native int[] nativeGetSupportedAxes(long native_instance);
@@ -1212,4 +1232,6 @@ public class Typeface {
@CriticalNative
private static native long nativeGetReleaseFunc();
+
+ private static native void nativeRegisterGenericFamily(String str, long nativePtr);
}
diff --git a/graphics/java/android/graphics/Xfermode.java b/graphics/java/android/graphics/Xfermode.java
index a5da5d09ebaf..6f4adfde7ff9 100644
--- a/graphics/java/android/graphics/Xfermode.java
+++ b/graphics/java/android/graphics/Xfermode.java
@@ -21,6 +21,8 @@
package android.graphics;
+import android.annotation.UnsupportedAppUsage;
+
/**
* Xfermode is the base class for objects that are called to implement custom
* "transfer-modes" in the drawing pipeline. The static function Create(Modes)
@@ -30,5 +32,6 @@ package android.graphics;
*/
public class Xfermode {
static final int DEFAULT = PorterDuff.Mode.SRC_OVER.nativeInt;
+ @UnsupportedAppUsage
int porterDuffMode = DEFAULT;
}
diff --git a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
index fdd638adba81..fab96a1e9fbd 100644
--- a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
+++ b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
@@ -19,6 +19,7 @@ package android.graphics.drawable;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
+import android.app.ActivityThread;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.content.res.Resources;
@@ -26,6 +27,7 @@ import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
+import android.graphics.BlendMode;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
@@ -34,7 +36,6 @@ import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
-import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.Shader;
@@ -108,11 +109,10 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
* Scaled mask based on the view bounds.
*/
private final Path mMask;
+ private final Path mMaskScaleOnly;
private final Matrix mMaskMatrix;
private final Region mTransparentRegion;
- private Bitmap mMaskBitmap;
-
/**
* Indices used to access {@link #mLayerState.mChildDrawable} array for foreground and
* background layer.
@@ -151,13 +151,16 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
*/
AdaptiveIconDrawable(@Nullable LayerState state, @Nullable Resources res) {
mLayerState = createConstantState(state, res);
-
- if (sMask == null) {
- sMask = PathParser.createPathFromPathData(
- Resources.getSystem().getString(R.string.config_icon_mask));
- }
- mMask = PathParser.createPathFromPathData(
- Resources.getSystem().getString(R.string.config_icon_mask));
+ // config_icon_mask from context bound resource may have been chaged using
+ // OverlayManager. Read that one first.
+ Resources r = ActivityThread.currentActivityThread() == null
+ ? Resources.getSystem()
+ : ActivityThread.currentActivityThread().getApplication().getResources();
+ // TODO: either make sMask update only when config_icon_mask changes OR
+ // get rid of it all-together in layoutlib
+ sMask = PathParser.createPathFromPathData(r.getString(R.string.config_icon_mask));
+ mMask = new Path(sMask);
+ mMaskScaleOnly = new Path(mMask);
mMaskMatrix = new Matrix();
mCanvas = new Canvas();
mTransparentRegion = new Region();
@@ -329,24 +332,19 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
}
private void updateMaskBoundsInternal(Rect b) {
+ // reset everything that depends on the view bounds
mMaskMatrix.setScale(b.width() / MASK_SIZE, b.height() / MASK_SIZE);
+ sMask.transform(mMaskMatrix, mMaskScaleOnly);
+
+ mMaskMatrix.postTranslate(b.left, b.top);
sMask.transform(mMaskMatrix, mMask);
- if (mMaskBitmap == null || mMaskBitmap.getWidth() != b.width() ||
- mMaskBitmap.getHeight() != b.height()) {
- mMaskBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ALPHA_8);
+ if (mLayersBitmap == null || mLayersBitmap.getWidth() != b.width()
+ || mLayersBitmap.getHeight() != b.height()) {
mLayersBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ARGB_8888);
}
- // mMaskBitmap bound [0, w] x [0, h]
- mCanvas.setBitmap(mMaskBitmap);
- mPaint.setShader(null);
- mCanvas.drawPath(mMask, mPaint);
- // mMask bound [left, top, right, bottom]
- mMaskMatrix.postTranslate(b.left, b.top);
- mMask.reset();
- sMask.transform(mMaskMatrix, mMask);
- // reset everything that depends on the view bounds
+ mPaint.setShader(null);
mTransparentRegion.setEmpty();
mLayersShader = null;
}
@@ -371,9 +369,11 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
mLayersShader = new BitmapShader(mLayersBitmap, TileMode.CLAMP, TileMode.CLAMP);
mPaint.setShader(mLayersShader);
}
- if (mMaskBitmap != null) {
+ if (mMaskScaleOnly != null) {
Rect bounds = getBounds();
- canvas.drawBitmap(mMaskBitmap, bounds.left, bounds.top, mPaint);
+ canvas.translate(bounds.left, bounds.top);
+ canvas.drawPath(mMaskScaleOnly, mPaint);
+ canvas.translate(-bounds.left, -bounds.top);
}
}
@@ -549,7 +549,7 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
final ChildDrawable[] layers = mLayerState.mChildren;
for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
- if (layers[i].mDrawable.isProjected()) {
+ if (layers[i].mDrawable != null && layers[i].mDrawable.isProjected()) {
return true;
}
}
@@ -674,7 +674,7 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
@Override
public int getAlpha() {
- return PixelFormat.TRANSLUCENT;
+ return mPaint.getAlpha();
}
@Override
@@ -701,13 +701,13 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
}
@Override
- public void setTintMode(Mode tintMode) {
+ public void setTintBlendMode(@NonNull BlendMode blendMode) {
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.N_CHILDREN;
for (int i = 0; i < N; i++) {
final Drawable dr = array[i].mDrawable;
if (dr != null) {
- dr.setTintMode(tintMode);
+ dr.setTintBlendMode(blendMode);
}
}
}
@@ -718,10 +718,7 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
@Override
public int getOpacity() {
- if (mLayerState.mOpacityOverride != PixelFormat.UNKNOWN) {
- return mLayerState.mOpacityOverride;
- }
- return mLayerState.getOpacity();
+ return PixelFormat.TRANSLUCENT;
}
@Override
diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
index 4f467d9aabae..82f587086428 100644
--- a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
@@ -19,6 +19,7 @@ package android.graphics.drawable;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
@@ -290,8 +291,8 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 {
*/
public AnimatedImageDrawable(long nativeImageDecoder,
@Nullable ImageDecoder decoder, int width, int height,
- int srcDensity, int dstDensity, Rect cropRect,
- InputStream inputStream, AssetFileDescriptor afd)
+ long colorSpaceHandle, boolean extended, int srcDensity, int dstDensity,
+ Rect cropRect, InputStream inputStream, AssetFileDescriptor afd)
throws IOException {
width = Bitmap.scaleFromDensity(width, srcDensity, dstDensity);
height = Bitmap.scaleFromDensity(height, srcDensity, dstDensity);
@@ -308,11 +309,11 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 {
mIntrinsicHeight = cropRect.height();
}
- mState = new State(nCreate(nativeImageDecoder, decoder, width, height, cropRect),
- inputStream, afd);
+ mState = new State(nCreate(nativeImageDecoder, decoder, width, height, colorSpaceHandle,
+ extended, cropRect), inputStream, afd);
final long nativeSize = nNativeByteSize(mState.mNativePtr);
- NativeAllocationRegistry registry = new NativeAllocationRegistry(
+ NativeAllocationRegistry registry = NativeAllocationRegistry.createMalloced(
AnimatedImageDrawable.class.getClassLoader(), nGetNativeFinalizer(), nativeSize);
registry.registerNativeAllocation(mState, mState.mNativePtr);
}
@@ -562,6 +563,7 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 {
* callback, so no need to post.
*/
@SuppressWarnings("unused")
+ @UnsupportedAppUsage
private void onAnimationEnd() {
if (mAnimationCallbacks != null) {
for (Animatable2.AnimationCallback callback : mAnimationCallbacks) {
@@ -572,8 +574,8 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 {
private static native long nCreate(long nativeImageDecoder,
- @Nullable ImageDecoder decoder, int width, int height, Rect cropRect)
- throws IOException;
+ @Nullable ImageDecoder decoder, int width, int height, long colorSpaceHandle,
+ boolean extended, Rect cropRect) throws IOException;
@FastNative
private static native long nGetNativeFinalizer();
private static native long nDraw(long nativePtr, long canvasNativePtr);
diff --git a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
index d714ca830976..b29fd4db5803 100644
--- a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
@@ -18,6 +18,7 @@ package android.graphics.drawable;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.content.res.Resources;
@@ -202,11 +203,13 @@ public class AnimatedRotateDrawable extends DrawableWrapper implements Animatabl
R.styleable.AnimatedRotateDrawable_frameDuration, state.mFrameDuration));
}
+ @UnsupportedAppUsage
public void setFramesCount(int framesCount) {
mState.mFramesCount = framesCount;
mIncrement = 360.0f / mState.mFramesCount;
}
+ @UnsupportedAppUsage
public void setFramesDuration(int framesDuration) {
mState.mFrameDuration = framesDuration;
}
diff --git a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
index 3ed6a788b640..11a46c4ba9b9 100644
--- a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
@@ -20,9 +20,11 @@ import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
+import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.util.LongSparseLongArray;
@@ -66,6 +68,7 @@ public class AnimatedStateListDrawable extends StateListDrawable {
private static final String ELEMENT_TRANSITION = "transition";
private static final String ELEMENT_ITEM = "item";
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private AnimatedStateListState mState;
/** The currently running transition, if any. */
@@ -558,7 +561,9 @@ public class AnimatedStateListDrawable extends StateListDrawable {
int[] mAnimThemeAttrs;
+ @UnsupportedAppUsage
LongSparseLongArray mTransitions;
+ @UnsupportedAppUsage
SparseIntArray mStateIds;
AnimatedStateListState(@Nullable AnimatedStateListState orig,
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index e397ed90c2e2..66947da9166f 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -25,6 +25,7 @@ import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
import android.app.Application;
import android.content.pm.ActivityInfo.Config;
@@ -32,14 +33,17 @@ import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
+import android.graphics.BlendMode;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Insets;
import android.graphics.Outline;
import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
+import android.graphics.RecordingCanvas;
import android.graphics.Rect;
+import android.graphics.RenderNode;
import android.os.Build;
+import android.os.Handler;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.IntArray;
@@ -49,8 +53,7 @@ import android.util.PathParser;
import android.util.Property;
import android.util.TimeUtils;
import android.view.Choreographer;
-import android.view.DisplayListCanvas;
-import android.view.RenderNode;
+import android.view.NativeVectorDrawableAnimator;
import android.view.RenderNodeAnimatorSetHelper;
import android.view.View;
@@ -305,6 +308,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false;
/** Local, mutable animator set. */
+ @UnsupportedAppUsage
private VectorDrawableAnimator mAnimatorSet;
/**
@@ -313,6 +317,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
*/
private Resources mRes;
+ @UnsupportedAppUsage
private AnimatedVectorDrawableState mAnimatedVectorState;
/** The animator set that is parsed from the xml. */
@@ -472,8 +477,8 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
}
@Override
- public void setTintMode(PorterDuff.Mode tintMode) {
- mAnimatedVectorState.mVectorDrawable.setTintMode(tintMode);
+ public void setTintBlendMode(@NonNull BlendMode blendMode) {
+ mAnimatedVectorState.mVectorDrawable.setTintBlendMode(blendMode);
}
@Override
@@ -516,7 +521,6 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
mAnimatedVectorState.mVectorDrawable.getOutline(outline);
}
- /** @hide */
@Override
public Insets getOpticalInsets() {
return mAnimatedVectorState.mVectorDrawable.getOpticalInsets();
@@ -642,6 +646,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
* Force to animate on UI thread.
* @hide
*/
+ @UnsupportedAppUsage
public void forceAnimationOnUI() {
if (mAnimatorSet instanceof VectorDrawableAnimatorRT) {
VectorDrawableAnimatorRT animator = (VectorDrawableAnimatorRT) mAnimatorSet;
@@ -1228,7 +1233,8 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
/**
* @hide
*/
- public static class VectorDrawableAnimatorRT implements VectorDrawableAnimator {
+ public static class VectorDrawableAnimatorRT implements VectorDrawableAnimator,
+ NativeVectorDrawableAnimator {
private static final int START_ANIMATION = 1;
private static final int REVERSE_ANIMATION = 2;
private static final int RESET_ANIMATION = 3;
@@ -1236,6 +1242,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
// 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 Handler mHandler;
private AnimatorListener mListener = null;
private final LongArray mStartDelays = new LongArray();
private PropertyValuesHolder.PropertyValues mTmpValues =
@@ -1246,7 +1253,6 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
private boolean mInitialized = false;
private boolean mIsReversible = false;
private boolean mIsInfinite = false;
- // TODO: Consider using NativeAllocationRegistery to track native allocation
private final VirtualRefBasePtr mSetRefBasePtr;
private WeakReference<RenderNode> mLastSeenTarget = null;
private int mLastListenerId = 0;
@@ -1537,11 +1543,11 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
}
/**
- * Holds a weak reference to the target that was last seen (through the DisplayListCanvas
+ * Holds a weak reference to the target that was last seen (through the RecordingCanvas
* in the last draw call), so that when animator set needs to start, we can add the animator
* to the last seen RenderNode target and start right away.
*/
- protected void recordLastSeenTarget(DisplayListCanvas canvas) {
+ protected void recordLastSeenTarget(RecordingCanvas canvas) {
final RenderNode node = RenderNodeAnimatorSetHelper.getTarget(canvas);
mLastSeenTarget = new WeakReference<RenderNode>(node);
// Add the animator to the list of animators on every draw
@@ -1666,6 +1672,9 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
.mRootName);
}
mStarted = true;
+ if (mHandler == null) {
+ mHandler = new Handler();
+ }
nStart(mSetPtr, this, ++mLastListenerId);
invalidateOwningView();
if (mListener != null) {
@@ -1701,6 +1710,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
}
}
+ @Override
public long getAnimatorNativePtr() {
return mSetPtr;
}
@@ -1736,7 +1746,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
@Override
public void onDraw(Canvas canvas) {
if (canvas.isHardwareAccelerated()) {
- recordLastSeenTarget((DisplayListCanvas) canvas);
+ recordLastSeenTarget((RecordingCanvas) canvas);
}
}
@@ -1772,8 +1782,9 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
}
// onFinished: should be called from native
+ @UnsupportedAppUsage
private static void callOnFinished(VectorDrawableAnimatorRT set, int id) {
- set.onAnimationEnd(id);
+ set.mHandler.post(() -> set.onAnimationEnd(id));
}
private void transferPendingActions(VectorDrawableAnimator animatorSet) {
diff --git a/graphics/java/android/graphics/drawable/AnimationDrawable.java b/graphics/java/android/graphics/drawable/AnimationDrawable.java
index 0fd1741610ec..57764c2cb693 100644
--- a/graphics/java/android/graphics/drawable/AnimationDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimationDrawable.java
@@ -24,6 +24,7 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.annotation.NonNull;
+import android.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.Resources.Theme;
@@ -88,6 +89,7 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An
private AnimationState mAnimationState;
/** The current frame, ranging from 0 to {@link #mAnimationState#getChildCount() - 1} */
+ @UnsupportedAppUsage
private int mCurFrame = 0;
/** Whether the drawable has an animation callback posted. */
diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java
index 44b783bb6e63..e4aa774fd434 100644
--- a/graphics/java/android/graphics/drawable/BitmapDrawable.java
+++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java
@@ -17,14 +17,16 @@
package android.graphics.drawable;
import android.annotation.NonNull;
+import android.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
+import android.graphics.BlendMode;
+import android.graphics.BlendModeColorFilter;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.ImageDecoder;
@@ -33,9 +35,7 @@ import android.graphics.Matrix;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
import android.graphics.PorterDuff.Mode;
-import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.Xfermode;
@@ -88,9 +88,11 @@ public class BitmapDrawable extends Drawable {
private final Rect mDstRect = new Rect(); // #updateDstRectAndInsetsIfDirty() sets this
+ @UnsupportedAppUsage
private BitmapState mBitmapState;
- private PorterDuffColorFilter mTintFilter;
+ private BlendModeColorFilter mBlendModeFilter;
+ @UnsupportedAppUsage
private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
private boolean mDstRectAndInsetsDirty = true;
@@ -238,6 +240,7 @@ public class BitmapDrawable extends Drawable {
}
/** @hide */
+ @UnsupportedAppUsage
public void setBitmap(Bitmap bitmap) {
if (mBitmapState.mBitmap != bitmap) {
mBitmapState.mBitmap = bitmap;
@@ -524,8 +527,8 @@ public class BitmapDrawable extends Drawable {
}
final boolean clearColorFilter;
- if (mTintFilter != null && paint.getColorFilter() == null) {
- paint.setColorFilter(mTintFilter);
+ if (mBlendModeFilter != null && paint.getColorFilter() == null) {
+ paint.setColorFilter(mBlendModeFilter);
clearColorFilter = true;
} else {
clearColorFilter = false;
@@ -627,9 +630,6 @@ public class BitmapDrawable extends Drawable {
mDstRectAndInsetsDirty = false;
}
- /**
- * @hide
- */
@Override
public Insets getOpticalInsets() {
updateDstRectAndInsetsIfDirty();
@@ -678,17 +678,19 @@ public class BitmapDrawable extends Drawable {
final BitmapState state = mBitmapState;
if (state.mTint != tint) {
state.mTint = tint;
- mTintFilter = updateTintFilter(mTintFilter, tint, mBitmapState.mTintMode);
+ mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, tint,
+ mBitmapState.mBlendMode);
invalidateSelf();
}
}
@Override
- public void setTintMode(PorterDuff.Mode tintMode) {
+ public void setTintBlendMode(@NonNull BlendMode blendMode) {
final BitmapState state = mBitmapState;
- if (state.mTintMode != tintMode) {
- state.mTintMode = tintMode;
- mTintFilter = updateTintFilter(mTintFilter, mBitmapState.mTint, tintMode);
+ if (state.mBlendMode != blendMode) {
+ state.mBlendMode = blendMode;
+ mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, mBitmapState.mTint,
+ blendMode);
invalidateSelf();
}
}
@@ -696,6 +698,7 @@ public class BitmapDrawable extends Drawable {
/**
* @hide only needed by a hack within ProgressBar
*/
+ @UnsupportedAppUsage
public ColorStateList getTint() {
return mBitmapState.mTint;
}
@@ -703,8 +706,9 @@ public class BitmapDrawable extends Drawable {
/**
* @hide only needed by a hack within ProgressBar
*/
+ @UnsupportedAppUsage
public Mode getTintMode() {
- return mBitmapState.mTintMode;
+ return BlendMode.blendModeToPorterDuffMode(mBitmapState.mBlendMode);
}
/**
@@ -742,8 +746,9 @@ public class BitmapDrawable extends Drawable {
@Override
protected boolean onStateChange(int[] stateSet) {
final BitmapState state = mBitmapState;
- if (state.mTint != null && state.mTintMode != null) {
- mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
+ if (state.mTint != null && state.mBlendMode != null) {
+ mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, state.mTint,
+ state.mBlendMode);
return true;
}
return false;
@@ -862,7 +867,7 @@ public class BitmapDrawable extends Drawable {
final int tintMode = a.getInt(R.styleable.BitmapDrawable_tintMode, -1);
if (tintMode != -1) {
- state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
+ state.mBlendMode = Drawable.parseBlendMode(tintMode, BlendMode.SRC_IN);
}
final ColorStateList tint = a.getColorStateList(R.styleable.BitmapDrawable_tint);
@@ -977,7 +982,8 @@ public class BitmapDrawable extends Drawable {
int[] mThemeAttrs = null;
Bitmap mBitmap = null;
ColorStateList mTint = null;
- Mode mTintMode = DEFAULT_TINT_MODE;
+ BlendMode mBlendMode = DEFAULT_BLEND_MODE;
+
int mGravity = Gravity.FILL;
float mBaseAlpha = 1.0f;
Shader.TileMode mTileModeX = null;
@@ -1003,7 +1009,7 @@ public class BitmapDrawable extends Drawable {
BitmapState(BitmapState bitmapState) {
mBitmap = bitmapState.mBitmap;
mTint = bitmapState.mTint;
- mTintMode = bitmapState.mTintMode;
+ mBlendMode = bitmapState.mBlendMode;
mThemeAttrs = bitmapState.mThemeAttrs;
mChangingConfigurations = bitmapState.mChangingConfigurations;
mGravity = bitmapState.mGravity;
@@ -1063,7 +1069,8 @@ public class BitmapDrawable extends Drawable {
*/
private void updateLocalState(Resources res) {
mTargetDensity = resolveDensity(res, mBitmapState.mTargetDensity);
- mTintFilter = updateTintFilter(mTintFilter, mBitmapState.mTint, mBitmapState.mTintMode);
+ mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, mBitmapState.mTint,
+ mBitmapState.mBlendMode);
computeBitmapSize();
}
}
diff --git a/graphics/java/android/graphics/drawable/ClipDrawable.java b/graphics/java/android/graphics/drawable/ClipDrawable.java
index d925b6b95c66..31fdb025bbc5 100644
--- a/graphics/java/android/graphics/drawable/ClipDrawable.java
+++ b/graphics/java/android/graphics/drawable/ClipDrawable.java
@@ -23,6 +23,7 @@ import org.xmlpull.v1.XmlPullParserException;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.Resources.Theme;
@@ -58,6 +59,7 @@ public class ClipDrawable extends DrawableWrapper {
private final Rect mTmpRect = new Rect();
+ @UnsupportedAppUsage
private ClipState mState;
ClipDrawable() {
diff --git a/graphics/java/android/graphics/drawable/ColorDrawable.java b/graphics/java/android/graphics/drawable/ColorDrawable.java
index 9ae747de2f82..f5fa8c546bed 100644
--- a/graphics/java/android/graphics/drawable/ColorDrawable.java
+++ b/graphics/java/android/graphics/drawable/ColorDrawable.java
@@ -20,13 +20,20 @@ import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
-import android.graphics.*;
-import android.graphics.PorterDuff.Mode;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
+import android.graphics.BlendMode;
+import android.graphics.BlendModeColorFilter;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Xfermode;
import android.util.AttributeSet;
import android.view.ViewDebug;
@@ -46,11 +53,12 @@ import java.io.IOException;
* @attr ref android.R.styleable#ColorDrawable_color
*/
public class ColorDrawable extends Drawable {
+ @UnsupportedAppUsage
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@ViewDebug.ExportedProperty(deepExport = true, prefix = "state_")
private ColorState mColorState;
- private PorterDuffColorFilter mTintFilter;
+ private BlendModeColorFilter mBlendModeColorFilter;
private boolean mMutated;
@@ -103,9 +111,10 @@ public class ColorDrawable extends Drawable {
@Override
public void draw(Canvas canvas) {
final ColorFilter colorFilter = mPaint.getColorFilter();
- if ((mColorState.mUseColor >>> 24) != 0 || colorFilter != null || mTintFilter != null) {
+ if ((mColorState.mUseColor >>> 24) != 0 || colorFilter != null
+ || mBlendModeColorFilter != null) {
if (colorFilter == null) {
- mPaint.setColorFilter(mTintFilter);
+ mPaint.setColorFilter(mBlendModeColorFilter);
}
mPaint.setColor(mColorState.mUseColor);
@@ -141,9 +150,13 @@ public class ColorDrawable extends Drawable {
}
/**
- * Returns the alpha value of this drawable's color.
+ * Returns the alpha value of this drawable's color. Note this may not be the same alpha value
+ * provided in {@link Drawable#setAlpha(int)}. Instead this will return the alpha of the color
+ * combined with the alpha provided by setAlpha
*
* @return A value between 0 and 255.
+ *
+ * @see ColorDrawable#setAlpha(int)
*/
@Override
public int getAlpha() {
@@ -151,7 +164,9 @@ public class ColorDrawable extends Drawable {
}
/**
- * Sets the color's alpha value.
+ * Applies the given alpha to the underlying color. Note if the color already has
+ * an alpha applied to it, this will apply this alpha to the existing value instead of
+ * overwriting it.
*
* @param alpha The alpha value to set, between 0 and 255.
*/
@@ -180,25 +195,39 @@ public class ColorDrawable extends Drawable {
mPaint.setColorFilter(colorFilter);
}
+ /**
+ * Returns the color filter applied to this color configured by
+ * {@link #setColorFilter(ColorFilter)}
+ *
+ * @see android.graphics.drawable.Drawable#getColorFilter()
+ */
+ @Override
+ public @Nullable ColorFilter getColorFilter() {
+ return mPaint.getColorFilter();
+ }
+
@Override
public void setTintList(ColorStateList tint) {
mColorState.mTint = tint;
- mTintFilter = updateTintFilter(mTintFilter, tint, mColorState.mTintMode);
+ mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, tint,
+ mColorState.mBlendMode);
invalidateSelf();
}
@Override
- public void setTintMode(Mode tintMode) {
- mColorState.mTintMode = tintMode;
- mTintFilter = updateTintFilter(mTintFilter, mColorState.mTint, tintMode);
+ public void setTintBlendMode(@NonNull BlendMode blendMode) {
+ mColorState.mBlendMode = blendMode;
+ mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, mColorState.mTint,
+ blendMode);
invalidateSelf();
}
@Override
protected boolean onStateChange(int[] stateSet) {
final ColorState state = mColorState;
- if (state.mTint != null && state.mTintMode != null) {
- mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
+ if (state.mTint != null && state.mBlendMode != null) {
+ mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, state.mTint,
+ state.mBlendMode);
return true;
}
return false;
@@ -236,7 +265,7 @@ public class ColorDrawable extends Drawable {
@Override
public int getOpacity() {
- if (mTintFilter != null || mPaint.getColorFilter() != null) {
+ if (mBlendModeColorFilter != null || mPaint.getColorFilter() != null) {
return PixelFormat.TRANSLUCENT;
}
@@ -319,10 +348,11 @@ public class ColorDrawable extends Drawable {
int[] mThemeAttrs;
int mBaseColor; // base color, independent of setAlpha()
@ViewDebug.ExportedProperty
+ @UnsupportedAppUsage
int mUseColor; // basecolor modulated by setAlpha()
@Config int mChangingConfigurations;
ColorStateList mTint = null;
- Mode mTintMode = DEFAULT_TINT_MODE;
+ BlendMode mBlendMode = DEFAULT_BLEND_MODE;
ColorState() {
// Empty constructor.
@@ -334,7 +364,7 @@ public class ColorDrawable extends Drawable {
mUseColor = state.mUseColor;
mChangingConfigurations = state.mChangingConfigurations;
mTint = state.mTint;
- mTintMode = state.mTintMode;
+ mBlendMode = state.mBlendMode;
}
@Override
@@ -372,6 +402,7 @@ public class ColorDrawable extends Drawable {
* after inflating or applying a theme.
*/
private void updateLocalState(Resources r) {
- mTintFilter = updateTintFilter(mTintFilter, mColorState.mTint, mColorState.mTintMode);
+ mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, mColorState.mTint,
+ mColorState.mBlendMode);
}
}
diff --git a/graphics/java/android/graphics/drawable/ColorStateListDrawable.java b/graphics/java/android/graphics/drawable/ColorStateListDrawable.java
new file mode 100644
index 000000000000..20cd825fe306
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/ColorStateListDrawable.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.graphics.drawable;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.ActivityInfo;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.BlendMode;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.util.MathUtils;
+
+/**
+ * A Drawable that manages a {@link ColorDrawable} to make it stateful and backed by a
+ * {@link ColorStateList}.
+ */
+public class ColorStateListDrawable extends Drawable implements Drawable.Callback {
+ private ColorDrawable mColorDrawable;
+ private ColorStateListDrawableState mState;
+ private boolean mMutated = false;
+
+ public ColorStateListDrawable() {
+ mState = new ColorStateListDrawableState();
+ initializeColorDrawable();
+ }
+
+ public ColorStateListDrawable(@NonNull ColorStateList colorStateList) {
+ mState = new ColorStateListDrawableState();
+ initializeColorDrawable();
+ setColorStateList(colorStateList);
+ }
+
+ @Override
+ public void draw(@NonNull Canvas canvas) {
+ mColorDrawable.draw(canvas);
+ }
+
+ @Override
+ @IntRange(from = 0, to = 255)
+ public int getAlpha() {
+ return mColorDrawable.getAlpha();
+ }
+
+ @Override
+ public boolean isStateful() {
+ return mState.isStateful();
+ }
+
+ @Override
+ public boolean hasFocusStateSpecified() {
+ return mState.hasFocusStateSpecified();
+ }
+
+ @Override
+ public @NonNull Drawable getCurrent() {
+ return mColorDrawable;
+ }
+
+ @Override
+ public void applyTheme(@NonNull Resources.Theme t) {
+ super.applyTheme(t);
+
+ if (mState.mColor != null) {
+ setColorStateList(mState.mColor.obtainForTheme(t));
+ }
+
+ if (mState.mTint != null) {
+ setTintList(mState.mTint.obtainForTheme(t));
+ }
+ }
+
+ @Override
+ public boolean canApplyTheme() {
+ return super.canApplyTheme() || mState.canApplyTheme();
+ }
+
+ @Override
+ public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {
+ mState.mAlpha = alpha;
+ onStateChange(getState());
+ }
+
+ /**
+ * Remove the alpha override, reverting to the alpha defined on each color in the
+ * {@link ColorStateList}.
+ */
+ public void clearAlpha() {
+ mState.mAlpha = -1;
+ onStateChange(getState());
+ }
+
+ @Override
+ public void setTintList(@Nullable ColorStateList tint) {
+ mState.mTint = tint;
+ mColorDrawable.setTintList(tint);
+ onStateChange(getState());
+ }
+
+ @Override
+ public void setTintBlendMode(@NonNull BlendMode blendMode) {
+ mState.mBlendMode = blendMode;
+ mColorDrawable.setTintBlendMode(blendMode);
+ onStateChange(getState());
+ }
+
+ @Override
+ public @Nullable ColorFilter getColorFilter() {
+ return mColorDrawable.getColorFilter();
+ }
+
+ @Override
+ public void setColorFilter(@Nullable ColorFilter colorFilter) {
+ mColorDrawable.setColorFilter(colorFilter);
+ }
+
+ @Override
+ public @PixelFormat.Opacity int getOpacity() {
+ return mColorDrawable.getOpacity();
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+ mColorDrawable.setBounds(bounds);
+ }
+
+ @Override
+ protected boolean onStateChange(int[] state) {
+ if (mState.mColor != null) {
+ int color = mState.mColor.getColorForState(state, mState.mColor.getDefaultColor());
+
+ if (mState.mAlpha != -1) {
+ color = (color & 0xFFFFFF) | MathUtils.constrain(mState.mAlpha, 0, 255) << 24;
+ }
+
+ if (color != mColorDrawable.getColor()) {
+ mColorDrawable.setColor(color);
+ mColorDrawable.setState(state);
+ return true;
+ } else {
+ return mColorDrawable.setState(state);
+ }
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void invalidateDrawable(@NonNull Drawable who) {
+ if (who == mColorDrawable && getCallback() != null) {
+ getCallback().invalidateDrawable(this);
+ }
+ }
+
+ @Override
+ public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
+ if (who == mColorDrawable && getCallback() != null) {
+ getCallback().scheduleDrawable(this, what, when);
+ }
+ }
+
+ @Override
+ public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
+ if (who == mColorDrawable && getCallback() != null) {
+ getCallback().unscheduleDrawable(this, what);
+ }
+ }
+
+ @Override
+ public @NonNull ConstantState getConstantState() {
+ mState.mChangingConfigurations = mState.mChangingConfigurations
+ | (getChangingConfigurations() & ~mState.getChangingConfigurations());
+ return mState;
+ }
+
+ /**
+ * Returns the ColorStateList backing this Drawable, or a new ColorStateList of the default
+ * ColorDrawable color if one hasn't been defined yet.
+ *
+ * @return a ColorStateList
+ */
+ public @NonNull ColorStateList getColorStateList() {
+ if (mState.mColor == null) {
+ return ColorStateList.valueOf(mColorDrawable.getColor());
+ } else {
+ return mState.mColor;
+ }
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return super.getChangingConfigurations() | mState.getChangingConfigurations();
+ }
+
+ @Override
+ public @NonNull Drawable mutate() {
+ if (!mMutated && super.mutate() == this) {
+ mState = new ColorStateListDrawableState(mState);
+ mMutated = true;
+ }
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void clearMutated() {
+ super.clearMutated();
+ mMutated = false;
+ }
+
+ /**
+ * Replace this Drawable's ColorStateList. It is not copied, so changes will propagate on the
+ * next call to {@link #setState(int[])}.
+ *
+ * @param colorStateList A color state list to attach.
+ */
+ public void setColorStateList(@NonNull ColorStateList colorStateList) {
+ mState.mColor = colorStateList;
+ onStateChange(getState());
+ }
+
+ static final class ColorStateListDrawableState extends ConstantState {
+ ColorStateList mColor = null;
+ ColorStateList mTint = null;
+ int mAlpha = -1;
+ BlendMode mBlendMode = DEFAULT_BLEND_MODE;
+ @ActivityInfo.Config int mChangingConfigurations = 0;
+
+ ColorStateListDrawableState() {
+ }
+
+ ColorStateListDrawableState(ColorStateListDrawableState state) {
+ mColor = state.mColor;
+ mTint = state.mTint;
+ mAlpha = state.mAlpha;
+ mBlendMode = state.mBlendMode;
+ mChangingConfigurations = state.mChangingConfigurations;
+ }
+
+ @Override
+ public Drawable newDrawable() {
+ return new ColorStateListDrawable(this);
+ }
+
+ @Override
+ public @ActivityInfo.Config int getChangingConfigurations() {
+ return mChangingConfigurations
+ | (mColor != null ? mColor.getChangingConfigurations() : 0)
+ | (mTint != null ? mTint.getChangingConfigurations() : 0);
+ }
+
+ public boolean isStateful() {
+ return (mColor != null && mColor.isStateful())
+ || (mTint != null && mTint.isStateful());
+ }
+
+ public boolean hasFocusStateSpecified() {
+ return (mColor != null && mColor.hasFocusStateSpecified())
+ || (mTint != null && mTint.hasFocusStateSpecified());
+ }
+
+ @Override
+ public boolean canApplyTheme() {
+ return (mColor != null && mColor.canApplyTheme())
+ || (mTint != null && mTint.canApplyTheme());
+ }
+ }
+
+ private ColorStateListDrawable(@NonNull ColorStateListDrawableState state) {
+ mState = state;
+ initializeColorDrawable();
+ }
+
+ private void initializeColorDrawable() {
+ mColorDrawable = new ColorDrawable();
+ mColorDrawable.setCallback(this);
+
+ if (mState.mTint != null) {
+ mColorDrawable.setTintList(mState.mTint);
+ }
+
+ if (mState.mBlendMode != DEFAULT_BLEND_MODE) {
+ mColorDrawable.setTintBlendMode(mState.mBlendMode);
+ }
+ }
+}
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 8af2fd8bbb5e..08409869c626 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -16,17 +16,13 @@
package android.graphics.drawable;
-import com.android.internal.R;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import android.annotation.AttrRes;
import android.annotation.ColorInt;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.content.res.Resources;
@@ -34,6 +30,8 @@ import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.graphics.BlendMode;
+import android.graphics.BlendModeColorFilter;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
@@ -57,6 +55,11 @@ import android.util.TypedValue;
import android.util.Xml;
import android.view.View;
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -181,11 +184,13 @@ public abstract class Drawable {
private static final Rect ZERO_BOUNDS_RECT = new Rect();
static final PorterDuff.Mode DEFAULT_TINT_MODE = PorterDuff.Mode.SRC_IN;
+ static final BlendMode DEFAULT_BLEND_MODE = BlendMode.SRC_IN;
private int[] mStateSet = StateSet.WILD_CARD;
private int mLevel = 0;
private @Config int mChangingConfigurations = 0;
private Rect mBounds = ZERO_BOUNDS_RECT; // lazily becomes a new Rect()
+ @UnsupportedAppUsage
private WeakReference<Callback> mCallback = null;
private boolean mVisible = true;
@@ -204,9 +209,28 @@ public abstract class Drawable {
*
* @hide
*/
+ @UnsupportedAppUsage
protected int mSrcDensityOverride = 0;
/**
+ * Flag used to break the recursive loop between setTintBlendMode(PorterDuff.Mode) and
+ * setTintBlendMode(BlendMode) as each default implementation invokes the other in order to
+ * support new use cases that utilize the new blending modes as well as support the legacy
+ * use cases. This flag tracks that {@link #setTintBlendMode(BlendMode)} is only invoked once
+ * per invocation.
+ */
+ private boolean mSetBlendModeInvoked = false;
+
+ /**
+ * Flag used to break the recursive loop between setTintBlendMode(PorterDuff.Mode) and
+ * setTintBlendMode(BlendMode) as each default implementation invokes the other in order to
+ * support new use cases that utilize the new blending modes as well as support the legacy
+ * use cases. This flag tracks that {@link #setTintMode(Mode)} is only invoked once
+ * per invocation;
+ */
+ private boolean mSetTintModeInvoked = false;
+
+ /**
* Draw in its bounds (set via setBounds) respecting optional effects such
* as alpha (set via setAlpha) and color filter (set via setColorFilter).
*
@@ -592,7 +616,12 @@ public abstract class Drawable {
* <p class="note"><strong>Note:</strong> Setting a color filter disables
* {@link #setTintList(ColorStateList) tint}.
* </p>
+ *
+ * @see {@link #setColorFilter(ColorFilter)} }
+ * @deprecated use {@link #setColorFilter(ColorFilter)} with an instance
+ * of {@link android.graphics.BlendModeColorFilter}
*/
+ @Deprecated
public void setColorFilter(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
if (getColorFilter() instanceof PorterDuffColorFilter) {
PorterDuffColorFilter existing = (PorterDuffColorFilter) getColorFilter();
@@ -622,6 +651,7 @@ public abstract class Drawable {
* @param tintColor Color to use for tinting this drawable
* @see #setTintList(ColorStateList)
* @see #setTintMode(PorterDuff.Mode)
+ * @see #setTintBlendMode(BlendMode)
*/
public void setTint(@ColorInt int tintColor) {
setTintList(ColorStateList.valueOf(tintColor));
@@ -643,6 +673,7 @@ public abstract class Drawable {
* {@code null} to clear the tint
* @see #setTint(int)
* @see #setTintMode(PorterDuff.Mode)
+ * @see #setTintBlendMode(BlendMode)
*/
public void setTintList(@Nullable ColorStateList tint) {}
@@ -657,11 +688,44 @@ public abstract class Drawable {
* {@link #setColorFilter(int, PorterDuff.Mode)} overrides tint.
* </p>
*
- * @param tintMode A Porter-Duff blending mode
+ * @param tintMode A Porter-Duff blending mode to apply to the drawable, a value of null sets
+ * the default Porter-Diff blending mode value
+ * of {@link PorterDuff.Mode#SRC_IN}
* @see #setTint(int)
* @see #setTintList(ColorStateList)
*/
- public void setTintMode(@NonNull PorterDuff.Mode tintMode) {}
+ public void setTintMode(@Nullable PorterDuff.Mode tintMode) {
+ if (!mSetTintModeInvoked) {
+ mSetTintModeInvoked = true;
+ BlendMode mode = tintMode != null ? BlendMode.fromValue(tintMode.nativeInt) : null;
+ setTintBlendMode(mode != null ? mode : Drawable.DEFAULT_BLEND_MODE);
+ mSetTintModeInvoked = false;
+ }
+ }
+
+ /**
+ * Specifies a tint blending mode for this drawable.
+ * <p>
+ * Defines how this drawable's tint color should be blended into the drawable
+ * before it is drawn to screen. Default tint mode is {@link BlendMode#SRC_IN}.
+ * </p>
+ * <p class="note"><strong>Note:</strong> Setting a color filter via
+ * {@link #setColorFilter(ColorFilter)}
+ * </p>
+ *
+ * @param blendMode BlendMode to apply to the drawable, a value of null sets the default
+ * blend mode value of {@link BlendMode#SRC_IN}
+ * @see #setTint(int)
+ * @see #setTintList(ColorStateList)
+ */
+ public void setTintBlendMode(@Nullable BlendMode blendMode) {
+ if (!mSetBlendModeInvoked) {
+ mSetBlendModeInvoked = true;
+ PorterDuff.Mode mode = BlendMode.blendModeToPorterDuffMode(blendMode);
+ setTintMode(mode != null ? mode : Drawable.DEFAULT_TINT_MODE);
+ mSetBlendModeInvoked = false;
+ }
+ }
/**
* Returns the current color filter, or {@code null} if none set.
@@ -710,9 +774,11 @@ public abstract class Drawable {
}
/**
- * Whether this drawable requests projection.
+ * Whether this drawable requests projection. Indicates that the
+ * {@link android.graphics.RenderNode} this Drawable will draw into should be drawn immediately
+ * after the closest ancestor RenderNode containing a projection receiver.
*
- * @hide magic!
+ * @see android.graphics.RenderNode#setProjectBackwards(boolean)
*/
public boolean isProjected() {
return false;
@@ -930,11 +996,13 @@ public abstract class Drawable {
* do account for the value of {@link #setAlpha}, but the general behavior is dependent
* upon the implementation of the subclass.
*
+ * @deprecated This method is no longer used in graphics optimizations
+ *
* @return int The opacity class of the Drawable.
*
* @see android.graphics.PixelFormat
*/
- public abstract @PixelFormat.Opacity int getOpacity();
+ @Deprecated public abstract @PixelFormat.Opacity int getOpacity();
/**
* Return the appropriate opacity value for two source opacities. If
@@ -1088,7 +1156,6 @@ public abstract class Drawable {
* Return in insets the layout insets suggested by this Drawable for use with alignment
* operations during layout.
*
- * @hide
*/
public @NonNull Insets getOpticalInsets() {
return Insets.NONE;
@@ -1234,6 +1301,9 @@ public abstract class Drawable {
return ImageDecoder.decodeDrawable(source, (decoder, info, src) -> {
decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+ decoder.setOnPartialImageListener((e) -> {
+ return e.getError() == ImageDecoder.DecodeException.SOURCE_INCOMPLETE;
+ });
});
} catch (IOException e) {
/* do nothing.
@@ -1390,6 +1460,7 @@ public abstract class Drawable {
* @throws XmlPullParserException
* @throws IOException
*/
+ @UnsupportedAppUsage
void inflateWithAttributes(@NonNull @SuppressWarnings("unused") Resources r,
@NonNull @SuppressWarnings("unused") XmlPullParser parser, @NonNull TypedArray attrs,
@AttrRes int visibleAttr) throws XmlPullParserException, IOException {
@@ -1509,6 +1580,7 @@ public abstract class Drawable {
* Ensures the tint filter is consistent with the current tint color and
* mode.
*/
+ @UnsupportedAppUsage
@Nullable PorterDuffColorFilter updateTintFilter(@Nullable PorterDuffColorFilter tintFilter,
@Nullable ColorStateList tint, @Nullable PorterDuff.Mode tintMode) {
if (tint == null || tintMode == null) {
@@ -1516,15 +1588,28 @@ public abstract class Drawable {
}
final int color = tint.getColorForState(getState(), Color.TRANSPARENT);
- if (tintFilter == null) {
+ if (tintFilter == null || tintFilter.getColor() != color
+ || tintFilter.getMode() != tintMode) {
return new PorterDuffColorFilter(color, tintMode);
}
- tintFilter.setColor(color);
- tintFilter.setMode(tintMode);
return tintFilter;
}
+ @Nullable BlendModeColorFilter updateBlendModeFilter(@Nullable BlendModeColorFilter blendFilter,
+ @Nullable ColorStateList tint, @Nullable BlendMode blendMode) {
+ if (tint == null || blendMode == null) {
+ return null;
+ }
+
+ final int color = tint.getColorForState(getState(), Color.TRANSPARENT);
+ if (blendFilter == null || blendFilter.getColor() != color
+ || blendFilter.getMode() != blendMode) {
+ return new BlendModeColorFilter(color, blendMode);
+ }
+ return blendFilter;
+ }
+
/**
* Obtains styled attributes from the theme, if available, or unstyled
* resources if the theme is null.
@@ -1615,6 +1700,7 @@ public abstract class Drawable {
*
* @hide
*/
+ @UnsupportedAppUsage
public static PorterDuff.Mode parseTintMode(int value, Mode defaultMode) {
switch (value) {
case 3: return Mode.SRC_OVER;
@@ -1626,5 +1712,26 @@ public abstract class Drawable {
default: return defaultMode;
}
}
+
+ /**
+ * Parses a {@link android.graphics.BlendMode} from a tintMode
+ * attribute's enum value.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static BlendMode parseBlendMode(int value, BlendMode defaultMode) {
+ switch (value) {
+ case 3: return BlendMode.SRC_OVER;
+ case 5: return BlendMode.SRC_IN;
+ case 9: return BlendMode.SRC_ATOP;
+ // b/73224934 PorterDuff Multiply maps to Skia Modulate so actually
+ // return BlendMode.MODULATE here
+ case 14: return BlendMode.MODULATE;
+ case 15: return BlendMode.SCREEN;
+ case 16: return BlendMode.PLUS;
+ default: return defaultMode;
+ }
+ }
}
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index aa4cd9cba4a7..090d915a2f67 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -17,17 +17,19 @@
package android.graphics.drawable;
import android.annotation.NonNull;
+import android.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
+import android.graphics.BlendMode;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Insets;
import android.graphics.Outline;
import android.graphics.PixelFormat;
-import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
+import android.os.Build;
import android.os.SystemClock;
import android.util.DisplayMetrics;
import android.util.LayoutDirection;
@@ -54,9 +56,11 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
* to improve the quality at negligible cost.
*/
private static final boolean DEFAULT_DITHER = true;
+ @UnsupportedAppUsage
private DrawableContainerState mDrawableContainerState;
private Rect mHotspotBounds;
private Drawable mCurrDrawable;
+ @UnsupportedAppUsage
private Drawable mLastDrawable;
private int mAlpha = 0xFF;
@@ -120,9 +124,6 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
return result;
}
- /**
- * @hide
- */
@Override
public Insets getOpticalInsets() {
if (mCurrDrawable != null) {
@@ -195,14 +196,14 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
}
@Override
- public void setTintMode(Mode tintMode) {
+ public void setTintBlendMode(@NonNull BlendMode blendMode) {
mDrawableContainerState.mHasTintMode = true;
- if (mDrawableContainerState.mTintMode != tintMode) {
- mDrawableContainerState.mTintMode = tintMode;
+ if (mDrawableContainerState.mBlendMode != blendMode) {
+ mDrawableContainerState.mBlendMode = blendMode;
if (mCurrDrawable != null) {
- mCurrDrawable.setTintMode(tintMode);
+ mCurrDrawable.setTintBlendMode(blendMode);
}
}
}
@@ -543,7 +544,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
d.setTintList(mDrawableContainerState.mTintList);
}
if (mDrawableContainerState.mHasTintMode) {
- d.setTintMode(mDrawableContainerState.mTintMode);
+ d.setTintBlendMode(mDrawableContainerState.mBlendMode);
}
}
@@ -689,11 +690,13 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
@Config int mChildrenChangingConfigurations;
SparseArray<ConstantState> mDrawableFutures;
+ @UnsupportedAppUsage
Drawable[] mDrawables;
int mNumChildren;
boolean mVariablePadding = false;
boolean mCheckedPadding;
+ @UnsupportedAppUsage
Rect mConstantPadding;
boolean mConstantSize = false;
@@ -723,16 +726,18 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
boolean mAutoMirrored;
ColorFilter mColorFilter;
+ @UnsupportedAppUsage
boolean mHasColorFilter;
ColorStateList mTintList;
- Mode mTintMode;
+ BlendMode mBlendMode;
boolean mHasTintList;
boolean mHasTintMode;
/**
* @hide
*/
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
protected DrawableContainerState(DrawableContainerState orig, DrawableContainer owner,
Resources res) {
mOwner = owner;
@@ -757,7 +762,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
mColorFilter = orig.mColorFilter;
mHasColorFilter = orig.mHasColorFilter;
mTintList = orig.mTintList;
- mTintMode = orig.mTintMode;
+ mBlendMode = orig.mBlendMode;
mHasTintList = orig.mHasTintList;
mHasTintMode = orig.mHasTintMode;
diff --git a/graphics/java/android/graphics/drawable/DrawableInflater.java b/graphics/java/android/graphics/drawable/DrawableInflater.java
index 0ee9071f4d06..bad3791a9c24 100644
--- a/graphics/java/android/graphics/drawable/DrawableInflater.java
+++ b/graphics/java/android/graphics/drawable/DrawableInflater.java
@@ -22,6 +22,7 @@ import org.xmlpull.v1.XmlPullParserException;
import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
@@ -49,6 +50,7 @@ public final class DrawableInflater {
new HashMap<>();
private final Resources mRes;
+ @UnsupportedAppUsage
private final ClassLoader mClassLoader;
/**
diff --git a/graphics/java/android/graphics/drawable/DrawableWrapper.java b/graphics/java/android/graphics/drawable/DrawableWrapper.java
index b71f3ef594a9..64fc7042dfc7 100644
--- a/graphics/java/android/graphics/drawable/DrawableWrapper.java
+++ b/graphics/java/android/graphics/drawable/DrawableWrapper.java
@@ -16,35 +16,38 @@
package android.graphics.drawable;
-import com.android.internal.R;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
+import android.graphics.BlendMode;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Insets;
import android.graphics.Outline;
import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
import android.graphics.Rect;
+import android.graphics.Xfermode;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.View;
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.IOException;
/**
* Drawable container with only one child element.
*/
public abstract class DrawableWrapper extends Drawable implements Drawable.Callback {
+ @UnsupportedAppUsage
private DrawableWrapperState mState;
private Drawable mDrawable;
private boolean mMutated;
@@ -62,7 +65,7 @@ public abstract class DrawableWrapper extends Drawable implements Drawable.Callb
*/
public DrawableWrapper(@Nullable Drawable dr) {
mState = null;
- mDrawable = dr;
+ setDrawable(dr);
}
/**
@@ -78,6 +81,16 @@ public abstract class DrawableWrapper extends Drawable implements Drawable.Callb
}
/**
+ * @hide
+ */
+ @Override
+ public void setXfermode(Xfermode mode) {
+ if (mDrawable != null) {
+ mDrawable.setXfermode(mode);
+ }
+ }
+
+ /**
* Sets the wrapped drawable.
*
* @param dr the wrapped drawable
@@ -240,7 +253,6 @@ public abstract class DrawableWrapper extends Drawable implements Drawable.Callb
return mDrawable != null && mDrawable.getPadding(padding);
}
- /** @hide */
@Override
public Insets getOpticalInsets() {
return mDrawable != null ? mDrawable.getOpticalInsets() : Insets.NONE;
@@ -312,9 +324,9 @@ public abstract class DrawableWrapper extends Drawable implements Drawable.Callb
}
@Override
- public void setTintMode(@Nullable PorterDuff.Mode tintMode) {
+ public void setTintBlendMode(@NonNull BlendMode blendMode) {
if (mDrawable != null) {
- mDrawable.setTintMode(tintMode);
+ mDrawable.setTintBlendMode(blendMode);
}
}
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index dfdddb2a599a..b9945cc735d8 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -17,14 +17,19 @@
package android.graphics.drawable;
import android.annotation.ColorInt;
+import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.Px;
+import android.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
+import android.graphics.BlendMode;
+import android.graphics.BlendModeColorFilter;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
@@ -35,14 +40,13 @@ import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.SweepGradient;
import android.graphics.Xfermode;
+import android.os.Build;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -155,13 +159,17 @@ public class GradientDrawable extends Drawable {
private static final float DEFAULT_INNER_RADIUS_RATIO = 3.0f;
private static final float DEFAULT_THICKNESS_RATIO = 9.0f;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
private GradientState mGradientState;
+ @UnsupportedAppUsage
private final Paint mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124051827)
private Rect mPadding;
+ @UnsupportedAppUsage
private Paint mStrokePaint; // optional, set by the caller
private ColorFilter mColorFilter; // optional, set by the caller
- private PorterDuffColorFilter mTintFilter;
+ private BlendModeColorFilter mBlendModeColorFilter;
private int mAlpha = 0xFF; // modified by the caller
private final Path mPath = new Path();
@@ -392,6 +400,7 @@ public class GradientDrawable extends Drawable {
e = new DashPathEffect(new float[] { dashWidth, dashGap }, 0);
}
mStrokePaint.setPathEffect(e);
+ mGradientIsDirty = true;
invalidateSelf();
}
@@ -574,9 +583,9 @@ public class GradientDrawable extends Drawable {
* The default value for this property is {@code false}.
* <p>
* <strong>Note</strong>: This property corresponds to the
- * {@code android:useLevel} attribute on the inner {@code &lt;gradient&gt;}
+ * {@code android:useLevel} attribute on the inner {@code <gradient>}
* tag, NOT the {@code android:useLevel} attribute on the outer
- * {@code &lt;shape&gt;} tag. For example,
+ * {@code <shape>} tag. For example,
* <pre>{@code
* <shape ...>
* <gradient
@@ -628,7 +637,7 @@ public class GradientDrawable extends Drawable {
* @see #setOrientation(Orientation)
*/
public Orientation getOrientation() {
- return mGradientState.mOrientation;
+ return mGradientState.getOrientation();
}
/**
@@ -644,7 +653,7 @@ public class GradientDrawable extends Drawable {
* @see #getOrientation()
*/
public void setOrientation(Orientation orientation) {
- mGradientState.mOrientation = orientation;
+ mGradientState.setOrientation(orientation);
mGradientIsDirty = true;
invalidateSelf();
}
@@ -663,8 +672,29 @@ public class GradientDrawable extends Drawable {
* @see #mutate()
* @see #setColor(int)
*/
- public void setColors(@ColorInt int[] colors) {
+ public void setColors(@Nullable @ColorInt int[] colors) {
+ setColors(colors, null);
+ }
+
+ /**
+ * Sets the colors and offsets used to draw the gradient.
+ * <p>
+ * Each color is specified as an ARGB integer and the array must contain at
+ * least 2 colors.
+ * <p>
+ * <strong>Note</strong>: changing colors will affect all instances of a
+ * drawable loaded from a resource. It is recommended to invoke
+ * {@link #mutate()} before changing the colors.
+ *
+ * @param colors an array containing 2 or more ARGB colors
+ * @param offsets optional array of floating point parameters representing the positions
+ * of the colors. Null evenly disperses the colors
+ * @see #mutate()
+ * @see #setColors(int[])
+ */
+ public void setColors(@Nullable @ColorInt int[] colors, @Nullable float[] offsets) {
mGradientState.setGradientColors(colors);
+ mGradientState.mPositions = offsets;
mGradientIsDirty = true;
invalidateSelf();
}
@@ -701,7 +731,7 @@ public class GradientDrawable extends Drawable {
mStrokePaint.getStrokeWidth() > 0;
final boolean haveFill = currFillAlpha > 0;
final GradientState st = mGradientState;
- final ColorFilter colorFilter = mColorFilter != null ? mColorFilter : mTintFilter;
+ final ColorFilter colorFilter = mColorFilter != null ? mColorFilter : mBlendModeColorFilter;
/* we need a layer iff we're drawing both a fill and stroke, and the
stroke is non-opaque, and our shapetype actually supports
@@ -843,6 +873,123 @@ public class GradientDrawable extends Drawable {
}
}
+ /**
+ * Inner radius of the ring expressed as a ratio of the ring's width.
+ *
+ * @see #getInnerRadiusRatio()
+ * @attr ref android.R.styleable#GradientDrawable_innerRadiusRatio
+ */
+ public void setInnerRadiusRatio(
+ @FloatRange(from = 0.0f, fromInclusive = false) float innerRadiusRatio) {
+ if (innerRadiusRatio <= 0) {
+ throw new IllegalArgumentException("Ratio must be greater than zero");
+ }
+ mGradientState.mInnerRadiusRatio = innerRadiusRatio;
+ mPathIsDirty = true;
+ invalidateSelf();
+ }
+
+ /**
+ * Return the inner radius of the ring expressed as a ratio of the ring's width.
+ *
+ * @see #setInnerRadiusRatio(float)
+ * @attr ref android.R.styleable#GradientDrawable_innerRadiusRatio
+ */
+ public float getInnerRadiusRatio() {
+ return mGradientState.mInnerRadiusRatio;
+ }
+
+ /**
+ * Configure the inner radius of the ring.
+ *
+ * @see #getInnerRadius()
+ * @attr ref android.R.styleable#GradientDrawable_innerRadius
+ */
+ public void setInnerRadius(@Px int innerRadius) {
+ mGradientState.mInnerRadius = innerRadius;
+ mPathIsDirty = true;
+ invalidateSelf();
+ }
+
+ /**
+ * Retrn the inner radius of the ring
+ *
+ * @see #setInnerRadius(int)
+ * @attr ref android.R.styleable#GradientDrawable_innerRadius
+ */
+ public @Px int getInnerRadius() {
+ return mGradientState.mInnerRadius;
+ }
+
+ /**
+ * Configure the thickness of the ring expressed as a ratio of the ring's width.
+ *
+ * @see #getThicknessRatio()
+ * @attr ref android.R.styleable#GradientDrawable_thicknessRatio
+ */
+ public void setThicknessRatio(
+ @FloatRange(from = 0.0f, fromInclusive = false) float thicknessRatio) {
+ if (thicknessRatio <= 0) {
+ throw new IllegalArgumentException("Ratio must be greater than zero");
+ }
+ mGradientState.mThicknessRatio = thicknessRatio;
+ mPathIsDirty = true;
+ invalidateSelf();
+ }
+
+ /**
+ * Return the thickness ratio of the ring expressed as a ratio of the ring's width.
+ *
+ * @see #setThicknessRatio(float)
+ * @attr ref android.R.styleable#GradientDrawable_thicknessRatio
+ */
+ public float getThicknessRatio() {
+ return mGradientState.mThicknessRatio;
+ }
+
+ /**
+ * Configure the thickness of the ring.
+ *
+ * @attr ref android.R.styleable#GradientDrawable_thickness
+ */
+ public void setThickness(@Px int thickness) {
+ mGradientState.mThickness = thickness;
+ mPathIsDirty = true;
+ invalidateSelf();
+ }
+
+ /**
+ * Return the thickness of the ring
+ *
+ * @see #setThickness(int)
+ * @attr ref android.R.styleable#GradientDrawable_thickness
+ */
+ public @Px int getThickness() {
+ return mGradientState.mThickness;
+ }
+
+ /**
+ * Configure the padding of the gradient shape
+ * @param left Left padding of the gradient shape
+ * @param top Top padding of the gradient shape
+ * @param right Right padding of the gradient shape
+ * @param bottom Bottom padding of the gradient shape
+ *
+ * @attr ref android.R.styleable#GradientDrawablePadding_left
+ * @attr ref android.R.styleable#GradientDrawablePadding_top
+ * @attr ref android.R.styleable#GradientDrawablePadding_right
+ * @attr ref android.R.styleable#GradientDrawablePadding_bottom
+ */
+ public void setPadding(@Px int left, @Px int top, @Px int right, @Px int bottom) {
+ if (mGradientState.mPadding == null) {
+ mGradientState.mPadding = new Rect();
+ }
+
+ mGradientState.mPadding.set(left, top, right, bottom);
+ mPadding = mGradientState.mPadding;
+ invalidateSelf();
+ }
+
private Path buildRing(GradientState st) {
if (mRingPath != null && (!st.mUseLevelForShape || !mPathIsDirty)) return mRingPath;
mPathIsDirty = false;
@@ -930,16 +1077,15 @@ public class GradientDrawable extends Drawable {
* @see #getColor
*/
public void setColor(@Nullable ColorStateList colorStateList) {
- mGradientState.setSolidColors(colorStateList);
- final int color;
if (colorStateList == null) {
- color = Color.TRANSPARENT;
+ setColor(Color.TRANSPARENT);
} else {
final int[] stateSet = getState();
- color = colorStateList.getColorForState(stateSet, 0);
+ final int color = colorStateList.getColorForState(stateSet, 0);
+ mGradientState.setSolidColors(colorStateList);
+ mFillPaint.setColor(color);
+ invalidateSelf();
}
- mFillPaint.setColor(color);
- invalidateSelf();
}
/**
@@ -984,8 +1130,9 @@ public class GradientDrawable extends Drawable {
}
}
- if (s.mTint != null && s.mTintMode != null) {
- mTintFilter = updateTintFilter(mTintFilter, s.mTint, s.mTintMode);
+ if (s.mTint != null && s.mBlendMode != null) {
+ mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, s.mTint,
+ s.mBlendMode);
invalidateSelf = true;
}
@@ -1058,14 +1205,16 @@ public class GradientDrawable extends Drawable {
@Override
public void setTintList(@Nullable ColorStateList tint) {
mGradientState.mTint = tint;
- mTintFilter = updateTintFilter(mTintFilter, tint, mGradientState.mTintMode);
+ mBlendModeColorFilter =
+ updateBlendModeFilter(mBlendModeColorFilter, tint, mGradientState.mBlendMode);
invalidateSelf();
}
@Override
- public void setTintMode(@Nullable PorterDuff.Mode tintMode) {
- mGradientState.mTintMode = tintMode;
- mTintFilter = updateTintFilter(mTintFilter, mGradientState.mTint, tintMode);
+ public void setTintBlendMode(@NonNull BlendMode blendMode) {
+ mGradientState.mBlendMode = blendMode;
+ mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, mGradientState.mTint,
+ blendMode);
invalidateSelf();
}
@@ -1121,7 +1270,7 @@ public class GradientDrawable extends Drawable {
if (st.mGradient == LINEAR_GRADIENT) {
final float level = st.mUseLevel ? getLevel() / 10000.0f : 1.0f;
- switch (st.mOrientation) {
+ switch (st.getOrientation()) {
case TOP_BOTTOM:
x0 = r.left; y0 = r.top;
x1 = x0; y1 = level * r.bottom;
@@ -1319,7 +1468,7 @@ public class GradientDrawable extends Drawable {
final int tintMode = a.getInt(R.styleable.GradientDrawable_tintMode, -1);
if (tintMode != -1) {
- state.mTintMode = Drawable.parseTintMode(tintMode, PorterDuff.Mode.SRC_IN);
+ state.mBlendMode = Drawable.parseBlendMode(tintMode, BlendMode.SRC_IN);
}
final ColorStateList tint = a.getColorStateList(R.styleable.GradientDrawable_tint);
@@ -1358,8 +1507,6 @@ public class GradientDrawable extends Drawable {
st.mAttrGradient, R.styleable.GradientDrawableGradient);
try {
updateGradientDrawableGradient(t.getResources(), a);
- } catch (XmlPullParserException e) {
- rethrowAsRuntimeException(e);
} finally {
a.recycle();
}
@@ -1547,8 +1694,7 @@ public class GradientDrawable extends Drawable {
}
}
- private void updateGradientDrawableGradient(Resources r, TypedArray a)
- throws XmlPullParserException {
+ private void updateGradientDrawableGradient(Resources r, TypedArray a) {
final GradientState st = mGradientState;
// Account for any configuration changes.
@@ -1566,15 +1712,32 @@ public class GradientDrawable extends Drawable {
st.mGradient = a.getInt(
R.styleable.GradientDrawableGradient_type, st.mGradient);
- // TODO: Update these to be themeable.
+ final boolean hasGradientColors = st.mGradientColors != null;
+ final boolean hasGradientCenter = st.hasCenterColor();
+ final int prevStart = hasGradientColors ? st.mGradientColors[0] : 0;
+ final int prevCenter = hasGradientCenter ? st.mGradientColors[1] : 0;
+ final int prevEnd;
+
+ if (st.hasCenterColor()) {
+ // if there is a center color, the end color is the last of the 3 values
+ prevEnd = st.mGradientColors[2];
+ } else if (hasGradientColors) {
+ // if there is not a center color but there are already colors configured, then
+ // the end color is the 2nd value in the array
+ prevEnd = st.mGradientColors[1];
+ } else {
+ // otherwise, there isn't a previously configured end color
+ prevEnd = 0;
+ }
+
final int startColor = a.getColor(
- R.styleable.GradientDrawableGradient_startColor, 0);
+ R.styleable.GradientDrawableGradient_startColor, prevStart);
final boolean hasCenterColor = a.hasValue(
- R.styleable.GradientDrawableGradient_centerColor);
+ R.styleable.GradientDrawableGradient_centerColor) || hasGradientCenter;
final int centerColor = a.getColor(
- R.styleable.GradientDrawableGradient_centerColor, 0);
+ R.styleable.GradientDrawableGradient_centerColor, prevCenter);
final int endColor = a.getColor(
- R.styleable.GradientDrawableGradient_endColor, 0);
+ R.styleable.GradientDrawableGradient_endColor, prevEnd);
if (hasCenterColor) {
st.mGradientColors = new int[3];
@@ -1593,75 +1756,33 @@ public class GradientDrawable extends Drawable {
st.mGradientColors[1] = endColor;
}
- if (st.mGradient == LINEAR_GRADIENT) {
- int angle = (int) a.getFloat(R.styleable.GradientDrawableGradient_angle, st.mAngle);
- angle %= 360;
-
- if (angle % 45 != 0) {
- throw new XmlPullParserException(a.getPositionDescription()
- + "<gradient> tag requires 'angle' attribute to "
- + "be a multiple of 45");
- }
+ int angle = (int) a.getFloat(R.styleable.GradientDrawableGradient_angle, st.mAngle);
+ st.mAngle = ((angle % 360) + 360) % 360; // offset negative angle measures
- st.mAngle = angle;
-
- switch (angle) {
- case 0:
- st.mOrientation = Orientation.LEFT_RIGHT;
- break;
- case 45:
- st.mOrientation = Orientation.BL_TR;
- break;
- case 90:
- st.mOrientation = Orientation.BOTTOM_TOP;
- break;
- case 135:
- st.mOrientation = Orientation.BR_TL;
- break;
- case 180:
- st.mOrientation = Orientation.RIGHT_LEFT;
- break;
- case 225:
- st.mOrientation = Orientation.TR_BL;
- break;
- case 270:
- st.mOrientation = Orientation.TOP_BOTTOM;
- break;
- case 315:
- st.mOrientation = Orientation.TL_BR;
- break;
- }
- } else {
- final TypedValue tv = a.peekValue(R.styleable.GradientDrawableGradient_gradientRadius);
- if (tv != null) {
- final float radius;
- final @RadiusType int radiusType;
- if (tv.type == TypedValue.TYPE_FRACTION) {
- radius = tv.getFraction(1.0f, 1.0f);
-
- final int unit = (tv.data >> TypedValue.COMPLEX_UNIT_SHIFT)
- & TypedValue.COMPLEX_UNIT_MASK;
- if (unit == TypedValue.COMPLEX_UNIT_FRACTION_PARENT) {
- radiusType = RADIUS_TYPE_FRACTION_PARENT;
- } else {
- radiusType = RADIUS_TYPE_FRACTION;
- }
- } else if (tv.type == TypedValue.TYPE_DIMENSION) {
- radius = tv.getDimension(r.getDisplayMetrics());
- radiusType = RADIUS_TYPE_PIXELS;
+ final TypedValue tv = a.peekValue(R.styleable.GradientDrawableGradient_gradientRadius);
+ if (tv != null) {
+ final float radius;
+ final @RadiusType int radiusType;
+ if (tv.type == TypedValue.TYPE_FRACTION) {
+ radius = tv.getFraction(1.0f, 1.0f);
+
+ final int unit = (tv.data >> TypedValue.COMPLEX_UNIT_SHIFT)
+ & TypedValue.COMPLEX_UNIT_MASK;
+ if (unit == TypedValue.COMPLEX_UNIT_FRACTION_PARENT) {
+ radiusType = RADIUS_TYPE_FRACTION_PARENT;
} else {
- radius = tv.getFloat();
- radiusType = RADIUS_TYPE_PIXELS;
+ radiusType = RADIUS_TYPE_FRACTION;
}
-
- st.mGradientRadius = radius;
- st.mGradientRadiusType = radiusType;
- } else if (st.mGradient == RADIAL_GRADIENT) {
- throw new XmlPullParserException(
- a.getPositionDescription()
- + "<gradient> tag requires 'gradientRadius' "
- + "attribute with radial type");
+ } else if (tv.type == TypedValue.TYPE_DIMENSION) {
+ radius = tv.getDimension(r.getDisplayMetrics());
+ radiusType = RADIUS_TYPE_PIXELS;
+ } else {
+ radius = tv.getFloat();
+ radiusType = RADIUS_TYPE_PIXELS;
}
+
+ st.mGradientRadius = radius;
+ st.mGradientRadiusType = radiusType;
}
}
@@ -1698,7 +1819,6 @@ public class GradientDrawable extends Drawable {
return mGradientState.mHeight;
}
- /** @hide */
@Override
public Insets getOpticalInsets() {
return mGradientState.mOpticalInsets;
@@ -1793,27 +1913,46 @@ public class GradientDrawable extends Drawable {
final static class GradientState extends ConstantState {
public @Config int mChangingConfigurations;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
public @Shape int mShape = RECTANGLE;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
public @GradientType int mGradient = LINEAR_GRADIENT;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
public int mAngle = 0;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
public Orientation mOrientation;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
public ColorStateList mSolidColors;
public ColorStateList mStrokeColors;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
public @ColorInt int[] mGradientColors;
public @ColorInt int[] mTempColors; // no need to copy
public float[] mTempPositions; // no need to copy
+ @UnsupportedAppUsage
public float[] mPositions;
+ @UnsupportedAppUsage(trackingBug = 124050917)
public int mStrokeWidth = -1; // if >= 0 use stroking.
+ @UnsupportedAppUsage(trackingBug = 124050917)
public float mStrokeDashWidth = 0.0f;
+ @UnsupportedAppUsage(trackingBug = 124050917)
public float mStrokeDashGap = 0.0f;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
public float mRadius = 0.0f; // use this if mRadiusArray is null
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
public float[] mRadiusArray = null;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
public Rect mPadding = null;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
public int mWidth = -1;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
public int mHeight = -1;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
public float mInnerRadiusRatio = DEFAULT_INNER_RADIUS_RATIO;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050218)
public float mThicknessRatio = DEFAULT_THICKNESS_RATIO;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
public int mInnerRadius = -1;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050218)
public int mThickness = -1;
public boolean mDither = false;
public Insets mOpticalInsets = Insets.NONE;
@@ -1829,7 +1968,7 @@ public class GradientDrawable extends Drawable {
boolean mOpaqueOverShape;
ColorStateList mTint = null;
- PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE;
+ BlendMode mBlendMode = DEFAULT_BLEND_MODE;
int mDensity = DisplayMetrics.DENSITY_DEFAULT;
@@ -1842,7 +1981,7 @@ public class GradientDrawable extends Drawable {
int[] mAttrPadding;
public GradientState(Orientation orientation, int[] gradientColors) {
- mOrientation = orientation;
+ setOrientation(orientation);
setGradientColors(gradientColors);
}
@@ -1887,7 +2026,7 @@ public class GradientDrawable extends Drawable {
mOpaqueOverBounds = orig.mOpaqueOverBounds;
mOpaqueOverShape = orig.mOpaqueOverShape;
mTint = orig.mTint;
- mTintMode = orig.mTintMode;
+ mBlendMode = orig.mBlendMode;
mThemeAttrs = orig.mThemeAttrs;
mAttrSize = orig.mAttrSize;
mAttrGradient = orig.mAttrGradient;
@@ -1920,6 +2059,10 @@ public class GradientDrawable extends Drawable {
}
}
+ public boolean hasCenterColor() {
+ return mGradientColors != null && mGradientColors.length == 3;
+ }
+
private void applyDensityScaling(int sourceDensity, int targetDensity) {
if (mInnerRadius > 0) {
mInnerRadius = Drawable.scaleFromDensity(
@@ -2041,6 +2184,93 @@ 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;
@@ -2170,7 +2400,8 @@ public class GradientDrawable extends Drawable {
}
}
- mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
+ mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, state.mTint,
+ state.mBlendMode);
mGradientIsDirty = true;
state.computeOpacity();
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index 361fe0bffbbc..3658f89abae1 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -21,6 +21,7 @@ import android.annotation.DrawableRes;
import android.annotation.IdRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -29,9 +30,11 @@ import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.graphics.BlendMode;
import android.graphics.PorterDuff;
import android.net.Uri;
import android.os.AsyncTask;
+import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.os.Parcel;
@@ -99,11 +102,12 @@ public final class Icon implements Parcelable {
private static final int VERSION_STREAM_SERIALIZER = 1;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private final int mType;
private ColorStateList mTintList;
- static final PorterDuff.Mode DEFAULT_TINT_MODE = Drawable.DEFAULT_TINT_MODE; // SRC_IN
- private PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE;
+ static final BlendMode DEFAULT_BLEND_MODE = Drawable.DEFAULT_BLEND_MODE; // SRC_IN
+ private BlendMode mBlendMode = Drawable.DEFAULT_BLEND_MODE;
// To avoid adding unnecessary overhead, we have a few basic objects that get repurposed
// based on the value of mType.
@@ -115,6 +119,7 @@ public final class Icon implements Parcelable {
// TYPE_RESOURCE: package name
// TYPE_URI: uri string
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private String mString1;
// TYPE_RESOURCE: resId
@@ -139,6 +144,7 @@ public final class Icon implements Parcelable {
* @return The {@link android.graphics.Bitmap} held by this {@link #TYPE_BITMAP} Icon.
* @hide
*/
+ @UnsupportedAppUsage
public Bitmap getBitmap() {
if (mType != TYPE_BITMAP && mType != TYPE_ADAPTIVE_BITMAP) {
throw new IllegalStateException("called getBitmap() on " + this);
@@ -154,6 +160,7 @@ public final class Icon implements Parcelable {
* @return The length of the compressed bitmap byte array held by this {@link #TYPE_DATA} Icon.
* @hide
*/
+ @UnsupportedAppUsage
public int getDataLength() {
if (mType != TYPE_DATA) {
throw new IllegalStateException("called getDataLength() on " + this);
@@ -168,6 +175,7 @@ public final class Icon implements Parcelable {
* valid compressed bitmap data is found.
* @hide
*/
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public int getDataOffset() {
if (mType != TYPE_DATA) {
throw new IllegalStateException("called getDataOffset() on " + this);
@@ -182,6 +190,7 @@ public final class Icon implements Parcelable {
* bitmap data.
* @hide
*/
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public byte[] getDataBytes() {
if (mType != TYPE_DATA) {
throw new IllegalStateException("called getDataBytes() on " + this);
@@ -195,6 +204,7 @@ public final class Icon implements Parcelable {
* @return The {@link android.content.res.Resources} for this {@link #TYPE_RESOURCE} Icon.
* @hide
*/
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public Resources getResources() {
if (mType != TYPE_RESOURCE) {
throw new IllegalStateException("called getResources() on " + this);
@@ -311,10 +321,10 @@ public final class Icon implements Parcelable {
*/
public Drawable loadDrawable(Context context) {
final Drawable result = loadDrawableInner(context);
- if (result != null && (mTintList != null || mTintMode != DEFAULT_TINT_MODE)) {
+ if (result != null && (mTintList != null || mBlendMode != DEFAULT_BLEND_MODE)) {
result.mutate();
result.setTintList(mTintList);
- result.setTintMode(mTintMode);
+ result.setTintBlendMode(mBlendMode);
}
return result;
}
@@ -560,6 +570,7 @@ public final class Icon implements Parcelable {
* Version of createWithResource that takes Resources. Do not use.
* @hide
*/
+ @UnsupportedAppUsage
public static Icon createWithResource(Resources res, @DrawableRes int resId) {
if (res == null) {
throw new IllegalArgumentException("Resource must not be null.");
@@ -686,14 +697,26 @@ public final class Icon implements Parcelable {
* @param mode a blending mode, as in {@link Drawable#setTintMode(PorterDuff.Mode)}, may be null
* @return this same object, for use in chained construction
*/
- public Icon setTintMode(PorterDuff.Mode mode) {
- mTintMode = mode;
+ public @NonNull Icon setTintMode(@NonNull PorterDuff.Mode mode) {
+ mBlendMode = BlendMode.fromValue(mode.nativeInt);
+ return this;
+ }
+
+ /**
+ * Store a blending mode to use whenever this Icon is drawn.
+ *
+ * @param mode a blending mode, as in {@link Drawable#setTintMode(PorterDuff.Mode)}, may be null
+ * @return this same object, for use in chained construction
+ */
+ public @NonNull Icon setTintBlendMode(@NonNull BlendMode mode) {
+ mBlendMode = mode;
return this;
}
/** @hide */
+ @UnsupportedAppUsage
public boolean hasTint() {
- return (mTintList != null) || (mTintMode != DEFAULT_TINT_MODE);
+ return (mTintList != null) || (mBlendMode != DEFAULT_BLEND_MODE);
}
/**
@@ -746,7 +769,7 @@ public final class Icon implements Parcelable {
sep = "|";
}
}
- if (mTintMode != DEFAULT_TINT_MODE) sb.append(" mode=").append(mTintMode);
+ if (mBlendMode != DEFAULT_BLEND_MODE) sb.append(" mode=").append(mBlendMode);
sb.append(")");
return sb.toString();
}
@@ -796,7 +819,7 @@ public final class Icon implements Parcelable {
if (in.readInt() == 1) {
mTintList = ColorStateList.CREATOR.createFromParcel(in);
}
- mTintMode = PorterDuff.intToMode(in.readInt());
+ mBlendMode = BlendMode.fromValue(in.readInt());
}
@Override
@@ -826,10 +849,10 @@ public final class Icon implements Parcelable {
dest.writeInt(1);
mTintList.writeToParcel(dest, flags);
}
- dest.writeInt(PorterDuff.modeToInt(mTintMode));
+ dest.writeInt(BlendMode.toValue(mBlendMode));
}
- public static final Parcelable.Creator<Icon> CREATOR
+ public static final @android.annotation.NonNull Parcelable.Creator<Icon> CREATOR
= new Parcelable.Creator<Icon>() {
public Icon createFromParcel(Parcel in) {
return new Icon(in);
diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java
index 443aa4931ee3..bc8a4cbd7e9d 100644
--- a/graphics/java/android/graphics/drawable/InsetDrawable.java
+++ b/graphics/java/android/graphics/drawable/InsetDrawable.java
@@ -16,13 +16,9 @@
package android.graphics.drawable;
-import com.android.internal.R;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
@@ -35,6 +31,11 @@ import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.IOException;
/**
@@ -57,6 +58,7 @@ public class InsetDrawable extends DrawableWrapper {
private final Rect mTmpRect = new Rect();
private final Rect mTmpInsetRect = new Rect();
+ @UnsupportedAppUsage
private InsetState mState;
/**
@@ -240,7 +242,6 @@ public class InsetDrawable extends DrawableWrapper {
| mTmpInsetRect.top | mTmpInsetRect.bottom) != 0;
}
- /** @hide */
@Override
public Insets getOpticalInsets() {
final Insets contentInsets = super.getOpticalInsets();
diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java
index 4725c2c4c0e5..760d554888ee 100644
--- a/graphics/java/android/graphics/drawable/LayerDrawable.java
+++ b/graphics/java/android/graphics/drawable/LayerDrawable.java
@@ -18,16 +18,17 @@ package android.graphics.drawable;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
+import android.graphics.BlendMode;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Outline;
import android.graphics.PixelFormat;
-import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
@@ -93,6 +94,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback {
public static final int INSET_UNDEFINED = Integer.MIN_VALUE;
@NonNull
+ @UnsupportedAppUsage
LayerState mLayerState;
private int[] mPaddingL;
@@ -137,9 +139,12 @@ public class LayerDrawable extends Drawable implements Drawable.Callback {
final ChildDrawable[] r = new ChildDrawable[length];
for (int i = 0; i < length; i++) {
r[i] = new ChildDrawable(mLayerState.mDensity);
- r[i].mDrawable = layers[i];
- layers[i].setCallback(this);
- mLayerState.mChildrenChangingConfigurations |= layers[i].getChangingConfigurations();
+ Drawable child = layers[i];
+ r[i].mDrawable = child;
+ if (child != null) {
+ child.setCallback(this);
+ mLayerState.mChildrenChangingConfigurations |= child.getChangingConfigurations();
+ }
}
mLayerState.mNumChildren = length;
mLayerState.mChildren = r;
@@ -414,7 +419,8 @@ public class LayerDrawable extends Drawable implements Drawable.Callback {
final ChildDrawable[] layers = mLayerState.mChildren;
final int N = mLayerState.mNumChildren;
for (int i = 0; i < N; i++) {
- if (layers[i].mDrawable.isProjected()) {
+ Drawable childDrawable = layers[i].mDrawable;
+ if (childDrawable != null && childDrawable.isProjected()) {
return true;
}
}
@@ -428,6 +434,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback {
* @param layer The layer to add.
* @return The index of the layer.
*/
+ @UnsupportedAppUsage
int addLayer(@NonNull ChildDrawable layer) {
final LayerState st = mLayerState;
final int N = st.mChildren != null ? st.mChildren.length : 0;
@@ -1394,13 +1401,13 @@ public class LayerDrawable extends Drawable implements Drawable.Callback {
}
@Override
- public void setTintMode(Mode tintMode) {
+ public void setTintBlendMode(@NonNull BlendMode blendMode) {
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNumChildren;
for (int i = 0; i < N; i++) {
final Drawable dr = array[i].mDrawable;
if (dr != null) {
- dr.setTintMode(tintMode);
+ dr.setTintBlendMode(blendMode);
}
}
}
@@ -1739,6 +1746,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback {
/**
* Ensures the child padding caches are large enough.
*/
+ @UnsupportedAppUsage
void ensurePadding() {
final int N = mLayerState.mNumChildren;
if (mPaddingL != null && mPaddingL.length >= N) {
@@ -1820,6 +1828,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback {
}
static class ChildDrawable {
+ @UnsupportedAppUsage
public Drawable mDrawable;
public int[] mThemeAttrs;
public int mDensity = DisplayMetrics.DENSITY_DEFAULT;
@@ -1922,6 +1931,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback {
private int[] mThemeAttrs;
int mNumChildren;
+ @UnsupportedAppUsage
ChildDrawable[] mChildren;
int mDensity;
diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
index 5ff49aba88ae..8561d95ddd88 100644
--- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java
+++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
@@ -18,12 +18,15 @@ package android.graphics.drawable;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
+import android.graphics.BlendMode;
+import android.graphics.BlendModeColorFilter;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.ImageDecoder;
@@ -32,9 +35,6 @@ import android.graphics.NinePatch;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.Region;
import android.util.AttributeSet;
@@ -70,8 +70,9 @@ public class NinePatchDrawable extends Drawable {
/** Temporary rect used for density scaling. */
private Rect mTempRect;
+ @UnsupportedAppUsage
private NinePatchState mNinePatchState;
- private PorterDuffColorFilter mTintFilter;
+ private BlendModeColorFilter mBlendModeFilter;
private Rect mPadding;
private Insets mOpticalInsets = Insets.NONE;
private Rect mOutlineInsets;
@@ -196,8 +197,8 @@ public class NinePatchDrawable extends Drawable {
int restoreToCount = -1;
final boolean clearColorFilter;
- if (mTintFilter != null && getPaint().getColorFilter() == null) {
- mPaint.setColorFilter(mTintFilter);
+ if (mBlendModeFilter != null && getPaint().getColorFilter() == null) {
+ mPaint.setColorFilter(mBlendModeFilter);
clearColorFilter = true;
} else {
clearColorFilter = false;
@@ -299,9 +300,6 @@ public class NinePatchDrawable extends Drawable {
super.getOutline(outline);
}
- /**
- * @hide
- */
@Override
public Insets getOpticalInsets() {
final Insets opticalInsets = mOpticalInsets;
@@ -345,14 +343,16 @@ public class NinePatchDrawable extends Drawable {
@Override
public void setTintList(@Nullable ColorStateList tint) {
mNinePatchState.mTint = tint;
- mTintFilter = updateTintFilter(mTintFilter, tint, mNinePatchState.mTintMode);
+ mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, tint,
+ mNinePatchState.mBlendMode);
invalidateSelf();
}
@Override
- public void setTintMode(@Nullable PorterDuff.Mode tintMode) {
- mNinePatchState.mTintMode = tintMode;
- mTintFilter = updateTintFilter(mTintFilter, mNinePatchState.mTint, tintMode);
+ public void setTintBlendMode(@Nullable BlendMode blendMode) {
+ mNinePatchState.mBlendMode = blendMode;
+ mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, mNinePatchState.mTint,
+ blendMode);
invalidateSelf();
}
@@ -468,7 +468,7 @@ public class NinePatchDrawable extends Drawable {
final int tintMode = a.getInt(R.styleable.NinePatchDrawable_tintMode, -1);
if (tintMode != -1) {
- state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
+ state.mBlendMode = Drawable.parseBlendMode(tintMode, BlendMode.SRC_IN);
}
final ColorStateList tint = a.getColorStateList(R.styleable.NinePatchDrawable_tint);
@@ -567,8 +567,9 @@ public class NinePatchDrawable extends Drawable {
@Override
protected boolean onStateChange(int[] stateSet) {
final NinePatchState state = mNinePatchState;
- if (state.mTint != null && state.mTintMode != null) {
- mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
+ if (state.mTint != null && state.mBlendMode != null) {
+ mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, state.mTint,
+ state.mBlendMode);
return true;
}
@@ -591,9 +592,10 @@ public class NinePatchDrawable extends Drawable {
@Config int mChangingConfigurations;
// Values loaded during inflation.
+ @UnsupportedAppUsage
NinePatch mNinePatch = null;
ColorStateList mTint = null;
- Mode mTintMode = DEFAULT_TINT_MODE;
+ BlendMode mBlendMode = DEFAULT_BLEND_MODE;
Rect mPadding = null;
Insets mOpticalInsets = Insets.NONE;
float mBaseAlpha = 1.0f;
@@ -628,7 +630,7 @@ public class NinePatchDrawable extends Drawable {
mChangingConfigurations = orig.mChangingConfigurations;
mNinePatch = orig.mNinePatch;
mTint = orig.mTint;
- mTintMode = orig.mTintMode;
+ mBlendMode = orig.mBlendMode;
mPadding = orig.mPadding;
mOpticalInsets = orig.mOpticalInsets;
mBaseAlpha = orig.mBaseAlpha;
@@ -751,7 +753,7 @@ public class NinePatchDrawable extends Drawable {
} else {
mTargetDensity = Drawable.resolveDensity(res, mTargetDensity);
}
- mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
+ mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, state.mTint, state.mBlendMode);
computeBitmapSize();
}
}
diff --git a/graphics/java/android/graphics/drawable/RippleComponent.java b/graphics/java/android/graphics/drawable/RippleComponent.java
index 626bcee9454b..c1f8798faaeb 100644
--- a/graphics/java/android/graphics/drawable/RippleComponent.java
+++ b/graphics/java/android/graphics/drawable/RippleComponent.java
@@ -16,15 +16,8 @@
package android.graphics.drawable;
-import android.animation.Animator;
-import android.graphics.Canvas;
-import android.graphics.Paint;
import android.graphics.Rect;
import android.util.DisplayMetrics;
-import android.view.DisplayListCanvas;
-import android.view.RenderNodeAnimator;
-
-import java.util.ArrayList;
/**
* Abstract class that handles size & positioning common to the ripple & focus states.
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 0da61c29bd8d..1540cc22e295 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -18,6 +18,7 @@ package android.graphics.drawable;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.content.res.Resources;
@@ -120,6 +121,7 @@ public class RippleDrawable extends LayerDrawable {
private final Rect mDirtyBounds = new Rect();
/** Mirrors mLayerState with some extra information. */
+ @UnsupportedAppUsage
private RippleState mState;
/** The masking layer, e.g. the layer with id R.id.mask. */
@@ -157,6 +159,7 @@ public class RippleDrawable extends LayerDrawable {
private Paint mRipplePaint;
/** Target density of the display into which ripples are drawn. */
+ @UnsupportedAppUsage
private int mDensity;
/** Whether bounds are being overridden. */
@@ -857,6 +860,7 @@ public class RippleDrawable extends LayerDrawable {
mMask.draw(canvas);
}
+ @UnsupportedAppUsage
Paint getRipplePaint() {
if (mRipplePaint == null) {
mRipplePaint = new Paint();
@@ -888,7 +892,10 @@ public class RippleDrawable extends LayerDrawable {
// The ripple timing depends on the paint's alpha value, so we need
// to push just the alpha channel into the paint and let the filter
// handle the full-alpha color.
- mMaskColorFilter.setColor(color | 0xFF000000);
+ int maskColor = color | 0xFF000000;
+ if (mMaskColorFilter.getColor() != maskColor) {
+ mMaskColorFilter = new PorterDuffColorFilter(maskColor, mMaskColorFilter.getMode());
+ }
p.setColor(color & 0xFF000000);
p.setColorFilter(mMaskColorFilter);
p.setShader(mMaskShader);
@@ -942,6 +949,7 @@ public class RippleDrawable extends LayerDrawable {
* @param forceSoftware true if RenderThread animations should be disabled, false otherwise
* @hide
*/
+ @UnsupportedAppUsage
public void setForceSoftware(boolean forceSoftware) {
mForceSoftware = forceSoftware;
}
@@ -972,6 +980,7 @@ public class RippleDrawable extends LayerDrawable {
static class RippleState extends LayerState {
int[] mTouchThemeAttrs;
+ @UnsupportedAppUsage
ColorStateList mColor = ColorStateList.valueOf(Color.MAGENTA);
int mMaxRadius = RADIUS_AUTO;
diff --git a/graphics/java/android/graphics/drawable/RippleForeground.java b/graphics/java/android/graphics/drawable/RippleForeground.java
index a8dc34af292b..cce9ba31929f 100644
--- a/graphics/java/android/graphics/drawable/RippleForeground.java
+++ b/graphics/java/android/graphics/drawable/RippleForeground.java
@@ -23,10 +23,10 @@ import android.animation.TimeInterpolator;
import android.graphics.Canvas;
import android.graphics.CanvasProperty;
import android.graphics.Paint;
+import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.util.FloatProperty;
import android.util.MathUtils;
-import android.view.DisplayListCanvas;
import android.view.RenderNodeAnimator;
import android.view.animation.AnimationUtils;
import android.view.animation.LinearInterpolator;
@@ -132,7 +132,7 @@ class RippleForeground extends RippleComponent {
}
}
- private void startPending(DisplayListCanvas c) {
+ private void startPending(RecordingCanvas c) {
if (!mPendingHwAnimators.isEmpty()) {
for (int i = 0; i < mPendingHwAnimators.size(); i++) {
RenderNodeAnimator animator = mPendingHwAnimators.get(i);
@@ -164,7 +164,7 @@ class RippleForeground extends RippleComponent {
}
}
- private void drawHardware(DisplayListCanvas c, Paint p) {
+ private void drawHardware(RecordingCanvas c, Paint p) {
startPending(c);
pruneHwFinished();
if (mPropPaint != null) {
@@ -332,11 +332,11 @@ class RippleForeground extends RippleComponent {
* @param p the paint used to draw the ripple
*/
public void draw(Canvas c, Paint p) {
- final boolean hasDisplayListCanvas = !mForceSoftware && c instanceof DisplayListCanvas;
+ final boolean hasDisplayListCanvas = !mForceSoftware && c instanceof RecordingCanvas;
pruneSwFinished();
if (hasDisplayListCanvas) {
- final DisplayListCanvas hw = (DisplayListCanvas) c;
+ final RecordingCanvas hw = (RecordingCanvas) c;
drawHardware(hw, p);
} else {
drawSoftware(c, p);
diff --git a/graphics/java/android/graphics/drawable/RotateDrawable.java b/graphics/java/android/graphics/drawable/RotateDrawable.java
index c0dfe77cf4f3..db5f082bd853 100644
--- a/graphics/java/android/graphics/drawable/RotateDrawable.java
+++ b/graphics/java/android/graphics/drawable/RotateDrawable.java
@@ -23,6 +23,7 @@ import org.xmlpull.v1.XmlPullParserException;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.content.res.Resources;
@@ -54,6 +55,7 @@ import java.io.IOException;
public class RotateDrawable extends DrawableWrapper {
private static final int MAX_LEVEL = 10000;
+ @UnsupportedAppUsage
private RotateState mState;
/**
diff --git a/graphics/java/android/graphics/drawable/ScaleDrawable.java b/graphics/java/android/graphics/drawable/ScaleDrawable.java
index 51e143baeac4..91ed061e511d 100644
--- a/graphics/java/android/graphics/drawable/ScaleDrawable.java
+++ b/graphics/java/android/graphics/drawable/ScaleDrawable.java
@@ -23,6 +23,7 @@ import org.xmlpull.v1.XmlPullParserException;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
@@ -66,6 +67,7 @@ public class ScaleDrawable extends DrawableWrapper {
private final Rect mTmpRect = new Rect();
+ @UnsupportedAppUsage
private ScaleState mState;
ScaleDrawable() {
diff --git a/graphics/java/android/graphics/drawable/ShapeDrawable.java b/graphics/java/android/graphics/drawable/ShapeDrawable.java
index 34da928bb6f1..9774b59f98a9 100644
--- a/graphics/java/android/graphics/drawable/ShapeDrawable.java
+++ b/graphics/java/android/graphics/drawable/ShapeDrawable.java
@@ -24,14 +24,13 @@ import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
+import android.graphics.BlendMode;
+import android.graphics.BlendModeColorFilter;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.Xfermode;
@@ -75,7 +74,7 @@ import java.io.IOException;
*/
public class ShapeDrawable extends Drawable {
private @NonNull ShapeState mShapeState;
- private PorterDuffColorFilter mTintFilter;
+ private BlendModeColorFilter mBlendModeColorFilter;
private boolean mMutated;
/**
@@ -238,8 +237,8 @@ public class ShapeDrawable extends Drawable {
// only draw shape if it may affect output
if (paint.getAlpha() != 0 || paint.getXfermode() != null || paint.hasShadowLayer()) {
final boolean clearColorFilter;
- if (mTintFilter != null && paint.getColorFilter() == null) {
- paint.setColorFilter(mTintFilter);
+ if (mBlendModeColorFilter != null && paint.getColorFilter() == null) {
+ paint.setColorFilter(mBlendModeColorFilter);
clearColorFilter = true;
} else {
clearColorFilter = false;
@@ -292,14 +291,16 @@ public class ShapeDrawable extends Drawable {
@Override
public void setTintList(ColorStateList tint) {
mShapeState.mTint = tint;
- mTintFilter = updateTintFilter(mTintFilter, tint, mShapeState.mTintMode);
+ mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, tint,
+ mShapeState.mBlendMode);
invalidateSelf();
}
@Override
- public void setTintMode(PorterDuff.Mode tintMode) {
- mShapeState.mTintMode = tintMode;
- mTintFilter = updateTintFilter(mTintFilter, mShapeState.mTint, tintMode);
+ public void setTintBlendMode(@NonNull BlendMode blendMode) {
+ mShapeState.mBlendMode = blendMode;
+ mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, mShapeState.mTint,
+ blendMode);
invalidateSelf();
}
@@ -352,8 +353,9 @@ public class ShapeDrawable extends Drawable {
@Override
protected boolean onStateChange(int[] stateSet) {
final ShapeState state = mShapeState;
- if (state.mTint != null && state.mTintMode != null) {
- mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
+ if (state.mTint != null && state.mBlendMode != null) {
+ mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, state.mTint,
+ state.mBlendMode);
return true;
}
return false;
@@ -475,7 +477,7 @@ public class ShapeDrawable extends Drawable {
final int tintMode = a.getInt(R.styleable.ShapeDrawable_tintMode, -1);
if (tintMode != -1) {
- state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
+ state.mBlendMode = Drawable.parseBlendMode(tintMode, BlendMode.SRC_IN);
}
final ColorStateList tint = a.getColorStateList(R.styleable.ShapeDrawable_tint);
@@ -540,7 +542,7 @@ public class ShapeDrawable extends Drawable {
int[] mThemeAttrs;
Shape mShape;
ColorStateList mTint;
- Mode mTintMode = DEFAULT_TINT_MODE;
+ BlendMode mBlendMode = DEFAULT_BLEND_MODE;
Rect mPadding;
int mIntrinsicWidth;
int mIntrinsicHeight;
@@ -573,7 +575,7 @@ public class ShapeDrawable extends Drawable {
}
}
mTint = orig.mTint;
- mTintMode = orig.mTintMode;
+ mBlendMode = orig.mBlendMode;
if (orig.mPadding != null) {
mPadding = new Rect(orig.mPadding);
}
@@ -594,12 +596,12 @@ public class ShapeDrawable extends Drawable {
@Override
public Drawable newDrawable() {
- return new ShapeDrawable(this, null);
+ return new ShapeDrawable(new ShapeState(this), null);
}
@Override
public Drawable newDrawable(Resources res) {
- return new ShapeDrawable(this, res);
+ return new ShapeDrawable(new ShapeState(this), res);
}
@Override
@@ -625,7 +627,8 @@ public class ShapeDrawable extends Drawable {
* after inflating or applying a theme.
*/
private void updateLocalState() {
- mTintFilter = updateTintFilter(mTintFilter, mShapeState.mTint, mShapeState.mTintMode);
+ mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, mShapeState.mTint,
+ mShapeState.mBlendMode);
}
/**
diff --git a/graphics/java/android/graphics/drawable/StateListDrawable.java b/graphics/java/android/graphics/drawable/StateListDrawable.java
index c98f1608c665..f67188c22609 100644
--- a/graphics/java/android/graphics/drawable/StateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/StateListDrawable.java
@@ -16,6 +16,15 @@
package android.graphics.drawable;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.util.StateSet;
+
import com.android.internal.R;
import org.xmlpull.v1.XmlPullParser;
@@ -24,14 +33,6 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.Arrays;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.Resources.Theme;
-import android.util.AttributeSet;
-import android.util.StateSet;
-
/**
* Lets you assign a number of graphic images to a single Drawable and swap out the visible item by a string
* ID value.
@@ -63,6 +64,7 @@ public class StateListDrawable extends DrawableContainer {
private static final boolean DEBUG = false;
+ @UnsupportedAppUsage
private StateListState mStateListState;
private boolean mMutated;
@@ -73,9 +75,11 @@ public class StateListDrawable extends DrawableContainer {
/**
* Add a new image/string ID to the set of images.
*
- * @param stateSet - An array of resource Ids to associate with the image.
+ * @param stateSet An array of resource Ids to associate with the image.
* Switch to this image by calling setState().
- * @param drawable -The image to show.
+ * @param drawable The image to show. Note this must be a unique Drawable that is not shared
+ * between any other View or Drawable otherwise the results are
+ * undefined and can lead to unexpected rendering behavior
*/
public void addState(int[] stateSet, Drawable drawable) {
if (drawable != null) {
@@ -127,6 +131,7 @@ public class StateListDrawable extends DrawableContainer {
/**
* Updates the constant state from the values in the typed array.
*/
+ @UnsupportedAppUsage
private void updateStateFromTypedArray(TypedArray a) {
final StateListState state = mStateListState;
@@ -204,6 +209,7 @@ public class StateListDrawable extends DrawableContainer {
* @param attrs The attribute set.
* @return An array of state_ attributes.
*/
+ @UnsupportedAppUsage
int[] extractStateSet(AttributeSet attrs) {
int j = 0;
final int numAttrs = attrs.getAttributeCount();
@@ -235,7 +241,6 @@ public class StateListDrawable extends DrawableContainer {
* Gets the number of states contained in this drawable.
*
* @return The number of states contained in this drawable.
- * @hide pending API council
* @see #getStateSet(int)
* @see #getStateDrawable(int)
*/
@@ -248,11 +253,10 @@ public class StateListDrawable extends DrawableContainer {
*
* @param index The index of the state set.
* @return The state set at the index.
- * @hide pending API council
* @see #getStateCount()
* @see #getStateDrawable(int)
*/
- public int[] getStateSet(int index) {
+ public @NonNull int[] getStateSet(int index) {
return mStateListState.mStateSets[index];
}
@@ -261,11 +265,10 @@ public class StateListDrawable extends DrawableContainer {
*
* @param index The index of the drawable.
* @return The drawable at the index.
- * @hide pending API council
* @see #getStateCount()
* @see #getStateSet(int)
*/
- public Drawable getStateDrawable(int index) {
+ public @Nullable Drawable getStateDrawable(int index) {
return mStateListState.getChild(index);
}
@@ -274,11 +277,10 @@ public class StateListDrawable extends DrawableContainer {
*
* @param stateSet the state set to look up
* @return the index of the provided state set, or -1 if not found
- * @hide pending API council
* @see #getStateDrawable(int)
* @see #getStateSet(int)
*/
- public int getStateDrawableIndex(int[] stateSet) {
+ public int findStateDrawableIndex(@NonNull int[] stateSet) {
return mStateListState.indexOfStateSet(stateSet);
}
@@ -331,6 +333,7 @@ public class StateListDrawable extends DrawableContainer {
mStateSets = stateSets;
}
+ @UnsupportedAppUsage
int addStateSet(int[] stateSet, Drawable drawable) {
final int pos = addChild(drawable);
mStateSets[pos] = stateSet;
diff --git a/graphics/java/android/graphics/drawable/TransitionDrawable.java b/graphics/java/android/graphics/drawable/TransitionDrawable.java
index 3dfd68018a6b..276f3662189b 100644
--- a/graphics/java/android/graphics/drawable/TransitionDrawable.java
+++ b/graphics/java/android/graphics/drawable/TransitionDrawable.java
@@ -16,6 +16,7 @@
package android.graphics.drawable;
+import android.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.Resources;
import android.graphics.Canvas;
@@ -65,10 +66,13 @@ public class TransitionDrawable extends LayerDrawable implements Drawable.Callba
private boolean mReverse;
private long mStartTimeMillis;
private int mFrom;
+ @UnsupportedAppUsage
private int mTo;
private int mDuration;
private int mOriginalDuration;
+ @UnsupportedAppUsage
private int mAlpha = 0;
+ @UnsupportedAppUsage
private boolean mCrossFade;
/**
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index c71585f32155..aa19b2a0e94c 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -16,6 +16,7 @@ package android.graphics.drawable;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.content.res.ComplexColor;
@@ -23,11 +24,13 @@ import android.content.res.GradientColor;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
+import android.graphics.BlendMode;
+import android.graphics.BlendModeColorFilter;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Insets;
import android.graphics.PixelFormat;
-import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.Shader;
@@ -46,6 +49,9 @@ import android.util.Xml;
import com.android.internal.R;
import com.android.internal.util.VirtualRefBasePtr;
+import dalvik.annotation.optimization.FastNative;
+import dalvik.system.VMRuntime;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -56,9 +62,6 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Stack;
-import dalvik.annotation.optimization.FastNative;
-import dalvik.system.VMRuntime;
-
/**
* This lets you create a drawable based on an XML vector graphic.
* <p/>
@@ -321,7 +324,10 @@ public class VectorDrawable extends Drawable {
private VectorDrawableState mVectorState;
+ @UnsupportedAppUsage
private PorterDuffColorFilter mTintFilter;
+
+ private BlendModeColorFilter mBlendModeColorFilter;
private ColorFilter mColorFilter;
private boolean mMutated;
@@ -369,7 +375,7 @@ public class VectorDrawable extends Drawable {
mDpiScaledDirty = true;
}
- mTintFilter = updateTintFilter(mTintFilter, mVectorState.mTint, mVectorState.mTintMode);
+ updateColorFilters(mVectorState.mBlendMode, mVectorState.mTint);
}
@Override
@@ -389,6 +395,7 @@ public class VectorDrawable extends Drawable {
mMutated = false;
}
+ @UnsupportedAppUsage
Object getTargetByName(String name) {
return mVectorState.mVGTargetsMap.get(name);
}
@@ -410,7 +417,8 @@ public class VectorDrawable extends Drawable {
}
// Color filters always override tint filters.
- final ColorFilter colorFilter = (mColorFilter == null ? mTintFilter : mColorFilter);
+ final ColorFilter colorFilter = (mColorFilter == null ? mBlendModeColorFilter :
+ mColorFilter);
final long colorFilterNativeInstance = colorFilter == null ? 0 :
colorFilter.getNativeInstance();
boolean canReuseCache = mVectorState.canReuseCache();
@@ -472,17 +480,19 @@ public class VectorDrawable extends Drawable {
final VectorDrawableState state = mVectorState;
if (state.mTint != tint) {
state.mTint = tint;
- mTintFilter = updateTintFilter(mTintFilter, tint, state.mTintMode);
+
+ updateColorFilters(mVectorState.mBlendMode, tint);
invalidateSelf();
}
}
@Override
- public void setTintMode(Mode tintMode) {
+ public void setTintBlendMode(@NonNull BlendMode blendMode) {
final VectorDrawableState state = mVectorState;
- if (state.mTintMode != tintMode) {
- state.mTintMode = tintMode;
- mTintFilter = updateTintFilter(mTintFilter, state.mTint, tintMode);
+ if (state.mBlendMode != blendMode) {
+ state.mBlendMode = blendMode;
+
+ updateColorFilters(state.mBlendMode, state.mTint);
invalidateSelf();
}
}
@@ -512,14 +522,22 @@ public class VectorDrawable extends Drawable {
changed = true;
state.mCacheDirty = true;
}
- if (state.mTint != null && state.mTintMode != null) {
- mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
+ if (state.mTint != null && state.mBlendMode != null) {
+ BlendMode blendMode = state.mBlendMode;
+ ColorStateList tint = state.mTint;
+ updateColorFilters(blendMode, tint);
changed = true;
}
return changed;
}
+ private void updateColorFilters(@Nullable BlendMode blendMode, ColorStateList tint) {
+ PorterDuff.Mode mode = BlendMode.blendModeToPorterDuffMode(blendMode);
+ mTintFilter = updateTintFilter(mTintFilter, tint, mode);
+ mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, tint, blendMode);
+ }
+
@Override
public int getOpacity() {
// We can't tell whether the drawable is fully opaque unless we examine all the pixels,
@@ -543,7 +561,6 @@ public class VectorDrawable extends Drawable {
return mDpiScaledHeight;
}
- /** @hide */
@Override
public Insets getOpticalInsets() {
if (mDpiScaledDirty) {
@@ -735,7 +752,7 @@ public class VectorDrawable extends Drawable {
final int tintMode = a.getInt(R.styleable.VectorDrawable_tintMode, -1);
if (tintMode != -1) {
- state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
+ state.mBlendMode = Drawable.parseBlendMode(tintMode, BlendMode.SRC_IN);
}
final ColorStateList tint = a.getColorStateList(R.styleable.VectorDrawable_tint);
@@ -868,6 +885,7 @@ public class VectorDrawable extends Drawable {
return super.getChangingConfigurations() | mVectorState.getChangingConfigurations();
}
+ @UnsupportedAppUsage
void setAllowCaching(boolean allowCaching) {
nSetAllowCaching(mVectorState.getNativeRenderer(), allowCaching);
}
@@ -908,7 +926,7 @@ public class VectorDrawable extends Drawable {
int[] mThemeAttrs;
@Config int mChangingConfigurations;
ColorStateList mTint = null;
- Mode mTintMode = DEFAULT_TINT_MODE;
+ BlendMode mBlendMode = DEFAULT_BLEND_MODE;
boolean mAutoMirrored;
int mBaseWidth = 0;
@@ -926,7 +944,7 @@ public class VectorDrawable extends Drawable {
// Fields for cache
int[] mCachedThemeAttrs;
ColorStateList mCachedTint;
- Mode mCachedTintMode;
+ BlendMode mCachedBlendMode;
boolean mCachedAutoMirrored;
boolean mCacheDirty;
@@ -967,7 +985,7 @@ public class VectorDrawable extends Drawable {
mThemeAttrs = copy.mThemeAttrs;
mChangingConfigurations = copy.mChangingConfigurations;
mTint = copy.mTint;
- mTintMode = copy.mTintMode;
+ mBlendMode = copy.mBlendMode;
mAutoMirrored = copy.mAutoMirrored;
mRootGroup = new VGroup(copy.mRootGroup, mVGTargetsMap);
createNativeTreeFromCopy(copy, mRootGroup);
@@ -1023,7 +1041,7 @@ public class VectorDrawable extends Drawable {
if (!mCacheDirty
&& mCachedThemeAttrs == mThemeAttrs
&& mCachedTint == mTint
- && mCachedTintMode == mTintMode
+ && mCachedBlendMode == mBlendMode
&& mCachedAutoMirrored == mAutoMirrored) {
return true;
}
@@ -1036,7 +1054,7 @@ public class VectorDrawable extends Drawable {
// likely hit cache miss more, but practically not much difference.
mCachedThemeAttrs = mThemeAttrs;
mCachedTint = mTint;
- mCachedTintMode = mTintMode;
+ mCachedBlendMode = mBlendMode;
mCachedAutoMirrored = mAutoMirrored;
mCacheDirty = false;
}
@@ -1499,6 +1517,7 @@ public class VectorDrawable extends Drawable {
}
@SuppressWarnings("unused")
+ @UnsupportedAppUsage
public void setRotation(float rotation) {
if (isTreeValid()) {
nSetRotation(mNativePtr, rotation);
@@ -1511,6 +1530,7 @@ public class VectorDrawable extends Drawable {
}
@SuppressWarnings("unused")
+ @UnsupportedAppUsage
public void setPivotX(float pivotX) {
if (isTreeValid()) {
nSetPivotX(mNativePtr, pivotX);
@@ -1523,6 +1543,7 @@ public class VectorDrawable extends Drawable {
}
@SuppressWarnings("unused")
+ @UnsupportedAppUsage
public void setPivotY(float pivotY) {
if (isTreeValid()) {
nSetPivotY(mNativePtr, pivotY);
@@ -1559,6 +1580,7 @@ public class VectorDrawable extends Drawable {
}
@SuppressWarnings("unused")
+ @UnsupportedAppUsage
public void setTranslateX(float translateX) {
if (isTreeValid()) {
nSetTranslateX(mNativePtr, translateX);
@@ -1571,6 +1593,7 @@ public class VectorDrawable extends Drawable {
}
@SuppressWarnings("unused")
+ @UnsupportedAppUsage
public void setTranslateY(float translateY) {
if (isTreeValid()) {
nSetTranslateY(mNativePtr, translateY);
@@ -2025,7 +2048,7 @@ public class VectorDrawable extends Drawable {
if (fillColors instanceof GradientColor) {
mFillColors = fillColors;
fillGradient = ((GradientColor) fillColors).getShader();
- } else if (fillColors.isStateful()) {
+ } else if (fillColors.isStateful() || fillColors.canApplyTheme()) {
mFillColors = fillColors;
} else {
mFillColors = null;
@@ -2041,7 +2064,7 @@ public class VectorDrawable extends Drawable {
if (strokeColors instanceof GradientColor) {
mStrokeColors = strokeColors;
strokeGradient = ((GradientColor) strokeColors).getShader();
- } else if (strokeColors.isStateful()) {
+ } else if (strokeColors.isStateful() || strokeColors.canApplyTheme()) {
mStrokeColors = strokeColors;
} else {
mStrokeColors = null;
diff --git a/graphics/java/android/graphics/drawable/shapes/ArcShape.java b/graphics/java/android/graphics/drawable/shapes/ArcShape.java
index 85ba0a9e9f6f..90d07bce2617 100644
--- a/graphics/java/android/graphics/drawable/shapes/ArcShape.java
+++ b/graphics/java/android/graphics/drawable/shapes/ArcShape.java
@@ -20,6 +20,8 @@ import android.graphics.Canvas;
import android.graphics.Outline;
import android.graphics.Paint;
+import java.util.Objects;
+
/**
* Creates an arc shape. The arc shape starts at a specified angle and sweeps
* clockwise, drawing slices of pie.
@@ -74,5 +76,26 @@ public class ArcShape extends RectShape {
public ArcShape clone() throws CloneNotSupportedException {
return (ArcShape) super.clone();
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+ ArcShape arcShape = (ArcShape) o;
+ return Float.compare(arcShape.mStartAngle, mStartAngle) == 0
+ && Float.compare(arcShape.mSweepAngle, mSweepAngle) == 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), mStartAngle, mSweepAngle);
+ }
}
diff --git a/graphics/java/android/graphics/drawable/shapes/PathShape.java b/graphics/java/android/graphics/drawable/shapes/PathShape.java
index ce5552b7a1bb..393fdee87bb8 100644
--- a/graphics/java/android/graphics/drawable/shapes/PathShape.java
+++ b/graphics/java/android/graphics/drawable/shapes/PathShape.java
@@ -21,6 +21,8 @@ import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
+import java.util.Objects;
+
/**
* Creates geometric paths, utilizing the {@link android.graphics.Path} class.
* <p>
@@ -74,5 +76,30 @@ public class PathShape extends Shape {
shape.mPath = new Path(mPath);
return shape;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+ PathShape pathShape = (PathShape) o;
+ return Float.compare(pathShape.mStdWidth, mStdWidth) == 0
+ && Float.compare(pathShape.mStdHeight, mStdHeight) == 0
+ && Float.compare(pathShape.mScaleX, mScaleX) == 0
+ && Float.compare(pathShape.mScaleY, mScaleY) == 0
+ // Path does not have equals implementation but incase it gains one, use it here
+ && Objects.equals(mPath, pathShape.mPath);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), mStdWidth, mStdHeight, mPath, mScaleX, mScaleY);
+ }
}
diff --git a/graphics/java/android/graphics/drawable/shapes/RectShape.java b/graphics/java/android/graphics/drawable/shapes/RectShape.java
index e339a212a794..1bbd1d7236d9 100644
--- a/graphics/java/android/graphics/drawable/shapes/RectShape.java
+++ b/graphics/java/android/graphics/drawable/shapes/RectShape.java
@@ -21,6 +21,8 @@ import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.RectF;
+import java.util.Objects;
+
/**
* Defines a rectangle shape.
* <p>
@@ -63,4 +65,24 @@ public class RectShape extends Shape {
shape.mRect = new RectF(mRect);
return shape;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+ RectShape rectShape = (RectShape) o;
+ return Objects.equals(mRect, rectShape.mRect);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), mRect);
+ }
}
diff --git a/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java b/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java
index f5cbb24a53ef..475e0bb70f2b 100644
--- a/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java
+++ b/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java
@@ -23,6 +23,9 @@ import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
+import java.util.Arrays;
+import java.util.Objects;
+
/**
* Creates a rounded-corner rectangle. Optionally, an inset (rounded) rectangle
* can be included (to make a sort of "O" shape).
@@ -137,4 +140,31 @@ public class RoundRectShape extends RectShape {
shape.mPath = new Path(mPath);
return shape;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+ RoundRectShape that = (RoundRectShape) o;
+ return Arrays.equals(mOuterRadii, that.mOuterRadii)
+ && Objects.equals(mInset, that.mInset)
+ && Arrays.equals(mInnerRadii, that.mInnerRadii)
+ && Objects.equals(mInnerRect, that.mInnerRect)
+ && Objects.equals(mPath, that.mPath);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Objects.hash(super.hashCode(), mInset, mInnerRect, mPath);
+ result = 31 * result + Arrays.hashCode(mOuterRadii);
+ result = 31 * result + Arrays.hashCode(mInnerRadii);
+ return result;
+ }
}
diff --git a/graphics/java/android/graphics/drawable/shapes/Shape.java b/graphics/java/android/graphics/drawable/shapes/Shape.java
index 30b28f3c2242..3b044ec631a2 100644
--- a/graphics/java/android/graphics/drawable/shapes/Shape.java
+++ b/graphics/java/android/graphics/drawable/shapes/Shape.java
@@ -21,6 +21,8 @@ import android.graphics.Canvas;
import android.graphics.Outline;
import android.graphics.Paint;
+import java.util.Objects;
+
/**
* Defines a generic graphical "shape."
* <p>
@@ -115,4 +117,22 @@ public abstract class Shape implements Cloneable {
public Shape clone() throws CloneNotSupportedException {
return (Shape) super.clone();
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Shape shape = (Shape) o;
+ return Float.compare(shape.mWidth, mWidth) == 0
+ && Float.compare(shape.mHeight, mHeight) == 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mWidth, mHeight);
+ }
}
diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java
new file mode 100644
index 000000000000..552088f7c478
--- /dev/null
+++ b/graphics/java/android/graphics/fonts/Font.java
@@ -0,0 +1,541 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.fonts;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.os.LocaleList;
+import android.os.ParcelFileDescriptor;
+import android.util.TypedValue;
+
+import com.android.internal.util.Preconditions;
+
+import dalvik.annotation.optimization.CriticalNative;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * A font class can be used for creating FontFamily.
+ */
+public final class Font {
+ private static final String TAG = "Font";
+
+ private static final int NOT_SPECIFIED = -1;
+ private static final int STYLE_ITALIC = 1;
+ private static final int STYLE_NORMAL = 0;
+
+ /**
+ * 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());
+
+ private @Nullable ByteBuffer mBuffer;
+ private @Nullable File mFile;
+ private @NonNull String mLocaleList = "";
+ private @IntRange(from = -1, to = 1000) int mWeight = NOT_SPECIFIED;
+ private @IntRange(from = -1, to = 1) int mItalic = NOT_SPECIFIED;
+ private @IntRange(from = 0) int mTtcIndex = 0;
+ private @Nullable FontVariationAxis[] mAxes = null;
+ private @Nullable IOException mException;
+
+ /**
+ * Constructs a builder with a byte buffer.
+ *
+ * Note that only direct buffer can be used as the source of font data.
+ *
+ * @see ByteBuffer#allocateDirect(int)
+ * @param buffer a byte buffer of a font data
+ */
+ public Builder(@NonNull ByteBuffer buffer) {
+ Preconditions.checkNotNull(buffer, "buffer can not be null");
+ if (!buffer.isDirect()) {
+ throw new IllegalArgumentException(
+ "Only direct buffer can be used as the source of font data.");
+ }
+ mBuffer = buffer;
+ }
+
+ /**
+ * Construct a builder with a byte buffer and file path.
+ *
+ * This method is intended to be called only from SystemFonts.
+ * @hide
+ */
+ public Builder(@NonNull ByteBuffer buffer, @NonNull File path,
+ @NonNull String localeList) {
+ this(buffer);
+ mFile = path;
+ mLocaleList = localeList;
+ }
+
+ /**
+ * Constructs a builder with a file path.
+ *
+ * @param path a file path to the font file
+ */
+ public Builder(@NonNull File path) {
+ Preconditions.checkNotNull(path, "path can not be null");
+ try (FileInputStream fis = new FileInputStream(path)) {
+ final FileChannel fc = fis.getChannel();
+ mBuffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
+ } catch (IOException e) {
+ mException = e;
+ }
+ mFile = path;
+ }
+
+ /**
+ * Constructs a builder with a file descriptor.
+ *
+ * @param fd a file descriptor
+ */
+ public Builder(@NonNull ParcelFileDescriptor fd) {
+ this(fd, 0, -1);
+ }
+
+ /**
+ * Constructs a builder with a file descriptor.
+ *
+ * @param fd a file descriptor
+ * @param offset an offset to of the font data in the file
+ * @param size a size of the font data. If -1 is passed, use until end of the file.
+ */
+ public Builder(@NonNull ParcelFileDescriptor fd, @IntRange(from = 0) long offset,
+ @IntRange(from = -1) long size) {
+ try (FileInputStream fis = new FileInputStream(fd.getFileDescriptor())) {
+ final FileChannel fc = fis.getChannel();
+ size = (size == -1) ? fc.size() - offset : size;
+ mBuffer = fc.map(FileChannel.MapMode.READ_ONLY, offset, size);
+ } catch (IOException e) {
+ mException = e;
+ }
+ }
+
+ /**
+ * Constructs a builder from an asset manager and a file path in an asset directory.
+ *
+ * @param am the application's asset manager
+ * @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 */);
+ }
+
+ /**
+ * Constructs a builder from an asset manager and a file path in an asset directory.
+ *
+ * @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
+ * @hide
+ */
+ 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;
+ }
+ mBuffer = b;
+ }
+
+ /**
+ * Constructs a builder from resources.
+ *
+ * Resource ID must points the font file. XML font can not be used here.
+ *
+ * @param res the resource of this application.
+ * @param resId the resource ID of font file.
+ */
+ public Builder(@NonNull Resources res, int resId) {
+ final TypedValue value = new TypedValue();
+ res.getValue(resId, value, true);
+ if (value.string == null) {
+ mException = new FileNotFoundException(resId + " not found");
+ return;
+ }
+ final String str = value.string.toString();
+ if (str.toLowerCase().endsWith(".xml")) {
+ 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;
+ }
+ final ByteBuffer b = nGetAssetBuffer(nativeAsset);
+ sAssetByteBufferRegistry.registerNativeAllocation(b, nativeAsset);
+ if (b == null) {
+ mException = new FileNotFoundException(str + " not found");
+ return;
+ }
+ mBuffer = b;
+ }
+
+ /**
+ * Sets weight of the font.
+ *
+ * Tells the system the weight of the given font. If this function is not called, the system
+ * will resolve the weight value by reading font tables.
+ *
+ * Here are pairs of the common names and their values.
+ * <p>
+ * <table>
+ * <thead>
+ * <tr>
+ * <th align="center">Value</th>
+ * <th align="center">Name</th>
+ * <th align="center">Android Definition</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td align="center">100</td>
+ * <td align="center">Thin</td>
+ * <td align="center">{@link FontStyle#FONT_WEIGHT_THIN}</td>
+ * </tr>
+ * <tr>
+ * <td align="center">200</td>
+ * <td align="center">Extra Light (Ultra Light)</td>
+ * <td align="center">{@link FontStyle#FONT_WEIGHT_EXTRA_LIGHT}</td>
+ * </tr>
+ * <tr>
+ * <td align="center">300</td>
+ * <td align="center">Light</td>
+ * <td align="center">{@link FontStyle#FONT_WEIGHT_LIGHT}</td>
+ * </tr>
+ * <tr>
+ * <td align="center">400</td>
+ * <td align="center">Normal (Regular)</td>
+ * <td align="center">{@link FontStyle#FONT_WEIGHT_NORMAL}</td>
+ * </tr>
+ * <tr>
+ * <td align="center">500</td>
+ * <td align="center">Medium</td>
+ * <td align="center">{@link FontStyle#FONT_WEIGHT_MEDIUM}</td>
+ * </tr>
+ * <tr>
+ * <td align="center">600</td>
+ * <td align="center">Semi Bold (Demi Bold)</td>
+ * <td align="center">{@link FontStyle#FONT_WEIGHT_SEMI_BOLD}</td>
+ * </tr>
+ * <tr>
+ * <td align="center">700</td>
+ * <td align="center">Bold</td>
+ * <td align="center">{@link FontStyle#FONT_WEIGHT_BOLD}</td>
+ * </tr>
+ * <tr>
+ * <td align="center">800</td>
+ * <td align="center">Extra Bold (Ultra Bold)</td>
+ * <td align="center">{@link FontStyle#FONT_WEIGHT_EXTRA_BOLD}</td>
+ * </tr>
+ * <tr>
+ * <td align="center">900</td>
+ * <td align="center">Black (Heavy)</td>
+ * <td align="center">{@link FontStyle#FONT_WEIGHT_BLACK}</td>
+ * </tr>
+ * </tbody>
+ * </p>
+ *
+ * @see FontStyle#FONT_WEIGHT_THIN
+ * @see FontStyle#FONT_WEIGHT_EXTRA_LIGHT
+ * @see FontStyle#FONT_WEIGHT_LIGHT
+ * @see FontStyle#FONT_WEIGHT_NORMAL
+ * @see FontStyle#FONT_WEIGHT_MEDIUM
+ * @see FontStyle#FONT_WEIGHT_SEMI_BOLD
+ * @see FontStyle#FONT_WEIGHT_BOLD
+ * @see FontStyle#FONT_WEIGHT_EXTRA_BOLD
+ * @see FontStyle#FONT_WEIGHT_BLACK
+ * @param weight a weight value
+ * @return this builder
+ */
+ public @NonNull Builder setWeight(
+ @IntRange(from = FontStyle.FONT_WEIGHT_MIN, to = FontStyle.FONT_WEIGHT_MAX)
+ int weight) {
+ Preconditions.checkArgument(
+ FontStyle.FONT_WEIGHT_MIN <= weight && weight <= FontStyle.FONT_WEIGHT_MAX);
+ mWeight = weight;
+ return this;
+ }
+
+ /**
+ * Sets italic information of the font.
+ *
+ * Tells the system the style of the given font. If this function is not called, the system
+ * will resolve the style by reading font tables.
+ *
+ * For example, if you want to use italic font as upright font, call {@code
+ * setSlant(FontStyle.FONT_SLANT_UPRIGHT)} explicitly.
+ *
+ * @return this builder
+ */
+ public @NonNull Builder setSlant(@FontStyle.FontSlant int slant) {
+ mItalic = slant == FontStyle.FONT_SLANT_UPRIGHT ? STYLE_NORMAL : STYLE_ITALIC;
+ return this;
+ }
+
+ /**
+ * Sets an index of the font collection. See {@link android.R.attr#ttcIndex}.
+ *
+ * @param ttcIndex An index of the font collection. If the font source is not font
+ * collection, do not call this method or specify 0.
+ * @return this builder
+ */
+ public @NonNull Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) {
+ mTtcIndex = ttcIndex;
+ return this;
+ }
+
+ /**
+ * Sets the font variation settings.
+ *
+ * @param variationSettings see {@link FontVariationAxis#fromFontVariationSettings(String)}
+ * @return this builder
+ * @throws IllegalArgumentException If given string is not a valid font variation settings
+ * format.
+ */
+ public @NonNull Builder setFontVariationSettings(@Nullable String variationSettings) {
+ mAxes = FontVariationAxis.fromFontVariationSettings(variationSettings);
+ return this;
+ }
+
+ /**
+ * Sets the font variation settings.
+ *
+ * @param axes an array of font variation axis tag-value pairs
+ * @return this builder
+ */
+ public @NonNull Builder setFontVariationSettings(@Nullable FontVariationAxis[] axes) {
+ mAxes = axes == null ? null : axes.clone();
+ return this;
+ }
+
+ /**
+ * Creates the font based on the configured values.
+ * @return the Font object
+ */
+ public @NonNull Font build() throws IOException {
+ if (mException != null) {
+ throw new IOException("Failed to read font contents", mException);
+ }
+ if (mWeight == NOT_SPECIFIED || mItalic == NOT_SPECIFIED) {
+ final int packed = FontFileUtil.analyzeStyle(mBuffer, mTtcIndex, mAxes);
+ if (FontFileUtil.isSuccess(packed)) {
+ if (mWeight == NOT_SPECIFIED) {
+ mWeight = FontFileUtil.unpackWeight(packed);
+ }
+ if (mItalic == NOT_SPECIFIED) {
+ mItalic = FontFileUtil.unpackItalic(packed) ? STYLE_ITALIC : STYLE_NORMAL;
+ }
+ } else {
+ mWeight = 400;
+ mItalic = STYLE_NORMAL;
+ }
+ }
+ mWeight = Math.max(FontStyle.FONT_WEIGHT_MIN,
+ Math.min(FontStyle.FONT_WEIGHT_MAX, mWeight));
+ final boolean italic = (mItalic == STYLE_ITALIC);
+ final int slant = (mItalic == STYLE_ITALIC)
+ ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+ final long builderPtr = nInitBuilder();
+ if (mAxes != null) {
+ for (FontVariationAxis axis : mAxes) {
+ nAddAxis(builderPtr, axis.getOpenTypeTagValue(), axis.getStyleValue());
+ }
+ }
+ final ByteBuffer readonlyBuffer = mBuffer.asReadOnlyBuffer();
+ final String filePath = mFile == null ? "" : mFile.getAbsolutePath();
+ final long ptr = nBuild(builderPtr, readonlyBuffer, filePath, mWeight, italic,
+ mTtcIndex);
+ final Font font = new Font(ptr, readonlyBuffer, mFile,
+ new FontStyle(mWeight, slant), mTtcIndex, mAxes, mLocaleList);
+ sFontRegistry.registerNativeAllocation(font, ptr);
+ return 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();
+ @CriticalNative
+ private static native void nAddAxis(long builderPtr, int tag, float value);
+ private static native long nBuild(
+ long builderPtr, @NonNull ByteBuffer buffer, @NonNull String filePath, int weight,
+ boolean italic, int ttcIndex);
+ @CriticalNative
+ private static native long nGetReleaseNativeFont();
+ }
+
+ private final long mNativePtr; // address of the shared ptr of minikin::Font
+ private final @NonNull ByteBuffer mBuffer;
+ private final @Nullable File mFile;
+ private final FontStyle mFontStyle;
+ private final @IntRange(from = 0) int mTtcIndex;
+ private final @Nullable FontVariationAxis[] mAxes;
+ private final @NonNull String mLocaleList;
+
+ /**
+ * Use Builder instead
+ */
+ private Font(long nativePtr, @NonNull ByteBuffer buffer, @Nullable File file,
+ @NonNull FontStyle fontStyle, @IntRange(from = 0) int ttcIndex,
+ @Nullable FontVariationAxis[] axes, @NonNull String localeList) {
+ mBuffer = buffer;
+ mFile = file;
+ mFontStyle = fontStyle;
+ mNativePtr = nativePtr;
+ mTtcIndex = ttcIndex;
+ mAxes = axes;
+ mLocaleList = localeList;
+ }
+
+ /**
+ * Returns a font file buffer.
+ *
+ * @return a font buffer
+ */
+ public @NonNull ByteBuffer getBuffer() {
+ return mBuffer;
+ }
+
+ /**
+ * Returns a file path of this font.
+ *
+ * This returns null if this font is not created from regular file.
+ *
+ * @return a file path of the font
+ */
+ public @Nullable File getFile() {
+ return mFile;
+ }
+
+ /**
+ * Get a style associated with this font.
+ *
+ * @see Builder#setWeight(int)
+ * @see Builder#setSlant(int)
+ * @return a font style
+ */
+ public @NonNull FontStyle getStyle() {
+ return mFontStyle;
+ }
+
+ /**
+ * Get a TTC index value associated with this font.
+ *
+ * If TTF/OTF file is provided, this value is always 0.
+ *
+ * @see Builder#setTtcIndex(int)
+ * @return a TTC index value
+ */
+ public @IntRange(from = 0) int getTtcIndex() {
+ return mTtcIndex;
+ }
+
+ /**
+ * Get a font variation settings associated with this font
+ *
+ * @see Builder#setFontVariationSettings(String)
+ * @see Builder#setFontVariationSettings(FontVariationAxis[])
+ * @return font variation settings
+ */
+ public @Nullable FontVariationAxis[] getAxes() {
+ return mAxes == null ? null : mAxes.clone();
+ }
+
+ /**
+ * Get a locale list of this font.
+ *
+ * This is always empty if this font is not a system font.
+ * @return a locale list
+ */
+ public @NonNull LocaleList getLocaleList() {
+ return LocaleList.forLanguageTags(mLocaleList);
+ }
+
+ /** @hide */
+ public long getNativePtr() {
+ return mNativePtr;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (o == null || !(o instanceof Font)) {
+ return false;
+ }
+ Font f = (Font) o;
+ return mFontStyle.equals(f.mFontStyle) && f.mTtcIndex == mTtcIndex
+ && Arrays.equals(f.mAxes, mAxes) && f.mBuffer.equals(mBuffer);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mFontStyle, mTtcIndex, Arrays.hashCode(mAxes), mBuffer);
+ }
+
+ @Override
+ public String toString() {
+ return "Font {"
+ + "path=" + mFile
+ + ", style=" + mFontStyle
+ + ", ttcIndex=" + mTtcIndex
+ + ", axes=" + FontVariationAxis.toFontVariationSettings(mAxes)
+ + ", localeList=" + mLocaleList
+ + ", buffer=" + mBuffer
+ + "}";
+ }
+}
diff --git a/graphics/java/android/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
new file mode 100644
index 000000000000..0291d7484dc5
--- /dev/null
+++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.fonts;
+
+import android.annotation.NonNull;
+import android.graphics.FontListParser;
+import android.text.FontConfig;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashSet;
+
+/**
+ * Parser for font customization
+ *
+ * @hide
+ */
+public class FontCustomizationParser {
+ /**
+ * Represents a customization XML
+ */
+ public static class Result {
+ ArrayList<FontConfig.Family> mAdditionalNamedFamilies = new ArrayList<>();
+ ArrayList<FontConfig.Alias> mAdditionalAliases = new ArrayList<>();
+ }
+
+ /**
+ * Parses the customization XML
+ *
+ * Caller must close the input stream
+ */
+ public static Result parse(@NonNull InputStream in, @NonNull String fontDir)
+ throws XmlPullParserException, IOException {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(in, null);
+ parser.nextTag();
+ return readFamilies(parser, fontDir);
+ }
+
+ private static void validate(Result result) {
+ HashSet<String> familyNames = new HashSet<>();
+ for (int i = 0; i < result.mAdditionalNamedFamilies.size(); ++i) {
+ final FontConfig.Family family = result.mAdditionalNamedFamilies.get(i);
+ final String name = family.getName();
+ if (name == null) {
+ throw new IllegalArgumentException("new-named-family requires name attribute");
+ }
+ if (!familyNames.add(name)) {
+ throw new IllegalArgumentException(
+ "new-named-family requires unique name attribute");
+ }
+ }
+ }
+
+ private static Result readFamilies(XmlPullParser parser, String fontDir)
+ throws XmlPullParserException, IOException {
+ Result out = new Result();
+ parser.require(XmlPullParser.START_TAG, null, "fonts-modification");
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) continue;
+ String tag = parser.getName();
+ if (tag.equals("family")) {
+ readFamily(parser, fontDir, out);
+ } else if (tag.equals("alias")) {
+ out.mAdditionalAliases.add(FontListParser.readAlias(parser));
+ } else {
+ FontListParser.skip(parser);
+ }
+ }
+ validate(out);
+ return out;
+ }
+
+ private static void readFamily(XmlPullParser parser, String fontDir, Result out)
+ throws XmlPullParserException, IOException {
+ final String customizationType = parser.getAttributeValue(null, "customizationType");
+ if (customizationType == null) {
+ throw new IllegalArgumentException("customizationType must be specified");
+ }
+ if (customizationType.equals("new-named-family")) {
+ out.mAdditionalNamedFamilies.add(FontListParser.readFamily(parser, fontDir));
+ } else {
+ throw new IllegalArgumentException("Unknown customizationType=" + customizationType);
+ }
+ }
+}
diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java
new file mode 100644
index 000000000000..75ea12062929
--- /dev/null
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.fonts;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.text.FontConfig;
+
+import com.android.internal.util.Preconditions;
+
+import dalvik.annotation.optimization.CriticalNative;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+/**
+ * A font family class can be used for creating Typeface.
+ *
+ * <p>
+ * A font family is a bundle of fonts for drawing text in various styles.
+ * For example, you can bundle regular style font and bold style font into a single font family,
+ * then system will select the correct style font from family for drawing.
+ *
+ * <pre>
+ * FontFamily family = new FontFamily.Builder(new Font.Builder("regular.ttf").build())
+ * .addFont(new Font.Builder("bold.ttf").build()).build();
+ * Typeface typeface = new Typeface.Builder2(family).build();
+ *
+ * SpannableStringBuilder ssb = new SpannableStringBuilder("Hello, World.");
+ * ssb.setSpan(new StyleSpan(Typeface.Bold), 6, 12, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ *
+ * textView.setTypeface(typeface);
+ * textView.setText(ssb);
+ * </pre>
+ *
+ * In this example, "Hello, " is drawn with "regular.ttf", and "World." is drawn with "bold.ttf".
+ *
+ * If there is no font exactly matches with the text style, the system will select the closest font.
+ * </p>
+ *
+ */
+public final class FontFamily {
+ private static final String TAG = "FontFamily";
+
+ /**
+ * A builder class for creating new FontFamily.
+ */
+ public static final class Builder {
+ private static final NativeAllocationRegistry sFamilyRegistory =
+ NativeAllocationRegistry.createMalloced(FontFamily.class.getClassLoader(),
+ nGetReleaseNativeFamily());
+
+ private final ArrayList<Font> mFonts = new ArrayList<>();
+ private final HashSet<Integer> mStyleHashSet = new HashSet<>();
+
+ /**
+ * Constructs a builder.
+ *
+ * @param font a font
+ */
+ public Builder(@NonNull Font font) {
+ Preconditions.checkNotNull(font, "font can not be null");
+ mStyleHashSet.add(makeStyleIdentifier(font));
+ mFonts.add(font);
+ }
+
+ /**
+ * Adds different style font to the builder.
+ *
+ * System will select the font if the text style is closest to the font.
+ * If the same style font is already added to the builder, this method will fail with
+ * {@link IllegalArgumentException}.
+ *
+ * Note that system assumes all fonts bundled in FontFamily have the same coverage for the
+ * code points. For example, regular style font and bold style font must have the same code
+ * point coverage, otherwise some character may be shown as tofu.
+ *
+ * @param font a font
+ * @return this builder
+ */
+ public @NonNull Builder addFont(@NonNull Font font) {
+ Preconditions.checkNotNull(font, "font can not be null");
+ if (!mStyleHashSet.add(makeStyleIdentifier(font))) {
+ throw new IllegalArgumentException(font + " has already been added");
+ }
+ mFonts.add(font);
+ return this;
+ }
+
+ /**
+ * Build the font family
+ * @return a font family
+ */
+ public @NonNull FontFamily build() {
+ return build("", FontConfig.Family.VARIANT_DEFAULT, true /* isCustomFallback */);
+ }
+
+ /** @hide */
+ public @NonNull FontFamily build(@NonNull String langTags, int variant,
+ boolean isCustomFallback) {
+ final long builderPtr = nInitBuilder();
+ for (int i = 0; i < mFonts.size(); ++i) {
+ nAddFont(builderPtr, mFonts.get(i).getNativePtr());
+ }
+ final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback);
+ final FontFamily family = new FontFamily(mFonts, ptr);
+ sFamilyRegistory.registerNativeAllocation(family, ptr);
+ return family;
+ }
+
+ private static int makeStyleIdentifier(@NonNull Font font) {
+ return font.getStyle().getWeight() | (font.getStyle().getSlant() << 16);
+ }
+
+ private static native long nInitBuilder();
+ @CriticalNative
+ private static native void nAddFont(long builderPtr, long fontPtr);
+ private static native long nBuild(long builderPtr, String langTags, int variant,
+ boolean isCustomFallback);
+ @CriticalNative
+ private static native long nGetReleaseNativeFamily();
+ }
+
+ private final ArrayList<Font> mFonts;
+ private final long mNativePtr;
+
+ // Use Builder instead.
+ private FontFamily(@NonNull ArrayList<Font> fonts, long ptr) {
+ mFonts = fonts;
+ mNativePtr = ptr;
+ }
+
+ /**
+ * Returns a font
+ *
+ * @param index an index of the font
+ * @return a registered font
+ */
+ public @NonNull Font getFont(@IntRange(from = 0) int index) {
+ return mFonts.get(index);
+ }
+
+ /**
+ * Returns the number of fonts in this FontFamily.
+ *
+ * @return the number of fonts registered in this family.
+ */
+ public @IntRange(from = 1) int getSize() {
+ return mFonts.size();
+ }
+
+ /** @hide */
+ public long getNativePtr() {
+ return mNativePtr;
+ }
+}
diff --git a/graphics/java/android/graphics/fonts/FontFileUtil.java b/graphics/java/android/graphics/fonts/FontFileUtil.java
new file mode 100644
index 000000000000..f8b456b982c5
--- /dev/null
+++ b/graphics/java/android/graphics/fonts/FontFileUtil.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.fonts;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Provides a utility for font file operations.
+ * @hide
+ */
+public class FontFileUtil {
+
+ private FontFileUtil() {} // Do not instanciate
+
+ /**
+ * Unpack the weight value from packed integer.
+ */
+ public static int unpackWeight(int packed) {
+ return packed & 0xFFFF;
+ }
+
+ /**
+ * Unpack the italic value from packed integer.
+ */
+ public static boolean unpackItalic(int packed) {
+ return (packed & 0x10000) != 0;
+ }
+
+ /**
+ * Returns true if the analyzeStyle succeeded
+ */
+ public static boolean isSuccess(int packed) {
+ return packed != ANALYZE_ERROR;
+ }
+
+ private static int pack(@IntRange(from = 0, to = 1000) int weight, boolean italic) {
+ return weight | (italic ? 0x10000 : 0);
+ }
+
+ private static final int SFNT_VERSION_1 = 0x00010000;
+ private static final int SFNT_VERSION_OTTO = 0x4F54544F;
+ private static final int TTC_TAG = 0x74746366;
+ private static final int OS2_TABLE_TAG = 0x4F532F32;
+
+ private static final int ANALYZE_ERROR = 0xFFFFFFFF;
+
+ /**
+ * Analyze the font file returns packed style info
+ */
+ public static final int analyzeStyle(@NonNull ByteBuffer buffer,
+ @IntRange(from = 0) int ttcIndex, @Nullable FontVariationAxis[] varSettings) {
+ int weight = -1;
+ int italic = -1;
+ if (varSettings != null) {
+ for (FontVariationAxis axis :varSettings) {
+ if ("wght".equals(axis.getTag())) {
+ weight = (int) axis.getStyleValue();
+ } else if ("ital".equals(axis.getTag())) {
+ italic = (axis.getStyleValue() == 1.0f) ? 1 : 0;
+ }
+ }
+ }
+
+ if (weight != -1 && italic != -1) {
+ // Both weight/italic style are specifeid by variation settings.
+ // No need to look into OS/2 table.
+ // TODO: Good to look HVAR table to check if this font supports wght/ital axes.
+ return pack(weight, italic == 1);
+ }
+
+ ByteOrder originalOrder = buffer.order();
+ buffer.order(ByteOrder.BIG_ENDIAN);
+ try {
+ int fontFileOffset = 0;
+ int magicNumber = buffer.getInt(0);
+ if (magicNumber == TTC_TAG) {
+ // TTC file.
+ if (ttcIndex >= buffer.getInt(8 /* offset to number of fonts in TTC */)) {
+ return ANALYZE_ERROR;
+ }
+ fontFileOffset = buffer.getInt(
+ 12 /* offset to array of offsets of font files */ + 4 * ttcIndex);
+ }
+ int sfntVersion = buffer.getInt(fontFileOffset);
+
+ if (sfntVersion != SFNT_VERSION_1 && sfntVersion != SFNT_VERSION_OTTO) {
+ return ANALYZE_ERROR;
+ }
+
+ int numTables = buffer.getShort(fontFileOffset + 4 /* offset to number of tables */);
+ int os2TableOffset = -1;
+ for (int i = 0; i < numTables; ++i) {
+ int tableOffset = fontFileOffset + 12 /* size of offset table */
+ + i * 16 /* size of table record */;
+ if (buffer.getInt(tableOffset) == OS2_TABLE_TAG) {
+ os2TableOffset = buffer.getInt(tableOffset + 8 /* offset to the table */);
+ break;
+ }
+ }
+
+ if (os2TableOffset == -1) {
+ // Couldn't find OS/2 table. use regular style
+ return pack(400, false);
+ }
+
+ int weightFromOS2 = buffer.getShort(os2TableOffset + 4 /* offset to weight class */);
+ boolean italicFromOS2 =
+ (buffer.getShort(os2TableOffset + 62 /* offset to fsSelection */) & 1) != 0;
+ return pack(weight == -1 ? weightFromOS2 : weight,
+ italic == -1 ? italicFromOS2 : italic == 1);
+ } finally {
+ buffer.order(originalOrder);
+ }
+ }
+}
diff --git a/graphics/java/android/graphics/fonts/FontStyle.java b/graphics/java/android/graphics/fonts/FontStyle.java
new file mode 100644
index 000000000000..af517d623b01
--- /dev/null
+++ b/graphics/java/android/graphics/fonts/FontStyle.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.fonts;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * A font style object.
+ *
+ * This class represents a single font style which is a pair of weight value and slant value.
+ * Here are common font styles examples:
+ * <p>
+ * <pre>
+ * <code>
+ * final FontStyle NORMAL = new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT);
+ * final FontStyle BOLD = new FontStyle(FONT_WEIGHT_BOLD, FONT_SLANT_UPRIGHT);
+ * final FontStyle ITALIC = new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC);
+ * final FontStyle BOLD_ITALIC = new FontStyle(FONT_WEIGHT_BOLD, FONT_SLANT_ITALIC);
+ * </code>
+ * </pre>
+ * </p>
+ *
+ */
+public final class FontStyle {
+ private static final String TAG = "FontStyle";
+
+ /**
+ * A minimum weight value for the font
+ */
+ public static final int FONT_WEIGHT_MIN = 1;
+
+ /**
+ * A font weight value for the thin weight
+ */
+ public static final int FONT_WEIGHT_THIN = 100;
+
+ /**
+ * A font weight value for the extra-light weight
+ */
+ public static final int FONT_WEIGHT_EXTRA_LIGHT = 200;
+
+ /**
+ * A font weight value for the light weight
+ */
+ public static final int FONT_WEIGHT_LIGHT = 300;
+
+ /**
+ * A font weight value for the normal weight
+ */
+ public static final int FONT_WEIGHT_NORMAL = 400;
+
+ /**
+ * A font weight value for the medium weight
+ */
+ public static final int FONT_WEIGHT_MEDIUM = 500;
+
+ /**
+ * A font weight value for the semi-bold weight
+ */
+ public static final int FONT_WEIGHT_SEMI_BOLD = 600;
+
+ /**
+ * A font weight value for the bold weight.
+ */
+ public static final int FONT_WEIGHT_BOLD = 700;
+
+ /**
+ * A font weight value for the extra-bold weight
+ */
+ public static final int FONT_WEIGHT_EXTRA_BOLD = 800;
+
+ /**
+ * A font weight value for the black weight
+ */
+ public static final int FONT_WEIGHT_BLACK = 900;
+
+ /**
+ * A maximum weight value for the font
+ */
+ public static final int FONT_WEIGHT_MAX = 1000;
+
+ /**
+ * A font slant value for upright
+ */
+ public static final int FONT_SLANT_UPRIGHT = 0;
+
+ /**
+ * A font slant value for italic
+ */
+ public static final int FONT_SLANT_ITALIC = 1;
+
+ // TODO: Support FONT_SLANT_OBLIQUE
+
+ /** @hide */
+ @IntDef(prefix = { "FONT_SLANT_" }, value = {
+ FONT_SLANT_UPRIGHT,
+ FONT_SLANT_ITALIC
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FontSlant {}
+
+ private final @IntRange(from = 0, to = 1000) int mWeight;
+ private final @FontSlant int mSlant;
+ // TODO: Support width
+
+ public FontStyle() {
+ mWeight = FONT_WEIGHT_NORMAL;
+ mSlant = FONT_SLANT_UPRIGHT;
+ }
+
+ /**
+ * Create FontStyle with specific weight and italic
+ *
+ * <p>
+ * <table>
+ * <thead>
+ * <tr>
+ * <th align="center">Value</th>
+ * <th align="center">Name</th>
+ * <th align="center">Android Definition</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td align="center">100</td>
+ * <td align="center">Thin</td>
+ * <td align="center">{@link FontStyle#FONT_WEIGHT_THIN}</td>
+ * </tr>
+ * <tr>
+ * <td align="center">200</td>
+ * <td align="center">Extra Light (Ultra Light)</td>
+ * <td align="center">{@link FontStyle#FONT_WEIGHT_EXTRA_LIGHT}</td>
+ * </tr>
+ * <tr>
+ * <td align="center">300</td>
+ * <td align="center">Light</td>
+ * <td align="center">{@link FontStyle#FONT_WEIGHT_LIGHT}</td>
+ * </tr>
+ * <tr>
+ * <td align="center">400</td>
+ * <td align="center">Normal (Regular)</td>
+ * <td align="center">{@link FontStyle#FONT_WEIGHT_NORMAL}</td>
+ * </tr>
+ * <tr>
+ * <td align="center">500</td>
+ * <td align="center">Medium</td>
+ * <td align="center">{@link FontStyle#FONT_WEIGHT_MEDIUM}</td>
+ * </tr>
+ * <tr>
+ * <td align="center">600</td>
+ * <td align="center">Semi Bold (Demi Bold)</td>
+ * <td align="center">{@link FontStyle#FONT_WEIGHT_SEMI_BOLD}</td>
+ * </tr>
+ * <tr>
+ * <td align="center">700</td>
+ * <td align="center">Bold</td>
+ * <td align="center">{@link FontStyle#FONT_WEIGHT_BOLD}</td>
+ * </tr>
+ * <tr>
+ * <td align="center">800</td>
+ * <td align="center">Extra Bold (Ultra Bold)</td>
+ * <td align="center">{@link FontStyle#FONT_WEIGHT_EXTRA_BOLD}</td>
+ * </tr>
+ * <tr>
+ * <td align="center">900</td>
+ * <td align="center">Black (Heavy)</td>
+ * <td align="center">{@link FontStyle#FONT_WEIGHT_BLACK}</td>
+ * </tr>
+ * </tbody>
+ * </p>
+ *
+ * @see FontStyle#FONT_WEIGHT_THIN
+ * @see FontStyle#FONT_WEIGHT_EXTRA_LIGHT
+ * @see FontStyle#FONT_WEIGHT_LIGHT
+ * @see FontStyle#FONT_WEIGHT_NORMAL
+ * @see FontStyle#FONT_WEIGHT_MEDIUM
+ * @see FontStyle#FONT_WEIGHT_SEMI_BOLD
+ * @see FontStyle#FONT_WEIGHT_BOLD
+ * @see FontStyle#FONT_WEIGHT_EXTRA_BOLD
+ * @see FontStyle#FONT_WEIGHT_BLACK
+ * @param weight a weight value
+ * @param slant a slant value
+ */
+ public FontStyle(int weight, @FontSlant int slant) {
+ Preconditions.checkArgument(FONT_WEIGHT_MIN <= weight && weight <= FONT_WEIGHT_MAX,
+ "weight value must be [" + FONT_WEIGHT_MIN + ", " + FONT_WEIGHT_MAX + "]");
+ Preconditions.checkArgument(slant == FONT_SLANT_UPRIGHT || slant == FONT_SLANT_ITALIC,
+ "slant value must be FONT_SLANT_UPRIGHT or FONT_SLANT_UPRIGHT");
+ mWeight = weight;
+ mSlant = slant;
+ }
+
+
+ /**
+ * Gets the weight value
+ *
+ * @see FontStyle#setWeight(int)
+ * @return a weight value
+ */
+ public @IntRange(from = 0, to = 1000) int getWeight() {
+ return mWeight;
+ }
+
+ /**
+ * Gets the slant value
+ *
+ * @return a slant value
+ */
+ public @FontSlant int getSlant() {
+ return mSlant;
+ }
+
+ /**
+ * Compute the matching score for another style.
+ *
+ * The smaller is better.
+ * @hide
+ */
+ public int getMatchScore(@NonNull FontStyle o) {
+ return Math.abs((getWeight() - o.getWeight())) / 100 + (getSlant() == o.getSlant() ? 0 : 2);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (o == null || !(o instanceof FontStyle)) {
+ return false;
+ }
+ FontStyle fontStyle = (FontStyle) o;
+ return fontStyle.mWeight == mWeight && fontStyle.mSlant == mSlant;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mWeight, mSlant);
+ }
+
+ @Override
+ public String toString() {
+ return "FontStyle { weight=" + mWeight + ", slant=" + mSlant + "}";
+ }
+}
diff --git a/graphics/java/android/graphics/fonts/FontVariationAxis.java b/graphics/java/android/graphics/fonts/FontVariationAxis.java
index 1b7408a03294..bcee559d8291 100644
--- a/graphics/java/android/graphics/fonts/FontVariationAxis.java
+++ b/graphics/java/android/graphics/fonts/FontVariationAxis.java
@@ -18,17 +18,22 @@ package android.graphics.fonts;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
+import android.os.Build;
import android.text.TextUtils;
import java.util.ArrayList;
+import java.util.Objects;
import java.util.regex.Pattern;
/**
* Class that holds information about single font variation axis.
*/
public final class FontVariationAxis {
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private final int mTag;
private final String mTagString;
+ @UnsupportedAppUsage
private final float mStyleValue;
/**
@@ -183,5 +188,22 @@ public final class FontVariationAxis {
}
return TextUtils.join(",", axes);
}
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (o == null || !(o instanceof FontVariationAxis)) {
+ return false;
+ }
+ FontVariationAxis axis = (FontVariationAxis) o;
+ return axis.mTag == mTag && axis.mStyleValue == mStyleValue;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTag, mStyleValue);
+ }
}
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
new file mode 100644
index 000000000000..4a9cf14d04a5
--- /dev/null
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.fonts;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.FontListParser;
+import android.text.FontConfig;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Provides the system font configurations.
+ */
+public final class SystemFonts {
+ private static final String TAG = "SystemFonts";
+ private static final String DEFAULT_FAMILY = "sans-serif";
+
+ private SystemFonts() {} // Do not instansiate.
+
+ private static final Map<String, FontFamily[]> sSystemFallbackMap;
+ private static final FontConfig.Alias[] sAliases;
+ private static final List<Font> sAvailableFonts;
+
+ /**
+ * Returns all available font files in the system.
+ *
+ * @return a set of system fonts
+ */
+ public static @NonNull Set<Font> getAvailableFonts() {
+ HashSet<Font> set = new HashSet<>();
+ set.addAll(sAvailableFonts);
+ return set;
+ }
+
+ /**
+ * Returns fallback list for the given family name.
+ *
+ * If no fallback found for the given family name, returns fallback for the default family.
+ *
+ * @param familyName family name, e.g. "serif"
+ * @hide
+ */
+ public static @NonNull FontFamily[] getSystemFallback(@Nullable String familyName) {
+ final FontFamily[] families = sSystemFallbackMap.get(familyName);
+ return families == null ? sSystemFallbackMap.get(DEFAULT_FAMILY) : families;
+ }
+
+ /**
+ * Returns raw system fallback map.
+ *
+ * This method is intended to be used only by Typeface static initializer.
+ * @hide
+ */
+ public static @NonNull Map<String, FontFamily[]> getRawSystemFallbackMap() {
+ return sSystemFallbackMap;
+ }
+
+ /**
+ * Returns a list of aliases.
+ *
+ * This method is intended to be used only by Typeface static initializer.
+ * @hide
+ */
+ public static @NonNull FontConfig.Alias[] getAliases() {
+ return sAliases;
+ }
+
+ private static @Nullable ByteBuffer mmap(@NonNull String fullPath) {
+ try (FileInputStream file = new FileInputStream(fullPath)) {
+ final FileChannel fileChannel = file.getChannel();
+ final long fontSize = fileChannel.size();
+ return fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
+ } catch (IOException e) {
+ Log.e(TAG, "Error mapping font file " + fullPath);
+ return null;
+ }
+ }
+
+ private static void pushFamilyToFallback(@NonNull FontConfig.Family xmlFamily,
+ @NonNull ArrayMap<String, ArrayList<FontFamily>> fallbackMap,
+ @NonNull Map<String, ByteBuffer> cache,
+ @NonNull ArrayList<Font> availableFonts) {
+
+ final String languageTags = xmlFamily.getLanguages();
+ final int variant = xmlFamily.getVariant();
+
+ final ArrayList<FontConfig.Font> defaultFonts = new ArrayList<>();
+ final ArrayMap<String, ArrayList<FontConfig.Font>> specificFallbackFonts = new ArrayMap<>();
+
+ // Collect default fallback and specific fallback fonts.
+ for (final FontConfig.Font font : xmlFamily.getFonts()) {
+ final String fallbackName = font.getFallbackFor();
+ if (fallbackName == null) {
+ defaultFonts.add(font);
+ } else {
+ ArrayList<FontConfig.Font> fallback = specificFallbackFonts.get(fallbackName);
+ if (fallback == null) {
+ fallback = new ArrayList<>();
+ specificFallbackFonts.put(fallbackName, fallback);
+ }
+ fallback.add(font);
+ }
+ }
+
+ final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily(
+ xmlFamily.getName(), defaultFonts, languageTags, variant, cache, availableFonts);
+
+ // Insert family into fallback map.
+ for (int i = 0; i < fallbackMap.size(); i++) {
+ final ArrayList<FontConfig.Font> fallback =
+ specificFallbackFonts.get(fallbackMap.keyAt(i));
+ if (fallback == null) {
+ if (defaultFamily != null) {
+ fallbackMap.valueAt(i).add(defaultFamily);
+ }
+ } else {
+ final FontFamily family = createFontFamily(
+ xmlFamily.getName(), fallback, languageTags, variant, cache,
+ availableFonts);
+ if (family != null) {
+ fallbackMap.valueAt(i).add(family);
+ } else if (defaultFamily != null) {
+ fallbackMap.valueAt(i).add(defaultFamily);
+ } else {
+ // There is no valid for for default fallback. Ignore.
+ }
+ }
+ }
+ }
+
+ private static @Nullable FontFamily createFontFamily(@NonNull String familyName,
+ @NonNull List<FontConfig.Font> fonts,
+ @NonNull String languageTags,
+ @FontConfig.Family.Variant int variant,
+ @NonNull Map<String, ByteBuffer> cache,
+ @NonNull ArrayList<Font> availableFonts) {
+ if (fonts.size() == 0) {
+ return null;
+ }
+
+ FontFamily.Builder b = null;
+ for (int i = 0; i < fonts.size(); i++) {
+ final FontConfig.Font fontConfig = fonts.get(i);
+ final String fullPath = fontConfig.getFontName();
+ ByteBuffer buffer = cache.get(fullPath);
+ if (buffer == null) {
+ if (cache.containsKey(fullPath)) {
+ continue; // Already failed to mmap. Skip it.
+ }
+ buffer = mmap(fullPath);
+ cache.put(fullPath, buffer);
+ if (buffer == null) {
+ continue;
+ }
+ }
+
+ final Font font;
+ try {
+ font = new Font.Builder(buffer, new File(fullPath), languageTags)
+ .setWeight(fontConfig.getWeight())
+ .setSlant(fontConfig.isItalic() ? FontStyle.FONT_SLANT_ITALIC
+ : FontStyle.FONT_SLANT_UPRIGHT)
+ .setTtcIndex(fontConfig.getTtcIndex())
+ .setFontVariationSettings(fontConfig.getAxes())
+ .build();
+ } catch (IOException e) {
+ throw new RuntimeException(e); // Never reaches here
+ }
+
+ availableFonts.add(font);
+ if (b == null) {
+ b = new FontFamily.Builder(font);
+ } else {
+ b.addFont(font);
+ }
+ }
+ return b == null ? null : b.build(languageTags, variant, false /* isCustomFallback */);
+ }
+
+ private static void appendNamedFamily(@NonNull FontConfig.Family xmlFamily,
+ @NonNull HashMap<String, ByteBuffer> bufferCache,
+ @NonNull ArrayMap<String, ArrayList<FontFamily>> fallbackListMap,
+ @NonNull ArrayList<Font> availableFonts) {
+ final String familyName = xmlFamily.getName();
+ final FontFamily family = createFontFamily(
+ familyName, Arrays.asList(xmlFamily.getFonts()),
+ xmlFamily.getLanguages(), xmlFamily.getVariant(), bufferCache, availableFonts);
+ if (family == null) {
+ return;
+ }
+ final ArrayList<FontFamily> fallback = new ArrayList<>();
+ fallback.add(family);
+ fallbackListMap.put(familyName, fallback);
+ }
+
+ /**
+ * Build the system fallback from xml file.
+ *
+ * @param xmlPath A full path string to the fonts.xml file.
+ * @param fontDir A full path string to the system font directory. This must end with
+ * slash('/').
+ * @param fallbackMap An output system fallback map. Caller must pass empty map.
+ * @return a list of aliases
+ * @hide
+ */
+ @VisibleForTesting
+ public static FontConfig.Alias[] buildSystemFallback(@NonNull String xmlPath,
+ @NonNull String fontDir,
+ @NonNull FontCustomizationParser.Result oemCustomization,
+ @NonNull ArrayMap<String, FontFamily[]> fallbackMap,
+ @NonNull ArrayList<Font> availableFonts) {
+ try {
+ final FileInputStream fontsIn = new FileInputStream(xmlPath);
+ final FontConfig fontConfig = FontListParser.parse(fontsIn, fontDir);
+
+ final HashMap<String, ByteBuffer> bufferCache = new HashMap<String, ByteBuffer>();
+ final FontConfig.Family[] xmlFamilies = fontConfig.getFamilies();
+
+ final ArrayMap<String, ArrayList<FontFamily>> fallbackListMap = new ArrayMap<>();
+ // First traverse families which have a 'name' attribute to create fallback map.
+ for (final FontConfig.Family xmlFamily : xmlFamilies) {
+ final String familyName = xmlFamily.getName();
+ if (familyName == null) {
+ continue;
+ }
+ appendNamedFamily(xmlFamily, bufferCache, fallbackListMap, availableFonts);
+ }
+
+ for (int i = 0; i < oemCustomization.mAdditionalNamedFamilies.size(); ++i) {
+ appendNamedFamily(oemCustomization.mAdditionalNamedFamilies.get(i),
+ bufferCache, fallbackListMap, availableFonts);
+ }
+
+ // Then, add fallback fonts to the each fallback map.
+ for (int i = 0; i < xmlFamilies.length; i++) {
+ final FontConfig.Family xmlFamily = xmlFamilies[i];
+ // The first family (usually the sans-serif family) is always placed immediately
+ // after the primary family in the fallback.
+ if (i == 0 || xmlFamily.getName() == null) {
+ pushFamilyToFallback(xmlFamily, fallbackListMap, bufferCache, availableFonts);
+ }
+ }
+
+ // Build the font map and fallback map.
+ for (int i = 0; i < fallbackListMap.size(); i++) {
+ final String fallbackName = fallbackListMap.keyAt(i);
+ final List<FontFamily> familyList = fallbackListMap.valueAt(i);
+ final FontFamily[] families = familyList.toArray(new FontFamily[familyList.size()]);
+
+ fallbackMap.put(fallbackName, families);
+ }
+
+ final ArrayList<FontConfig.Alias> list = new ArrayList<>();
+ list.addAll(Arrays.asList(fontConfig.getAliases()));
+ list.addAll(oemCustomization.mAdditionalAliases);
+ return list.toArray(new FontConfig.Alias[list.size()]);
+ } catch (IOException | XmlPullParserException e) {
+ Log.e(TAG, "Failed initialize system fallbacks.", e);
+ return ArrayUtils.emptyArray(FontConfig.Alias.class);
+ }
+ }
+
+ private static FontCustomizationParser.Result readFontCustomization(
+ @NonNull String customizeXml, @NonNull String customFontsDir) {
+ try (FileInputStream f = new FileInputStream(customizeXml)) {
+ return FontCustomizationParser.parse(f, customFontsDir);
+ } catch (IOException e) {
+ return new FontCustomizationParser.Result();
+ } catch (XmlPullParserException e) {
+ Log.e(TAG, "Failed to parse font customization XML", e);
+ return new FontCustomizationParser.Result();
+ }
+ }
+
+ static {
+ final ArrayMap<String, FontFamily[]> systemFallbackMap = new ArrayMap<>();
+ final ArrayList<Font> availableFonts = new ArrayList<>();
+ final FontCustomizationParser.Result oemCustomization =
+ readFontCustomization("/product/etc/fonts_customization.xml", "/product/fonts/");
+ sAliases = buildSystemFallback("/system/etc/fonts.xml", "/system/fonts/",
+ oemCustomization, systemFallbackMap, availableFonts);
+ sSystemFallbackMap = Collections.unmodifiableMap(systemFallbackMap);
+ sAvailableFonts = Collections.unmodifiableList(availableFonts);
+ }
+}
diff --git a/graphics/java/android/graphics/pdf/PdfEditor.java b/graphics/java/android/graphics/pdf/PdfEditor.java
index 3821bc7ab063..21ce1b8392d2 100644
--- a/graphics/java/android/graphics/pdf/PdfEditor.java
+++ b/graphics/java/android/graphics/pdf/PdfEditor.java
@@ -27,7 +27,6 @@ import android.system.Os;
import android.system.OsConstants;
import dalvik.system.CloseGuard;
import libcore.io.IoUtils;
-import libcore.io.Libcore;
import java.io.IOException;
diff --git a/graphics/java/android/graphics/pdf/PdfRenderer.java b/graphics/java/android/graphics/pdf/PdfRenderer.java
index 4a91705239c1..bd1a49205fd5 100644
--- a/graphics/java/android/graphics/pdf/PdfRenderer.java
+++ b/graphics/java/android/graphics/pdf/PdfRenderer.java
@@ -19,6 +19,7 @@ package android.graphics.pdf;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Matrix;
@@ -118,6 +119,7 @@ public final class PdfRenderer implements AutoCloseable {
private ParcelFileDescriptor mInput;
+ @UnsupportedAppUsage
private Page mCurrentPage;
/** @hide */
@@ -242,6 +244,7 @@ public final class PdfRenderer implements AutoCloseable {
}
}
+ @UnsupportedAppUsage
private void doClose() {
if (mCurrentPage != null) {
mCurrentPage.close();
@@ -432,8 +435,9 @@ public final class PdfRenderer implements AutoCloseable {
final long transformPtr = transform.native_instance;
synchronized (sPdfiumLock) {
- nativeRenderPage(mNativeDocument, mNativePage, destination, contentLeft,
- contentTop, contentRight, contentBottom, transformPtr, renderMode);
+ nativeRenderPage(mNativeDocument, mNativePage, destination.getNativeInstance(),
+ contentLeft, contentTop, contentRight, contentBottom, transformPtr,
+ renderMode);
}
}
@@ -484,7 +488,7 @@ public final class PdfRenderer implements AutoCloseable {
private static native void nativeClose(long documentPtr);
private static native int nativeGetPageCount(long documentPtr);
private static native boolean nativeScaleForPrinting(long documentPtr);
- private static native void nativeRenderPage(long documentPtr, long pagePtr, Bitmap dest,
+ private static native void nativeRenderPage(long documentPtr, long pagePtr, long bitmapHandle,
int clipLeft, int clipTop, int clipRight, int clipBottom, long transformPtr,
int renderMode);
private static native long nativeOpenPageAndGetSize(long documentPtr, int pageIndex,
diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java
new file mode 100644
index 000000000000..54622c5e74df
--- /dev/null
+++ b/graphics/java/android/graphics/text/LineBreaker.java
@@ -0,0 +1,537 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.text;
+
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.Px;
+
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Provides automatic line breaking for a <em>single</em> paragraph.
+ *
+ * <p>
+ * <pre>
+ * <code>
+ * Paint paint = new Paint();
+ * Paint bigPaint = new Paint();
+ * bigPaint.setTextSize(paint.getTextSize() * 2.0);
+ * String text = "Hello, Android.";
+ *
+ * // Prepare the measured text
+ * MeasuredText mt = new MeasuredText.Builder(text.toCharArray())
+ * .appendStyleRun(paint, 7, false) // Use paint for "Hello, "
+ * .appednStyleRun(bigPaint, 8, false) // Use bigPaint for "Hello, "
+ * .build();
+ *
+ * LineBreaker lb = new LineBreaker.Builder()
+ * // Use simple line breaker
+ * .setBreakStrategy(LineBreaker.BREAK_STRATEGY_SIMPLE)
+ * // Do not add hyphenation.
+ * .setHyphenationFrequency(LineBreaker.HYPHENATION_FREQUENCY_NONE)
+ * // Build the LineBreaker
+ * .build();
+ *
+ * ParagraphConstraints c = new ParagraphConstraints();
+ * c.setWidth(240); // Set the line wieth as 1024px
+ *
+ * // Do the line breaking
+ * Result r = lb.computeLineBreaks(mt, c, 0);
+ *
+ * // Compute the total height of the text.
+ * float totalHeight = 0;
+ * for (int i = 0; i < r.getLineCount(); ++i) { // iterate over the lines
+ * totalHeight += r.getLineDescent(i) - r.getLineAscent(i);
+ * }
+ *
+ * // Draw text to the canvas
+ * Bitmap bmp = Bitmap.createBitmap(240, totalHeight, Bitmap.Config.ARGB_8888);
+ * Canvas c = new Canvas(bmp);
+ * float yOffset = 0f;
+ * int prevOffset = 0;
+ * for (int i = 0; i < r.getLineCount(); ++i) { // iterate over the lines
+ * int nextOffset = r.getLineBreakOffset(i);
+ * c.drawText(text, prevOffset, nextOffset, 0f, yOffset, paint);
+ *
+ * prevOffset = nextOffset;
+ * yOffset += r.getLineDescent(i) - r.getLineAscent(i);
+ * }
+ * </code>
+ * </pre>
+ * </p>
+ */
+public class LineBreaker {
+ /** @hide */
+ @IntDef(prefix = { "BREAK_STRATEGY_" }, value = {
+ BREAK_STRATEGY_SIMPLE,
+ BREAK_STRATEGY_HIGH_QUALITY,
+ BREAK_STRATEGY_BALANCED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BreakStrategy {}
+
+ /**
+ * Value for break strategy indicating simple line breaking.
+ *
+ * The line breaker puts words to the line as much as possible and breaks line if no more words
+ * can fit into the same line. Automatic hyphens are only added when a line has a single word
+ * and that word is longer than line width. This is the fastest break strategy and ideal for
+ * editor.
+ */
+ public static final int BREAK_STRATEGY_SIMPLE = 0;
+
+ /**
+ * Value for break strategy indicating high quality line breaking.
+ *
+ * With this option line breaker does whole-paragraph optimization for more readable text, and
+ * also applies automatic hyphenation when required.
+ */
+ public static final int BREAK_STRATEGY_HIGH_QUALITY = 1;
+
+ /**
+ * Value for break strategy indicating balanced line breaking.
+ *
+ * The line breaker does whole-paragraph optimization for making all lines similar length, and
+ * also applies automatic hyphenation when required. This break strategy is good for small
+ * screen devices such as watch screens.
+ */
+ public static final int BREAK_STRATEGY_BALANCED = 2;
+
+ /** @hide */
+ @IntDef(prefix = { "HYPHENATION_FREQUENCY_" }, value = {
+ HYPHENATION_FREQUENCY_NORMAL,
+ HYPHENATION_FREQUENCY_FULL,
+ HYPHENATION_FREQUENCY_NONE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface HyphenationFrequency {}
+
+ /**
+ * Value for hyphenation frequency indicating no automatic hyphenation.
+ *
+ * Using this option disables auto hyphenation which results in better text layout performance.
+ * A word may be broken without hyphens when a line has a single word and that word is longer
+ * than line width. Soft hyphens are ignored and will not be used as suggestions for potential
+ * line breaks.
+ */
+ public static final int HYPHENATION_FREQUENCY_NONE = 0;
+
+ /**
+ * Value for hyphenation frequency indicating a light amount of automatic hyphenation.
+ *
+ * This hyphenation frequency is useful for informal cases, such as short sentences or chat
+ * messages.
+ */
+ public static final int HYPHENATION_FREQUENCY_NORMAL = 1;
+
+ /**
+ * Value for hyphenation frequency indicating the full amount of automatic hyphenation.
+ *
+ * This hyphenation frequency is useful for running text and where it's important to put the
+ * maximum amount of text in a screen with limited space.
+ */
+ public static final int HYPHENATION_FREQUENCY_FULL = 2;
+
+ /** @hide */
+ @IntDef(prefix = { "JUSTIFICATION_MODE_" }, value = {
+ JUSTIFICATION_MODE_NONE,
+ JUSTIFICATION_MODE_INTER_WORD
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface JustificationMode {}
+
+ /**
+ * Value for justification mode indicating no justification.
+ */
+ public static final int JUSTIFICATION_MODE_NONE = 0;
+
+ /**
+ * Value for justification mode indicating the text is justified by stretching word spacing.
+ */
+ public static final int JUSTIFICATION_MODE_INTER_WORD = 1;
+
+ /**
+ * Helper class for creating a {@link LineBreaker}.
+ */
+ public static final class Builder {
+ private @BreakStrategy int mBreakStrategy = BREAK_STRATEGY_SIMPLE;
+ private @HyphenationFrequency int mHyphenationFrequency = HYPHENATION_FREQUENCY_NONE;
+ private @JustificationMode int mJustificationMode = JUSTIFICATION_MODE_NONE;
+ private @Nullable int[] mIndents = null;
+
+ /**
+ * Set break strategy.
+ *
+ * You can change the line breaking behavior by setting break strategy. The default value is
+ * {@link #BREAK_STRATEGY_SIMPLE}.
+ */
+ public @NonNull Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
+ mBreakStrategy = breakStrategy;
+ return this;
+ }
+
+ /**
+ * Set hyphenation frequency.
+ *
+ * You can change the amount of automatic hyphenation used. The default value is
+ * {@link #HYPHENATION_FREQUENCY_NONE}.
+ */
+ public @NonNull Builder setHyphenationFrequency(
+ @HyphenationFrequency int hyphenationFrequency) {
+ mHyphenationFrequency = hyphenationFrequency;
+ return this;
+ }
+
+ /**
+ * Set whether the text is justified.
+ *
+ * By setting {@link #JUSTIFICATION_MODE_INTER_WORD}, the line breaker will change the
+ * internal parameters for justification.
+ * The default value is {@link #JUSTIFICATION_MODE_NONE}
+ */
+ public @NonNull Builder setJustificationMode(@JustificationMode int justificationMode) {
+ mJustificationMode = justificationMode;
+ return this;
+ }
+
+ /**
+ * Set indents.
+ *
+ * The supplied array provides the total amount of indentation per line, in pixel. This
+ * amount is the sum of both left and right indentations. For lines past the last element in
+ * the array, the indentation amount of the last element is used.
+ */
+ public @NonNull Builder setIndents(@Nullable int[] indents) {
+ mIndents = indents;
+ return this;
+ }
+
+ /**
+ * Build a new LineBreaker with given parameters.
+ *
+ * You can reuse the Builder instance even after calling this method.
+ */
+ public @NonNull LineBreaker build() {
+ return new LineBreaker(mBreakStrategy, mHyphenationFrequency, mJustificationMode,
+ mIndents);
+ }
+ }
+
+ /**
+ * Line breaking constraints for single paragraph.
+ */
+ public static class ParagraphConstraints {
+ private @FloatRange(from = 0.0f) float mWidth = 0;
+ private @FloatRange(from = 0.0f) float mFirstWidth = 0;
+ private @IntRange(from = 0) int mFirstWidthLineCount = 0;
+ private @Nullable float[] mVariableTabStops = null;
+ private @FloatRange(from = 0) float mDefaultTabStop = 0;
+
+ public ParagraphConstraints() {}
+
+ /**
+ * Set width for this paragraph.
+ *
+ * @see #getWidth()
+ */
+ public void setWidth(@Px @FloatRange(from = 0.0f) float width) {
+ mWidth = width;
+ }
+
+ /**
+ * Set indent for this paragraph.
+ *
+ * @param firstWidth the line width of the starting of the paragraph
+ * @param firstWidthLineCount the number of lines that applies the firstWidth
+ * @see #getFirstWidth()
+ * @see #getFirstWidthLineCount()
+ */
+ public void setIndent(@Px @FloatRange(from = 0.0f) float firstWidth,
+ @Px @IntRange(from = 0) int firstWidthLineCount) {
+ mFirstWidth = firstWidth;
+ mFirstWidthLineCount = firstWidthLineCount;
+ }
+
+ /**
+ * Set tab stops for this paragraph.
+ *
+ * @param tabStops the array of pixels of tap stopping position
+ * @param defaultTabStop pixels of the default tab stopping position
+ * @see #getTabStops()
+ * @see #getDefaultTabStop()
+ */
+ public void setTabStops(@Nullable float[] tabStops,
+ @Px @FloatRange(from = 0) float defaultTabStop) {
+ mVariableTabStops = tabStops;
+ mDefaultTabStop = defaultTabStop;
+ }
+
+ /**
+ * Return the width for this paragraph in pixels.
+ *
+ * @see #setWidth(float)
+ */
+ public @Px @FloatRange(from = 0.0f) float getWidth() {
+ return mWidth;
+ }
+
+ /**
+ * Return the first line's width for this paragraph in pixel.
+ *
+ * @see #setIndent(float, int)
+ */
+ public @Px @FloatRange(from = 0.0f) float getFirstWidth() {
+ return mFirstWidth;
+ }
+
+ /**
+ * Return the number of lines to apply the first line's width.
+ *
+ * @see #setIndent(float, int)
+ */
+ public @Px @IntRange(from = 0) int getFirstWidthLineCount() {
+ return mFirstWidthLineCount;
+ }
+
+ /**
+ * Returns the array of tab stops in pixels.
+ *
+ * @see #setTabStops(float[], int)
+ */
+ public @Nullable float[] getTabStops() {
+ return mVariableTabStops;
+ }
+
+ /**
+ * Returns the default tab stops in pixels.
+ *
+ * @see #setTabStop(float[], int)
+ */
+ public @Px @FloatRange(from = 0) float getDefaultTabStop() {
+ return mDefaultTabStop;
+ }
+ }
+
+ /**
+ * Holds the result of the {@link LineBreaker#computeLineBreaks line breaking algorithm}.
+ * @see LineBreaker#computeLineBreaks
+ */
+ public static class Result {
+ // Following two contstant must be synced with minikin's line breaker.
+ // TODO(nona): Remove these constatns by introducing native methods.
+ private static final int TAB_MASK = 0x20000000;
+ private static final int HYPHEN_MASK = 0xFF;
+ private static final int START_HYPHEN_MASK = 0x18; // 0b11000
+ private static final int END_HYPHEN_MASK = 0x7; // 0b00111
+ private static final int START_HYPHEN_BITS_SHIFT = 3;
+
+ private static final NativeAllocationRegistry sRegistry =
+ NativeAllocationRegistry.createMalloced(
+ Result.class.getClassLoader(), nGetReleaseResultFunc());
+ private final long mPtr;
+
+ private Result(long ptr) {
+ mPtr = ptr;
+ sRegistry.registerNativeAllocation(this, mPtr);
+ }
+
+ /**
+ * Returns the number of lines in the paragraph.
+ *
+ * @return number of lines
+ */
+ public @IntRange(from = 0) int getLineCount() {
+ return nGetLineCount(mPtr);
+ }
+
+ /**
+ * Returns character offset of the break for a given line.
+ *
+ * @param lineIndex an index of the line.
+ * @return the break offset.
+ */
+ public @IntRange(from = 0) int getLineBreakOffset(@IntRange(from = 0) int lineIndex) {
+ return nGetLineBreakOffset(mPtr, lineIndex);
+ }
+
+ /**
+ * Returns width of a given line in pixels.
+ *
+ * @param lineIndex an index of the line.
+ * @return width of the line in pixels
+ */
+ public @Px float getLineWidth(@IntRange(from = 0) int lineIndex) {
+ return nGetLineWidth(mPtr, lineIndex);
+ }
+
+ /**
+ * Returns font ascent of the line in pixels.
+ *
+ * @param lineIndex an index of the line.
+ * @return an entier font ascent of the line in pixels.
+ */
+ public @Px float getLineAscent(@IntRange(from = 0) int lineIndex) {
+ return nGetLineAscent(mPtr, lineIndex);
+ }
+
+ /**
+ * Returns font descent of the line in pixels.
+ *
+ * @param lineIndex an index of the line.
+ * @return an entier font descent of the line in pixels.
+ */
+ public @Px float getLineDescent(@IntRange(from = 0) int lineIndex) {
+ return nGetLineDescent(mPtr, lineIndex);
+ }
+
+ /**
+ * Returns true if the line has a TAB character.
+ *
+ * @param lineIndex an index of the line.
+ * @return true if the line has a TAB character
+ */
+ public boolean hasLineTab(int lineIndex) {
+ return (nGetLineFlag(mPtr, lineIndex) & TAB_MASK) != 0;
+ }
+
+ /**
+ * Returns a start hyphen edit for the line.
+ *
+ * @param lineIndex an index of the line.
+ * @return a start hyphen edit for the line.
+ *
+ * @see android.graphics.Paint#setStartHyphenEdit
+ * @see android.graphics.Paint#getStartHyphenEdit
+ */
+ public int getStartLineHyphenEdit(int lineIndex) {
+ return (nGetLineFlag(mPtr, lineIndex) & START_HYPHEN_MASK) >> START_HYPHEN_BITS_SHIFT;
+ }
+
+ /**
+ * Returns an end hyphen edit for the line.
+ *
+ * @param lineIndex an index of the line.
+ * @return an end hyphen edit for the line.
+ *
+ * @see android.graphics.Paint#setEndHyphenEdit
+ * @see android.graphics.Paint#getEndHyphenEdit
+ */
+ public int getEndLineHyphenEdit(int lineIndex) {
+ return nGetLineFlag(mPtr, lineIndex) & END_HYPHEN_MASK;
+ }
+ }
+
+ private static final NativeAllocationRegistry sRegistry =
+ NativeAllocationRegistry.createMalloced(
+ LineBreaker.class.getClassLoader(), nGetReleaseFunc());
+
+ private final long mNativePtr;
+
+ /**
+ * Use Builder instead.
+ */
+ private LineBreaker(@BreakStrategy int breakStrategy,
+ @HyphenationFrequency int hyphenationFrequency, @JustificationMode int justify,
+ @Nullable int[] indents) {
+ mNativePtr = nInit(breakStrategy, hyphenationFrequency,
+ justify == JUSTIFICATION_MODE_INTER_WORD, indents);
+ sRegistry.registerNativeAllocation(this, mNativePtr);
+ }
+
+ /**
+ * Break paragraph into lines.
+ *
+ * The result is filled to out param.
+ *
+ * @param measuredPara a result of the text measurement
+ * @param constraints for a single paragraph
+ * @param lineNumber a line number of this paragraph
+ */
+ public @NonNull Result computeLineBreaks(
+ @NonNull MeasuredText measuredPara,
+ @NonNull ParagraphConstraints constraints,
+ @IntRange(from = 0) int lineNumber) {
+ return new Result(nComputeLineBreaks(
+ mNativePtr,
+
+ // Inputs
+ measuredPara.getChars(),
+ measuredPara.getNativePtr(),
+ measuredPara.getChars().length,
+ constraints.mFirstWidth,
+ constraints.mFirstWidthLineCount,
+ constraints.mWidth,
+ constraints.mVariableTabStops,
+ constraints.mDefaultTabStop,
+ lineNumber));
+ }
+
+ @FastNative
+ private static native long nInit(@BreakStrategy int breakStrategy,
+ @HyphenationFrequency int hyphenationFrequency, boolean isJustified,
+ @Nullable int[] indents);
+
+ @CriticalNative
+ private static native long nGetReleaseFunc();
+
+ // populates LineBreaks and returns the number of breaks found
+ //
+ // the arrays inside the LineBreaks objects are passed in as well
+ // to reduce the number of JNI calls in the common case where the
+ // arrays do not have to be resized
+ // The individual character widths will be returned in charWidths. The length of
+ // charWidths must be at least the length of the text.
+ private static native long nComputeLineBreaks(
+ /* non zero */ long nativePtr,
+
+ // Inputs
+ @NonNull char[] text,
+ /* Non Zero */ long measuredTextPtr,
+ @IntRange(from = 0) int length,
+ @FloatRange(from = 0.0f) float firstWidth,
+ @IntRange(from = 0) int firstWidthLineCount,
+ @FloatRange(from = 0.0f) float restWidth,
+ @Nullable float[] variableTabStops,
+ float defaultTabStop,
+ @IntRange(from = 0) int indentsOffset);
+
+ // Result accessors
+ @CriticalNative
+ private static native int nGetLineCount(long ptr);
+ @CriticalNative
+ private static native int nGetLineBreakOffset(long ptr, int idx);
+ @CriticalNative
+ private static native float nGetLineWidth(long ptr, int idx);
+ @CriticalNative
+ private static native float nGetLineAscent(long ptr, int idx);
+ @CriticalNative
+ private static native float nGetLineDescent(long ptr, int idx);
+ @CriticalNative
+ private static native int nGetLineFlag(long ptr, int idx);
+ @CriticalNative
+ private static native long nGetReleaseResultFunc();
+}
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
new file mode 100644
index 000000000000..b6d8fa19fca8
--- /dev/null
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.text;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.Px;
+import android.graphics.Paint;
+import android.graphics.Rect;
+
+import com.android.internal.util.Preconditions;
+
+import dalvik.annotation.optimization.CriticalNative;
+
+import libcore.util.NativeAllocationRegistry;
+
+/**
+ * Result of text shaping of the single paragraph string.
+ *
+ * <p>
+ * <pre>
+ * <code>
+ * Paint paint = new Paint();
+ * Paint bigPaint = new Paint();
+ * bigPaint.setTextSize(paint.getTextSize() * 2.0);
+ * String text = "Hello, Android.";
+ * MeasuredText mt = new MeasuredText.Builder(text.toCharArray())
+ * .appendStyleRun(paint, 7, false) // Use paint for "Hello, "
+ * .appendStyleRun(bigPaint, 8, false) // Use bigPaint for "Android."
+ * .build();
+ * </code>
+ * </pre>
+ * </p>
+ */
+public class MeasuredText {
+ private long mNativePtr;
+ private boolean mComputeHyphenation;
+ private boolean mComputeLayout;
+ private @NonNull char[] mChars;
+
+ // Use builder instead.
+ private MeasuredText(long ptr, @NonNull char[] chars, boolean computeHyphenation,
+ boolean computeLayout) {
+ mNativePtr = ptr;
+ mChars = chars;
+ mComputeHyphenation = computeHyphenation;
+ mComputeLayout = computeLayout;
+ }
+
+ /**
+ * Returns the characters in the paragraph used to compute this MeasuredText instance.
+ * @hide
+ */
+ public @NonNull char[] getChars() {
+ return mChars;
+ }
+
+ /**
+ * Returns the width of a given range.
+ *
+ * @param start an inclusive start index of the range
+ * @param end an exclusive end index of the range
+ */
+ public @FloatRange(from = 0.0) @Px float getWidth(
+ @IntRange(from = 0) int start, @IntRange(from = 0) int end) {
+ Preconditions.checkArgument(0 <= start && start <= mChars.length,
+ "start(" + start + ") must be 0 <= start <= " + mChars.length);
+ Preconditions.checkArgument(0 <= end && end <= mChars.length,
+ "end(" + end + ") must be 0 <= end <= " + mChars.length);
+ Preconditions.checkArgument(start <= end,
+ "start(" + start + ") is larger than end(" + end + ")");
+ return nGetWidth(mNativePtr, start, end);
+ }
+
+ /**
+ * Returns a memory usage of the native object.
+ *
+ * @hide
+ */
+ public int getMemoryUsage() {
+ return nGetMemoryUsage(mNativePtr);
+ }
+
+ /**
+ * Retrieves the boundary box of the given range
+ *
+ * @param start an inclusive start index of the range
+ * @param end an exclusive end index of the range
+ * @param rect an output parameter
+ */
+ public void getBounds(@IntRange(from = 0) int start, @IntRange(from = 0) int end,
+ @NonNull Rect rect) {
+ Preconditions.checkArgument(0 <= start && start <= mChars.length,
+ "start(" + start + ") must be 0 <= start <= " + mChars.length);
+ Preconditions.checkArgument(0 <= end && end <= mChars.length,
+ "end(" + end + ") must be 0 <= end <= " + mChars.length);
+ Preconditions.checkArgument(start <= end,
+ "start(" + start + ") is larger than end(" + end + ")");
+ Preconditions.checkNotNull(rect);
+ nGetBounds(mNativePtr, mChars, start, end, rect);
+ }
+
+ /**
+ * Returns the width of the character at the given offset.
+ *
+ * @param offset an offset of the character.
+ */
+ public @FloatRange(from = 0.0f) @Px float getCharWidthAt(@IntRange(from = 0) int offset) {
+ Preconditions.checkArgument(0 <= offset && offset < mChars.length,
+ "offset(" + offset + ") is larger than text length: " + mChars.length);
+ return nGetCharWidthAt(mNativePtr, offset);
+ }
+
+ /**
+ * Returns a native pointer of the underlying native object.
+ *
+ * @hide
+ */
+ public long getNativePtr() {
+ return mNativePtr;
+ }
+
+ @CriticalNative
+ private static native float nGetWidth(/* Non Zero */ long nativePtr,
+ @IntRange(from = 0) int start,
+ @IntRange(from = 0) int end);
+
+ @CriticalNative
+ private static native /* Non Zero */ long nGetReleaseFunc();
+
+ @CriticalNative
+ private static native int nGetMemoryUsage(/* Non Zero */ long nativePtr);
+
+ private static native void nGetBounds(long nativePtr, char[] buf, int start, int end,
+ Rect rect);
+
+ @CriticalNative
+ private static native float nGetCharWidthAt(long nativePtr, int offset);
+
+ /**
+ * Helper class for creating a {@link MeasuredText}.
+ * <p>
+ * <pre>
+ * <code>
+ * Paint paint = new Paint();
+ * String text = "Hello, Android.";
+ * MeasuredText mt = new MeasuredText.Builder(text.toCharArray())
+ * .appendStyleRun(paint, text.length, false)
+ * .build();
+ * </code>
+ * </pre>
+ * </p>
+ *
+ * Note: The appendStyle and appendReplacementRun should be called to cover the text length.
+ */
+ public static final class Builder {
+ private static final NativeAllocationRegistry sRegistry =
+ NativeAllocationRegistry.createMalloced(
+ MeasuredText.class.getClassLoader(), nGetReleaseFunc());
+
+ private long mNativePtr;
+
+ private final @NonNull char[] mText;
+ private boolean mComputeHyphenation = false;
+ private boolean mComputeLayout = true;
+ private int mCurrentOffset = 0;
+ private @Nullable MeasuredText mHintMt = null;
+
+ /**
+ * Construct a builder.
+ *
+ * The MeasuredText returned by build method will hold a reference of the text. Developer is
+ * not supposed to modify the text.
+ *
+ * @param text a text
+ */
+ public Builder(@NonNull char[] text) {
+ Preconditions.checkNotNull(text);
+ mText = text;
+ mNativePtr = nInitBuilder();
+ }
+
+ /**
+ * Construct a builder with existing MeasuredText.
+ *
+ * The MeasuredText returned by build method will hold a reference of the text. Developer is
+ * not supposed to modify the text.
+ *
+ * @param text a text
+ */
+ public Builder(@NonNull MeasuredText text) {
+ Preconditions.checkNotNull(text);
+ mText = text.mChars;
+ mNativePtr = nInitBuilder();
+ if (!text.mComputeLayout) {
+ throw new IllegalArgumentException(
+ "The input MeasuredText must not be created with setComputeLayout(false).");
+ }
+ mComputeHyphenation = text.mComputeHyphenation;
+ mComputeLayout = text.mComputeLayout;
+ mHintMt = text;
+ }
+
+ /**
+ * Apply styles to the given length.
+ *
+ * Keeps an internal offset which increases at every append. The initial value for this
+ * offset is zero. After the style is applied the internal offset is moved to {@code offset
+ * + length}, and next call will start from this new position.
+ *
+ * @param paint a paint
+ * @param length a length to be applied with a given paint, can not exceed the length of the
+ * text
+ * @param isRtl true if the text is in RTL context, otherwise false.
+ */
+ public @NonNull Builder appendStyleRun(@NonNull Paint paint, @IntRange(from = 0) int length,
+ boolean isRtl) {
+ Preconditions.checkNotNull(paint);
+ Preconditions.checkArgument(length > 0, "length can not be negative");
+ final int end = mCurrentOffset + length;
+ Preconditions.checkArgument(end <= mText.length, "Style exceeds the text length");
+ nAddStyleRun(mNativePtr, paint.getNativeInstance(), mCurrentOffset, end, isRtl);
+ mCurrentOffset = end;
+ return this;
+ }
+
+ /**
+ * Used to inform the text layout that the given length is replaced with the object of given
+ * width.
+ *
+ * Keeps an internal offset which increases at every append. The initial value for this
+ * offset is zero. After the style is applied the internal offset is moved to {@code offset
+ * + length}, and next call will start from this new position.
+ *
+ * Informs the layout engine that the given length should not be processed, instead the
+ * provided width should be used for calculating the width of that range.
+ *
+ * @param length a length to be replaced with the object, can not exceed the length of the
+ * text
+ * @param width a replacement width of the range
+ */
+ public @NonNull Builder appendReplacementRun(@NonNull Paint paint,
+ @IntRange(from = 0) int length, @Px @FloatRange(from = 0) float width) {
+ Preconditions.checkArgument(length > 0, "length can not be negative");
+ final int end = mCurrentOffset + length;
+ Preconditions.checkArgument(end <= mText.length, "Replacement exceeds the text length");
+ nAddReplacementRun(mNativePtr, paint.getNativeInstance(), mCurrentOffset, end, width);
+ mCurrentOffset = end;
+ return this;
+ }
+
+ /**
+ * By passing true to this method, the build method will compute all possible hyphenation
+ * pieces as well.
+ *
+ * If you don't want to use automatic hyphenation, you can pass false to this method and
+ * save the computation time of hyphenation. The default value is false.
+ *
+ * Even if you pass false to this method, you can still enable automatic hyphenation of
+ * LineBreaker but line break computation becomes slower.
+ *
+ * @param computeHyphenation true if you want to use automatic hyphenations.
+ */
+ public @NonNull Builder setComputeHyphenation(boolean computeHyphenation) {
+ mComputeHyphenation = computeHyphenation;
+ return this;
+ }
+
+ /**
+ * By passing true to this method, the build method will compute all full layout
+ * information.
+ *
+ * If you don't use {@link MeasuredText#getBounds(int,int,android.graphics.Rect)}, you can
+ * pass false to this method and save the memory spaces. The default value is true.
+ *
+ * Even if you pass false to this method, you can still call getBounds but it becomes
+ * slower.
+ *
+ * @param computeLayout true if you want to retrieve full layout info, e.g. bbox.
+ */
+ public @NonNull Builder setComputeLayout(boolean computeLayout) {
+ mComputeLayout = computeLayout;
+ return this;
+ }
+
+ /**
+ * Creates a MeasuredText.
+ *
+ * Once you called build() method, you can't reuse the Builder class again.
+ * @throws IllegalStateException if this Builder is reused.
+ * @throws IllegalStateException if the whole text is not covered by one or more runs (style
+ * or replacement)
+ */
+ public @NonNull MeasuredText build() {
+ ensureNativePtrNoReuse();
+ if (mCurrentOffset != mText.length) {
+ throw new IllegalStateException("Style info has not been provided for all text.");
+ }
+ if (mHintMt != null && mHintMt.mComputeHyphenation != mComputeHyphenation) {
+ throw new IllegalArgumentException(
+ "The hyphenation configuration is different from given hint MeasuredText");
+ }
+ try {
+ long hintPtr = (mHintMt == null) ? 0 : mHintMt.getNativePtr();
+ long ptr = nBuildMeasuredText(mNativePtr, hintPtr, mText, mComputeHyphenation,
+ mComputeLayout);
+ final MeasuredText res = new MeasuredText(ptr, mText, mComputeHyphenation,
+ mComputeLayout);
+ sRegistry.registerNativeAllocation(res, ptr);
+ return res;
+ } finally {
+ nFreeBuilder(mNativePtr);
+ mNativePtr = 0;
+ }
+ }
+
+ /**
+ * Ensures {@link #mNativePtr} is not reused.
+ *
+ * <p/> This is a method by itself to help increase testability - eg. Robolectric might want
+ * to override the validation behavior in test environment.
+ */
+ private void ensureNativePtrNoReuse() {
+ if (mNativePtr == 0) {
+ throw new IllegalStateException("Builder can not be reused.");
+ }
+ }
+
+ private static native /* Non Zero */ long nInitBuilder();
+
+ /**
+ * Apply style to make native measured text.
+ *
+ * @param nativeBuilderPtr The native MeasuredParagraph builder pointer.
+ * @param paintPtr The native paint pointer to be applied.
+ * @param start The start offset in the copied buffer.
+ * @param end The end offset in the copied buffer.
+ * @param isRtl True if the text is RTL.
+ */
+ private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr,
+ /* Non Zero */ long paintPtr,
+ @IntRange(from = 0) int start,
+ @IntRange(from = 0) int end,
+ boolean isRtl);
+ /**
+ * Apply ReplacementRun to make native measured text.
+ *
+ * @param nativeBuilderPtr The native MeasuredParagraph builder pointer.
+ * @param paintPtr The native paint pointer to be applied.
+ * @param start The start offset in the copied buffer.
+ * @param end The end offset in the copied buffer.
+ * @param width The width of the replacement.
+ */
+ private static native void nAddReplacementRun(/* Non Zero */ long nativeBuilderPtr,
+ /* Non Zero */ long paintPtr,
+ @IntRange(from = 0) int start,
+ @IntRange(from = 0) int end,
+ @FloatRange(from = 0) float width);
+
+ private static native long nBuildMeasuredText(
+ /* Non Zero */ long nativeBuilderPtr,
+ long hintMtPtr,
+ @NonNull char[] text,
+ boolean computeHyphenation,
+ boolean computeLayout);
+
+ private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr);
+ }
+}
diff --git a/graphics/proto/Android.bp b/graphics/proto/Android.bp
new file mode 100644
index 000000000000..1d06348fb02f
--- /dev/null
+++ b/graphics/proto/Android.bp
@@ -0,0 +1,11 @@
+java_library_static {
+ name: "game-driver-protos",
+ host_supported: true,
+ proto: {
+ type: "lite",
+ },
+ srcs: ["game_driver.proto"],
+ no_framework_libs: true,
+ jarjar_rules: "jarjar-rules.txt",
+ sdk_version: "28",
+}
diff --git a/graphics/proto/game_driver.proto b/graphics/proto/game_driver.proto
new file mode 100644
index 000000000000..fd7ffccac24c
--- /dev/null
+++ b/graphics/proto/game_driver.proto
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package android.gamedriver;
+
+option java_package = "android.gamedriver";
+option java_outer_classname = "GameDriverProto";
+
+message Blacklist {
+ optional int64 version_code = 1;
+ repeated string package_names = 2;
+}
+
+message Blacklists {
+ repeated Blacklist blacklists = 1;
+}
diff --git a/graphics/proto/jarjar-rules.txt b/graphics/proto/jarjar-rules.txt
new file mode 100644
index 000000000000..4e4063706352
--- /dev/null
+++ b/graphics/proto/jarjar-rules.txt
@@ -0,0 +1 @@
+rule com.google.protobuf.** com.android.framework.protobuf.@1