summaryrefslogtreecommitdiff
path: root/graphics/java
diff options
context:
space:
mode:
Diffstat (limited to 'graphics/java')
-rw-r--r--graphics/java/android/graphics/BaseCanvas.java93
-rw-r--r--graphics/java/android/graphics/BaseRecordingCanvas.java674
-rw-r--r--graphics/java/android/graphics/Bitmap.java123
-rw-r--r--graphics/java/android/graphics/Camera.java10
-rw-r--r--graphics/java/android/graphics/Canvas.java125
-rw-r--r--graphics/java/android/graphics/ColorSpace.java50
-rw-r--r--graphics/java/android/graphics/FontFamily.java1
-rw-r--r--graphics/java/android/graphics/FontListParser.java42
-rw-r--r--graphics/java/android/graphics/FrameInfo.java128
-rw-r--r--graphics/java/android/graphics/HardwareRenderer.java1030
-rw-r--r--graphics/java/android/graphics/ImageDecoder.java94
-rw-r--r--graphics/java/android/graphics/ImageFormat.java18
-rw-r--r--graphics/java/android/graphics/Insets.java19
-rw-r--r--graphics/java/android/graphics/Movie.java1
-rw-r--r--graphics/java/android/graphics/Paint.java547
-rw-r--r--graphics/java/android/graphics/Path.java47
-rw-r--r--graphics/java/android/graphics/Picture.java5
-rw-r--r--graphics/java/android/graphics/Point.java22
-rw-r--r--graphics/java/android/graphics/PointF.java10
-rw-r--r--graphics/java/android/graphics/PorterDuffColorFilter.java41
-rw-r--r--graphics/java/android/graphics/RecordingCanvas.java304
-rw-r--r--graphics/java/android/graphics/Rect.java78
-rw-r--r--graphics/java/android/graphics/RectF.java42
-rw-r--r--graphics/java/android/graphics/Region.java43
-rw-r--r--graphics/java/android/graphics/RenderNode.java1482
-rw-r--r--graphics/java/android/graphics/Typeface.java711
-rw-r--r--graphics/java/android/graphics/drawable/AnimatedImageDrawable.java2
-rw-r--r--graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java16
-rw-r--r--graphics/java/android/graphics/drawable/BitmapDrawable.java4
-rw-r--r--graphics/java/android/graphics/drawable/ColorDrawable.java31
-rw-r--r--graphics/java/android/graphics/drawable/ColorStateListDrawable.java304
-rw-r--r--graphics/java/android/graphics/drawable/Drawable.java20
-rw-r--r--graphics/java/android/graphics/drawable/DrawableContainer.java3
-rw-r--r--graphics/java/android/graphics/drawable/DrawableWrapper.java22
-rw-r--r--graphics/java/android/graphics/drawable/GradientDrawable.java13
-rw-r--r--graphics/java/android/graphics/drawable/InsetDrawable.java11
-rw-r--r--graphics/java/android/graphics/drawable/NinePatchDrawable.java3
-rw-r--r--graphics/java/android/graphics/drawable/RippleComponent.java7
-rw-r--r--graphics/java/android/graphics/drawable/RippleDrawable.java5
-rw-r--r--graphics/java/android/graphics/drawable/RippleForeground.java10
-rw-r--r--graphics/java/android/graphics/drawable/ShapeDrawable.java4
-rw-r--r--graphics/java/android/graphics/drawable/StateListDrawable.java30
-rw-r--r--graphics/java/android/graphics/drawable/VectorDrawable.java11
-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.java256
-rw-r--r--graphics/java/android/graphics/fonts/FontVariationAxis.java18
-rw-r--r--graphics/java/android/graphics/fonts/SystemFonts.java319
-rw-r--r--graphics/java/android/graphics/text/LineBreaker.java517
-rw-r--r--graphics/java/android/graphics/text/MeasuredText.java348
57 files changed, 7769 insertions, 999 deletions
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index 1745992022e1..3db240b54299 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -23,15 +23,15 @@ import android.annotation.Size;
import android.annotation.UnsupportedAppUsage;
import android.graphics.Canvas.VertexMode;
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.
@@ -83,7 +83,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
@@ -376,6 +376,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, float[] outerRadii,
+ @NonNull RectF inner, 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) |
@@ -488,21 +535,31 @@ 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.
+ nDrawTextRun(mNativeCanvasWrapper,
+ mp.getChars(),
+ start - paraStart,
+ end - start,
+ contextStart - paraStart,
+ contextEnd - contextStart,
+ x, y, isRtl, paint.getNativeInstance(),
+ mp.getMeasuredText().getNativePtr());
+ 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);
}
}
@@ -621,6 +678,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);
diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java
new file mode 100644
index 000000000000..4de7ca708eb6
--- /dev/null
+++ b/graphics/java/android/graphics/BaseRecordingCanvas.java
@@ -0,0 +1,674 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.Size;
+import android.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, 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, 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, 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, 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, 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, PorterDuff.Mode.SRC_OVER.nativeInt);
+ }
+
+ @Override
+ public final void drawColor(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
+ nDrawColor(mNativeCanvasWrapper, color, mode.nativeInt);
+ }
+
+ @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.
+ nDrawTextRun(mNativeCanvasWrapper,
+ mp.getChars(),
+ start - paraStart,
+ end - start,
+ contextStart - paraStart,
+ contextEnd - contextStart,
+ x, y, isRtl, paint.getNativeInstance(),
+ mp.getMeasuredText().getNativePtr());
+ 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 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, Bitmap bitmap, float left, float top,
+ long nativePaintOrZero, int canvasDensity, int screenDensity, int bitmapDensity);
+
+ @FastNative
+ private static native void nDrawBitmap(long nativeCanvas, Bitmap bitmap,
+ float srcLeft, float srcTop, float srcRight, float srcBottom,
+ float dstLeft, float dstTop, float dstRight, float dstBottom,
+ long nativePaintOrZero, int screenDensity, int bitmapDensity);
+
+ @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 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, Bitmap bitmap,
+ long nativeMatrix, long nativePaint);
+
+ @FastNative
+ private static native void nDrawBitmapMesh(long nativeCanvas, Bitmap bitmap, 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 d6131cf2e5d4..790b37eec4c5 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -24,16 +24,17 @@ import android.annotation.Size;
import android.annotation.UnsupportedAppUsage;
import android.annotation.WorkerThread;
import android.content.res.ResourcesImpl;
+import android.hardware.HardwareBuffer;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.StrictMode;
import android.os.Trace;
import android.util.DisplayMetrics;
import android.util.Log;
-import android.view.DisplayListCanvas;
-import android.view.RenderNode;
import android.view.ThreadedRenderer;
+import dalvik.annotation.optimization.CriticalNative;
+
import libcore.util.NativeAllocationRegistry;
import java.io.OutputStream;
@@ -61,8 +62,6 @@ public final class Bitmap implements Parcelable {
@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
@@ -125,8 +124,8 @@ public final class Bitmap implements Parcelable {
* int (pointer).
*/
// called from JNI
- Bitmap(long nativeBitmap, int width, int height, int density,
- boolean isMutable, boolean requestPremultiplied,
+ @UnsupportedAppUsage
+ Bitmap(long nativeBitmap, int width, int height, int density, boolean requestPremultiplied,
byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {
if (nativeBitmap == 0) {
throw new RuntimeException("internal error: native bitmap is 0");
@@ -134,7 +133,6 @@ public final class Bitmap implements Parcelable {
mWidth = width;
mHeight = height;
- mIsMutable = isMutable;
mRequestPremultiplied = requestPremultiplied;
mNinePatchChunk = ninePatchChunk;
@@ -356,14 +354,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;
}
}
@@ -730,6 +723,48 @@ public final class Bitmap implements Parcelable {
}
/**
+ * Create a hardware bitmap backed by a {@link HardwareBuffer}.
+ *
+ * <p>The passed HardwareBuffer's usage flags must contain
+ * {@link HardwareBuffer#USAGE_GPU_SAMPLED_IMAGE}.
+ *
+ * <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();
+ ColorSpace.Rgb rgb = null;
+ if (colorSpace != null) {
+ if (!(colorSpace instanceof ColorSpace.Rgb)) {
+ throw new IllegalArgumentException("colorSpace must be an RGB color space");
+ }
+ rgb = (ColorSpace.Rgb) colorSpace;
+ } else {
+ rgb = (ColorSpace.Rgb) ColorSpace.get(ColorSpace.Named.SRGB);
+ }
+ 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);
+ return nativeWrapHardwareBufferBitmap(hardwareBuffer, d50.getTransform(), parameters);
+ }
+
+ /**
* Creates a new bitmap, scaled from an existing bitmap, when possible. If the
* specified width and height are the same as the current width and height of
* the source bitmap, the source bitmap is returned and no new bitmap is
@@ -763,7 +798,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.
*/
@@ -772,7 +807,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.
@@ -792,7 +827,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
@@ -802,6 +837,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
@@ -1239,11 +1280,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) {
@@ -1261,13 +1300,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.startRecording(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);
@@ -1282,7 +1322,7 @@ public final class Bitmap implements Parcelable {
}
canvas.drawPicture(source);
canvas.setBitmap(null);
- bitmap.makeImmutable();
+ bitmap.setImmutable();
return bitmap;
}
}
@@ -1373,13 +1413,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);
+ }
}
/**
@@ -1946,7 +1995,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");
}
}
@@ -2065,7 +2114,7 @@ public final class Bitmap implements Parcelable {
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);
@@ -2116,9 +2165,19 @@ public final class Bitmap implements Parcelable {
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,
+ @Size(9) float[] xyzD50,
+ ColorSpace.Rgb.TransferParameters p);
private static native GraphicBuffer nativeCreateGraphicBufferHandle(long nativeBitmap);
private static native boolean nativeGetColorSpace(long nativePtr, float[] xyz, float[] params);
private static native boolean nativeIsSRGB(long nativePtr);
private static native boolean nativeIsSRGBLinear(long nativePtr);
private static native void nativeCopyColorSpace(long srcBitmap, long dstBitmap);
+
+ private static native void nativeSetImmutable(long nativePtr);
+
+ // ---------------- @CriticalNative -------------------
+
+ @CriticalNative
+ private static native boolean nativeIsImmutable(long nativePtr);
}
diff --git a/graphics/java/android/graphics/Camera.java b/graphics/java/android/graphics/Camera.java
index 33889410a54b..cbd4eadca30a 100644
--- a/graphics/java/android/graphics/Camera.java
+++ b/graphics/java/android/graphics/Camera.java
@@ -24,8 +24,6 @@ import android.annotation.UnsupportedAppUsage;
* {@link Canvas}.
*/
public class Camera {
- private Matrix mMatrix;
-
/**
* Creates a new camera, with empty transformations.
*/
@@ -151,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);
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index 36c1c2133c2c..3b0dc9d9f125 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -205,11 +205,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
@@ -1877,6 +1936,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, float[] outerRadii,
+ @NonNull RectF inner, 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.
*
@@ -2065,4 +2169,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/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 8fd02c0eeb59..2e1d81a294e9 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -239,7 +239,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 +247,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 +302,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 +312,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 +367,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 +375,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 +402,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 +410,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 +464,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,7 +472,7 @@ 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.04045 \\
+ * 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>
@@ -499,7 +499,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 +507,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 +534,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 +542,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 +596,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 +604,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 +759,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*}$$
*
@@ -2028,7 +2028,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 +2066,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}\)
*
@@ -2525,9 +2525,7 @@ public abstract class ColorSpace {
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);
+ mTransferParameters = new TransferParameters(1.0, 0.0, 0.0, 0.0, gamma);
}
/**
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
index 06469153c3ac..229923ca8202 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -64,6 +64,7 @@ public class FontFamily {
mNativeBuilderCleaner = sBuilderRegistry.registerNativeAllocation(this, mBuilderPtr);
}
+ @UnsupportedAppUsage
public FontFamily(@Nullable String[] langs, int variant) {
final String langsString;
if (langs == null || langs.length == 0) {
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index e3e8380716f3..21cc3757a40e 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -40,17 +40,25 @@ 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<>();
@@ -60,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 {
@@ -71,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);
}
@@ -95,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);
}
@@ -103,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);
@@ -126,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)
@@ -138,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");
@@ -153,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/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
new file mode 100644
index 000000000000..e4020554786b
--- /dev/null
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -0,0 +1,1030 @@
+/*
+ * 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.view.FrameMetricsObserver;
+import android.view.IGraphicsStats;
+import android.view.IGraphicsStatsCallback;
+import android.view.NativeVectorDrawableAnimator;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.TextureLayer;
+
+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 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>Threading</h3>
+ * <p>HardwareRenderer is not thread safe. An instance of a HardwareRenderer must only be
+ * created & used from a single thread. It does not matter what thread is used, however
+ * it must have a {@link android.os.Looper}. Multiple instances do not have to share the same
+ * thread, although they can.</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>
+ *
+ * @hide
+ */
+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.
+ *
+ * If this is returned from syncAndDrawFrame the expectation is that syncAndDrawFrame
+ * 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#destroy()} was called. The user should no longer
+ * attempt to call syncAndDrawFrame until a new surface has been provided by calling
+ * setSurface.
+ *
+ * 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;
+
+ @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 FrameInfo mScratchInfo;
+
+ /**
+ * 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}.
+ *
+ * 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 with {@link #syncAndDrawFrame(long)}.
+ *
+ * It is suggested to call this in response to callbacks such as
+ * {@link android.view.SurfaceHolder.Callback#surfaceDestroyed(SurfaceHolder)}.
+ *
+ * Note that if there are any outstanding frame commit callbacks they may end up 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(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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * These values are typically provided by the current theme, see
+ * {@link android.R.attr#spotShadowAlpha} and {@link android.R.attr#ambientShadowAlpha}.
+ *
+ * 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 {@link #syncAndDrawFrame(long)}
+ * is called.
+ *
+ * @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.startRecording();
+ 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);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Syncs the RenderNode tree to the render thread and requests a frame to be drawn.
+ *
+ * @param vsyncTime The vsync timestamp for this frame. Typically this comes from
+ * {@link android.view.Choreographer.FrameCallback}. Must be set and be valid
+ * as the renderer uses this time internally to drive animations.
+ * @return The result of the sync operation. See {@link SyncAndDrawResult}.
+ */
+ @SyncAndDrawResult
+ public int syncAndDrawFrame(long vsyncTime) {
+ if (mScratchInfo == null) {
+ mScratchInfo = new FrameInfo();
+ }
+ mScratchInfo.setVsync(vsyncTime, vsyncTime);
+ mScratchInfo.addFlags(FrameInfo.FLAG_SURFACE_CANVAS);
+ return syncAndDrawFrame(mScratchInfo);
+ }
+
+ /**
+ * Syncs the RenderNode tree to the render thread and requests a frame to be drawn.
+ * frameCommitCallback callback will be invoked when the current rendering content has been
+ * rendered into a frame and submitted to the swap chain.
+ *
+ * @param vsyncTime The vsync timestamp for this frame. Typically this comes from
+ * {@link android.view.Choreographer.FrameCallback}. Must be set and
+ * be valid as the renderer uses this time internally to drive
+ * animations.
+ * @param frameCommitCallback The callback to invoke when the frame content has been drawn.
+ * Will be invoked on the current {@link android.os.Looper} thread.
+ * @return The result of the sync operation. See {@link SyncAndDrawResult}.
+ */
+ @SyncAndDrawResult
+ public int syncAndDrawFrame(long vsyncTime,
+ @Nullable Runnable frameCommitCallback) {
+ if (frameCommitCallback != null) {
+ setFrameCompleteCallback(frameNr -> frameCommitCallback.run());
+ }
+ return syncAndDrawFrame(vsyncTime);
+ }
+
+ /**
+ * 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.
+ *
+ * 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 #syncAndDrawFrame(long)} 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 #syncAndDrawFrame(long)} will still
+ * sync over the latest rendering content, however they will not render and instead
+ * {@link #SYNC_CONTEXT_IS_STOPPED} will be returned.
+ *
+ * If false is passed then rendering will resume as normal. Any pending rendering requests
+ * will produce a new frame at the next vsync signal.
+ *
+ * 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
+ */
+ public void setStopped(boolean stopped) {
+ nSetStopped(mNativeProxy, stopped);
+ }
+
+ /**
+ * Destroys all hardware rendering resources 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.
+ *
+ * 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 #setStopped(boolean)}
+ */
+ public void destroyHardwareResources() {
+ nDestroyHardwareResources(mNativeProxy);
+ }
+
+ /**
+ * Whether or not the force-dark feature should be used for this renderer.
+ */
+ 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.
+ *
+ * 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.
+ *
+ * Must be called after a {@link Surface} has been set.
+ */
+ public void allocateBuffers() {
+ nAllocateBuffers(mNativeProxy);
+ }
+
+ /**
+ * Notifies the hardware renderer that a call to {@link #syncAndDrawFrame(long)} 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.
+ *
+ * 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) {
+ 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 #syncAndDrawFrame(long)} 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);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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);
+ }
+
+ 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);
+ } else {
+ return nCopySurfaceInto(surface, srcRect.left, srcRect.top,
+ srcRect.right, srcRect.bottom, bitmap);
+ }
+ }
+
+ /**
+ * 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();
+
+ /** @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, Bitmap bitmap);
+
+ 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 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, Bitmap bitmap);
+
+ 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..009e042f74a7 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,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Retention;
import java.nio.ByteBuffer;
+import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -186,7 +188,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 +284,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 +336,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.
*
@@ -527,6 +534,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.
*/
@@ -970,6 +1000,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
@@ -1856,6 +1907,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);
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index 9546a4aec330..c580c46cc888 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -16,8 +16,6 @@
package android.graphics;
-import android.annotation.UnsupportedAppUsage;
-
public class ImageFormat {
/*
* these constants are chosen to be binary compatible with their previous
@@ -92,20 +90,21 @@ 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
*/
- @UnsupportedAppUsage
public static final int Y8 = 0x20203859;
/**
@@ -787,6 +786,7 @@ public class ImageFormat {
case DEPTH_POINT_CLOUD:
case PRIVATE:
case RAW_DEPTH:
+ case Y8:
return true;
}
diff --git a/graphics/java/android/graphics/Insets.java b/graphics/java/android/graphics/Insets.java
index 156f9903a632..de110c849338 100644
--- a/graphics/java/android/graphics/Insets.java
+++ b/graphics/java/android/graphics/Insets.java
@@ -16,6 +16,9 @@
package android.graphics;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
/**
* 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,9 +26,8 @@ package android.graphics;
* <p>
* Insets are immutable so may be treated as values.
*
- * @hide
*/
-public class Insets {
+public final class Insets {
public static final Insets NONE = new Insets(0, 0, 0, 0);
public final int left;
@@ -52,7 +54,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 +68,20 @@ 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 intance with the appropriate values.
+ *
+ * @hide
+ */
+ public @NonNull Rect toRect() {
+ return new Rect(left, top, right, bottom);
+ }
+
+ /**
* Two Insets instances are equal iff they belong to the same class and their fields are
* pairwise equal.
*
diff --git a/graphics/java/android/graphics/Movie.java b/graphics/java/android/graphics/Movie.java
index b03eae592211..6f030ffac2df 100644
--- a/graphics/java/android/graphics/Movie.java
+++ b/graphics/java/android/graphics/Movie.java
@@ -26,6 +26,7 @@ import java.io.InputStream;
/**
* @deprecated Prefer {@link android.graphics.drawable.AnimatedImageDrawable}.
*/
+@Deprecated
public class Movie {
@UnsupportedAppUsage
private long mNativeMovie;
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index e229c12d64f1..69ff3bca6528 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -17,10 +17,13 @@
package android.graphics;
import android.annotation.ColorInt;
+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.FontListParser;
import android.graphics.fonts.FontVariationAxis;
import android.os.LocaleList;
import android.text.GraphicsOperations;
@@ -35,6 +38,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;
@@ -307,38 +312,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;
@@ -578,49 +592,6 @@ 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) {
@@ -806,25 +777,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);
}
@@ -833,6 +818,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);
@@ -841,26 +829,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);
}
@@ -869,6 +871,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);
@@ -1347,6 +1352,38 @@ public class Paint {
}
/**
+ * Returns the blur radius of the shadow layer.
+ * @see #setShadowLayer(float,float,float,int)
+ */
+ public float getShadowLayerRadius() {
+ return mShadowLayerRadius;
+ }
+
+ /**
+ * Returns the x offset of the shadow layer.
+ * @see #setShadowLayer(float,float,float,int)
+ */
+ public float getShadowLayerDx() {
+ return mShadowLayerDx;
+ }
+
+ /**
+ * Returns the y offset of the shadow layer.
+ * @see #setShadowLayer(float,float,float,int)
+ */
+ public float getShadowLayerDy() {
+ return mShadowLayerDy;
+ }
+
+ /**
+ * Returns the color of the shadow layer.
+ * @see #setShadowLayer(float,float,float,int)
+ */
+ public @ColorInt int getShadowLayerColor() {
+ 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
@@ -1561,22 +1598,25 @@ 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() {
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) {
nSetWordSpacing(mNativePaint, wordSpacing);
@@ -1705,26 +1745,52 @@ public class Paint {
}
/**
- * Get the current value of hyphen edit.
+ * Get the current value of packed hyphen edit.
*
- * @return the current hyphen edit value
+ * You can extract start hyphen edit and end hyphen edit by using
+ * {@link android.text.Hyphenator#unpackStartHyphenEdit(int)} and
+ * {@link android.text.Hyphenator#unpackEndHyphenEdit(int)}.
*
- * @hide
+ * The default value is 0 which is equivalent to packed value of
+ * {@link android.text.Hyphenator#START_HYPHEN_EDIT_NO_EDIT} and
+ * {@link android.text.Hyphenator#END_HYPHEN_EDIT_NO_EDIT}.
+ *
+ * @return the current hyphen edit value
+ * @see #setHyphenEdit(int)
*/
public int getHyphenEdit() {
return nGetHyphenEdit(mNativePaint);
}
/**
- * Set a hyphen edit on the paint (causes a hyphen to be added to text when
- * measured or drawn).
+ * Set a packed hyphen edit on the paint.
*
- * @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.
+ * By setting hyphen edit, the measurement and drawing is performed with modifying hyphenation
+ * at the start of line and end of line. For example, by passing
+ * {@link android.text.Hyphenator#END_HYPHEN_EDIT_INSERT_HYPHEN} like as follows, HYPHEN(U+2010)
+ * character is appended at the end of line.
*
- * @hide
+ * <pre>
+ * <code>
+ * Paint paint = new Paint();
+ * paint.setHyphenEdit(Hyphenator.packHyphenEdit(
+ * Hyphenator.START_HYPHEN_EDIT_NO_EDIT,
+ * Hyphenator.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>
+ *
+ * You can pack start hyphen edit and end hyphen edit by
+ * {@link android.text.Hyphenator#packHyphenEdit(int,int)}
+ *
+ * The default value is 0 which is equivalent to packed value of
+ * {@link android.text.Hyphenator#START_HYPHEN_EDIT_NO_EDIT} and
+ * {@link android.text.Hyphenator#END_HYPHEN_EDIT_NO_EDIT}.
+ *
+ * @param hyphen a packed hyphen edit value.
+ * @see #getHyphenEdit()
*/
- @UnsupportedAppUsage
public void setHyphenEdit(int hyphen) {
nSetHyphenEdit(mNativePaint, hyphen);
}
@@ -2262,17 +2328,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
- */
- @UnsupportedAppUsage
- 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");
}
@@ -2309,159 +2411,32 @@ public class Paint {
}
/**
- * Convenience overload that takes a CharSequence instead of a
- * String.
+ * Returns the next cursor position in the run.
*
- * @see #getTextRunAdvances(String, int, int, int, int, boolean, float[], int)
- * @hide
- */
- public float getTextRunAdvances(CharSequence text, int start, int end,
- int contextStart, int contextEnd, boolean isRtl, float[] advances,
- int advancesIndex) {
- if (text == null) {
- throw new IllegalArgumentException("text cannot be null");
- }
- if ((start | end | contextStart | contextEnd | advancesIndex | (end - start)
- | (start - contextStart) | (contextEnd - end)
- | (text.length() - contextEnd)
- | (advances == null ? 0 :
- (advances.length - advancesIndex - (end - start)))) < 0) {
- throw new IndexOutOfBoundsException();
- }
-
- if (text instanceof String) {
- return getTextRunAdvances((String) text, start, end,
- contextStart, contextEnd, isRtl, advances, advancesIndex);
- }
- if (text instanceof SpannedString ||
- text instanceof SpannableString) {
- return getTextRunAdvances(text.toString(), start, end,
- contextStart, contextEnd, isRtl, advances, advancesIndex);
- }
- if (text instanceof GraphicsOperations) {
- return ((GraphicsOperations) text).getTextRunAdvances(start, end,
- contextStart, contextEnd, isRtl, advances, advancesIndex, this);
- }
- if (text.length() == 0 || end == start) {
- return 0f;
- }
-
- int contextLen = contextEnd - contextStart;
- int len = end - start;
- char[] buf = TemporaryBuffer.obtain(contextLen);
- TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
- float result = getTextRunAdvances(buf, start - contextStart, len,
- 0, contextLen, isRtl, advances, advancesIndex);
- TemporaryBuffer.recycle(buf);
- return result;
- }
-
- /**
- * Returns the total advance width for the characters in the run
- * between start and end, and if advances is not null, the advance
- * assigned to each of these characters (java chars).
- *
- * <p>The trailing surrogate in a valid surrogate pair is assigned
- * an advance of 0. Thus the number of returned advances is
- * always equal to count, not to the number of unicode codepoints
- * represented by the run.
- *
- * <p>In the case of conjuncts or combining marks, the total
- * advance is assigned to the first logical character, and the
- * following characters are assigned an advance of 0.
- *
- * <p>This generates the sum of the advances of glyphs for
- * characters in a reordered cluster as the width of the first
- * logical character in the cluster, and 0 for the widths of all
- * other characters in the cluster. In effect, such clusters are
- * treated like conjuncts.
- *
- * <p>The shaping bounds limit the amount of context available
- * outside start and end that can be used for shaping analysis.
- * These bounds typically reflect changes in bidi level or font
- * metrics across which shaping does not occur.
+ * This avoids placing the cursor between surrogates, between characters that form conjuncts,
+ * between base characters and combining marks, or within a reordering cluster.
*
- * @param text the text to measure. Cannot be null.
- * @param start the index of the first character to measure
- * @param end the index past the last character to measure
- * @param contextStart the index of the first character to use for shaping context,
- * must be <= start
- * @param contextEnd the index past the last character to use for shaping context,
- * must be >= end
- * @param isRtl whether the run is in RTL direction
- * @param advances array to receive the advances, must have room for all advances,
- * can be null if only total advance is needed
- * @param advancesIndex the position in advances at which to put the
- * advance corresponding to the character at start
- * @return the total advance
- *
- * @hide
- */
- public float getTextRunAdvances(String text, int start, int end, int contextStart,
- int contextEnd, boolean isRtl, float[] advances, int advancesIndex) {
- if (text == null) {
- throw new IllegalArgumentException("text cannot be null");
- }
- if ((start | end | contextStart | contextEnd | advancesIndex | (end - start)
- | (start - contextStart) | (contextEnd - end)
- | (text.length() - contextEnd)
- | (advances == null ? 0 :
- (advances.length - advancesIndex - (end - start)))) < 0) {
- throw new IndexOutOfBoundsException();
- }
-
- if (text.length() == 0 || start == end) {
- return 0f;
- }
-
- if (!mHasCompatScaling) {
- return nGetTextAdvances(mNativePaint, text, start, end, contextStart, contextEnd,
- isRtl ? BIDI_FORCE_RTL : BIDI_FORCE_LTR, advances, advancesIndex);
- }
-
- final float oldSize = getTextSize();
- setTextSize(oldSize * mCompatScaling);
- final float totalAdvance = nGetTextAdvances(mNativePaint, text, start, end, contextStart,
- contextEnd, isRtl ? BIDI_FORCE_RTL : BIDI_FORCE_LTR, advances, advancesIndex);
- setTextSize(oldSize);
-
- if (advances != null) {
- for (int i = advancesIndex, e = i + (end - start); i < e; i++) {
- advances[i] *= mInvCompatScaling;
- }
- }
- return totalAdvance * mInvCompatScaling; // assume errors are insignificant
- }
-
- /**
- * Returns the next cursor position in the run. This avoids placing the
- * cursor between surrogates, between characters that form conjuncts,
- * between base characters and combining marks, or within a reordering
- * cluster.
- *
- * <p>ContextStart and offset are relative to the start of text.
- * The context is the shaping context for cursor movement, generally
- * the bounds of the metric span enclosing the cursor in the direction of
- * movement.
+ * <p>
+ * 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
*/
- @UnsupportedAppUsage
- 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)
@@ -2470,85 +2445,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>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>
+ * 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>
*
* @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)
@@ -2556,8 +2533,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);
}
/**
@@ -2623,11 +2600,13 @@ public class Paint {
* 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) {
if ((start | end | (end - start) | (text.length() - end)) < 0) {
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index ec48d2c91872..405ab0bbcb34 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -69,7 +69,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;
@@ -171,7 +171,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);
}
@@ -189,7 +189,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;
@@ -258,6 +258,7 @@ public class Path {
*
* @return the path's fill type
*/
+ @NonNull
public FillType getFillType() {
return sFillTypeArray[nGetFillType(mNativePath)];
}
@@ -267,7 +268,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);
}
@@ -321,7 +322,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);
}
@@ -464,7 +465,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);
}
@@ -480,7 +481,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);
}
@@ -545,7 +546,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);
}
@@ -558,7 +559,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);
}
@@ -569,7 +570,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);
}
@@ -578,7 +579,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);
}
@@ -591,7 +592,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);
}
@@ -603,7 +604,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);
}
@@ -627,7 +628,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);
}
@@ -639,7 +640,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);
}
@@ -653,7 +654,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");
}
@@ -668,8 +669,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");
}
@@ -683,7 +684,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);
}
@@ -693,7 +694,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);
}
@@ -703,7 +704,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);
}
@@ -763,7 +764,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;
@@ -777,7 +778,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 b36d752ba5d1..f6d801b3ba43 100644
--- a/graphics/java/android/graphics/Picture.java
+++ b/graphics/java/android/graphics/Picture.java
@@ -17,6 +17,7 @@
package android.graphics;
import android.annotation.UnsupportedAppUsage;
+
import java.io.InputStream;
import java.io.OutputStream;
@@ -165,6 +166,7 @@ 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.
@@ -181,6 +183,7 @@ 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.
*/
@@ -214,7 +217,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/Point.java b/graphics/java/android/graphics/Point.java
index c6b6c668f03a..ea9350174f97 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,7 +132,7 @@ public class Point implements Parcelable {
* @param fieldId Field Id of the Rect as defined in the parent message
* @hide
*/
- public void writeToProto(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);
@@ -141,6 +143,7 @@ public class Point implements Parcelable {
/**
* 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..a3b41944f7ab 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;
}
@@ -134,6 +134,7 @@ public class PointF implements Parcelable {
/**
* 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/PorterDuffColorFilter.java b/graphics/java/android/graphics/PorterDuffColorFilter.java
index 3c90b72eaed0..6665220c293c 100644
--- a/graphics/java/android/graphics/PorterDuffColorFilter.java
+++ b/graphics/java/android/graphics/PorterDuffColorFilter.java
@@ -36,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;
@@ -49,7 +47,6 @@ public class PorterDuffColorFilter extends ColorFilter {
* is applied.
*
* @see Color
- * @see #setColor(int)
*
* @hide
*/
@@ -60,30 +57,10 @@ public class PorterDuffColorFilter extends ColorFilter {
}
/**
- * 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
*/
@@ -92,24 +69,6 @@ public class PorterDuffColorFilter extends ColorFilter {
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);
diff --git a/graphics/java/android/graphics/RecordingCanvas.java b/graphics/java/android/graphics/RecordingCanvas.java
new file mode 100644
index 000000000000..fd5d62406da7
--- /dev/null
+++ b/graphics/java/android/graphics/RecordingCanvas.java
@@ -0,0 +1,304 @@
+/*
+ * 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.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#startRecording()} 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 BaseRecordingCanvas {
+ // 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
+ ///////////////////////////////////////////////////////////////////////////
+
+ private 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
+ */
+ 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
+ */
+ public void drawGLFunctor2(long drawGLFunctor, @Nullable Runnable releasedCallback) {
+ nCallDrawGLFunction(mNativeCanvasWrapper, drawGLFunctor, releasedCallback);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // 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);
+}
diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java
index 9cada3c5b729..c4dc0adb3be0 100644
--- a/graphics/java/android/graphics/Rect.java
+++ b/graphics/java/android/graphics/Rect.java
@@ -17,13 +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.annotation.UnsupportedAppUsage;
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;
@@ -90,7 +94,7 @@ 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 {
@@ -141,6 +145,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));
}
@@ -149,7 +154,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);
@@ -165,6 +171,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
@@ -183,7 +190,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;
}
@@ -203,7 +211,7 @@ public final class Rect implements Parcelable {
* @hide
*/
@UnsupportedAppUsage
- 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(']');
@@ -217,7 +225,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);
@@ -227,6 +235,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() {
@@ -311,7 +353,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;
@@ -368,7 +410,7 @@ 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;
@@ -434,7 +476,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
@@ -483,7 +525,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);
}
@@ -493,7 +535,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);
@@ -513,7 +555,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);
@@ -552,7 +594,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;
}
@@ -589,7 +631,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);
}
@@ -636,6 +678,7 @@ public final class Rect implements Parcelable {
/**
* Parcelable interface methods
*/
+ @Override
public int describeContents() {
return 0;
}
@@ -645,6 +688,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);
@@ -656,6 +700,7 @@ public final class Rect implements Parcelable {
/**
* Return a new rectangle from the data in the specified parcel.
*/
+ @Override
public Rect createFromParcel(Parcel in) {
Rect r = new Rect();
r.readFromParcel(in);
@@ -665,6 +710,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];
}
@@ -676,7 +722,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();
diff --git a/graphics/java/android/graphics/RectF.java b/graphics/java/android/graphics/RectF.java
index b49054550956..d6447ac237ab 100644
--- a/graphics/java/android/graphics/RectF.java
+++ b/graphics/java/android/graphics/RectF.java
@@ -16,12 +16,15 @@
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).
@@ -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);
@@ -557,6 +565,7 @@ public class RectF implements Parcelable {
/**
* 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 db6ebd5f22c3..29d936778d44 100644
--- a/graphics/java/android/graphics/Region.java
+++ b/graphics/java/android/graphics/Region.java
@@ -16,6 +16,7 @@
package android.graphics;
+import android.annotation.NonNull;
import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
@@ -62,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);
}
@@ -89,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);
}
@@ -112,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);
}
@@ -135,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);
@@ -145,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();
}
@@ -156,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());
@@ -166,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());
}
@@ -181,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);
}
@@ -199,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);
}
@@ -251,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);
}
@@ -259,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);
}
@@ -268,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);
}
@@ -277,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);
}
@@ -285,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);
}
@@ -294,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);
}
@@ -308,6 +312,7 @@ public class Region implements Parcelable {
*
* @hide
*/
+ @NonNull
public static Region obtain() {
Region region = sPool.acquire();
return (region != null) ? region : new Region();
@@ -320,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;
@@ -346,6 +352,7 @@ public class Region implements Parcelable {
* @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) {
@@ -353,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;
}
@@ -367,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();
@@ -382,6 +392,7 @@ public class Region implements Parcelable {
return nativeEquals(mNativeRegion, peer.mNativeRegion);
}
+ @Override
protected void finalize() throws Throwable {
try {
nativeDestructor(mNativeRegion);
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
new file mode 100644
index 000000000000..45d7a2178b84
--- /dev/null
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -0,0 +1,1482 @@
+/*
+ * 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.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 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 = RenderNode.create("myRenderNode");
+ * renderNode.setLeftTopRightBottom(0, 0, 50, 50); // Set the size to 50x50
+ * RecordingCanvas canvas = renderNode.startRecording();
+ * 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 instanceof RecordingCanvas) {
+ * RecordingCanvas recordingCanvas = (RecordingCanvas) canvas;
+ * // 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.
+ * recordingCanvas.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 #setLeft(int)}, 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 = RenderNode.create("MyRenderNode");
+ * mRenderNode.setLeftTopRightBottom(0, 0, width, height);
+ * RecordingCanvas canvas = mRenderNode.startRecording();
+ * 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 instanceof RecordingCanvas) {
+ * RecordingCanvas recordingCanvas = (RecordingCanvas) canvas;
+ * recordingCanvas.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>
+ *
+ * <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 = new NativeAllocationRegistry(
+ RenderNode.class.getClassLoader(), nGetNativeFinalizer(), 1024);
+ }
+
+ /**
+ * Not for general use; use only if you are ThreadedRenderer or RecordingCanvas.
+ *
+ * @hide
+ */
+ public final long mNativeRenderNode;
+ private final AnimationHost mAnimationHost;
+ private RecordingCanvas mCurrentRecordingCanvas;
+
+ /**
+ * 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(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);
+
+ }
+
+ /**
+ * Enable callbacks for position changes.
+ *
+ * @hide
+ */
+ public void requestPositionUpdates(PositionUpdateListener listener) {
+ nRequestPositionUpdates(mNativeRenderNode, listener);
+ }
+
+
+ /**
+ * 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.
+ *
+ * @param width The width of the recording viewport. This will not alter the width of the
+ * RenderNode itself, that must be set with {@link #setLeft(int)} and
+ * {@link #setRight(int)}
+ * @param height The height of the recording viewport. This will not alter the height of the
+ * RenderNode itself, that must be set with {@link #setTop(int)} and
+ * {@link #setBottom(int)}.
+ * @return A canvas to record drawing operations.
+ * @see #endRecording()
+ * @see #hasDisplayList()
+ */
+ public RecordingCanvas startRecording(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 #startRecording(int, int)} with the width & height set
+ * to the RenderNode's own width & height. The RenderNode's width & height may be set
+ * with {@link #setLeftTopRightBottom(int, int, int, int)}.
+ */
+ public RecordingCanvas startRecording() {
+ return startRecording(nGetWidth(mNativeRenderNode), nGetHeight(mNativeRenderNode));
+ }
+
+ /**
+ * @hide
+ * @deprecated use {@link #startRecording(int, int)} instead
+ */
+ @Deprecated
+ public RecordingCanvas start(int width, int height) {
+ return startRecording(width, height);
+ }
+
+ /**
+ * `
+ * Ends the recording for this display list. Calling this method marks
+ * the display list valid and {@link #hasDisplayList()} will return true.
+ *
+ * @see #startRecording(int, int)
+ * @see #hasDisplayList()
+ */
+ public void endRecording() {
+ if (mCurrentRecordingCanvas == null) {
+ throw new IllegalStateException(
+ "No recording in progress, forgot to call #startRecording()?");
+ }
+ RecordingCanvas canvas = mCurrentRecordingCanvas;
+ mCurrentRecordingCanvas = null;
+ long displayList = canvas.finishRecording();
+ nSetDisplayList(mNativeRenderNode, displayList);
+ canvas.recycle();
+ }
+
+ /**
+ * @hide
+ * @deprecated use {@link #endRecording()} instead
+ */
+ @Deprecated
+ public void end(RecordingCanvas canvas) {
+ if (mCurrentRecordingCanvas != canvas) {
+ throw new IllegalArgumentException(
+ "Canvas given isn't the one that was returned from #startRecording");
+ }
+ 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 #startRecording()} 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)}.
+ *
+ * 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.
+ *
+ * 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.
+ * @return true if anything changed, false otherwise
+ */
+ 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;
+ }
+
+ /**
+ * Sets the clip bounds of the RenderNode.
+ *
+ * @param rect the bounds to clip to. If null, the clip bounds are reset
+ * @return True if the clip bounds changed, false otherwise
+ */
+ public boolean setClipBounds(@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 property is controlled by
+ * the view's parent.
+ *
+ * @param clipToBounds true if the display list should clip to its bounds
+ */
+ public boolean setClipToBounds(boolean clipToBounds) {
+ return nSetClipToBounds(mNativeRenderNode, clipToBounds);
+ }
+
+ /**
+ * Sets whether the RenderNode should be drawn immediately after the
+ * closest ancestor RenderNode containing a projection receiver.
+ *
+ * @param shouldProject true if the display list should be projected onto a
+ * containing volume.
+ */
+ public boolean setProjectBackwards(boolean shouldProject) {
+ return nSetProjectBackwards(mNativeRenderNode, shouldProject);
+ }
+
+ /**
+ * Sets whether the RenderNode is a projection receiver - that its parent
+ * RenderNode should draw any descendent RenderNodes with
+ * ProjectBackwards=true directly on top of it. Default value is false.
+ */
+ 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.
+ */
+ 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.
+ */
+ public boolean setSpotShadowColor(int color) {
+ return nSetSpotShadowColor(mNativeRenderNode, color);
+ }
+
+ /**
+ * @return The shadow color set by {@link #setSpotShadowColor(int)}, or black if nothing
+ * was set
+ */
+ public 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.
+ */
+ public boolean setAmbientShadowColor(int color) {
+ return nSetAmbientShadowColor(mNativeRenderNode, color);
+ }
+
+ /**
+ * @return The shadow color set by {@link #setAmbientShadowColor(int)}, or black if
+ * nothing was set
+ */
+ public int getAmbientShadowColor() {
+ return nGetAmbientShadowColor(mNativeRenderNode);
+ }
+
+ /**
+ * Enables or disables clipping to the outline.
+ *
+ * @param clipToOutline true if clipping to the outline.
+ */
+ 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 #setRotation(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.
+ * @hide TODO Do we want this?
+ */
+ public boolean setAnimationMatrix(Matrix matrix) {
+ return nSetAnimationMatrix(mNativeRenderNode,
+ (matrix != null) ? matrix.native_instance : 0);
+ }
+
+ /**
+ * 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()
+ */
+ 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 elevation changed, false if it was the same
+ */
+ 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()
+ */
+ 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()
+ */
+ 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()
+ */
+ 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#setRotation(float)
+ * @see #getRotation()
+ */
+ public boolean setRotation(float rotation) {
+ return nSetRotation(mNativeRenderNode, rotation);
+ }
+
+ /**
+ * Returns the rotation value for this display list around the Z axis, in degrees.
+ *
+ * @see #setRotation(float)
+ */
+ public float getRotation() {
+ 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()
+ */
+ 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()
+ */
+ 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()
+ */
+ 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()
+ */
+ 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()
+ */
+ 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()
+ */
+ 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.
+ */
+ 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)
+ */
+ public boolean setCameraDistance(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 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
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ public boolean setBottom(int bottom) {
+ return nSetBottom(mNativeRenderNode, bottom);
+ }
+
+ /**
+ * Gets the left position for the RenderNode.
+ *
+ * See {@link #setLeft(int)}
+ *
+ * @return the left position in pixels
+ */
+ public int getLeft() {
+ return nGetLeft(mNativeRenderNode);
+ }
+
+ /**
+ * Gets the top position for the RenderNode.
+ *
+ * See {@link #setTop(int)}
+ *
+ * @return the top position in pixels
+ */
+ public int getTop() {
+ return nGetTop(mNativeRenderNode);
+ }
+
+ /**
+ * Gets the right position for the RenderNode.
+ *
+ * See {@link #setRight(int)}
+ *
+ * @return the right position in pixels
+ */
+ public int getRight() {
+ return nGetRight(mNativeRenderNode);
+ }
+
+ /**
+ * Gets the bottom position for the RenderNode.
+ *
+ * See {@link #setBottom(int)}
+ *
+ * @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.
+ * @see #setLeft(int)
+ * @see #setTop(int)
+ * @see #setRight(int)
+ * @see #setBottom(int)
+ */
+ public boolean setLeftTopRightBottom(int left, int top, int right, int bottom) {
+ return nSetLeftTopRightBottom(mNativeRenderNode, left, top, right, 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 any values changed, false otherwise.
+ */
+ 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 any values changed, false otherwise.
+ */
+ 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.
+ */
+ public int 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 has changed, false otherwise.
+ */
+ 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);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // 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 boolean nSetLayerPaint(long renderNode, long paint);
+
+ @CriticalNative
+ private static native boolean nSetClipToBounds(long renderNode, boolean clipToBounds);
+
+ @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 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);
+}
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 5b4e327f7caf..bf969ef15c5b 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -27,15 +27,16 @@ 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.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;
@@ -48,18 +49,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;
@@ -122,9 +117,15 @@ 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
@UnsupportedAppUsage
static final Map<String, Typeface> sSystemFontMap;
- static final Map<String, FontFamily[]> sSystemFallbackMap;
+
+ // We cannot support sSystemFallbackMap since we will migrate to public FontFamily API.
+ @UnsupportedAppUsage
+ static final Map<String, android.graphics.FontFamily[]> sSystemFallbackMap =
+ Collections.emptyMap();
/**
* @hide
@@ -147,13 +148,7 @@ public class Typeface {
@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
@@ -168,6 +163,9 @@ public class Typeface {
private int[] mSupportedAxes;
private static final int[] EMPTY_AXES = {};
+ // The underlying font families.
+ private final FontFamily[] mFamilies;
+
@UnsupportedAppUsage
private static void setDefault(Typeface t) {
sDefaultTypeface = t;
@@ -196,38 +194,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
@@ -262,21 +228,34 @@ 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 (familyBuilder == null) {
+ return Typeface.DEFAULT;
+ }
+ typeface = new Typeface.CustomFallbackBuilder(familyBuilder.build()).build();
+ } catch (IOException e) {
+ typeface = Typeface.DEFAULT;
}
- if (!fontFamily.freeze()) {
- return null;
- }
- FontFamily[] familyChain = { fontFamily };
- typeface = createFromFamiliesWithDefault(familyChain, DEFAULT_FAMILY,
- RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
synchronized (sDynamicCacheLock) {
final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */,
null /* axes */, RESOLVE_BY_FONT_TABLE /* weight */,
@@ -343,15 +322,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 Font.Builder mFontBuilder;
private String mFallbackFamilyName;
@@ -364,7 +339,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;
}
/**
@@ -376,7 +353,9 @@ public class Typeface {
* @param fd The file descriptor. The passed fd must be mmap-able.
*/
public Builder(@NonNull FileDescriptor fd) {
- mFd = fd;
+ mFontBuilder = new Font.Builder(fd);
+ mAssetManager = null;
+ mPath = null;
}
/**
@@ -385,7 +364,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;
}
/**
@@ -395,27 +376,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;
}
/**
@@ -427,6 +403,7 @@ public class Typeface {
*/
public Builder setWeight(@IntRange(from = 1, to = 1000) int weight) {
mWeight = weight;
+ mFontBuilder.setWeight(weight);
return this;
}
@@ -438,7 +415,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;
}
@@ -450,11 +428,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;
}
@@ -466,14 +440,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;
}
@@ -483,14 +450,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;
}
@@ -566,11 +526,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;
}
@@ -587,91 +543,198 @@ 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,
+ try {
+ final Font font = mFontBuilder.build();
+ final String key = mAssetManager == null ? null : createAssetUid(
+ mAssetManager, mPath, font.getTtcIndex(), font.getAxes(),
+ font.getStyle().getWeight(), font.getStyle().getSlant(),
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 (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 boolean success = fontFamily.addFontFromBuffer(fontBuffer,
- font.getTtcIndex(), font.getAxes(), font.getWeight(),
- font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL);
- if (!success) {
- fontFamily.abortCreation();
- return null;
+ final Typeface typeface = builder.build();
+ if (key != null) {
+ synchronized (sDynamicCacheLock) {
+ sDynamicTypefaceCache.put(key, typeface);
}
- 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();
}
+ }
+ }
- // Must not reach here.
- throw new IllegalArgumentException("No source was set.");
+ /**
+ * 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 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;
+
+ /**
+ * 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 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 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 CustomFallbackBuilder addCustomFallback(@NonNull FontFamily family) {
+ Preconditions.checkNotNull(family);
+ Preconditions.checkArgument(mFamilies.size() < MAX_CUSTOM_FALLBACK,
+ "Custom fallback limit exceeded(" + MAX_CUSTOM_FALLBACK + ")");
+ mFamilies.add(family);
+ return this;
+ }
+
+ /**
+ * Create the Typeface based on the configured values.
+ *
+ * @return the Typeface object
+ */
+ public Typeface build() {
+ final int userFallbackSize = mFamilies.size();
+ final FontFamily[] fallback = SystemFonts.getSystemFallback(mFallbackName);
+ final FontFamily[] fullFamilies = new FontFamily[fallback.length + userFallbackSize];
+ final long[] ptrArray = new long[fallback.length + userFallbackSize];
+ for (int i = 0; i < userFallbackSize; ++i) {
+ ptrArray[i] = mFamilies.get(i).getNativePtr();
+ fullFamilies[i] = mFamilies.get(i);
+ }
+ for (int i = 0; i < fallback.length; ++i) {
+ ptrArray[i + userFallbackSize] = fallback[i].getNativePtr();
+ fullFamilies[i + userFallbackSize] = fallback[i];
+ }
+ 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), fullFamilies);
}
}
@@ -687,7 +750,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);
}
/**
@@ -736,7 +799,7 @@ public class Typeface {
}
}
- typeface = new Typeface(nativeCreateFromTypeface(ni, style));
+ typeface = new Typeface(nativeCreateFromTypeface(ni, style), family.mFamilies);
styles.put(style, typeface);
}
return typeface;
@@ -805,7 +868,7 @@ public class Typeface {
typeface = new Typeface(
nativeCreateFromTypefaceWithExactStyle(
- base.native_instance, weight, italic));
+ base.native_instance, weight, italic), base.mFamilies);
innerCache.put(key, typeface);
}
return typeface;
@@ -814,8 +877,9 @@ 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),
+ base.mFamilies);
}
/**
@@ -897,9 +961,11 @@ public class Typeface {
* Create a new typeface from an array of font families.
*
* @param families array of font families
+ * @deprecated
*/
+ @Deprecated
@UnsupportedAppUsage
- private static Typeface createFromFamilies(FontFamily[] families) {
+ 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;
@@ -909,11 +975,26 @@ 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), families);
+ }
+
+ /**
* This method is used by supportlib-v27.
* TODO: Remove private API use in supportlib: http://b/72665240
*/
- private static Typeface createFromFamiliesWithDefault(FontFamily[] families, int weight,
- int italic) {
+ @UnsupportedAppUsage
+ private static Typeface createFromFamiliesWithDefault(
+ android.graphics.FontFamily[] families, int weight, int italic) {
return createFromFamiliesWithDefault(families, DEFAULT_FAMILY, weight, italic);
}
@@ -931,18 +1012,15 @@ public class Typeface {
* @param families array of font families
*/
@UnsupportedAppUsage
- private static Typeface createFromFamiliesWithDefault(FontFamily[] families,
+ 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));
}
@@ -955,198 +1033,68 @@ public class Typeface {
}
native_instance = ni;
+ mFamilies = new FontFamily[0];
sRegistry.registerNativeAllocation(this, native_instance);
mStyle = nativeGetStyle(ni);
mWeight = nativeGetWeight(ni);
}
- private static @Nullable ByteBuffer mmap(String fullPath) {
- try (FileInputStream file = new FileInputStream(fullPath)) {
- final FileChannel fileChannel = file.getChannel();
- final long fontSize = fileChannel.size();
- return fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
- } catch (IOException e) {
- Log.e(TAG, "Error mapping font file " + fullPath);
- return null;
+ private Typeface(long ni, @NonNull FontFamily[] families) {
+ if (ni == 0) {
+ throw new IllegalStateException("native typeface cannot be made");
}
- }
- private static @Nullable FontFamily createFontFamily(
- String familyName, List<FontConfig.Font> fonts, String[] languageTags, int variant,
- Map<String, ByteBuffer> cache, String fontDir) {
- final FontFamily family = new FontFamily(languageTags, variant);
- for (int i = 0; i < fonts.size(); i++) {
- final FontConfig.Font font = fonts.get(i);
- final String fullPath = fontDir + font.getFontName();
- ByteBuffer buffer = cache.get(fullPath);
- if (buffer == null) {
- if (cache.containsKey(fullPath)) {
- continue; // Already failed to mmap. Skip it.
- }
- buffer = mmap(fullPath);
- cache.put(fullPath, buffer);
- if (buffer == null) {
- continue;
- }
- }
- if (!family.addFontFromBuffer(buffer, font.getTtcIndex(), font.getAxes(),
- font.getWeight(), font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL)) {
- Log.e(TAG, "Error creating font " + fullPath + "#" + font.getTtcIndex());
- }
- }
- if (!family.freeze()) {
- Log.e(TAG, "Unable to load Family: " + familyName + " : "
- + Arrays.toString(languageTags));
- return null;
- }
- return family;
+ native_instance = ni;
+ mFamilies = families;
+ sRegistry.registerNativeAllocation(this, native_instance);
+ mStyle = nativeGetStyle(ni);
+ mWeight = nativeGetWeight(ni);
}
- 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<>();
+ private static Typeface getSystemDefaultTypeface(@NonNull String familyName) {
+ Typeface tf = sSystemFontMap.get(familyName);
+ return tf == null ? Typeface.DEFAULT : tf;
+ }
- // 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);
- }
+ /** @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()));
}
- 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.
- }
+ for (FontConfig.Alias alias : aliases) {
+ if (systemFontMap.containsKey(alias.getName())) {
+ continue; // If alias and named family are conflict, use named family.
}
+ final Typeface base = systemFontMap.get(alias.getToName());
+ final int weight = alias.getWeight();
+ final Typeface newFace = weight == 400 ? base :
+ new Typeface(nativeCreateWeightAlias(base.native_instance, weight),
+ base.mFamilies);
+ 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);
@@ -1162,6 +1110,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
@@ -1224,4 +1181,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/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
index 4f467d9aabae..3aaec3123d7d 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;
@@ -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) {
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index 19ca098d57db..789e38c4e650 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -39,7 +39,9 @@ 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.util.ArrayMap;
import android.util.AttributeSet;
@@ -50,8 +52,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;
@@ -519,7 +520,6 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
mAnimatedVectorState.mVectorDrawable.getOutline(outline);
}
- /** @hide */
@Override
public Insets getOpticalInsets() {
return mAnimatedVectorState.mVectorDrawable.getOpticalInsets();
@@ -1232,7 +1232,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;
@@ -1541,11 +1542,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
@@ -1705,6 +1706,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
}
}
+ @Override
public long getAnimatorNativePtr() {
return mSetPtr;
}
@@ -1740,7 +1742,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
@Override
public void onDraw(Canvas canvas) {
if (canvas.isHardwareAccelerated()) {
- recordLastSeenTarget((DisplayListCanvas) canvas);
+ recordLastSeenTarget((RecordingCanvas) canvas);
}
}
diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java
index 9d9b3eb159c8..976190109e7c 100644
--- a/graphics/java/android/graphics/drawable/BitmapDrawable.java
+++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java
@@ -24,7 +24,6 @@ import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
@@ -631,9 +630,6 @@ public class BitmapDrawable extends Drawable {
mDstRectAndInsetsDirty = false;
}
- /**
- * @hide
- */
@Override
public Insets getOpticalInsets() {
updateDstRectAndInsetsIfDirty();
diff --git a/graphics/java/android/graphics/drawable/ColorDrawable.java b/graphics/java/android/graphics/drawable/ColorDrawable.java
index 7cbe92910f57..3c449166d9d5 100644
--- a/graphics/java/android/graphics/drawable/ColorDrawable.java
+++ b/graphics/java/android/graphics/drawable/ColorDrawable.java
@@ -22,12 +22,18 @@ 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.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Xfermode;
import android.util.AttributeSet;
import android.view.ViewDebug;
@@ -143,9 +149,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() {
@@ -153,7 +163,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.
*/
@@ -182,6 +194,17 @@ 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;
diff --git a/graphics/java/android/graphics/drawable/ColorStateListDrawable.java b/graphics/java/android/graphics/drawable/ColorStateListDrawable.java
new file mode 100644
index 000000000000..c0c6a4f424a1
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/ColorStateListDrawable.java
@@ -0,0 +1,304 @@
+/*
+ * 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.content.pm.ActivityInfo;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+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(ColorStateList colorStateList) {
+ mState = new ColorStateListDrawableState();
+ initializeColorDrawable();
+ setColorStateList(colorStateList);
+ }
+
+ @Override
+ public void draw(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(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(ColorStateList tint) {
+ mState.mTint = tint;
+ mColorDrawable.setTintList(tint);
+ onStateChange(getState());
+ }
+
+ @Override
+ public void setTintMode(PorterDuff.Mode tintMode) {
+ mState.mTintMode = tintMode;
+ mColorDrawable.setTintMode(tintMode);
+ onStateChange(getState());
+ }
+
+ @Override
+ public ColorFilter getColorFilter() {
+ return mColorDrawable.getColorFilter();
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+ mColorDrawable.setColorFilter(colorFilter);
+ }
+
+ @Override
+ public @PixelFormat.Opacity int getOpacity() {
+ return mColorDrawable.getOpacity();
+ }
+
+ @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(Drawable who) {
+ final Callback callback = getCallback();
+
+ if (callback != null) {
+ callback.invalidateDrawable(who);
+ }
+ }
+
+ @Override
+ public void scheduleDrawable(Drawable who, Runnable what, long when) {
+ final Callback callback = getCallback();
+
+ if (callback != null) {
+ callback.scheduleDrawable(who, what, when);
+ }
+ }
+
+ @Override
+ public void unscheduleDrawable(Drawable who, Runnable what) {
+ final Callback callback = getCallback();
+
+ if (callback != null) {
+ callback.unscheduleDrawable(who, what);
+ }
+ }
+
+ @Override
+ public 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 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(ColorStateList colorStateList) {
+ mState.mColor = colorStateList;
+ onStateChange(getState());
+ }
+
+ static final class ColorStateListDrawableState extends ConstantState {
+ ColorStateList mColor = null;
+ ColorStateList mTint = null;
+ int mAlpha = -1;
+ PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE;
+ @ActivityInfo.Config int mChangingConfigurations = 0;
+
+ ColorStateListDrawableState() {
+ }
+
+ ColorStateListDrawableState(ColorStateListDrawableState state) {
+ mColor = state.mColor;
+ mTint = state.mTint;
+ mAlpha = state.mAlpha;
+ mTintMode = state.mTintMode;
+ 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(ColorStateListDrawableState state) {
+ mState = state;
+ initializeColorDrawable();
+ }
+
+ private void initializeColorDrawable() {
+ mColorDrawable = new ColorDrawable();
+ mColorDrawable.setCallback(this);
+
+ if (mState.mTint != null) {
+ mColorDrawable.setTintList(mState.mTint);
+ }
+
+ if (mState.mTintMode != DEFAULT_TINT_MODE) {
+ mColorDrawable.setTintMode(mState.mTintMode);
+ }
+ }
+}
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 2a5254191527..caf610b8c236 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -16,11 +16,6 @@
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;
@@ -58,6 +53,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;
@@ -934,11 +934,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
@@ -1092,7 +1094,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;
@@ -1522,12 +1523,11 @@ 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;
}
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index 8c893e0d88f3..10f6fae3cf6d 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -123,9 +123,6 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
return result;
}
- /**
- * @hide
- */
@Override
public Insets getOpticalInsets() {
if (mCurrDrawable != null) {
diff --git a/graphics/java/android/graphics/drawable/DrawableWrapper.java b/graphics/java/android/graphics/drawable/DrawableWrapper.java
index a12575af939f..4ee45bfd8bc8 100644
--- a/graphics/java/android/graphics/drawable/DrawableWrapper.java
+++ b/graphics/java/android/graphics/drawable/DrawableWrapper.java
@@ -16,11 +16,6 @@
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;
@@ -36,10 +31,16 @@ 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;
/**
@@ -80,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
@@ -242,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;
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index d34d461e8a87..87402342a3c9 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -397,6 +397,7 @@ public class GradientDrawable extends Drawable {
e = new DashPathEffect(new float[] { dashWidth, dashGap }, 0);
}
mStrokePaint.setPathEffect(e);
+ mGradientIsDirty = true;
invalidateSelf();
}
@@ -935,16 +936,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();
}
/**
@@ -1703,7 +1703,6 @@ public class GradientDrawable extends Drawable {
return mGradientState.mHeight;
}
- /** @hide */
@Override
public Insets getOpticalInsets() {
return mGradientState.mOpticalInsets;
diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java
index f342dde24d01..bc8a4cbd7e9d 100644
--- a/graphics/java/android/graphics/drawable/InsetDrawable.java
+++ b/graphics/java/android/graphics/drawable/InsetDrawable.java
@@ -16,11 +16,6 @@
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;
@@ -36,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;
/**
@@ -242,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/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
index 68dd9137ae7a..b53477137331 100644
--- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java
+++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
@@ -301,9 +301,6 @@ public class NinePatchDrawable extends Drawable {
super.getOutline(outline);
}
- /**
- * @hide
- */
@Override
public Insets getOpticalInsets() {
final Insets opticalInsets = mOpticalInsets;
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 026dbbbbdfd2..1540cc22e295 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -892,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);
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/ShapeDrawable.java b/graphics/java/android/graphics/drawable/ShapeDrawable.java
index 34da928bb6f1..7bfb4c38d6c8 100644
--- a/graphics/java/android/graphics/drawable/ShapeDrawable.java
+++ b/graphics/java/android/graphics/drawable/ShapeDrawable.java
@@ -594,12 +594,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
diff --git a/graphics/java/android/graphics/drawable/StateListDrawable.java b/graphics/java/android/graphics/drawable/StateListDrawable.java
index fa18f64981b9..2855227a1002 100644
--- a/graphics/java/android/graphics/drawable/StateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/StateListDrawable.java
@@ -16,23 +16,23 @@
package android.graphics.drawable;
-import com.android.internal.R;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.Arrays;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
-import android.content.res.TypedArray;
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;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Arrays;
+
/**
* Lets you assign a number of graphic images to a single Drawable and swap out the visible item by a string
* ID value.
@@ -75,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) {
@@ -239,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)
*/
@@ -252,7 +253,6 @@ 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)
*/
@@ -265,7 +265,6 @@ 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)
*/
@@ -278,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(int[] stateSet) {
return mStateListState.indexOfStateSet(stateSet);
}
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 09badff01681..7325b857f02c 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -47,6 +47,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;
@@ -57,9 +60,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/>
@@ -546,7 +546,6 @@ public class VectorDrawable extends Drawable {
return mDpiScaledHeight;
}
- /** @hide */
@Override
public Insets getOpticalInsets() {
if (mDpiScaledDirty) {
@@ -2034,7 +2033,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;
@@ -2050,7 +2049,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..7f165bfc6a7d
--- /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.util.TypedValue;
+
+import com.android.internal.util.Preconditions;
+
+import dalvik.annotation.optimization.CriticalNative;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.io.File;
+import java.io.FileDescriptor;
+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 class Builder {
+ private static final NativeAllocationRegistry sAssetByteBufferRegistroy =
+ new NativeAllocationRegistry(ByteBuffer.class.getClassLoader(),
+ nGetReleaseNativeAssetFunc(), 64);
+
+ private static final NativeAllocationRegistry sFontRegistory =
+ new NativeAllocationRegistry(Font.class.getClassLoader(),
+ nGetReleaseNativeFont(), 64);
+
+ 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 FileDescriptor 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 FileDescriptor fd, @IntRange(from = 0) long offset,
+ @IntRange(from = -1) long size) {
+ try (FileInputStream fis = new FileInputStream(fd)) {
+ 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);
+ sAssetByteBufferRegistroy.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);
+ sAssetByteBufferRegistroy.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 @Nullable 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);
+ sFontRegistory.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 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..c0f1b163ea11
--- /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 class Builder {
+ private static final NativeAllocationRegistry sFamilyRegistory =
+ new NativeAllocationRegistry(FontFamily.class.getClassLoader(),
+ nGetReleaseNativeFamily(), 64);
+
+ 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 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 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..82fc7ac01772
--- /dev/null
+++ b/graphics/java/android/graphics/fonts/FontStyle.java
@@ -0,0 +1,256 @@
+/*
+ * 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.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;
+ }
+
+ @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 a240a78d4454..bcee559d8291 100644
--- a/graphics/java/android/graphics/fonts/FontVariationAxis.java
+++ b/graphics/java/android/graphics/fonts/FontVariationAxis.java
@@ -23,6 +23,7 @@ import android.os.Build;
import android.text.TextUtils;
import java.util.ArrayList;
+import java.util.Objects;
import java.util.regex.Pattern;
/**
@@ -187,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/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java
new file mode 100644
index 000000000000..16479095a63a
--- /dev/null
+++ b/graphics/java/android/graphics/text/LineBreaker.java
@@ -0,0 +1,517 @@
+/*
+ * 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 = new 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 class Builder {
+ private @BreakStrategy int mBreakStrategy = BREAK_STRATEGY_SIMPLE;
+ private @HyphenationFrequency int mHyphenationFrequency = HYPHENATION_FREQUENCY_NONE;
+ private @JustificationMode int mJustified = 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 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 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 Builder setJustified(@JustificationMode int justified) {
+ mJustified = justified;
+ 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 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 LineBreaker build() {
+ return new LineBreaker(mBreakStrategy, mHyphenationFrequency, mJustified, 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 int[] mVariableTabStops = null;
+ private @IntRange(from = 0) int 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 int[] tabStops,
+ @Px @IntRange(from = 0) int 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(int[], int)
+ */
+ public @Nullable int[] getTabStops() {
+ return mVariableTabStops;
+ }
+
+ /**
+ * Returns the default tab stops in pixels.
+ *
+ * @see #setTabStop(int[], int)
+ */
+ public @Px @IntRange(from = 0) int 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.
+ private static final int TAB_MASK = 0x20000000;
+ private static final int HYPHEN_MASK = 0xFF;
+
+ private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+ Result.class.getClassLoader(), nGetReleaseResultFunc(), 32);
+ 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 packed packed hyphen edit for the line.
+ *
+ * @param lineIndex an index of the line.
+ * @return a packed hyphen edit for the line.
+ *
+ * @see android.text.Hyphenator#unpackStartHyphenEdit(int)
+ * @see android.text.Hyphenator#unpackEndHyphenEdit(int)
+ * @see android.text.Hyphenator#packHyphenEdit(int,int)
+ */
+ public int getLineHyphenEdit(int lineIndex) {
+ return (nGetLineFlag(mPtr, lineIndex) & HYPHEN_MASK);
+ }
+ }
+
+ private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+ LineBreaker.class.getClassLoader(), nGetReleaseFunc(), 64);
+
+ 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 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 int[] variableTabStops,
+ int 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..6f786517ba12
--- /dev/null
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -0,0 +1,348 @@
+/*
+ * 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.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 "Hello, "
+ * .build();
+ * </code>
+ * </pre>
+ * </p>
+ */
+public class MeasuredText {
+ private long mNativePtr;
+ private @NonNull char[] mChars;
+
+ // Use builder instead.
+ private MeasuredText(long ptr, @NonNull char[] chars) {
+ mNativePtr = ptr;
+ mChars = chars;
+ }
+
+ /**
+ * Returns the characters in the paragraph used to compute this MeasuredText instance.
+ */
+ 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 class Builder {
+ private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+ MeasuredText.class.getClassLoader(), nGetReleaseFunc(), 1024);
+
+ private long mNativePtr;
+
+ private final @NonNull char[] mText;
+ private boolean mComputeHyphenation = false;
+ private boolean mComputeLayout = true;
+ private int mCurrentOffset = 0;
+
+ /**
+ * 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();
+ }
+
+ /**
+ * 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 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 Builder appendReplacementRun(@NonNull Paint paint,
+ @IntRange(from = 0) int length, @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 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 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 MeasuredText build() {
+ ensureNativePtrNoReuse();
+ if (mCurrentOffset != mText.length) {
+ throw new IllegalStateException("Style info has not been provided for all text.");
+ }
+ try {
+ long ptr = nBuildMeasuredText(mNativePtr, mText, mComputeHyphenation,
+ mComputeLayout);
+ MeasuredText res = new MeasuredText(ptr, mText);
+ 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,
+ @NonNull char[] text,
+ boolean computeHyphenation,
+ boolean computeLayout);
+
+ private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr);
+ }
+}