summaryrefslogtreecommitdiff
path: root/graphics/java/android
diff options
context:
space:
mode:
Diffstat (limited to 'graphics/java/android')
-rw-r--r--graphics/java/android/graphics/BaseCanvas.java63
-rw-r--r--graphics/java/android/graphics/Bitmap.java88
-rw-r--r--graphics/java/android/graphics/BitmapFactory.java17
-rw-r--r--graphics/java/android/graphics/Canvas.java11
-rw-r--r--graphics/java/android/graphics/FontFamily.java40
-rw-r--r--graphics/java/android/graphics/FontListParser.java21
-rw-r--r--graphics/java/android/graphics/ImageDecoder.java1104
-rw-r--r--graphics/java/android/graphics/Paint.java237
-rw-r--r--graphics/java/android/graphics/Picture.java38
-rw-r--r--graphics/java/android/graphics/PixelFormat.java48
-rw-r--r--graphics/java/android/graphics/Point.java16
-rw-r--r--graphics/java/android/graphics/PostProcessor.java87
-rw-r--r--graphics/java/android/graphics/Rect.java33
-rw-r--r--graphics/java/android/graphics/Typeface.java690
-rw-r--r--graphics/java/android/graphics/drawable/AnimatedImageDrawable.java470
-rw-r--r--graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java5
-rw-r--r--graphics/java/android/graphics/drawable/Drawable.java8
-rw-r--r--graphics/java/android/graphics/drawable/DrawableInflater.java2
-rw-r--r--graphics/java/android/graphics/drawable/GradientDrawable.java19
-rw-r--r--graphics/java/android/graphics/drawable/Icon.java6
-rw-r--r--graphics/java/android/graphics/drawable/RippleBackground.java146
-rw-r--r--graphics/java/android/graphics/drawable/RippleComponent.java252
-rw-r--r--graphics/java/android/graphics/drawable/RippleDrawable.java144
-rw-r--r--graphics/java/android/graphics/drawable/RippleForeground.java455
-rw-r--r--graphics/java/android/graphics/drawable/VectorDrawable.java93
-rw-r--r--graphics/java/android/graphics/fonts/FontVariationAxis.java2
-rw-r--r--graphics/java/android/graphics/pdf/PdfEditor.java29
-rw-r--r--graphics/java/android/graphics/pdf/PdfRenderer.java41
28 files changed, 2985 insertions, 1180 deletions
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index 1f339f7aaa54..07df0454362c 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -22,6 +22,7 @@ import android.annotation.Nullable;
import android.annotation.Size;
import android.graphics.Canvas.VertexMode;
import android.text.GraphicsOperations;
+import android.text.PrecomputedText;
import android.text.SpannableString;
import android.text.SpannedString;
import android.text.TextUtils;
@@ -375,7 +376,7 @@ public abstract class BaseCanvas {
}
throwIfHasHwBitmapInSwMode(paint);
nDrawText(mNativeCanvasWrapper, text, index, count, x, y, paint.mBidiFlags,
- paint.getNativeInstance(), paint.mNativeTypeface);
+ paint.getNativeInstance());
}
public void drawText(@NonNull CharSequence text, int start, int end, float x, float y,
@@ -387,7 +388,7 @@ public abstract class BaseCanvas {
if (text instanceof String || text instanceof SpannedString ||
text instanceof SpannableString) {
nDrawText(mNativeCanvasWrapper, text.toString(), start, end, x, y,
- paint.mBidiFlags, paint.getNativeInstance(), paint.mNativeTypeface);
+ paint.mBidiFlags, paint.getNativeInstance());
} else if (text instanceof GraphicsOperations) {
((GraphicsOperations) text).drawText(this, start, end, x, y,
paint);
@@ -395,7 +396,7 @@ public abstract class BaseCanvas {
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(), paint.mNativeTypeface);
+ paint.mBidiFlags, paint.getNativeInstance());
TemporaryBuffer.recycle(buf);
}
}
@@ -403,7 +404,7 @@ public abstract class BaseCanvas {
public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) {
throwIfHasHwBitmapInSwMode(paint);
nDrawText(mNativeCanvasWrapper, text, 0, text.length(), x, y, paint.mBidiFlags,
- paint.getNativeInstance(), paint.mNativeTypeface);
+ paint.getNativeInstance());
}
public void drawText(@NonNull String text, int start, int end, float x, float y,
@@ -413,7 +414,7 @@ public abstract class BaseCanvas {
}
throwIfHasHwBitmapInSwMode(paint);
nDrawText(mNativeCanvasWrapper, text, start, end, x, y, paint.mBidiFlags,
- paint.getNativeInstance(), paint.mNativeTypeface);
+ paint.getNativeInstance());
}
public void drawTextOnPath(@NonNull char[] text, int index, int count, @NonNull Path path,
@@ -424,7 +425,7 @@ public abstract class BaseCanvas {
throwIfHasHwBitmapInSwMode(paint);
nDrawTextOnPath(mNativeCanvasWrapper, text, index, count,
path.readOnlyNI(), hOffset, vOffset,
- paint.mBidiFlags, paint.getNativeInstance(), paint.mNativeTypeface);
+ paint.mBidiFlags, paint.getNativeInstance());
}
public void drawTextOnPath(@NonNull String text, @NonNull Path path, float hOffset,
@@ -432,7 +433,7 @@ public abstract class BaseCanvas {
if (text.length() > 0) {
throwIfHasHwBitmapInSwMode(paint);
nDrawTextOnPath(mNativeCanvasWrapper, text, path.readOnlyNI(), hOffset, vOffset,
- paint.mBidiFlags, paint.getNativeInstance(), paint.mNativeTypeface);
+ paint.mBidiFlags, paint.getNativeInstance());
}
}
@@ -453,7 +454,8 @@ public abstract class BaseCanvas {
throwIfHasHwBitmapInSwMode(paint);
nDrawTextRun(mNativeCanvasWrapper, text, index, count, contextIndex, contextCount,
- x, y, isRtl, paint.getNativeInstance(), paint.mNativeTypeface);
+ x, y, isRtl, paint.getNativeInstance(), 0 /* measured text */,
+ 0 /* measured text offset */);
}
public void drawTextRun(@NonNull CharSequence text, int start, int end, int contextStart,
@@ -474,7 +476,7 @@ public abstract class BaseCanvas {
if (text instanceof String || text instanceof SpannedString ||
text instanceof SpannableString) {
nDrawTextRun(mNativeCanvasWrapper, text.toString(), start, end, contextStart,
- contextEnd, x, y, isRtl, paint.getNativeInstance(), paint.mNativeTypeface);
+ contextEnd, x, y, isRtl, paint.getNativeInstance());
} else if (text instanceof GraphicsOperations) {
((GraphicsOperations) text).drawTextRun(this, start, end,
contextStart, contextEnd, x, y, isRtl, paint);
@@ -483,8 +485,20 @@ public abstract class BaseCanvas {
int len = end - start;
char[] buf = TemporaryBuffer.obtain(contextLen);
TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
+ long measuredTextPtr = 0;
+ int measuredTextOffset = 0;
+ if (text instanceof PrecomputedText) {
+ PrecomputedText mt = (PrecomputedText) text;
+ int paraIndex = mt.findParaIndex(start);
+ if (end <= mt.getParagraphEnd(paraIndex)) {
+ // Only suppor the same paragraph.
+ measuredTextPtr = mt.getMeasuredParagraph(paraIndex).getNativePtr();
+ measuredTextOffset = start - mt.getParagraphStart(paraIndex);
+ }
+ }
nDrawTextRun(mNativeCanvasWrapper, buf, start - contextStart, len,
- 0, contextLen, x, y, isRtl, paint.getNativeInstance(), paint.mNativeTypeface);
+ 0, contextLen, x, y, isRtl, paint.getNativeInstance(),
+ measuredTextPtr, measuredTextOffset);
TemporaryBuffer.recycle(buf);
}
}
@@ -526,10 +540,19 @@ public abstract class BaseCanvas {
return mAllowHwBitmapsInSwMode;
}
+ /**
+ * @hide
+ */
+ protected void onHwBitmapInSwMode() {
+ if (!mAllowHwBitmapsInSwMode) {
+ throw new IllegalArgumentException(
+ "Software rendering doesn't support hardware bitmaps");
+ }
+ }
+
private void throwIfHwBitmapInSwMode(Bitmap bitmap) {
- if (!mAllowHwBitmapsInSwMode && !isHardwareAccelerated()
- && bitmap.getConfig() == Bitmap.Config.HARDWARE) {
- throw new IllegalStateException("Software rendering doesn't support hardware bitmaps");
+ if (!isHardwareAccelerated() && bitmap.getConfig() == Bitmap.Config.HARDWARE) {
+ onHwBitmapInSwMode();
}
}
@@ -614,23 +637,21 @@ public abstract class BaseCanvas {
short[] indices, int indexOffset, int indexCount, long nativePaint);
private static native void nDrawText(long nativeCanvas, char[] text, int index, int count,
- float x, float y, int flags, long nativePaint, long nativeTypeface);
+ float x, float y, int flags, long nativePaint);
private static native void nDrawText(long nativeCanvas, String text, int start, int end,
- float x, float y, int flags, long nativePaint, long nativeTypeface);
+ float x, float y, int flags, long nativePaint);
private static native void nDrawTextRun(long nativeCanvas, String text, int start, int end,
- int contextStart, int contextEnd, float x, float y, boolean isRtl, long nativePaint,
- long nativeTypeface);
+ int contextStart, int contextEnd, float x, float y, boolean isRtl, long nativePaint);
private static native void nDrawTextRun(long nativeCanvas, char[] text, int start, int count,
int contextStart, int contextCount, float x, float y, boolean isRtl, long nativePaint,
- long nativeTypeface);
+ long nativePrecomputedText, int measuredTextOffset);
private static native void nDrawTextOnPath(long nativeCanvas, char[] text, int index, int count,
- long nativePath, float hOffset, float vOffset, int bidiFlags, long nativePaint,
- long nativeTypeface);
+ long nativePath, float hOffset, float vOffset, int bidiFlags, long nativePaint);
private static native void nDrawTextOnPath(long nativeCanvas, String text, long nativePath,
- float hOffset, float vOffset, int flags, long nativePaint, long nativeTypeface);
+ 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 57c75490ec47..44e7066c5c66 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -21,6 +21,7 @@ import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
+import android.annotation.WorkerThread;
import android.content.res.ResourcesImpl;
import android.os.Parcel;
import android.os.Parcelable;
@@ -28,6 +29,10 @@ 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 libcore.util.NativeAllocationRegistry;
import java.io.OutputStream;
@@ -1170,6 +1175,82 @@ public final class Bitmap implements Parcelable {
}
/**
+ * Creates a Bitmap from the given {@link Picture} source of recorded drawing commands.
+ *
+ * Equivalent to calling {@link #createBitmap(Picture, int, int, Config)} with
+ * width and height the same as the Picture's width and height and a Config.HARDWARE
+ * config.
+ *
+ * @param source The recorded {@link Picture} of drawing commands that will be
+ * drawn into the returned Bitmap.
+ * @return An immutable bitmap with a HARDWARE config whose contents are created
+ * from the recorded drawing commands in the Picture source.
+ */
+ public static @NonNull Bitmap createBitmap(@NonNull Picture source) {
+ return createBitmap(source, source.getWidth(), source.getHeight(), Config.HARDWARE);
+ }
+
+ /**
+ * Creates a Bitmap from the given {@link Picture} source of recorded drawing commands.
+ *
+ * The bitmap will be immutable with the given width and height. If the width and height
+ * are not the same as the Picture's width & height, the Picture will be scaled to
+ * fit the given width and height.
+ *
+ * @param source The recorded {@link Picture} of drawing commands that will be
+ * drawn into the returned Bitmap.
+ * @param width The width of the bitmap to create. The picture's width will be
+ * scaled to match if necessary.
+ * @param height The height of the bitmap to create. The picture's height will be
+ * scaled to match if necessary.
+ * @param config The {@link Config} of the created bitmap. If this is null then
+ * the bitmap will be {@link Config#HARDWARE}.
+ *
+ * @return An immutable bitmap with a HARDWARE config whose contents are created
+ * from the recorded drawing commands in the Picture source.
+ */
+ public static @NonNull Bitmap createBitmap(@NonNull Picture source, int width, int height,
+ @NonNull Config config) {
+ if (width <= 0 || height <= 0) {
+ throw new IllegalArgumentException("width & height must be > 0");
+ }
+ if (config == null) {
+ throw new IllegalArgumentException("Config must not be null");
+ }
+ if (source.requiresHardwareAcceleration() && config != Config.HARDWARE) {
+ StrictMode.noteSlowCall("GPU readback");
+ }
+ if (config == Config.HARDWARE || source.requiresHardwareAcceleration()) {
+ final RenderNode node = RenderNode.create("BitmapTemporary", null);
+ node.setLeftTopRightBottom(0, 0, width, height);
+ node.setClipToBounds(false);
+ final DisplayListCanvas canvas = node.start(width, height);
+ if (source.getWidth() != width || source.getHeight() != height) {
+ canvas.scale(width / (float) source.getWidth(),
+ height / (float) source.getHeight());
+ }
+ canvas.drawPicture(source);
+ node.end(canvas);
+ Bitmap bitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
+ if (config != Config.HARDWARE) {
+ bitmap = bitmap.copy(config, false);
+ }
+ return bitmap;
+ } else {
+ Bitmap bitmap = Bitmap.createBitmap(width, height, config);
+ Canvas canvas = new Canvas(bitmap);
+ if (source.getWidth() != width || source.getHeight() != height) {
+ canvas.scale(width / (float) source.getWidth(),
+ height / (float) source.getHeight());
+ }
+ canvas.drawPicture(source);
+ canvas.setBitmap(null);
+ bitmap.makeImmutable();
+ return bitmap;
+ }
+ }
+
+ /**
* Returns an optional array of private data, used by the UI system for
* some bitmaps. Not intended to be called by applications.
*/
@@ -1233,6 +1314,7 @@ public final class Bitmap implements Parcelable {
* @param stream The outputstream to write the compressed data.
* @return true if successfully compressed to the specified stream.
*/
+ @WorkerThread
public boolean compress(CompressFormat format, int quality, OutputStream stream) {
checkRecycled("Can't compress a recycled bitmap");
// do explicit check before calling the native method
@@ -1257,6 +1339,12 @@ public final class Bitmap implements Parcelable {
return mIsMutable;
}
+ /** @hide */
+ public final void makeImmutable() {
+ // todo mIsMutable = false;
+ // todo nMakeImmutable();
+ }
+
/**
* <p>Indicates whether pixels stored in this bitmaps are stored pre-multiplied.
* When a pixel is pre-multiplied, the RGB components have been multiplied by
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index ffb39e339119..7ea35e73619a 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -18,6 +18,8 @@ package android.graphics;
import static android.graphics.BitmapFactory.Options.validate;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Trace;
@@ -354,6 +356,7 @@ public class BitmapFactory {
* decode, in the case of which a more accurate, but slightly slower,
* IDCT method will be used instead.
*/
+ @Deprecated
public boolean inPreferQualityOverSpeed;
/**
@@ -412,6 +415,7 @@ public class BitmapFactory {
* can check, inbetween the bounds decode and the image decode, to see
* if the operation is canceled.
*/
+ @Deprecated
public boolean mCancel;
/**
@@ -426,6 +430,7 @@ public class BitmapFactory {
* or if inJustDecodeBounds is true, will set outWidth/outHeight
* to -1
*/
+ @Deprecated
public void requestCancelDecode() {
mCancel = true;
}
@@ -515,8 +520,9 @@ public class BitmapFactory {
* is not {@link ColorSpace.Model#RGB RGB}, or if the specified color space's transfer
* function is not an {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}
*/
- public static Bitmap decodeResourceStream(Resources res, TypedValue value,
- InputStream is, Rect pad, Options opts) {
+ @Nullable
+ public static Bitmap decodeResourceStream(@Nullable Resources res, @Nullable TypedValue value,
+ @Nullable InputStream is, @Nullable Rect pad, @Nullable Options opts) {
validate(opts);
if (opts == null) {
opts = new Options();
@@ -704,7 +710,9 @@ public class BitmapFactory {
* <code>is.mark(1024)</code> would be called. As of
* {@link android.os.Build.VERSION_CODES#KITKAT}, this is no longer the case.</p>
*/
- public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
+ @Nullable
+ public static Bitmap decodeStream(@Nullable InputStream is, @Nullable Rect outPadding,
+ @Nullable Options opts) {
// we don't throw in this case, thus allowing the caller to only check
// the cache, and not force the image to be decoded.
if (is == null) {
@@ -739,7 +747,8 @@ public class BitmapFactory {
* Private helper function for decoding an InputStream natively. Buffers the input enough to
* do a rewind as needed, and supplies temporary storage if necessary. is MUST NOT be null.
*/
- private static Bitmap decodeStreamInternal(InputStream is, Rect outPadding, Options opts) {
+ private static Bitmap decodeStreamInternal(@NonNull InputStream is,
+ @Nullable Rect outPadding, @Nullable Options opts) {
// ASSERT(is != null);
byte [] tempStorage = null;
if (opts != null) tempStorage = opts.inTempStorage;
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index 0301f2e6b555..d925441e3657 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -200,11 +200,6 @@ public class Canvas extends BaseCanvas {
}
/** @hide */
- public void setHighContrastText(boolean highContrastText) {
- nSetHighContrastText(mNativeCanvasWrapper, highContrastText);
- }
-
- /** @hide */
public void insertReorderBarrier() {}
/** @hide */
@@ -1224,10 +1219,14 @@ public class Canvas extends BaseCanvas {
nFreeTextLayoutCaches();
}
+ /** @hide */
+ public static void setCompatibilityVersion(int apiLevel) { nSetCompatibilityVersion(apiLevel); }
+
private static native void nFreeCaches();
private static native void nFreeTextLayoutCaches();
private static native long nInitRaster(Bitmap bitmap);
private static native long nGetNativeFinalizer();
+ private static native void nSetCompatibilityVersion(int apiLevel);
// ---------------- @FastNative -------------------
@@ -1242,8 +1241,6 @@ public class Canvas extends BaseCanvas {
@CriticalNative
private static native boolean nIsOpaque(long canvasHandle);
@CriticalNative
- private static native void nSetHighContrastText(long renderer, boolean highContrastText);
- @CriticalNative
private static native int nGetWidth(long canvasHandle);
@CriticalNative
private static native int nGetHeight(long canvasHandle);
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
index d9a77e752823..e7cfcfdf7760 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -16,10 +16,12 @@
package android.graphics;
+import android.annotation.Nullable;
import android.content.res.AssetManager;
import android.graphics.fonts.FontVariationAxis;
-import android.text.FontConfig;
+import android.text.TextUtils;
import android.util.Log;
+
import dalvik.annotation.optimization.CriticalNative;
import java.io.FileInputStream;
@@ -48,8 +50,16 @@ public class FontFamily {
mBuilderPtr = nInitBuilder(null, 0);
}
- public FontFamily(String lang, int variant) {
- mBuilderPtr = nInitBuilder(lang, variant);
+ public FontFamily(@Nullable String[] langs, int variant) {
+ final String langsString;
+ if (langs == null || langs.length == 0) {
+ langsString = null;
+ } else if (langs.length == 1) {
+ langsString = langs[0];
+ } else {
+ langsString = TextUtils.join(",", langs);
+ }
+ mBuilderPtr = nInitBuilder(langsString, variant);
}
/**
@@ -150,39 +160,17 @@ public class FontFamily {
isItalic);
}
- /**
- * Allow creating unsupported FontFamily.
- *
- * For compatibility reasons, we still need to create a FontFamily object even if Minikin failed
- * to find any usable 'cmap' table for some reasons, e.g. broken 'cmap' table, no 'cmap' table
- * encoded with Unicode code points, etc. Without calling this method, the freeze() method will
- * return null if Minikin fails to find any usable 'cmap' table. By calling this method, the
- * freeze() won't fail and will create an empty FontFamily. This empty FontFamily is placed at
- * the top of the fallback chain but is never used. if we don't create this empty FontFamily
- * and put it at top, bad things (performance regressions, unexpected glyph selection) will
- * happen.
- */
- public void allowUnsupportedFont() {
- if (mBuilderPtr == 0) {
- throw new IllegalStateException("Unable to allow unsupported font.");
- }
- nAllowUnsupportedFont(mBuilderPtr);
- }
-
// TODO: Remove once internal user stop using private API.
private static boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex) {
return nAddFont(builderPtr, font, ttcIndex, -1, -1);
}
- private static native long nInitBuilder(String lang, int variant);
+ private static native long nInitBuilder(String langs, int variant);
@CriticalNative
private static native long nCreateFamily(long mBuilderPtr);
@CriticalNative
- private static native void nAllowUnsupportedFont(long builderPtr);
-
- @CriticalNative
private static native void nAbort(long mBuilderPtr);
@CriticalNative
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 7c07a302dfe9..431d0e0eb7b4 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -16,16 +16,13 @@
package android.graphics;
-import android.text.FontConfig;
import android.graphics.fonts.FontVariationAxis;
+import android.text.FontConfig;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import android.annotation.Nullable;
-import com.android.internal.annotations.VisibleForTesting;
-
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
@@ -74,13 +71,14 @@ public class FontListParser {
private static FontConfig.Family readFamily(XmlPullParser parser)
throws XmlPullParserException, IOException {
- String name = parser.getAttributeValue(null, "name");
- String lang = parser.getAttributeValue(null, "lang");
- String variant = parser.getAttributeValue(null, "variant");
- List<FontConfig.Font> fonts = new ArrayList<FontConfig.Font>();
+ final String name = parser.getAttributeValue(null, "name");
+ final String lang = parser.getAttributeValue(null, "lang");
+ final String[] langs = lang == null ? null : lang.split("\\s+");
+ final String variant = parser.getAttributeValue(null, "variant");
+ final List<FontConfig.Font> fonts = new ArrayList<FontConfig.Font>();
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
- String tag = parser.getName();
+ final String tag = parser.getName();
if (tag.equals("font")) {
fonts.add(readFont(parser));
} else {
@@ -95,7 +93,7 @@ public class FontListParser {
intVariant = FontConfig.Family.VARIANT_ELEGANT;
}
}
- return new FontConfig.Family(name, fonts.toArray(new FontConfig.Font[fonts.size()]), lang,
+ return new FontConfig.Family(name, fonts.toArray(new FontConfig.Font[fonts.size()]), langs,
intVariant);
}
@@ -111,6 +109,7 @@ public class FontListParser {
String weightStr = parser.getAttributeValue(null, "weight");
int weight = weightStr == null ? 400 : Integer.parseInt(weightStr);
boolean isItalic = "italic".equals(parser.getAttributeValue(null, "style"));
+ String fallbackFor = parser.getAttributeValue(null, "fallbackFor");
StringBuilder filename = new StringBuilder();
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() == XmlPullParser.TEXT) {
@@ -126,7 +125,7 @@ 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);
+ axes.toArray(new FontVariationAxis[axes.size()]), weight, isItalic, fallbackFor);
}
private static FontVariationAxis readAxis(XmlPullParser parser)
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
new file mode 100644
index 000000000000..ee7abc5bd254
--- /dev/null
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -0,0 +1,1104 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import static android.system.OsConstants.SEEK_CUR;
+import static android.system.OsConstants.SEEK_SET;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RawRes;
+import android.content.ContentResolver;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.graphics.drawable.AnimatedImageDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.NinePatchDrawable;
+import android.net.Uri;
+import android.util.Size;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+
+import libcore.io.IoUtils;
+import dalvik.system.CloseGuard;
+
+import java.nio.ByteBuffer;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ArrayIndexOutOfBoundsException;
+import java.lang.AutoCloseable;
+import java.lang.NullPointerException;
+import java.lang.RuntimeException;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Class for decoding images as {@link Bitmap}s or {@link Drawable}s.
+ */
+public final class ImageDecoder implements AutoCloseable {
+ /**
+ * Source of the encoded image data.
+ */
+ public static abstract class Source {
+ private Source() {}
+
+ /* @hide */
+ @Nullable
+ Resources getResources() { return null; }
+
+ /* @hide */
+ int getDensity() { return Bitmap.DENSITY_NONE; }
+
+ /* @hide */
+ final int computeDstDensity() {
+ Resources res = getResources();
+ if (res == null) {
+ return Bitmap.getDefaultDensity();
+ }
+
+ return res.getDisplayMetrics().densityDpi;
+ }
+
+ /* @hide */
+ @NonNull
+ abstract ImageDecoder createImageDecoder() throws IOException;
+ };
+
+ private static class ByteArraySource extends Source {
+ ByteArraySource(@NonNull byte[] data, int offset, int length) {
+ mData = data;
+ mOffset = offset;
+ mLength = length;
+ };
+ private final byte[] mData;
+ private final int mOffset;
+ private final int mLength;
+
+ @Override
+ public ImageDecoder createImageDecoder() throws IOException {
+ return nCreate(mData, mOffset, mLength);
+ }
+ }
+
+ private static class ByteBufferSource extends Source {
+ ByteBufferSource(@NonNull ByteBuffer buffer) {
+ mBuffer = buffer;
+ }
+ private final ByteBuffer mBuffer;
+
+ @Override
+ public ImageDecoder createImageDecoder() throws IOException {
+ if (!mBuffer.isDirect() && mBuffer.hasArray()) {
+ int offset = mBuffer.arrayOffset() + mBuffer.position();
+ int length = mBuffer.limit() - mBuffer.position();
+ return nCreate(mBuffer.array(), offset, length);
+ }
+ return nCreate(mBuffer, mBuffer.position(), mBuffer.limit());
+ }
+ }
+
+ private static class ContentResolverSource extends Source {
+ ContentResolverSource(@NonNull ContentResolver resolver, @NonNull Uri uri,
+ @Nullable Resources res) {
+ mResolver = resolver;
+ mUri = uri;
+ mResources = res;
+ }
+
+ private final ContentResolver mResolver;
+ private final Uri mUri;
+ private final Resources mResources;
+
+ @Nullable
+ Resources getResources() { return mResources; }
+
+ @Override
+ public ImageDecoder createImageDecoder() throws IOException {
+ AssetFileDescriptor assetFd = null;
+ try {
+ if (mUri.getScheme() == ContentResolver.SCHEME_CONTENT) {
+ assetFd = mResolver.openTypedAssetFileDescriptor(mUri,
+ "image/*", null);
+ } else {
+ assetFd = mResolver.openAssetFileDescriptor(mUri, "r");
+ }
+ } catch (FileNotFoundException e) {
+ // Some images cannot be opened as AssetFileDescriptors (e.g.
+ // bmp, ico). Open them as InputStreams.
+ InputStream is = mResolver.openInputStream(mUri);
+ if (is == null) {
+ throw new FileNotFoundException(mUri.toString());
+ }
+
+ return createFromStream(is, true);
+ }
+
+ final FileDescriptor fd = assetFd.getFileDescriptor();
+ final long offset = assetFd.getStartOffset();
+
+ ImageDecoder decoder = null;
+ try {
+ try {
+ Os.lseek(fd, offset, SEEK_SET);
+ decoder = nCreate(fd);
+ } catch (ErrnoException e) {
+ decoder = createFromStream(new FileInputStream(fd), true);
+ }
+ } finally {
+ if (decoder == null) {
+ IoUtils.closeQuietly(assetFd);
+ } else {
+ decoder.mAssetFd = assetFd;
+ }
+ }
+ return decoder;
+ }
+ }
+
+ @NonNull
+ private static ImageDecoder createFromFile(@NonNull File file) throws IOException {
+ FileInputStream stream = new FileInputStream(file);
+ FileDescriptor fd = stream.getFD();
+ try {
+ Os.lseek(fd, 0, SEEK_CUR);
+ } catch (ErrnoException e) {
+ return createFromStream(stream, true);
+ }
+
+ ImageDecoder decoder = null;
+ try {
+ decoder = nCreate(fd);
+ } finally {
+ if (decoder == null) {
+ IoUtils.closeQuietly(stream);
+ } else {
+ decoder.mInputStream = stream;
+ decoder.mOwnsInputStream = true;
+ }
+ }
+ return decoder;
+ }
+
+ @NonNull
+ private static ImageDecoder createFromStream(@NonNull InputStream is,
+ boolean closeInputStream) throws IOException {
+ // Arbitrary size matches BitmapFactory.
+ byte[] storage = new byte[16 * 1024];
+ ImageDecoder decoder = null;
+ try {
+ decoder = nCreate(is, storage);
+ } finally {
+ if (decoder == null) {
+ if (closeInputStream) {
+ IoUtils.closeQuietly(is);
+ }
+ } else {
+ decoder.mInputStream = is;
+ decoder.mOwnsInputStream = closeInputStream;
+ decoder.mTempStorage = storage;
+ }
+ }
+
+ return decoder;
+ }
+
+ /**
+ * For backwards compatibility, this does *not* close the InputStream.
+ */
+ private static class InputStreamSource extends Source {
+ InputStreamSource(Resources res, InputStream is, int inputDensity) {
+ if (is == null) {
+ throw new IllegalArgumentException("The InputStream cannot be null");
+ }
+ mResources = res;
+ mInputStream = is;
+ mInputDensity = res != null ? inputDensity : Bitmap.DENSITY_NONE;
+ }
+
+ final Resources mResources;
+ InputStream mInputStream;
+ final int mInputDensity;
+
+ @Override
+ public Resources getResources() { return mResources; }
+
+ @Override
+ public int getDensity() { return mInputDensity; }
+
+ @Override
+ public ImageDecoder createImageDecoder() throws IOException {
+
+ synchronized (this) {
+ if (mInputStream == null) {
+ throw new IOException("Cannot reuse InputStreamSource");
+ }
+ InputStream is = mInputStream;
+ mInputStream = null;
+ return createFromStream(is, false);
+ }
+ }
+ }
+
+ private static class ResourceSource extends Source {
+ ResourceSource(@NonNull Resources res, int resId) {
+ mResources = res;
+ mResId = resId;
+ mResDensity = Bitmap.DENSITY_NONE;
+ }
+
+ final Resources mResources;
+ final int mResId;
+ int mResDensity;
+
+ @Override
+ public Resources getResources() { return mResources; }
+
+ @Override
+ public int getDensity() { return mResDensity; }
+
+ @Override
+ public ImageDecoder createImageDecoder() throws IOException {
+ // This is just used in order to access the underlying Asset and
+ // keep it alive. FIXME: Can we skip creating this object?
+ InputStream is = null;
+ ImageDecoder decoder = null;
+ TypedValue value = new TypedValue();
+ try {
+ is = mResources.openRawResource(mResId, value);
+
+ if (value.density == TypedValue.DENSITY_DEFAULT) {
+ mResDensity = DisplayMetrics.DENSITY_DEFAULT;
+ } else if (value.density != TypedValue.DENSITY_NONE) {
+ mResDensity = value.density;
+ }
+
+ if (!(is instanceof AssetManager.AssetInputStream)) {
+ // This should never happen.
+ throw new RuntimeException("Resource is not an asset?");
+ }
+ long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
+ decoder = nCreate(asset);
+ } finally {
+ if (decoder == null) {
+ IoUtils.closeQuietly(is);
+ } else {
+ decoder.mInputStream = is;
+ decoder.mOwnsInputStream = true;
+ }
+ }
+ return decoder;
+ }
+ }
+
+ private static class FileSource extends Source {
+ FileSource(@NonNull File file) {
+ mFile = file;
+ }
+
+ private final File mFile;
+
+ @Override
+ public ImageDecoder createImageDecoder() throws IOException {
+ return createFromFile(mFile);
+ }
+ }
+
+ /**
+ * Contains information about the encoded image.
+ */
+ public static class ImageInfo {
+ private final Size mSize;
+ private ImageDecoder mDecoder;
+
+ private ImageInfo(@NonNull ImageDecoder decoder) {
+ mSize = new Size(decoder.mWidth, decoder.mHeight);
+ mDecoder = decoder;
+ }
+
+ /**
+ * Size of the image, without scaling or cropping.
+ */
+ @NonNull
+ public Size getSize() {
+ return mSize;
+ }
+
+ /**
+ * The mimeType of the image.
+ */
+ @NonNull
+ public String getMimeType() {
+ return mDecoder.getMimeType();
+ }
+
+ /**
+ * Whether the image is animated.
+ *
+ * <p>Calling {@link #decodeDrawable} will return an
+ * {@link AnimatedImageDrawable}.</p>
+ */
+ public boolean isAnimated() {
+ return mDecoder.mAnimated;
+ }
+ };
+
+ /**
+ * Thrown if the provided data is incomplete.
+ */
+ public static class IncompleteException extends IOException {};
+
+ /**
+ * Optional listener supplied to {@link #decodeDrawable} or
+ * {@link #decodeBitmap}.
+ */
+ public static interface OnHeaderDecodedListener {
+ /**
+ * Called when the header is decoded and the size is known.
+ *
+ * @param decoder allows changing the default settings of the decode.
+ * @param info Information about the encoded image.
+ * @param source that created the decoder.
+ */
+ public void onHeaderDecoded(@NonNull ImageDecoder decoder,
+ @NonNull ImageInfo info, @NonNull Source source);
+
+ };
+
+ /**
+ * An Exception was thrown reading the {@link Source}.
+ */
+ public static final int ERROR_SOURCE_EXCEPTION = 1;
+
+ /**
+ * The encoded data was incomplete.
+ */
+ public static final int ERROR_SOURCE_INCOMPLETE = 2;
+
+ /**
+ * The encoded data contained an error.
+ */
+ public static final int ERROR_SOURCE_ERROR = 3;
+
+ @Retention(SOURCE)
+ @IntDef({ ERROR_SOURCE_EXCEPTION, ERROR_SOURCE_INCOMPLETE, ERROR_SOURCE_ERROR })
+ public @interface Error {};
+
+ /**
+ * Optional listener supplied to the ImageDecoder.
+ *
+ * Without this listener, errors will throw {@link java.io.IOException}.
+ */
+ public static interface OnPartialImageListener {
+ /**
+ * Called when there is only a partial image to display.
+ *
+ * If decoding is interrupted after having decoded a partial image,
+ * this listener lets the client know that and allows them to
+ * optionally finish the rest of the decode/creation process to create
+ * a partial {@link Drawable}/{@link Bitmap}.
+ *
+ * @param error indicating what interrupted the decode.
+ * @param source that had the error.
+ * @return True to create and return a {@link Drawable}/{@link Bitmap}
+ * with partial data. False (which is the default) to abort the
+ * decode and throw {@link java.io.IOException}.
+ */
+ public boolean onPartialImage(@Error int error, @NonNull Source source);
+ };
+
+ // Fields
+ private long mNativePtr;
+ private final int mWidth;
+ private final int mHeight;
+ private final boolean mAnimated;
+
+ private int mDesiredWidth;
+ private int mDesiredHeight;
+ private int mAllocator = ALLOCATOR_DEFAULT;
+ private boolean mRequireUnpremultiplied = false;
+ private boolean mMutable = false;
+ private boolean mPreferRamOverQuality = false;
+ private boolean mAsAlphaMask = false;
+ private Rect mCropRect;
+ private Source mSource;
+
+ private PostProcessor mPostProcessor;
+ private OnPartialImageListener mOnPartialImageListener;
+
+ // Objects for interacting with the input.
+ private InputStream mInputStream;
+ private boolean mOwnsInputStream;
+ private byte[] mTempStorage;
+ private AssetFileDescriptor mAssetFd;
+ private final AtomicBoolean mClosed = new AtomicBoolean();
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ /**
+ * Private constructor called by JNI. {@link #close} must be
+ * called after decoding to delete native resources.
+ */
+ @SuppressWarnings("unused")
+ private ImageDecoder(long nativePtr, int width, int height,
+ boolean animated) {
+ mNativePtr = nativePtr;
+ mWidth = width;
+ mHeight = height;
+ mDesiredWidth = width;
+ mDesiredHeight = height;
+ mAnimated = animated;
+ mCloseGuard.open("close");
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
+ // Avoid closing these in finalizer.
+ mInputStream = null;
+ mAssetFd = null;
+
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Create a new {@link Source} from an asset.
+ * @hide
+ *
+ * @param res the {@link Resources} object containing the image data.
+ * @param resId resource ID of the image data.
+ * // FIXME: Can be an @DrawableRes?
+ * @return a new Source object, which can be passed to
+ * {@link #decodeDrawable} or {@link #decodeBitmap}.
+ */
+ @NonNull
+ public static Source createSource(@NonNull Resources res, @RawRes int resId)
+ {
+ return new ResourceSource(res, resId);
+ }
+
+ /**
+ * Create a new {@link Source} from a {@link android.net.Uri}.
+ *
+ * @param cr to retrieve from.
+ * @param uri of the image file.
+ * @return a new Source object, which can be passed to
+ * {@link #decodeDrawable} or {@link #decodeBitmap}.
+ */
+ @NonNull
+ public static Source createSource(@NonNull ContentResolver cr,
+ @NonNull Uri uri) {
+ return new ContentResolverSource(cr, uri, null);
+ }
+
+ /**
+ * Provide Resources for density scaling.
+ *
+ * @hide
+ */
+ @NonNull
+ public static Source createSource(@NonNull ContentResolver cr,
+ @NonNull Uri uri, @Nullable Resources res) {
+ return new ContentResolverSource(cr, uri, res);
+ }
+
+ /**
+ * Create a new {@link Source} from a byte array.
+ *
+ * @param data byte array of compressed image data.
+ * @param offset offset into data for where the decoder should begin
+ * parsing.
+ * @param length number of bytes, beginning at offset, to parse.
+ * @throws NullPointerException if data is null.
+ * @throws ArrayIndexOutOfBoundsException if offset and length are
+ * not within data.
+ * @hide
+ */
+ @NonNull
+ public static Source createSource(@NonNull byte[] data, int offset,
+ int length) throws ArrayIndexOutOfBoundsException {
+ if (data == null) {
+ throw new NullPointerException("null byte[] in createSource!");
+ }
+ if (offset < 0 || length < 0 || offset >= data.length ||
+ offset + length > data.length) {
+ throw new ArrayIndexOutOfBoundsException(
+ "invalid offset/length!");
+ }
+ return new ByteArraySource(data, offset, length);
+ }
+
+ /**
+ * See {@link #createSource(byte[], int, int).
+ * @hide
+ */
+ @NonNull
+ public static Source createSource(@NonNull byte[] data) {
+ return createSource(data, 0, data.length);
+ }
+
+ /**
+ * Create a new {@link Source} from a {@link java.nio.ByteBuffer}.
+ *
+ * <p>The returned {@link Source} effectively takes ownership of the
+ * {@link java.nio.ByteBuffer}; i.e. no other code should modify it after
+ * this call.</p>
+ *
+ * Decoding will start from {@link java.nio.ByteBuffer#position()}. The
+ * position after decoding is undefined.
+ */
+ @NonNull
+ public static Source createSource(@NonNull ByteBuffer buffer) {
+ return new ByteBufferSource(buffer);
+ }
+
+ /**
+ * Internal API used to generate bitmaps for use by Drawables (i.e. BitmapDrawable)
+ * @hide
+ */
+ public static Source createSource(Resources res, InputStream is) {
+ return new InputStreamSource(res, is, Bitmap.getDefaultDensity());
+ }
+
+ /**
+ * Internal API used to generate bitmaps for use by Drawables (i.e. BitmapDrawable)
+ * @hide
+ */
+ public static Source createSource(Resources res, InputStream is, int density) {
+ return new InputStreamSource(res, is, density);
+ }
+
+ /**
+ * Create a new {@link Source} from a {@link java.io.File}.
+ */
+ @NonNull
+ public static Source createSource(@NonNull File file) {
+ return new FileSource(file);
+ }
+
+ /**
+ * Return the width and height of a given sample size.
+ *
+ * <p>This takes an input that functions like
+ * {@link BitmapFactory.Options#inSampleSize}. It returns a width and
+ * height that can be acheived by sampling the encoded image. Other widths
+ * and heights may be supported, but will require an additional (internal)
+ * scaling step. Such internal scaling is *not* supported with
+ * {@link #setRequireUnpremultiplied} set to {@code true}.</p>
+ *
+ * @param sampleSize Sampling rate of the encoded image.
+ * @return {@link android.util.Size} of the width and height after
+ * sampling.
+ */
+ @NonNull
+ public Size getSampledSize(int sampleSize) {
+ if (sampleSize <= 0) {
+ throw new IllegalArgumentException("sampleSize must be positive! "
+ + "provided " + sampleSize);
+ }
+ if (mNativePtr == 0) {
+ throw new IllegalStateException("ImageDecoder is closed!");
+ }
+
+ return nGetSampledSize(mNativePtr, sampleSize);
+ }
+
+ // Modifiers
+ /**
+ * Resize the output to have the following size.
+ *
+ * @param width must be greater than 0.
+ * @param height must be greater than 0.
+ */
+ public void setResize(int width, int height) {
+ if (width <= 0 || height <= 0) {
+ throw new IllegalArgumentException("Dimensions must be positive! "
+ + "provided (" + width + ", " + height + ")");
+ }
+
+ mDesiredWidth = width;
+ mDesiredHeight = height;
+ }
+
+ /**
+ * Resize based on a sample size.
+ *
+ * <p>This has the same effect as passing the result of
+ * {@link #getSampledSize} to {@link #setResize(int, int)}.</p>
+ *
+ * @param sampleSize Sampling rate of the encoded image.
+ */
+ public void setResize(int sampleSize) {
+ Size size = this.getSampledSize(sampleSize);
+ this.setResize(size.getWidth(), size.getHeight());
+ }
+
+ private boolean requestedResize() {
+ return mWidth != mDesiredWidth || mHeight != mDesiredHeight;
+ }
+
+ // These need to stay in sync with ImageDecoder.cpp's Allocator enum.
+ /**
+ * Use the default allocation for the pixel memory.
+ *
+ * Will typically result in a {@link Bitmap.Config#HARDWARE}
+ * allocation, but may be software for small images. In addition, this will
+ * switch to software when HARDWARE is incompatible, e.g.
+ * {@link #setMutable}, {@link #setAsAlphaMask}.
+ */
+ public static final int ALLOCATOR_DEFAULT = 0;
+
+ /**
+ * Use a software allocation for the pixel memory.
+ *
+ * Useful for drawing to a software {@link Canvas} or for
+ * accessing the pixels on the final output.
+ */
+ public static final int ALLOCATOR_SOFTWARE = 1;
+
+ /**
+ * Use shared memory for the pixel memory.
+ *
+ * Useful for sharing across processes.
+ */
+ public static final int ALLOCATOR_SHARED_MEMORY = 2;
+
+ /**
+ * Require a {@link Bitmap.Config#HARDWARE} {@link Bitmap}.
+ *
+ * When this is combined with incompatible options, like
+ * {@link #setMutable} or {@link #setAsAlphaMask}, {@link #decodeDrawable}
+ * / {@link #decodeBitmap} will throw an
+ * {@link java.lang.IllegalStateException}.
+ */
+ public static final int ALLOCATOR_HARDWARE = 3;
+
+ /** @hide **/
+ @Retention(SOURCE)
+ @IntDef({ ALLOCATOR_DEFAULT, ALLOCATOR_SOFTWARE, ALLOCATOR_SHARED_MEMORY,
+ ALLOCATOR_HARDWARE })
+ public @interface Allocator {};
+
+ /**
+ * Choose the backing for the pixel memory.
+ *
+ * This is ignored for animated drawables.
+ *
+ * @param allocator Type of allocator to use.
+ */
+ public void setAllocator(@Allocator int allocator) {
+ if (allocator < ALLOCATOR_DEFAULT || allocator > ALLOCATOR_HARDWARE) {
+ throw new IllegalArgumentException("invalid allocator " + allocator);
+ }
+ mAllocator = allocator;
+ }
+
+ /**
+ * Specify whether the {@link Bitmap} should have unpremultiplied pixels.
+ *
+ * By default, ImageDecoder will create a {@link Bitmap} with
+ * premultiplied pixels, which is required for drawing with the
+ * {@link android.view.View} system (i.e. to a {@link Canvas}). Calling
+ * this method with a value of {@code true} will result in
+ * {@link #decodeBitmap} returning a {@link Bitmap} with unpremultiplied
+ * pixels. See {@link Bitmap#isPremultiplied}. This is incompatible with
+ * {@link #decodeDrawable}; attempting to decode an unpremultiplied
+ * {@link Drawable} will throw an {@link java.lang.IllegalStateException}.
+ */
+ public void setRequireUnpremultiplied(boolean requireUnpremultiplied) {
+ mRequireUnpremultiplied = requireUnpremultiplied;
+ }
+
+ /**
+ * Modify the image after decoding and scaling.
+ *
+ * <p>This allows adding effects prior to returning a {@link Drawable} or
+ * {@link Bitmap}. For a {@code Drawable} or an immutable {@code Bitmap},
+ * this is the only way to process the image after decoding.</p>
+ *
+ * <p>If set on a nine-patch image, the nine-patch data is ignored.</p>
+ *
+ * <p>For an animated image, the drawing commands drawn on the
+ * {@link Canvas} will be recorded immediately and then applied to each
+ * frame.</p>
+ */
+ public void setPostProcessor(@Nullable PostProcessor p) {
+ mPostProcessor = p;
+ }
+
+ /**
+ * Set (replace) the {@link OnPartialImageListener} on this object.
+ *
+ * Will be called if there is an error in the input. Without one, a
+ * partial {@link Bitmap} will be created.
+ */
+ public void setOnPartialImageListener(@Nullable OnPartialImageListener l) {
+ mOnPartialImageListener = l;
+ }
+
+ /**
+ * Crop the output to {@code subset} of the (possibly) scaled image.
+ *
+ * <p>{@code subset} must be contained within the size set by
+ * {@link #setResize} or the bounds of the image if setResize was not
+ * called. Otherwise an {@link IllegalStateException} will be thrown by
+ * {@link #decodeDrawable}/{@link #decodeBitmap}.</p>
+ *
+ * <p>NOT intended as a replacement for
+ * {@link BitmapRegionDecoder#decodeRegion}. This supports all formats,
+ * but merely crops the output.</p>
+ */
+ public void setCrop(@Nullable Rect subset) {
+ mCropRect = subset;
+ }
+
+ /**
+ * Specify whether the {@link Bitmap} should be mutable.
+ *
+ * <p>By default, a {@link Bitmap} created will be immutable, but that can
+ * be changed with this call.</p>
+ *
+ * <p>Mutable Bitmaps are incompatible with {@link #ALLOCATOR_HARDWARE},
+ * because {@link Bitmap.Config#HARDWARE} Bitmaps cannot be mutable.
+ * Attempting to combine them will throw an
+ * {@link java.lang.IllegalStateException}.</p>
+ *
+ * <p>Mutable Bitmaps are also incompatible with {@link #decodeDrawable},
+ * which would require retrieving the Bitmap from the returned Drawable in
+ * order to modify. Attempting to decode a mutable {@link Drawable} will
+ * throw an {@link java.lang.IllegalStateException}.</p>
+ */
+ public void setMutable(boolean mutable) {
+ mMutable = mutable;
+ }
+
+ /**
+ * Specify whether to potentially save RAM at the expense of quality.
+ *
+ * Setting this to {@code true} may result in a {@link Bitmap} with a
+ * denser {@link Bitmap.Config}, depending on the image. For example, for
+ * an opaque {@link Bitmap}, this may result in a {@link Bitmap.Config}
+ * with no alpha information.
+ */
+ public void setPreferRamOverQuality(boolean preferRamOverQuality) {
+ mPreferRamOverQuality = preferRamOverQuality;
+ }
+
+ /**
+ * Specify whether to potentially treat the output as an alpha mask.
+ *
+ * <p>If this is set to {@code true} and the image is encoded in a format
+ * with only one channel, treat that channel as alpha. Otherwise this call has
+ * no effect.</p>
+ *
+ * <p>setAsAlphaMask is incompatible with {@link #ALLOCATOR_HARDWARE}. Trying to
+ * combine them will result in {@link #decodeDrawable}/
+ * {@link #decodeBitmap} throwing an
+ * {@link java.lang.IllegalStateException}.</p>
+ */
+ public void setAsAlphaMask(boolean asAlphaMask) {
+ mAsAlphaMask = asAlphaMask;
+ }
+
+ @Override
+ public void close() {
+ mCloseGuard.close();
+ if (!mClosed.compareAndSet(false, true)) {
+ return;
+ }
+ nClose(mNativePtr);
+ mNativePtr = 0;
+
+ if (mOwnsInputStream) {
+ IoUtils.closeQuietly(mInputStream);
+ }
+ IoUtils.closeQuietly(mAssetFd);
+
+ mInputStream = null;
+ mAssetFd = null;
+ mTempStorage = null;
+ }
+
+ private void checkState() {
+ if (mNativePtr == 0) {
+ throw new IllegalStateException("Cannot use closed ImageDecoder!");
+ }
+
+ checkSubset(mDesiredWidth, mDesiredHeight, mCropRect);
+
+ if (mAllocator == ALLOCATOR_HARDWARE) {
+ if (mMutable) {
+ throw new IllegalStateException("Cannot make mutable HARDWARE Bitmap!");
+ }
+ if (mAsAlphaMask) {
+ throw new IllegalStateException("Cannot make HARDWARE Alpha mask Bitmap!");
+ }
+ }
+
+ if (mPostProcessor != null && mRequireUnpremultiplied) {
+ throw new IllegalStateException("Cannot draw to unpremultiplied pixels!");
+ }
+ }
+
+ private static void checkSubset(int width, int height, Rect r) {
+ if (r == null) {
+ return;
+ }
+ if (r.left < 0 || r.top < 0 || r.right > width || r.bottom > height) {
+ throw new IllegalStateException("Subset " + r + " not contained by "
+ + "scaled image bounds: (" + width + " x " + height + ")");
+ }
+ }
+
+ @NonNull
+ private Bitmap decodeBitmap() throws IOException {
+ checkState();
+ // nDecodeBitmap calls onPartialImage only if mOnPartialImageListener
+ // exists
+ ImageDecoder partialImagePtr = mOnPartialImageListener == null ? null : this;
+ // nDecodeBitmap calls postProcessAndRelease only if mPostProcessor
+ // exists.
+ ImageDecoder postProcessPtr = mPostProcessor == null ? null : this;
+ return nDecodeBitmap(mNativePtr, partialImagePtr,
+ postProcessPtr, mDesiredWidth, mDesiredHeight, mCropRect,
+ mMutable, mAllocator, mRequireUnpremultiplied,
+ mPreferRamOverQuality, mAsAlphaMask);
+
+ }
+
+ private void callHeaderDecoded(@Nullable OnHeaderDecodedListener listener,
+ @NonNull Source src) {
+ if (listener != null) {
+ ImageInfo info = new ImageInfo(this);
+ try {
+ listener.onHeaderDecoded(this, info, src);
+ } finally {
+ info.mDecoder = null;
+ }
+ }
+ }
+
+ /**
+ * Create a {@link Drawable} from a {@code Source}.
+ *
+ * @param src representing the encoded image.
+ * @param listener for learning the {@link ImageInfo} and changing any
+ * default settings on the {@code ImageDecoder}. If not {@code null},
+ * this will be called on the same thread as {@code decodeDrawable}
+ * before that method returns.
+ * @return Drawable for displaying the image.
+ * @throws IOException if {@code src} is not found, is an unsupported
+ * format, or cannot be decoded for any reason.
+ */
+ @NonNull
+ public static Drawable decodeDrawable(@NonNull Source src,
+ @Nullable OnHeaderDecodedListener listener) throws IOException {
+ try (ImageDecoder decoder = src.createImageDecoder()) {
+ decoder.mSource = src;
+ decoder.callHeaderDecoded(listener, src);
+
+ if (decoder.mRequireUnpremultiplied) {
+ // Though this could be supported (ignored) for opaque images,
+ // it seems better to always report this error.
+ throw new IllegalStateException("Cannot decode a Drawable " +
+ "with unpremultiplied pixels!");
+ }
+
+ if (decoder.mMutable) {
+ throw new IllegalStateException("Cannot decode a mutable " +
+ "Drawable!");
+ }
+
+ // this call potentially manipulates the decoder so it must be performed prior to
+ // decoding the bitmap and after decode set the density on the resulting bitmap
+ final int srcDensity = computeDensity(src, decoder);
+ if (decoder.mAnimated) {
+ // AnimatedImageDrawable calls postProcessAndRelease only if
+ // mPostProcessor exists.
+ ImageDecoder postProcessPtr = decoder.mPostProcessor == null ?
+ null : decoder;
+ Drawable d = new AnimatedImageDrawable(decoder.mNativePtr,
+ postProcessPtr, decoder.mDesiredWidth,
+ decoder.mDesiredHeight, srcDensity,
+ src.computeDstDensity(), decoder.mCropRect,
+ decoder.mInputStream, decoder.mAssetFd);
+ // d has taken ownership of these objects.
+ decoder.mInputStream = null;
+ decoder.mAssetFd = null;
+ return d;
+ }
+
+ Bitmap bm = decoder.decodeBitmap();
+ bm.setDensity(srcDensity);
+
+ Resources res = src.getResources();
+ byte[] np = bm.getNinePatchChunk();
+ if (np != null && NinePatch.isNinePatchChunk(np)) {
+ Rect opticalInsets = new Rect();
+ bm.getOpticalInsets(opticalInsets);
+ Rect padding = new Rect();
+ nGetPadding(decoder.mNativePtr, padding);
+ return new NinePatchDrawable(res, bm, np, padding,
+ opticalInsets, null);
+ }
+
+ return new BitmapDrawable(res, bm);
+ }
+ }
+
+ /**
+ * See {@link #decodeDrawable(Source, OnHeaderDecodedListener)}.
+ */
+ @NonNull
+ public static Drawable decodeDrawable(@NonNull Source src)
+ throws IOException {
+ return decodeDrawable(src, null);
+ }
+
+ /**
+ * Create a {@link Bitmap} from a {@code Source}.
+ *
+ * @param src representing the encoded image.
+ * @param listener for learning the {@link ImageInfo} and changing any
+ * default settings on the {@code ImageDecoder}. If not {@code null},
+ * this will be called on the same thread as {@code decodeBitmap}
+ * before that method returns.
+ * @return Bitmap containing the image.
+ * @throws IOException if {@code src} is not found, is an unsupported
+ * format, or cannot be decoded for any reason.
+ */
+ @NonNull
+ public static Bitmap decodeBitmap(@NonNull Source src,
+ @Nullable OnHeaderDecodedListener listener) throws IOException {
+ try (ImageDecoder decoder = src.createImageDecoder()) {
+ decoder.mSource = src;
+ decoder.callHeaderDecoded(listener, src);
+
+ // this call potentially manipulates the decoder so it must be performed prior to
+ // decoding the bitmap
+ final int srcDensity = computeDensity(src, decoder);
+ Bitmap bm = decoder.decodeBitmap();
+ bm.setDensity(srcDensity);
+ return bm;
+ }
+ }
+
+ // This method may modify the decoder so it must be called prior to performing the decode
+ private static int computeDensity(@NonNull Source src, @NonNull ImageDecoder decoder) {
+ // if the caller changed the size then we treat the density as unknown
+ if (decoder.requestedResize()) {
+ return Bitmap.DENSITY_NONE;
+ }
+
+ // Special stuff for compatibility mode: if the target density is not
+ // the same as the display density, but the resource -is- the same as
+ // the display density, then don't scale it down to the target density.
+ // This allows us to load the system's density-correct resources into
+ // an application in compatibility mode, without scaling those down
+ // to the compatibility density only to have them scaled back up when
+ // drawn to the screen.
+ Resources res = src.getResources();
+ final int srcDensity = src.getDensity();
+ if (res != null && res.getDisplayMetrics().noncompatDensityDpi == srcDensity) {
+ return srcDensity;
+ }
+
+ // downscale the bitmap if the asset has a higher density than the default
+ final int dstDensity = src.computeDstDensity();
+ if (srcDensity != Bitmap.DENSITY_NONE && srcDensity > dstDensity) {
+ float scale = (float) dstDensity / srcDensity;
+ int scaledWidth = (int) (decoder.mWidth * scale + 0.5f);
+ int scaledHeight = (int) (decoder.mHeight * scale + 0.5f);
+ decoder.setResize(scaledWidth, scaledHeight);
+ return dstDensity;
+ }
+
+ return srcDensity;
+ }
+
+ @NonNull
+ private String getMimeType() {
+ return nGetMimeType(mNativePtr);
+ }
+
+ /**
+ * See {@link #decodeBitmap(Source, OnHeaderDecodedListener)}.
+ */
+ @NonNull
+ public static Bitmap decodeBitmap(@NonNull Source src) throws IOException {
+ return decodeBitmap(src, null);
+ }
+
+ /**
+ * Private method called by JNI.
+ */
+ @SuppressWarnings("unused")
+ private int postProcessAndRelease(@NonNull Canvas canvas) {
+ try {
+ return mPostProcessor.onPostProcess(canvas);
+ } finally {
+ canvas.release();
+ }
+ }
+
+ /**
+ * Private method called by JNI.
+ */
+ @SuppressWarnings("unused")
+ private boolean onPartialImage(@Error int error) {
+ return mOnPartialImageListener.onPartialImage(error, mSource);
+ }
+
+ private static native ImageDecoder nCreate(long asset) throws IOException;
+ private static native ImageDecoder nCreate(ByteBuffer buffer,
+ int position,
+ int limit) throws IOException;
+ private static native ImageDecoder nCreate(byte[] data, int offset,
+ int length) throws IOException;
+ private static native ImageDecoder nCreate(InputStream is, byte[] storage);
+ // The fd must be seekable.
+ private static native ImageDecoder nCreate(FileDescriptor fd) throws IOException;
+ @NonNull
+ private static native Bitmap nDecodeBitmap(long nativePtr,
+ @Nullable ImageDecoder partialImageListener,
+ @Nullable ImageDecoder postProcessor,
+ int width, int height,
+ @Nullable Rect cropRect, boolean mutable,
+ int allocator, boolean requireUnpremul,
+ boolean preferRamOverQuality, boolean asAlphaMask)
+ throws IOException;
+ private static native Size nGetSampledSize(long nativePtr,
+ int sampleSize);
+ private static native void nGetPadding(long nativePtr, @NonNull Rect outRect);
+ private static native void nClose(long nativePtr);
+ private static native String nGetMimeType(long nativePtr);
+}
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index aa9227c9bb08..42dac38affba 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -19,10 +19,8 @@ package android.graphics;
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Size;
-import android.graphics.FontListParser;
import android.graphics.fonts.FontVariationAxis;
import android.os.LocaleList;
-import android.text.FontConfig;
import android.text.GraphicsOperations;
import android.text.SpannableString;
import android.text.SpannedString;
@@ -33,14 +31,13 @@ import com.android.internal.annotations.GuardedBy;
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
+import libcore.util.NativeAllocationRegistry;
+
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
-import libcore.util.NativeAllocationRegistry;
-
/**
* The Paint class holds the style and color information about how to draw
* geometries, text and bitmaps.
@@ -60,11 +57,6 @@ public class Paint {
Paint.class.getClassLoader(), nGetNativeFinalizer(), NATIVE_PAINT_SIZE);
}
- /**
- * @hide
- */
- public long mNativeTypeface;
-
private ColorFilter mColorFilter;
private MaskFilter mMaskFilter;
private PathEffect mPathEffect;
@@ -93,7 +85,7 @@ public class Paint {
* A map from a string representation of the LocaleList to Minikin's language list ID.
*/
@GuardedBy("sCacheLock")
- private static final HashMap<String, Integer> sMinikinLangListIdCache = new HashMap<>();
+ private static final HashMap<String, Integer> sMinikinLocaleListIdCache = new HashMap<>();
/**
* @hide
@@ -523,7 +515,6 @@ public class Paint {
mShader = null;
mNativeShader = 0;
mTypeface = null;
- mNativeTypeface = 0;
mXfermode = null;
mHasCompatScaling = false;
@@ -566,7 +557,6 @@ public class Paint {
mShader = paint.mShader;
mNativeShader = paint.mNativeShader;
mTypeface = paint.mTypeface;
- mNativeTypeface = paint.mNativeTypeface;
mXfermode = paint.mXfermode;
mHasCompatScaling = paint.mHasCompatScaling;
@@ -822,14 +812,14 @@ public class Paint {
* @hide
*/
public float getUnderlinePosition() {
- return nGetUnderlinePosition(mNativePaint, mNativeTypeface);
+ return nGetUnderlinePosition(mNativePaint);
}
/**
* @hide
*/
public float getUnderlineThickness() {
- return nGetUnderlineThickness(mNativePaint, mNativeTypeface);
+ return nGetUnderlineThickness(mNativePaint);
}
/**
@@ -858,14 +848,14 @@ public class Paint {
* @hide
*/
public float getStrikeThruPosition() {
- return nGetStrikeThruPosition(mNativePaint, mNativeTypeface);
+ return nGetStrikeThruPosition(mNativePaint);
}
/**
* @hide
*/
public float getStrikeThruThickness() {
- return nGetStrikeThruThickness(mNativePaint, mNativeTypeface);
+ return nGetStrikeThruThickness(mNativePaint);
}
/**
@@ -1274,13 +1264,9 @@ public class Paint {
* @return typeface
*/
public Typeface setTypeface(Typeface typeface) {
- long typefaceNative = 0;
- if (typeface != null) {
- typefaceNative = typeface.native_instance;
- }
+ final long typefaceNative = typeface == null ? 0 : typeface.native_instance;
nSetTypeface(mNativePaint, typefaceNative);
mTypeface = typeface;
- mNativeTypeface = typefaceNative;
return typeface;
}
@@ -1456,16 +1442,16 @@ public class Paint {
private void syncTextLocalesWithMinikin() {
final String languageTags = mLocales.toLanguageTags();
- final Integer minikinLangListId;
+ final Integer minikinLocaleListId;
synchronized (sCacheLock) {
- minikinLangListId = sMinikinLangListIdCache.get(languageTags);
- if (minikinLangListId == null) {
+ minikinLocaleListId = sMinikinLocaleListIdCache.get(languageTags);
+ if (minikinLocaleListId == null) {
final int newID = nSetTextLocales(mNativePaint, languageTags);
- sMinikinLangListIdCache.put(languageTags, newID);
+ sMinikinLocaleListIdCache.put(languageTags, newID);
return;
}
}
- nSetTextLocalesByMinikinLangListId(mNativePaint, minikinLangListId.intValue());
+ nSetTextLocalesByMinikinLocaleListId(mNativePaint, minikinLocaleListId.intValue());
}
/**
@@ -1740,22 +1726,28 @@ public class Paint {
* Return the distance above (negative) the baseline (ascent) based on the
* current typeface and text size.
*
+ * <p>Note that this is the ascent of the main typeface, and actual text rendered may need a
+ * larger ascent because fallback fonts may get used in rendering the text.
+ *
* @return the distance above (negative) the baseline (ascent) based on the
* current typeface and text size.
*/
public float ascent() {
- return nAscent(mNativePaint, mNativeTypeface);
+ return nAscent(mNativePaint);
}
/**
* Return the distance below (positive) the baseline (descent) based on the
* current typeface and text size.
*
+ * <p>Note that this is the descent of the main typeface, and actual text rendered may need a
+ * larger descent because fallback fonts may get used in rendering the text.
+ *
* @return the distance below (positive) the baseline (descent) based on
* the current typeface and text size.
*/
public float descent() {
- return nDescent(mNativePaint, mNativeTypeface);
+ return nDescent(mNativePaint);
}
/**
@@ -1794,12 +1786,15 @@ public class Paint {
* settings for typeface, textSize, etc. If metrics is not null, return the
* fontmetric values in it.
*
+ * <p>Note that these are the values for the main typeface, and actual text rendered may need a
+ * larger set of values because fallback fonts may get used in rendering the text.
+ *
* @param metrics If this object is not null, its fields are filled with
* the appropriate values given the paint's text attributes.
* @return the font's recommended interline spacing.
*/
public float getFontMetrics(FontMetrics metrics) {
- return nGetFontMetrics(mNativePaint, mNativeTypeface, metrics);
+ return nGetFontMetrics(mNativePaint, metrics);
}
/**
@@ -1855,10 +1850,13 @@ public class Paint {
* and clipping. If you want more control over the rounding, call
* getFontMetrics().
*
+ * <p>Note that these are the values for the main typeface, and actual text rendered may need a
+ * larger set of values because fallback fonts may get used in rendering the text.
+ *
* @return the font's interline spacing.
*/
public int getFontMetricsInt(FontMetricsInt fmi) {
- return nGetFontMetricsInt(mNativePaint, mNativeTypeface, fmi);
+ return nGetFontMetricsInt(mNativePaint, fmi);
}
public FontMetricsInt getFontMetricsInt() {
@@ -1871,6 +1869,9 @@ public class Paint {
* Return the recommend line spacing based on the current typeface and
* text size.
*
+ * <p>Note that this is the value for the main typeface, and actual text rendered may need a
+ * larger value because fallback fonts may get used in rendering the text.
+ *
* @return recommend line spacing based on the current typeface and
* text size.
*/
@@ -1898,14 +1899,14 @@ public class Paint {
return 0f;
}
if (!mHasCompatScaling) {
- return (float) Math.ceil(nGetTextAdvances(mNativePaint, mNativeTypeface, text,
+ return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
index, count, index, count, mBidiFlags, null, 0));
}
final float oldSize = getTextSize();
setTextSize(oldSize * mCompatScaling);
- float w = nGetTextAdvances(mNativePaint, mNativeTypeface, text, index, count, index,
- count, mBidiFlags, null, 0);
+ final float w = nGetTextAdvances(mNativePaint, text, index, count, index, count,
+ mBidiFlags, null, 0);
setTextSize(oldSize);
return (float) Math.ceil(w*mInvCompatScaling);
}
@@ -1930,13 +1931,13 @@ public class Paint {
return 0f;
}
if (!mHasCompatScaling) {
- return (float) Math.ceil(nGetTextAdvances(mNativePaint, mNativeTypeface, text,
+ return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
start, end, start, end, mBidiFlags, null, 0));
}
final float oldSize = getTextSize();
setTextSize(oldSize * mCompatScaling);
- float w = nGetTextAdvances(mNativePaint, mNativeTypeface, text, start, end, start,
- end, mBidiFlags, null, 0);
+ final float w = nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags,
+ null, 0);
setTextSize(oldSize);
return (float) Math.ceil(w * mInvCompatScaling);
}
@@ -2019,14 +2020,14 @@ public class Paint {
return 0;
}
if (!mHasCompatScaling) {
- return nBreakText(mNativePaint, mNativeTypeface, text, index, count, maxWidth,
- mBidiFlags, measuredWidth);
+ return nBreakText(mNativePaint, text, index, count, maxWidth, mBidiFlags,
+ measuredWidth);
}
final float oldSize = getTextSize();
setTextSize(oldSize * mCompatScaling);
- int res = nBreakText(mNativePaint, mNativeTypeface, text, index, count,
- maxWidth * mCompatScaling, mBidiFlags, measuredWidth);
+ final int res = nBreakText(mNativePaint, text, index, count, maxWidth * mCompatScaling,
+ mBidiFlags, measuredWidth);
setTextSize(oldSize);
if (measuredWidth != null) measuredWidth[0] *= mInvCompatScaling;
return res;
@@ -2107,14 +2108,14 @@ public class Paint {
return 0;
}
if (!mHasCompatScaling) {
- return nBreakText(mNativePaint, mNativeTypeface, text, measureForwards,
+ return nBreakText(mNativePaint, text, measureForwards,
maxWidth, mBidiFlags, measuredWidth);
}
final float oldSize = getTextSize();
setTextSize(oldSize*mCompatScaling);
- int res = nBreakText(mNativePaint, mNativeTypeface, text, measureForwards,
- maxWidth*mCompatScaling, mBidiFlags, measuredWidth);
+ final int res = nBreakText(mNativePaint, text, measureForwards, maxWidth*mCompatScaling,
+ mBidiFlags, measuredWidth);
setTextSize(oldSize);
if (measuredWidth != null) measuredWidth[0] *= mInvCompatScaling;
return res;
@@ -2144,15 +2145,13 @@ public class Paint {
return 0;
}
if (!mHasCompatScaling) {
- nGetTextAdvances(mNativePaint, mNativeTypeface, text, index, count, index, count,
- mBidiFlags, widths, 0);
+ nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0);
return count;
}
final float oldSize = getTextSize();
setTextSize(oldSize * mCompatScaling);
- nGetTextAdvances(mNativePaint, mNativeTypeface, text, index, count, index, count,
- mBidiFlags, widths, 0);
+ nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0);
setTextSize(oldSize);
for (int i = 0; i < count; i++) {
widths[i] *= mInvCompatScaling;
@@ -2229,15 +2228,13 @@ public class Paint {
return 0;
}
if (!mHasCompatScaling) {
- nGetTextAdvances(mNativePaint, mNativeTypeface, text, start, end, start, end,
- mBidiFlags, widths, 0);
+ nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
return end - start;
}
final float oldSize = getTextSize();
setTextSize(oldSize * mCompatScaling);
- nGetTextAdvances(mNativePaint, mNativeTypeface, text, start, end, start, end,
- mBidiFlags, widths, 0);
+ nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
setTextSize(oldSize);
for (int i = 0; i < end - start; i++) {
widths[i] *= mInvCompatScaling;
@@ -2284,16 +2281,15 @@ public class Paint {
return 0f;
}
if (!mHasCompatScaling) {
- return nGetTextAdvances(mNativePaint, mNativeTypeface, chars, index, count,
- contextIndex, contextCount, isRtl ? BIDI_FORCE_RTL : BIDI_FORCE_LTR, advances,
+ return nGetTextAdvances(mNativePaint, chars, index, count, contextIndex, contextCount,
+ isRtl ? BIDI_FORCE_RTL : BIDI_FORCE_LTR, advances,
advancesIndex);
}
final float oldSize = getTextSize();
setTextSize(oldSize * mCompatScaling);
- float res = nGetTextAdvances(mNativePaint, mNativeTypeface, chars, index, count,
- contextIndex, contextCount, isRtl ? BIDI_FORCE_RTL : BIDI_FORCE_LTR, advances,
- advancesIndex);
+ final float res = nGetTextAdvances(mNativePaint, chars, index, count, contextIndex,
+ contextCount, isRtl ? BIDI_FORCE_RTL : BIDI_FORCE_LTR, advances, advancesIndex);
setTextSize(oldSize);
if (advances != null) {
@@ -2411,16 +2407,14 @@ public class Paint {
}
if (!mHasCompatScaling) {
- return nGetTextAdvances(mNativePaint, mNativeTypeface, text, start, end,
- contextStart, contextEnd, isRtl ? BIDI_FORCE_RTL : BIDI_FORCE_LTR, advances,
- advancesIndex);
+ return nGetTextAdvances(mNativePaint, text, start, end, contextStart, contextEnd,
+ isRtl ? BIDI_FORCE_RTL : BIDI_FORCE_LTR, advances, advancesIndex);
}
final float oldSize = getTextSize();
setTextSize(oldSize * mCompatScaling);
- float totalAdvance = nGetTextAdvances(mNativePaint, mNativeTypeface, text, start,
- end, contextStart, contextEnd, isRtl ? BIDI_FORCE_RTL : BIDI_FORCE_LTR, advances,
- advancesIndex);
+ final float totalAdvance = nGetTextAdvances(mNativePaint, text, start, end, contextStart,
+ contextEnd, isRtl ? BIDI_FORCE_RTL : BIDI_FORCE_LTR, advances, advancesIndex);
setTextSize(oldSize);
if (advances != null) {
@@ -2467,8 +2461,8 @@ public class Paint {
throw new IndexOutOfBoundsException();
}
- return nGetTextRunCursor(mNativePaint, mNativeTypeface, text,
- contextStart, contextLength, dir, offset, cursorOpt);
+ return nGetTextRunCursor(mNativePaint, text, contextStart, contextLength, dir, offset,
+ cursorOpt);
}
/**
@@ -2553,8 +2547,8 @@ public class Paint {
throw new IndexOutOfBoundsException();
}
- return nGetTextRunCursor(mNativePaint, mNativeTypeface, text,
- contextStart, contextEnd, dir, offset, cursorOpt);
+ return nGetTextRunCursor(mNativePaint, text, contextStart, contextEnd, dir, offset,
+ cursorOpt);
}
/**
@@ -2574,8 +2568,7 @@ public class Paint {
if ((index | count) < 0 || index + count > text.length) {
throw new ArrayIndexOutOfBoundsException();
}
- nGetTextPath(mNativePaint, mNativeTypeface, mBidiFlags, text, index, count, x, y,
- path.mutateNI());
+ nGetTextPath(mNativePaint, mBidiFlags, text, index, count, x, y, path.mutateNI());
}
/**
@@ -2595,8 +2588,7 @@ public class Paint {
if ((start | end | (end - start) | (text.length() - end)) < 0) {
throw new IndexOutOfBoundsException();
}
- nGetTextPath(mNativePaint, mNativeTypeface, mBidiFlags, text, start, end, x, y,
- path.mutateNI());
+ nGetTextPath(mNativePaint, mBidiFlags, text, start, end, x, y, path.mutateNI());
}
/**
@@ -2615,7 +2607,7 @@ public class Paint {
if (bounds == null) {
throw new NullPointerException("need bounds Rect");
}
- nGetStringBounds(mNativePaint, mNativeTypeface, text, start, end, mBidiFlags, bounds);
+ nGetStringBounds(mNativePaint, text, start, end, mBidiFlags, bounds);
}
/**
@@ -2657,7 +2649,7 @@ public class Paint {
if (bounds == null) {
throw new NullPointerException("need bounds Rect");
}
- nGetCharArrayBounds(mNativePaint, mNativeTypeface, text, index, count, mBidiFlags,
+ nGetCharArrayBounds(mNativePaint, text, index, count, mBidiFlags,
bounds);
}
@@ -2678,7 +2670,7 @@ public class Paint {
* @return true if the typeface has a glyph for the string
*/
public boolean hasGlyph(String string) {
- return nHasGlyph(mNativePaint, mNativeTypeface, mBidiFlags, string);
+ return nHasGlyph(mNativePaint, mBidiFlags, string);
}
/**
@@ -2731,8 +2723,8 @@ public class Paint {
return 0.0f;
}
// TODO: take mCompatScaling into account (or eliminate compat scaling)?
- return nGetRunAdvance(mNativePaint, mNativeTypeface, text, start, end,
- contextStart, contextEnd, isRtl, offset);
+ return nGetRunAdvance(mNativePaint, text, start, end, contextStart, contextEnd, isRtl,
+ offset);
}
/**
@@ -2808,8 +2800,8 @@ public class Paint {
throw new IndexOutOfBoundsException();
}
// TODO: take mCompatScaling into account (or eliminate compat scaling)?
- return nGetOffsetForAdvance(mNativePaint, mNativeTypeface, text, start, end,
- contextStart, contextEnd, isRtl, advance);
+ return nGetOffsetForAdvance(mNativePaint, text, start, end, contextStart, contextEnd,
+ isRtl, advance);
}
/**
@@ -2843,42 +2835,45 @@ public class Paint {
return result;
}
+ /**
+ * Returns true of the passed {@link Paint} will have the same effect on text measurement
+ *
+ * @param other A {@link Paint} object.
+ * @return true if the other {@link Paint} has the same effect on text measurement.
+ */
+ public boolean equalsForTextMeasurement(@NonNull Paint other) {
+ return nEqualsForTextMeasurement(mNativePaint, other.mNativePaint);
+ }
+
// regular JNI
private static native long nGetNativeFinalizer();
private static native long nInit();
private static native long nInitWithPaint(long paint);
- private static native int nBreakText(long nObject, long nTypeface,
- char[] text, int index, int count,
+ private static native int nBreakText(long nObject, char[] text, int index, int count,
float maxWidth, int bidiFlags, float[] measuredWidth);
- private static native int nBreakText(long nObject, long nTypeface,
- String text, boolean measureForwards,
+ private static native int nBreakText(long nObject, String text, boolean measureForwards,
float maxWidth, int bidiFlags, float[] measuredWidth);
- private static native float nGetTextAdvances(long paintPtr, long typefacePtr,
- char[] text, int index, int count, int contextIndex, int contextCount,
- int bidiFlags, float[] advances, int advancesIndex);
- private static native float nGetTextAdvances(long paintPtr, long typefacePtr,
- String text, int start, int end, int contextStart, int contextEnd,
- int bidiFlags, float[] advances, int advancesIndex);
- private native int nGetTextRunCursor(long paintPtr, long typefacePtr, char[] text,
- int contextStart, int contextLength, int dir, int offset, int cursorOpt);
- private native int nGetTextRunCursor(long paintPtr, long typefacePtr, String text,
- int contextStart, int contextEnd, int dir, int offset, int cursorOpt);
- private static native void nGetTextPath(long paintPtr, long typefacePtr,
- int bidiFlags, char[] text, int index, int count, float x, float y, long path);
- private static native void nGetTextPath(long paintPtr, long typefacePtr,
- int bidiFlags, String text, int start, int end, float x, float y, long path);
- private static native void nGetStringBounds(long nativePaint, long typefacePtr,
- String text, int start, int end, int bidiFlags, Rect bounds);
- private static native void nGetCharArrayBounds(long nativePaint, long typefacePtr,
- char[] text, int index, int count, int bidiFlags, Rect bounds);
- private static native boolean nHasGlyph(long paintPtr, long typefacePtr,
- int bidiFlags, String string);
- private static native float nGetRunAdvance(long paintPtr, long typefacePtr,
- char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl,
- int offset);
- private static native int nGetOffsetForAdvance(long paintPtr,
- long typefacePtr, char[] text, int start, int end, int contextStart, int contextEnd,
- boolean isRtl, float advance);
+ private static native float nGetTextAdvances(long paintPtr, char[] text, int index, int count,
+ int contextIndex, int contextCount, int bidiFlags, float[] advances, int advancesIndex);
+ private static native float nGetTextAdvances(long paintPtr, String text, int start, int end,
+ int contextStart, int contextEnd, int bidiFlags, float[] advances, int advancesIndex);
+ private native int nGetTextRunCursor(long paintPtr, char[] text, int contextStart,
+ int contextLength, int dir, int offset, int cursorOpt);
+ private native int nGetTextRunCursor(long paintPtr, String text, int contextStart,
+ int contextEnd, int dir, int offset, int cursorOpt);
+ private static native void nGetTextPath(long paintPtr, int bidiFlags, char[] text, int index,
+ int count, float x, float y, long path);
+ private static native void nGetTextPath(long paintPtr, int bidiFlags, String text, int start,
+ int end, float x, float y, long path);
+ private static native void nGetStringBounds(long nativePaint, String text, int start, int end,
+ int bidiFlags, Rect bounds);
+ private static native void nGetCharArrayBounds(long nativePaint, char[] text, int index,
+ int count, int bidiFlags, Rect bounds);
+ private static native boolean nHasGlyph(long paintPtr, int bidiFlags, String string);
+ private static native float nGetRunAdvance(long paintPtr, char[] text, int start, int end,
+ int contextStart, int contextEnd, boolean isRtl, int offset);
+ private static native int nGetOffsetForAdvance(long paintPtr, char[] text, int start, int end,
+ int contextStart, int contextEnd, boolean isRtl, float advance);
// ---------------- @FastNative ------------------------
@@ -2888,11 +2883,9 @@ public class Paint {
@FastNative
private static native void nSetFontFeatureSettings(long paintPtr, String settings);
@FastNative
- private static native float nGetFontMetrics(long paintPtr,
- long typefacePtr, FontMetrics metrics);
+ private static native float nGetFontMetrics(long paintPtr, FontMetrics metrics);
@FastNative
- private static native int nGetFontMetricsInt(long paintPtr,
- long typefacePtr, FontMetricsInt fmi);
+ private static native int nGetFontMetricsInt(long paintPtr, FontMetricsInt fmi);
// ---------------- @CriticalNative ------------------------
@@ -2926,14 +2919,14 @@ public class Paint {
@CriticalNative
private static native long nSetMaskFilter(long paintPtr, long maskfilter);
@CriticalNative
- private static native long nSetTypeface(long paintPtr, long typeface);
+ private static native void nSetTypeface(long paintPtr, long typeface);
@CriticalNative
private static native int nGetTextAlign(long paintPtr);
@CriticalNative
private static native void nSetTextAlign(long paintPtr, int align);
@CriticalNative
- private static native void nSetTextLocalesByMinikinLangListId(long paintPtr,
- int mMinikinLangListId);
+ private static native void nSetTextLocalesByMinikinLocaleListId(long paintPtr,
+ int mMinikinLocaleListId);
@CriticalNative
private static native void nSetShadowLayer(long paintPtr,
float radius, float dx, float dy, int color);
@@ -3006,17 +2999,19 @@ public class Paint {
@CriticalNative
private static native void nSetTextSkewX(long paintPtr, float skewX);
@CriticalNative
- private static native float nAscent(long paintPtr, long typefacePtr);
+ private static native float nAscent(long paintPtr);
@CriticalNative
- private static native float nDescent(long paintPtr, long typefacePtr);
+ private static native float nDescent(long paintPtr);
@CriticalNative
- private static native float nGetUnderlinePosition(long paintPtr, long typefacePtr);
+ private static native float nGetUnderlinePosition(long paintPtr);
@CriticalNative
- private static native float nGetUnderlineThickness(long paintPtr, long typefacePtr);
+ private static native float nGetUnderlineThickness(long paintPtr);
@CriticalNative
- private static native float nGetStrikeThruPosition(long paintPtr, long typefacePtr);
+ private static native float nGetStrikeThruPosition(long paintPtr);
@CriticalNative
- private static native float nGetStrikeThruThickness(long paintPtr, long typefacePtr);
+ private static native float nGetStrikeThruThickness(long paintPtr);
@CriticalNative
private static native void nSetTextSize(long paintPtr, float textSize);
+ @CriticalNative
+ private static native boolean nEqualsForTextMeasurement(long leftPaintPtr, long rightPaintPtr);
}
diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java
index 08eeaff69f9b..9ac94d895a37 100644
--- a/graphics/java/android/graphics/Picture.java
+++ b/graphics/java/android/graphics/Picture.java
@@ -31,8 +31,9 @@ import java.io.OutputStream;
* be replayed on a hardware accelerated canvas.</p>
*/
public class Picture {
- private Canvas mRecordingCanvas;
+ private PictureCanvas mRecordingCanvas;
private long mNativePicture;
+ private boolean mRequiresHwAcceleration;
private static final int WORKING_STREAM_STORAGE = 16 * 1024;
@@ -78,8 +79,12 @@ public class Picture {
* into it.
*/
public Canvas beginRecording(int width, int height) {
+ if (mRecordingCanvas != null) {
+ throw new IllegalStateException("Picture already recording, must call #endRecording()");
+ }
long ni = nativeBeginRecording(mNativePicture, width, height);
- mRecordingCanvas = new RecordingCanvas(this, ni);
+ mRecordingCanvas = new PictureCanvas(this, ni);
+ mRequiresHwAcceleration = false;
return mRecordingCanvas;
}
@@ -91,6 +96,7 @@ public class Picture {
*/
public void endRecording() {
if (mRecordingCanvas != null) {
+ mRequiresHwAcceleration = mRecordingCanvas.mHoldsHwBitmap;
mRecordingCanvas = null;
nativeEndRecording(mNativePicture);
}
@@ -113,6 +119,18 @@ public class Picture {
}
/**
+ * Indicates whether or not this Picture contains recorded commands that only work when
+ * drawn to a hardware-accelerated canvas. If this returns true then this Picture can only
+ * be drawn to another Picture or to a Canvas where canvas.isHardwareAccelerated() is true.
+ *
+ * @return true if the Picture can only be drawn to a hardware-accelerated canvas,
+ * false otherwise.
+ */
+ public boolean requiresHardwareAcceleration() {
+ return mRequiresHwAcceleration;
+ }
+
+ /**
* Draw this picture on the canvas.
* <p>
* Prior to {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this call could
@@ -129,6 +147,9 @@ public class Picture {
if (mRecordingCanvas != null) {
endRecording();
}
+ if (mRequiresHwAcceleration && !canvas.isHardwareAccelerated()) {
+ canvas.onHwBitmapInSwMode();
+ }
nativeDraw(canvas.getNativeCanvasWrapper(), mNativePicture);
}
@@ -164,8 +185,7 @@ public class Picture {
if (stream == null) {
throw new NullPointerException();
}
- if (!nativeWriteToStream(mNativePicture, stream,
- new byte[WORKING_STREAM_STORAGE])) {
+ if (!nativeWriteToStream(mNativePicture, stream, new byte[WORKING_STREAM_STORAGE])) {
throw new RuntimeException();
}
}
@@ -182,10 +202,11 @@ public class Picture {
OutputStream stream, byte[] storage);
private static native void nativeDestructor(long nativePicture);
- private static class RecordingCanvas extends Canvas {
+ private static class PictureCanvas extends Canvas {
private final Picture mPicture;
+ boolean mHoldsHwBitmap;
- public RecordingCanvas(Picture pict, long nativeCanvas) {
+ public PictureCanvas(Picture pict, long nativeCanvas) {
super(nativeCanvas);
mPicture = pict;
}
@@ -202,5 +223,10 @@ public class Picture {
}
super.drawPicture(picture);
}
+
+ @Override
+ protected void onHwBitmapInSwMode() {
+ mHoldsHwBitmap = true;
+ }
}
}
diff --git a/graphics/java/android/graphics/PixelFormat.java b/graphics/java/android/graphics/PixelFormat.java
index f93886dcb06b..96d6eeece0a3 100644
--- a/graphics/java/android/graphics/PixelFormat.java
+++ b/graphics/java/android/graphics/PixelFormat.java
@@ -185,4 +185,52 @@ public class PixelFormat {
return false;
}
+
+ /**
+ * @hide
+ */
+ public static String formatToString(@Format int format) {
+ switch (format) {
+ case UNKNOWN:
+ return "UNKNOWN";
+ case TRANSLUCENT:
+ return "TRANSLUCENT";
+ case TRANSPARENT:
+ return "TRANSPARENT";
+ case RGBA_8888:
+ return "RGBA_8888";
+ case RGBX_8888:
+ return "RGBX_8888";
+ case RGB_888:
+ return "RGB_888";
+ case RGB_565:
+ return "RGB_565";
+ case RGBA_5551:
+ return "RGBA_5551";
+ case RGBA_4444:
+ return "RGBA_4444";
+ case A_8:
+ return "A_8";
+ case L_8:
+ return "L_8";
+ case LA_88:
+ return "LA_88";
+ case RGB_332:
+ return "RGB_332";
+ case YCbCr_422_SP:
+ return "YCbCr_422_SP";
+ case YCbCr_420_SP:
+ return "YCbCr_420_SP";
+ case YCbCr_422_I:
+ return "YCbCr_422_I";
+ case RGBA_F16:
+ return "RGBA_F16";
+ case RGBA_1010102:
+ return "RGBA_1010102";
+ case JPEG:
+ return "JPEG";
+ default:
+ return Integer.toString(format);
+ }
+ }
}
diff --git a/graphics/java/android/graphics/Point.java b/graphics/java/android/graphics/Point.java
index abcccbdbc9fb..c6b6c668f03a 100644
--- a/graphics/java/android/graphics/Point.java
+++ b/graphics/java/android/graphics/Point.java
@@ -18,6 +18,7 @@ package android.graphics;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.proto.ProtoOutputStream;
import java.io.PrintWriter;
@@ -121,6 +122,21 @@ public class Point implements Parcelable {
out.writeInt(y);
}
+ /**
+ * Write to a protocol buffer output stream.
+ * Protocol buffer message definition at {@link android.graphics.PointProto}
+ *
+ * @param protoOutputStream Stream to write the Rect object to.
+ * @param fieldId Field Id of the Rect as defined in the parent message
+ * @hide
+ */
+ public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
+ final long token = protoOutputStream.start(fieldId);
+ protoOutputStream.write(PointProto.X, x);
+ protoOutputStream.write(PointProto.Y, y);
+ protoOutputStream.end(token);
+ }
+
public static final Parcelable.Creator<Point> CREATOR = new Parcelable.Creator<Point>() {
/**
* Return a new point from the data in the specified parcel.
diff --git a/graphics/java/android/graphics/PostProcessor.java b/graphics/java/android/graphics/PostProcessor.java
new file mode 100644
index 000000000000..b1712e92e2fe
--- /dev/null
+++ b/graphics/java/android/graphics/PostProcessor.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.graphics.drawable.Drawable;
+
+/**
+ * Helper interface for adding custom processing to an image.
+ *
+ * <p>The image being processed may be a {@link Drawable}, {@link Bitmap} or frame
+ * of an animated image produced by {@link ImageDecoder}. This is called before
+ * the requested object is returned.</p>
+ *
+ * <p>This custom processing also applies to image types that are otherwise
+ * immutable, such as {@link Bitmap.Config#HARDWARE}.</p>
+ *
+ * <p>On an animated image, the callback will only be called once, but the drawing
+ * commands will be applied to each frame, as if the {@code Canvas} had been
+ * returned by {@link Picture#beginRecording}.<p>
+ *
+ * <p>Supplied to ImageDecoder via {@link ImageDecoder#setPostProcessor}.</p>
+ */
+public interface PostProcessor {
+ /**
+ * Do any processing after (for example) decoding.
+ *
+ * <p>Drawing to the {@link Canvas} will behave as if the initial processing
+ * (e.g. decoding) already exists in the Canvas. An implementation can draw
+ * effects on top of this, or it can even draw behind it using
+ * {@link PorterDuff.Mode#DST_OVER}. A common effect is to add transparency
+ * to the corners to achieve rounded corners. That can be done with the
+ * following code:</p>
+ *
+ * <code>
+ * Path path = new Path();
+ * path.setFillType(Path.FillType.INVERSE_EVEN_ODD);
+ * int width = canvas.getWidth();
+ * int height = canvas.getHeight();
+ * path.addRoundRect(0, 0, width, height, 20, 20, Path.Direction.CW);
+ * Paint paint = new Paint();
+ * paint.setAntiAlias(true);
+ * paint.setColor(Color.TRANSPARENT);
+ * paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
+ * canvas.drawPath(path, paint);
+ * return PixelFormat.TRANSLUCENT;
+ * </code>
+ *
+ *
+ * @param canvas The {@link Canvas} to draw to.
+ * @return Opacity of the result after drawing.
+ * {@link PixelFormat#UNKNOWN} means that the implementation did not
+ * change whether the image has alpha. Return this unless you added
+ * transparency (e.g. with the code above, in which case you should
+ * return {@code PixelFormat.TRANSLUCENT}) or you forced the image to
+ * be opaque (e.g. by drawing everywhere with an opaque color and
+ * {@code PorterDuff.Mode.DST_OVER}, in which case you should return
+ * {@code PixelFormat.OPAQUE}).
+ * {@link PixelFormat#TRANSLUCENT} means that the implementation added
+ * transparency. This is safe to return even if the image already had
+ * transparency. This is also safe to return if the result is opaque,
+ * though it may draw more slowly.
+ * {@link PixelFormat#OPAQUE} means that the implementation forced the
+ * image to be opaque. This is safe to return even if the image was
+ * already opaque.
+ * {@link PixelFormat#TRANSPARENT} (or any other integer) is not
+ * allowed, and will result in throwing an
+ * {@link java.lang.IllegalArgumentException}.
+ */
+ @PixelFormat.Opacity
+ public int onPostProcess(@NonNull Canvas canvas);
+}
diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java
index deafb6638ece..aff942da78d1 100644
--- a/graphics/java/android/graphics/Rect.java
+++ b/graphics/java/android/graphics/Rect.java
@@ -21,6 +21,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import android.util.proto.ProtoOutputStream;
import java.io.PrintWriter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -194,7 +195,24 @@ public final class Rect implements Parcelable {
pw.print(top); pw.print("]["); pw.print(right);
pw.print(','); pw.print(bottom); pw.print(']');
}
-
+
+ /**
+ * Write to a protocol buffer output stream.
+ * Protocol buffer message definition at {@link android.graphics.RectProto}
+ *
+ * @param protoOutputStream Stream to write the Rect object to.
+ * @param fieldId Field Id of the Rect as defined in the parent message
+ * @hide
+ */
+ public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
+ final long token = protoOutputStream.start(fieldId);
+ protoOutputStream.write(RectProto.LEFT, left);
+ protoOutputStream.write(RectProto.TOP, top);
+ protoOutputStream.write(RectProto.RIGHT, right);
+ protoOutputStream.write(RectProto.BOTTOM, bottom);
+ protoOutputStream.end(token);
+ }
+
/**
* Returns true if the rectangle is empty (left >= right or top >= bottom)
*/
@@ -457,6 +475,19 @@ public final class Rect implements Parcelable {
}
/**
+ * If the specified rectangle intersects this rectangle, set this rectangle to that
+ * intersection, otherwise set this rectangle to the empty rectangle.
+ * @see #inset(int, int, int, int) but without checking if the rects overlap.
+ * @hide
+ */
+ public void intersectUnchecked(Rect other) {
+ left = Math.max(left, other.left);
+ top = Math.max(top, other.top);
+ right = Math.min(right, other.right);
+ bottom = Math.min(bottom, other.bottom);
+ }
+
+ /**
* If rectangles a and b intersect, return true and set this rectangle to
* that intersection, otherwise return false and do not change this
* rectangle. No check is performed to see if either rectangle is empty.
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index c4b56c333c64..8595165aab27 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -16,28 +16,21 @@
package android.graphics;
-import static android.content.res.FontResourcesParser.ProviderResourceEntry;
-import static android.content.res.FontResourcesParser.FontFileResourceEntry;
-import static android.content.res.FontResourcesParser.FontFamilyFilesResourceEntry;
import static android.content.res.FontResourcesParser.FamilyResourceEntry;
+import static android.content.res.FontResourcesParser.FontFamilyFilesResourceEntry;
+import static android.content.res.FontResourcesParser.FontFileResourceEntry;
+import static android.content.res.FontResourcesParser.ProviderResourceEntry;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.AssetManager;
-import android.graphics.FontListParser;
import android.graphics.fonts.FontVariationAxis;
import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.ParcelFileDescriptor;
-import android.os.ResultReceiver;
import android.provider.FontRequest;
import android.provider.FontsContract;
import android.text.FontConfig;
+import android.util.ArrayMap;
import android.util.Base64;
import android.util.Log;
import android.util.LongSparseArray;
@@ -45,10 +38,9 @@ import android.util.LruCache;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
-import libcore.io.IoUtils;
-
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
@@ -56,18 +48,15 @@ import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
-import java.util.Arrays;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.atomic.AtomicReference;
/**
* The Typeface class specifies the typeface and intrinsic style of a font.
@@ -95,21 +84,33 @@ public class Typeface {
public static final Typeface MONOSPACE;
static Typeface[] sDefaults;
- private static final LongSparseArray<SparseArray<Typeface>> sTypefaceCache =
+
+ /**
+ * Cache for Typeface objects for style variant. Currently max size is 3.
+ */
+ @GuardedBy("sStyledCacheLock")
+ private static final LongSparseArray<SparseArray<Typeface>> sStyledTypefaceCache =
new LongSparseArray<>(3);
+ private static final Object sStyledCacheLock = new Object();
+
+ /**
+ * Cache for Typeface objects for weight variant. Currently max size is 3.
+ */
+ @GuardedBy("sWeightCacheLock")
+ private static final LongSparseArray<SparseArray<Typeface>> sWeightTypefaceCache =
+ new LongSparseArray<>(3);
+ private static final Object sWeightCacheLock = new Object();
/**
* Cache for Typeface objects dynamically loaded from assets. Currently max size is 16.
*/
- @GuardedBy("sLock")
+ @GuardedBy("sDynamicCacheLock")
private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16);
+ private static final Object sDynamicCacheLock = new Object();
static Typeface sDefaultTypeface;
- static Map<String, Typeface> sSystemFontMap;
- static FontFamily[] sFallbackFonts;
- private static final Object sLock = new Object();
-
- static final String FONTS_CONFIG = "fonts.xml";
+ static final Map<String, Typeface> sSystemFontMap;
+ static final Map<String, FontFamily[]> sSystemFallbackMap;
/**
* @hide
@@ -121,6 +122,7 @@ public class Typeface {
public static final int BOLD = 1;
public static final int ITALIC = 2;
public static final int BOLD_ITALIC = 3;
+ /** @hide */ public static final int STYLE_MASK = 0x03;
private int mStyle = 0;
private int mWeight = 0;
@@ -129,6 +131,7 @@ public class Typeface {
// Must be the same as the C++ constant in core/jni/android/graphics/FontFamily.cpp
/** @hide */
public static final int RESOLVE_BY_FONT_TABLE = -1;
+ private static final String DEFAULT_FAMILY = "sans-serif";
// Style value for building typeface.
private static final int STYLE_NORMAL = 0;
@@ -142,6 +145,13 @@ public class Typeface {
nativeSetDefault(t.native_instance);
}
+ // TODO: Make this public API. (b/64852739)
+ /** @hide */
+ @VisibleForTesting
+ public int getWeight() {
+ return mWeight;
+ }
+
/** Returns the typeface's intrinsic style attributes */
public int getStyle() {
return mStyle;
@@ -163,28 +173,27 @@ public class Typeface {
*/
@Nullable
public static Typeface createFromResources(AssetManager mgr, String path, int cookie) {
- if (sFallbackFonts != null) {
- synchronized (sDynamicTypefaceCache) {
- final String key = Builder.createAssetUid(
- mgr, path, 0 /* ttcIndex */, null /* axes */,
- RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */);
- 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,
- RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
- sDynamicTypefaceCache.put(key, typeface);
- return typeface;
+ 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;
@@ -197,61 +206,57 @@ public class Typeface {
@Nullable
public static Typeface createFromResources(
FamilyResourceEntry entry, AssetManager mgr, String path) {
- if (sFallbackFonts != null) {
- if (entry instanceof ProviderResourceEntry) {
- final ProviderResourceEntry providerEntry = (ProviderResourceEntry) entry;
- // Downloadable font
- List<List<String>> givenCerts = providerEntry.getCerts();
- List<List<byte[]>> certs = new ArrayList<>();
- if (givenCerts != null) {
- for (int i = 0; i < givenCerts.size(); i++) {
- List<String> certSet = givenCerts.get(i);
- List<byte[]> byteArraySet = new ArrayList<>();
- for (int j = 0; j < certSet.size(); j++) {
- byteArraySet.add(Base64.decode(certSet.get(j), Base64.DEFAULT));
- }
- certs.add(byteArraySet);
+ if (entry instanceof ProviderResourceEntry) {
+ final ProviderResourceEntry providerEntry = (ProviderResourceEntry) entry;
+ // Downloadable font
+ List<List<String>> givenCerts = providerEntry.getCerts();
+ List<List<byte[]>> certs = new ArrayList<>();
+ if (givenCerts != null) {
+ for (int i = 0; i < givenCerts.size(); i++) {
+ List<String> certSet = givenCerts.get(i);
+ List<byte[]> byteArraySet = new ArrayList<>();
+ for (int j = 0; j < certSet.size(); j++) {
+ byteArraySet.add(Base64.decode(certSet.get(j), Base64.DEFAULT));
}
+ certs.add(byteArraySet);
}
- // Downloaded font and it wasn't cached, request it again and return a
- // default font instead (nothing we can do now).
- FontRequest request = new FontRequest(providerEntry.getAuthority(),
- providerEntry.getPackage(), providerEntry.getQuery(), certs);
- Typeface typeface = FontsContract.getFontSync(request);
- return typeface == null ? DEFAULT : typeface;
}
+ // Downloaded font and it wasn't cached, request it again and return a
+ // default font instead (nothing we can do now).
+ FontRequest request = new FontRequest(providerEntry.getAuthority(),
+ providerEntry.getPackage(), providerEntry.getQuery(), certs);
+ Typeface typeface = FontsContract.getFontSync(request);
+ return typeface == null ? DEFAULT : typeface;
+ }
- Typeface typeface = findFromCache(mgr, path);
- if (typeface != null) return typeface;
+ Typeface typeface = findFromCache(mgr, path);
+ if (typeface != null) return typeface;
- // family is FontFamilyFilesResourceEntry
- final FontFamilyFilesResourceEntry filesEntry =
- (FontFamilyFilesResourceEntry) entry;
+ // family is FontFamilyFilesResourceEntry
+ final FontFamilyFilesResourceEntry filesEntry = (FontFamilyFilesResourceEntry) entry;
- FontFamily fontFamily = new FontFamily();
- for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) {
- // TODO: Add ttc and variation font support. (b/37853920)
- if (!fontFamily.addFontFromAssetManager(mgr, fontFile.getFileName(),
- 0 /* resourceCookie */, false /* isAsset */, 0 /* ttcIndex */,
- fontFile.getWeight(), fontFile.getItalic(), null /* axes */)) {
- return null;
- }
- }
- if (!fontFamily.freeze()) {
+ 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;
}
- FontFamily[] familyChain = { fontFamily };
- typeface = createFromFamiliesWithDefault(familyChain,
- RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
- synchronized (sDynamicTypefaceCache) {
- final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */,
- null /* axes */, RESOLVE_BY_FONT_TABLE /* weight */,
- RESOLVE_BY_FONT_TABLE /* italic */);
- sDynamicTypefaceCache.put(key, typeface);
- }
- return typeface;
}
- return null;
+ if (!fontFamily.freeze()) {
+ return null;
+ }
+ FontFamily[] familyChain = { fontFamily };
+ typeface = createFromFamiliesWithDefault(familyChain, DEFAULT_FAMILY,
+ RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
+ synchronized (sDynamicCacheLock) {
+ final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */,
+ null /* axes */, RESOLVE_BY_FONT_TABLE /* weight */,
+ RESOLVE_BY_FONT_TABLE /* italic */, DEFAULT_FAMILY);
+ sDynamicTypefaceCache.put(key, typeface);
+ }
+ return typeface;
}
/**
@@ -259,9 +264,10 @@ public class Typeface {
* @hide
*/
public static Typeface findFromCache(AssetManager mgr, String path) {
- synchronized (sDynamicTypefaceCache) {
+ synchronized (sDynamicCacheLock) {
final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, null /* axes */,
- RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */);
+ RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */,
+ DEFAULT_FAMILY);
Typeface typeface = sDynamicTypefaceCache.get(key);
if (typeface != null) {
return typeface;
@@ -375,7 +381,7 @@ public class Typeface {
* weight and italic information, so {@link #setWeight} and {@link #setItalic} are used
* for style matching during font selection.
*
- * @param results The array of {@link FontsContract.FontInfo}
+ * @param fonts The array of {@link FontsContract.FontInfo}
* @param buffers The mapping from URI to buffers to be used during building.
* @hide
*/
@@ -410,7 +416,7 @@ public class Typeface {
}
/**
- * Sets an index of the font collection.
+ * Sets an index of the font collection. See {@link android.R.attr#ttcIndex}.
*
* Can not be used for Typeface source. build() method will return null for invalid index.
* @param ttcIndex An index of the font collection. If the font source is not font
@@ -498,7 +504,7 @@ public class Typeface {
* @return Unique id for a given AssetManager and asset path.
*/
private static String createAssetUid(final AssetManager mgr, String path, int ttcIndex,
- @Nullable FontVariationAxis[] axes, int weight, int italic) {
+ @Nullable FontVariationAxis[] axes, int weight, int italic, String fallback) {
final SparseArray<String> pkgs = mgr.getAssignedPackageIdentifiers();
final StringBuilder builder = new StringBuilder();
final int size = pkgs.size();
@@ -513,7 +519,11 @@ public class Typeface {
builder.append(Integer.toString(weight));
builder.append("-");
builder.append(Integer.toString(italic));
- builder.append("-");
+ // Family name may contain hyphen. Use double hyphen for avoiding key conflicts before
+ // and after appending falblack name.
+ builder.append("--");
+ builder.append(fallback);
+ builder.append("--");
if (axes != null) {
for (FontVariationAxis axis : axes) {
builder.append(axis.getTag());
@@ -524,12 +534,6 @@ public class Typeface {
return builder.toString();
}
- private static final Object sLock = new Object();
- // TODO: Unify with Typeface.sTypefaceCache.
- @GuardedBy("sLock")
- private static final LongSparseArray<SparseArray<Typeface>> sTypefaceCache =
- new LongSparseArray<>(3);
-
private Typeface resolveFallbackTypeface() {
if (mFallbackFamilyName == null) {
return null;
@@ -547,29 +551,7 @@ public class Typeface {
final int weight = (mWeight == RESOLVE_BY_FONT_TABLE) ? base.mWeight : mWeight;
final boolean italic =
(mItalic == RESOLVE_BY_FONT_TABLE) ? (base.mStyle & ITALIC) != 0 : mItalic == 1;
- final int key = weight << 1 | (italic ? 1 : 0);
-
- Typeface typeface;
- synchronized(sLock) {
- SparseArray<Typeface> innerCache = sTypefaceCache.get(base.native_instance);
- if (innerCache != null) {
- typeface = innerCache.get(key);
- if (typeface != null) {
- return typeface;
- }
- }
-
- typeface = new Typeface(
- nativeCreateFromTypefaceWithExactStyle(
- base.native_instance, weight, italic));
-
- if (innerCache == null) {
- innerCache = new SparseArray<>(4); // [regular, bold] x [upright, italic]
- sTypefaceCache.put(base.native_instance, innerCache);
- }
- innerCache.put(key, typeface);
- }
- return typeface;
+ return createWeightStyle(base, weight, italic);
}
/**
@@ -593,14 +575,16 @@ public class Typeface {
return resolveFallbackTypeface();
}
FontFamily[] families = { fontFamily };
- return createFromFamiliesWithDefault(families, mWeight, mItalic);
+ 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);
- synchronized (sLock) {
+ mAssetManager, mPath, mTtcIndex, mAxes, mWeight, mItalic,
+ mFallbackFamilyName);
+ synchronized (sDynamicCacheLock) {
Typeface typeface = sDynamicTypefaceCache.get(key);
if (typeface != null) return typeface;
final FontFamily fontFamily = new FontFamily();
@@ -613,7 +597,8 @@ public class Typeface {
return resolveFallbackTypeface();
}
FontFamily[] families = { fontFamily };
- typeface = createFromFamiliesWithDefault(families, mWeight, mItalic);
+ typeface = createFromFamiliesWithDefault(families, mFallbackFamilyName,
+ mWeight, mItalic);
sDynamicTypefaceCache.put(key, typeface);
return typeface;
}
@@ -627,7 +612,8 @@ public class Typeface {
return resolveFallbackTypeface();
}
FontFamily[] families = { fontFamily };
- return createFromFamiliesWithDefault(families, mWeight, mItalic);
+ return createFromFamiliesWithDefault(families, mFallbackFamilyName, mWeight,
+ mItalic);
} else if (mFonts != null) {
final FontFamily fontFamily = new FontFamily();
boolean atLeastOneFont = false;
@@ -653,7 +639,8 @@ public class Typeface {
}
fontFamily.freeze();
FontFamily[] families = { fontFamily };
- return createFromFamiliesWithDefault(families, mWeight, mItalic);
+ return createFromFamiliesWithDefault(families, mFallbackFamilyName, mWeight,
+ mItalic);
}
// Must not reach here.
@@ -673,10 +660,7 @@ public class Typeface {
* @return The best matching typeface.
*/
public static Typeface create(String familyName, int style) {
- if (sSystemFontMap != null) {
- return create(sSystemFontMap.get(familyName), style);
- }
- return null;
+ return create(sSystemFontMap.get(familyName), style);
}
/**
@@ -685,42 +669,98 @@ public class Typeface {
* style from the same family of an existing typeface object. If family is
* null, this selects from the default font's family.
*
- * @param family May be null. The name of the existing type face.
+ * <p>
+ * This method is not thread safe on API 27 or before.
+ * This method is thread safe on API 28 or after.
+ * </p>
+ *
+ * @param family An existing {@link Typeface} object. In case of {@code null}, the default
+ * typeface is used instead.
* @param style The style (normal, bold, italic) of the typeface.
* e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
* @return The best matching typeface.
*/
public static Typeface create(Typeface family, int style) {
- if (style < 0 || style > 3) {
- style = 0;
+ if ((style & ~STYLE_MASK) != 0) {
+ style = NORMAL;
+ }
+ if (family == null) {
+ family = sDefaultTypeface;
}
- long ni = 0;
- if (family != null) {
- // Return early if we're asked for the same face/style
- if (family.mStyle == style) {
- return family;
- }
- ni = family.native_instance;
+ // Return early if we're asked for the same face/style
+ if (family.mStyle == style) {
+ return family;
}
+ final long ni = family.native_instance;
+
Typeface typeface;
- SparseArray<Typeface> styles = sTypefaceCache.get(ni);
+ synchronized (sStyledCacheLock) {
+ SparseArray<Typeface> styles = sStyledTypefaceCache.get(ni);
- if (styles != null) {
- typeface = styles.get(style);
- if (typeface != null) {
- return typeface;
+ if (styles == null) {
+ styles = new SparseArray<Typeface>(4);
+ sStyledTypefaceCache.put(ni, styles);
+ } else {
+ typeface = styles.get(style);
+ if (typeface != null) {
+ return typeface;
+ }
}
+
+ typeface = new Typeface(nativeCreateFromTypeface(ni, style));
+ styles.put(style, typeface);
}
+ return typeface;
+ }
- typeface = new Typeface(nativeCreateFromTypeface(ni, style));
- if (styles == null) {
- styles = new SparseArray<Typeface>(4);
- sTypefaceCache.put(ni, styles);
+ /**
+ * Creates a typeface object that best matches the specified existing typeface and the specified
+ * weight and italic style
+ *
+ * <p>
+ * This method is thread safe.
+ * </p>
+ *
+ * @param family An existing {@link Typeface} object. In case of {@code null}, the default
+ * typeface is used instead.
+ * @param weight The desired weight to be drawn.
+ * @param italic {@code true} if italic style is desired to be drawn. Otherwise, {@code false}
+ * @return A {@link Typeface} object for drawing specified weight and italic style. Never
+ * returns {@code null}
+ */
+ public static @NonNull Typeface create(@Nullable Typeface family,
+ @IntRange(from = 1, to = 1000) int weight, boolean italic) {
+ Preconditions.checkArgumentInRange(weight, 0, 1000, "weight");
+ if (family == null) {
+ family = sDefaultTypeface;
}
- styles.put(style, typeface);
+ return createWeightStyle(family, weight, italic);
+ }
+
+ private static @NonNull Typeface createWeightStyle(@NonNull Typeface base,
+ @IntRange(from = 1, to = 1000) int weight, boolean italic) {
+ final int key = (weight << 1) | (italic ? 1 : 0);
+
+ Typeface typeface;
+ synchronized(sWeightCacheLock) {
+ SparseArray<Typeface> innerCache = sWeightTypefaceCache.get(base.native_instance);
+ if (innerCache == null) {
+ innerCache = new SparseArray<>(4);
+ sWeightTypefaceCache.put(base.native_instance, innerCache);
+ } else {
+ typeface = innerCache.get(key);
+ if (typeface != null) {
+ return typeface;
+ }
+ }
+ typeface = new Typeface(
+ nativeCreateFromTypefaceWithExactStyle(
+ base.native_instance, weight, italic));
+ innerCache.put(key, typeface);
+ }
return typeface;
}
@@ -748,40 +788,18 @@ public class Typeface {
* @return The new typeface.
*/
public static Typeface createFromAsset(AssetManager mgr, String path) {
- if (path == null) {
- throw new NullPointerException(); // for backward compatibility
- }
- if (sFallbackFonts != null) {
- synchronized (sLock) {
- Typeface typeface = new Builder(mgr, path).build();
- if (typeface != null) return typeface;
-
- final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */,
- null /* axes */, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
- typeface = sDynamicTypefaceCache.get(key);
- if (typeface != null) return typeface;
+ Preconditions.checkNotNull(path); // for backward compatibility
+ Preconditions.checkNotNull(mgr);
- final FontFamily fontFamily = new FontFamily();
- if (fontFamily.addFontFromAssetManager(mgr, path, 0, true /* isAsset */,
- 0 /* ttc index */, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE,
- null /* axes */)) {
- // Due to backward compatibility, even if the font is not supported by our font
- // stack, we need to place the empty font at the first place. The typeface with
- // empty font behaves different from default typeface especially in fallback
- // font selection.
- fontFamily.allowUnsupportedFont();
- fontFamily.freeze();
- final FontFamily[] families = { fontFamily };
- typeface = createFromFamiliesWithDefault(families, RESOLVE_BY_FONT_TABLE,
- RESOLVE_BY_FONT_TABLE);
- sDynamicTypefaceCache.put(key, typeface);
- return typeface;
- } else {
- fontFamily.abortCreation();
- }
- }
+ Typeface typeface = new Builder(mgr, path).build();
+ if (typeface != null) return typeface;
+ // check if the file exists, and throw an exception for backward compatibility
+ try (InputStream inputStream = mgr.open(path)) {
+ } catch (IOException e) {
+ throw new RuntimeException("Font asset not found " + path);
}
- throw new RuntimeException("Font asset not found " + path);
+
+ return Typeface.DEFAULT;
}
/**
@@ -799,13 +817,22 @@ public class Typeface {
/**
* Create a new typeface from the specified font file.
*
- * @param path The path to the font data.
+ * @param file The path to the font data.
* @return The new typeface.
*/
- public static Typeface createFromFile(@Nullable File path) {
+ public static Typeface createFromFile(@Nullable File file) {
// For the compatibility reasons, leaving possible NPE here.
// See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull
- return createFromFile(path.getAbsolutePath());
+
+ Typeface typeface = new Builder(file).build();
+ if (typeface != null) return typeface;
+
+ // check if the file exists, and throw an exception for backward compatibility
+ if (!file.exists()) {
+ throw new RuntimeException("Font asset not found " + file.getAbsolutePath());
+ }
+
+ return Typeface.DEFAULT;
}
/**
@@ -815,24 +842,8 @@ public class Typeface {
* @return The new typeface.
*/
public static Typeface createFromFile(@Nullable String path) {
- if (sFallbackFonts != null) {
- final FontFamily fontFamily = new FontFamily();
- if (fontFamily.addFont(path, 0 /* ttcIndex */, null /* axes */,
- RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)) {
- // Due to backward compatibility, even if the font is not supported by our font
- // stack, we need to place the empty font at the first place. The typeface with
- // empty font behaves different from default typeface especially in fallback font
- // selection.
- fontFamily.allowUnsupportedFont();
- fontFamily.freeze();
- FontFamily[] families = { fontFamily };
- return createFromFamiliesWithDefault(families, RESOLVE_BY_FONT_TABLE,
- RESOLVE_BY_FONT_TABLE);
- } else {
- fontFamily.abortCreation();
- }
- }
- throw new RuntimeException("Font not found " + path);
+ Preconditions.checkNotNull(path); // for backward compatibility
+ return createFromFile(new File(path));
}
/**
@@ -852,24 +863,28 @@ public class Typeface {
/**
* Create a new typeface from an array of font families, including
* also the font families in the fallback list.
- * @param weight the weight for this family. {@link RESOLVE_BY_FONT_TABLE} can be used. In that
- * case, the table information in the first family's font is used. If the first
- * family has multiple fonts, the closest to the regular weight and upright font
- * is used.
- * @param italic the italic information for this family. {@link RESOLVE_BY_FONT_TABLE} can be
- * used. In that case, the table information in the first family's font is used.
- * If the first family has multiple fonts, the closest to the regular weight and
- * upright font is used.
+ * @param fallbackName the family name. If given families don't support characters, the
+ * characters will be rendered with this family.
+ * @param weight the weight for this family. In that case, the table information in the first
+ * family's font is used. If the first family has multiple fonts, the closest to
+ * the regular weight and upright font is used.
+ * @param italic the italic information for this family. In that case, the table information in
+ * the first family's font is used. If the first family has multiple fonts, the
+ * closest to the regular weight and upright font is used.
* @param families array of font families
*/
private static Typeface createFromFamiliesWithDefault(FontFamily[] families,
- int weight, int italic) {
- long[] ptrArray = new long[families.length + sFallbackFonts.length];
+ String fallbackName, int weight, int italic) {
+ FontFamily[] fallback = sSystemFallbackMap.get(fallbackName);
+ if (fallback == null) {
+ fallback = sSystemFallbackMap.get(DEFAULT_FAMILY);
+ }
+ long[] ptrArray = new long[families.length + fallback.length];
for (int i = 0; i < families.length; i++) {
ptrArray[i] = families[i].mNativePtr;
}
- for (int i = 0; i < sFallbackFonts.length; i++) {
- ptrArray[i + families.length] = sFallbackFonts[i].mNativePtr;
+ for (int i = 0; i < fallback.length; i++) {
+ ptrArray[i + families.length] = fallback[i].mNativePtr;
}
return new Typeface(nativeCreateFromArray(ptrArray, weight, italic));
}
@@ -885,113 +900,194 @@ public class Typeface {
mWeight = nativeGetWeight(ni);
}
- private static FontFamily makeFamilyFromParsed(FontConfig.Family family,
- Map<String, ByteBuffer> bufferForPath) {
- FontFamily fontFamily = new FontFamily(family.getLanguage(), family.getVariant());
- for (FontConfig.Font font : family.getFonts()) {
- String fullPathName = "/system/fonts/" + font.getFontName();
- ByteBuffer fontBuffer = bufferForPath.get(fullPathName);
- if (fontBuffer == null) {
- try (FileInputStream file = new FileInputStream(fullPathName)) {
- FileChannel fileChannel = file.getChannel();
- long fontSize = fileChannel.size();
- fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
- bufferForPath.put(fullPathName, fontBuffer);
- } catch (IOException e) {
- Log.e(TAG, "Error mapping font file " + fullPathName);
+ private static @Nullable ByteBuffer mmap(String fullPath) {
+ try (FileInputStream file = new FileInputStream(fullPath)) {
+ final FileChannel fileChannel = file.getChannel();
+ final long fontSize = fileChannel.size();
+ return fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
+ } catch (IOException e) {
+ Log.e(TAG, "Error mapping font file " + fullPath);
+ return null;
+ }
+ }
+
+ private static @Nullable FontFamily createFontFamily(
+ String familyName, List<FontConfig.Font> fonts, String[] languageTags, int variant,
+ Map<String, ByteBuffer> cache, String fontDir) {
+ final FontFamily family = new FontFamily(languageTags, variant);
+ for (int i = 0; i < fonts.size(); i++) {
+ final FontConfig.Font font = fonts.get(i);
+ final String fullPath = fontDir + font.getFontName();
+ ByteBuffer buffer = cache.get(fullPath);
+ if (buffer == null) {
+ if (cache.containsKey(fullPath)) {
+ continue; // Already failed to mmap. Skip it.
+ }
+ buffer = mmap(fullPath);
+ cache.put(fullPath, buffer);
+ if (buffer == null) {
continue;
}
}
- if (!fontFamily.addFontFromBuffer(fontBuffer, font.getTtcIndex(), font.getAxes(),
+ if (!family.addFontFromBuffer(buffer, font.getTtcIndex(), font.getAxes(),
font.getWeight(), font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL)) {
- Log.e(TAG, "Error creating font " + fullPathName + "#" + font.getTtcIndex());
+ Log.e(TAG, "Error creating font " + fullPath + "#" + font.getTtcIndex());
}
}
- if (!fontFamily.freeze()) {
- // Treat as system error since reaching here means that a system pre-installed font
- // can't be used by our font stack.
- Log.e(TAG, "Unable to load Family: " + family.getName() + ":" + family.getLanguage());
+ if (!family.freeze()) {
+ Log.e(TAG, "Unable to load Family: " + familyName + " : "
+ + Arrays.toString(languageTags));
return null;
}
- return fontFamily;
+ return family;
}
- /*
- * (non-Javadoc)
+ private static void pushFamilyToFallback(FontConfig.Family xmlFamily,
+ ArrayMap<String, ArrayList<FontFamily>> fallbackMap,
+ Map<String, ByteBuffer> cache,
+ String fontDir) {
+
+ final String[] languageTags = xmlFamily.getLanguages();
+ final int variant = xmlFamily.getVariant();
+
+ final ArrayList<FontConfig.Font> defaultFonts = new ArrayList<>();
+ final ArrayMap<String, ArrayList<FontConfig.Font>> specificFallbackFonts = new ArrayMap<>();
+
+ // Collect default fallback and specific fallback fonts.
+ for (final FontConfig.Font font : xmlFamily.getFonts()) {
+ final String fallbackName = font.getFallbackFor();
+ if (fallbackName == null) {
+ defaultFonts.add(font);
+ } else {
+ ArrayList<FontConfig.Font> fallback = specificFallbackFonts.get(fallbackName);
+ if (fallback == null) {
+ fallback = new ArrayList<>();
+ specificFallbackFonts.put(fallbackName, fallback);
+ }
+ fallback.add(font);
+ }
+ }
+
+ final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily(
+ xmlFamily.getName(), defaultFonts, languageTags, variant, cache, fontDir);
+
+ // Insert family into fallback map.
+ for (int i = 0; i < fallbackMap.size(); i++) {
+ final ArrayList<FontConfig.Font> fallback =
+ specificFallbackFonts.get(fallbackMap.keyAt(i));
+ if (fallback == null) {
+ if (defaultFamily != null) {
+ fallbackMap.valueAt(i).add(defaultFamily);
+ }
+ } else {
+ final FontFamily family = createFontFamily(
+ xmlFamily.getName(), fallback, languageTags, variant, cache, fontDir);
+ if (family != null) {
+ fallbackMap.valueAt(i).add(family);
+ } else if (defaultFamily != null) {
+ fallbackMap.valueAt(i).add(defaultFamily);
+ } else {
+ // There is no valid for for default fallback. Ignore.
+ }
+ }
+ }
+ }
+
+ /**
+ * Build the system fallback from xml file.
*
- * This should only be called once, from the static class initializer block.
+ * @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
*/
- private static void init() {
- // Load font config and initialize Minikin state
- File systemFontConfigLocation = getSystemFontConfigLocation();
- File configFilename = new File(systemFontConfigLocation, FONTS_CONFIG);
+ @VisibleForTesting
+ public static void buildSystemFallback(String xmlPath, String fontDir,
+ ArrayMap<String, Typeface> fontMap, ArrayMap<String, FontFamily[]> fallbackMap) {
try {
- FileInputStream fontsIn = new FileInputStream(configFilename);
- FontConfig fontConfig = FontListParser.parse(fontsIn);
-
- Map<String, ByteBuffer> bufferForPath = new HashMap<String, ByteBuffer>();
-
- List<FontFamily> familyList = new ArrayList<FontFamily>();
- // Note that the default typeface is always present in the fallback list;
- // this is an enhancement from pre-Minikin behavior.
- for (int i = 0; i < fontConfig.getFamilies().length; i++) {
- FontConfig.Family f = fontConfig.getFamilies()[i];
- if (i == 0 || f.getName() == null) {
- FontFamily family = makeFamilyFromParsed(f, bufferForPath);
- if (family != null) {
- familyList.add(family);
- }
+ 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);
}
- sFallbackFonts = familyList.toArray(new FontFamily[familyList.size()]);
- setDefault(Typeface.createFromFamilies(sFallbackFonts));
-
- Map<String, Typeface> systemFonts = new HashMap<String, Typeface>();
- for (int i = 0; i < fontConfig.getFamilies().length; i++) {
- Typeface typeface;
- FontConfig.Family f = fontConfig.getFamilies()[i];
- if (f.getName() != null) {
- if (i == 0) {
- // The first entry is the default typeface; no sense in
- // duplicating the corresponding FontFamily.
- typeface = sDefaultTypeface;
- } else {
- FontFamily fontFamily = makeFamilyFromParsed(f, bufferForPath);
- if (fontFamily == null) {
- continue;
- }
- FontFamily[] families = { fontFamily };
- typeface = Typeface.createFromFamiliesWithDefault(families,
- RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
- }
- systemFonts.put(f.getName(), typeface);
+
+ // 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);
}
}
- for (FontConfig.Alias alias : fontConfig.getAliases()) {
- Typeface base = systemFonts.get(alias.getToName());
+
+ // 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));
}
- systemFonts.put(alias.getName(), newFace);
+ fontMap.put(alias.getName(), newFace);
}
- sSystemFontMap = systemFonts;
-
} 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 " + configFilename, e);
+ Log.e(TAG, "Error opening " + xmlPath, e);
} catch (IOException e) {
- Log.e(TAG, "Error reading " + configFilename, e);
+ Log.e(TAG, "Error reading " + xmlPath, e);
} catch (XmlPullParserException e) {
- Log.e(TAG, "XML parse exception for " + configFilename, e);
+ Log.e(TAG, "XML parse exception for " + xmlPath, e);
}
}
static {
- init();
+ final ArrayMap<String, Typeface> systemFontMap = new ArrayMap<>();
+ final ArrayMap<String, FontFamily[]> systemFallbackMap = new ArrayMap<>();
+ buildSystemFallback("/system/etc/fonts.xml", "/system/fonts/", systemFontMap,
+ systemFallbackMap);
+ sSystemFontMap = Collections.unmodifiableMap(systemFontMap);
+ sSystemFallbackMap = Collections.unmodifiableMap(systemFallbackMap);
+
+ setDefault(sSystemFontMap.get(DEFAULT_FAMILY));
+
// Set up defaults and typefaces exposed in public API
DEFAULT = create((String) null, 0);
DEFAULT_BOLD = create((String) null, Typeface.BOLD);
@@ -1008,10 +1104,6 @@ public class Typeface {
}
- private static File getSystemFontConfigLocation() {
- return new File("/system/etc/");
- }
-
@Override
protected void finalize() throws Throwable {
try {
diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
new file mode 100644
index 000000000000..86e6fa8c2fc5
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
@@ -0,0 +1,470 @@
+/*
+ * 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.drawable;
+
+import dalvik.annotation.optimization.FastNative;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.InflateException;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.ImageDecoder;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import libcore.io.IoUtils;
+import libcore.util.NativeAllocationRegistry;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.Runnable;
+import java.util.ArrayList;
+
+/**
+ * {@link Drawable} for drawing animated images (like GIF).
+ *
+ * <p>Created by {@link ImageDecoder#decodeDrawable}. A user needs to call
+ * {@link #start} to start the animation.</p>
+ */
+public class AnimatedImageDrawable extends Drawable implements Animatable2 {
+ private int mIntrinsicWidth;
+ private int mIntrinsicHeight;
+
+ private boolean mStarting;
+
+ private Handler mHandler;
+
+ private class State {
+ State(long nativePtr, InputStream is, AssetFileDescriptor afd) {
+ mNativePtr = nativePtr;
+ mInputStream = is;
+ mAssetFd = afd;
+ }
+
+ public final long mNativePtr;
+
+ // These just keep references so the native code can continue using them.
+ private final InputStream mInputStream;
+ private final AssetFileDescriptor mAssetFd;
+ }
+
+ private State mState;
+
+ private Runnable mRunnable;
+
+ /**
+ * Pass this to {@link #setLoopCount} to loop infinitely.
+ *
+ * <p>{@link Animatable2.AnimationCallback#onAnimationEnd} will never be
+ * called unless there is an error.</p>
+ */
+ public static final int LOOP_INFINITE = -1;
+
+ /**
+ * Specify the number of times to loop the animation.
+ *
+ * <p>By default, the loop count in the encoded data is respected.</p>
+ */
+ public void setLoopCount(int loopCount) {
+ if (mState == null) {
+ throw new IllegalStateException("called setLoopCount on empty AnimatedImageDrawable");
+ }
+ nSetLoopCount(mState.mNativePtr, loopCount);
+ }
+
+ /**
+ * Create an empty AnimatedImageDrawable.
+ */
+ public AnimatedImageDrawable() {
+ mState = null;
+ }
+
+ @Override
+ public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+ throws XmlPullParserException, IOException {
+ super.inflate(r, parser, attrs, theme);
+
+ final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.AnimatedImageDrawable);
+ updateStateFromTypedArray(a, mSrcDensityOverride);
+ }
+
+ private void updateStateFromTypedArray(TypedArray a, int srcDensityOverride)
+ throws XmlPullParserException {
+ final Resources r = a.getResources();
+ final int srcResId = a.getResourceId(R.styleable.AnimatedImageDrawable_src, 0);
+ if (srcResId != 0) {
+ // Follow the density handling in BitmapDrawable.
+ final TypedValue value = new TypedValue();
+ r.getValueForDensity(srcResId, srcDensityOverride, value, true);
+ if (srcDensityOverride > 0 && value.density > 0
+ && value.density != TypedValue.DENSITY_NONE) {
+ if (value.density == srcDensityOverride) {
+ value.density = r.getDisplayMetrics().densityDpi;
+ } else {
+ value.density =
+ (value.density * r.getDisplayMetrics().densityDpi) / srcDensityOverride;
+ }
+ }
+
+ int density = Bitmap.DENSITY_NONE;
+ if (value.density == TypedValue.DENSITY_DEFAULT) {
+ density = DisplayMetrics.DENSITY_DEFAULT;
+ } else if (value.density != TypedValue.DENSITY_NONE) {
+ density = value.density;
+ }
+
+ Drawable drawable = null;
+ try {
+ InputStream is = r.openRawResource(srcResId, value);
+ ImageDecoder.Source source = ImageDecoder.createSource(r, is, density);
+ drawable = ImageDecoder.decodeDrawable(source, (decoder, info, src) -> {
+ if (!info.isAnimated()) {
+ throw new IllegalArgumentException("image is not animated");
+ }
+ });
+ } catch (IOException e) {
+ throw new XmlPullParserException(a.getPositionDescription() +
+ ": <animated-image> requires a valid 'src' attribute", null, e);
+ }
+
+ if (!(drawable instanceof AnimatedImageDrawable)) {
+ throw new XmlPullParserException(a.getPositionDescription() +
+ ": <animated-image> did not decode animated");
+ }
+
+ // Transfer the state of other to this one. other will be discarded.
+ AnimatedImageDrawable other = (AnimatedImageDrawable) drawable;
+ mState = other.mState;
+ other.mState = null;
+ mIntrinsicWidth = other.mIntrinsicWidth;
+ mIntrinsicHeight = other.mIntrinsicHeight;
+ }
+ }
+
+ /**
+ * @hide
+ * This should only be called by ImageDecoder.
+ *
+ * decoder is only non-null if it has a PostProcess
+ */
+ public AnimatedImageDrawable(long nativeImageDecoder,
+ @Nullable ImageDecoder decoder, int width, int height,
+ int srcDensity, int dstDensity, Rect cropRect,
+ InputStream inputStream, AssetFileDescriptor afd)
+ throws IOException {
+ width = Bitmap.scaleFromDensity(width, srcDensity, dstDensity);
+ height = Bitmap.scaleFromDensity(height, srcDensity, dstDensity);
+
+ if (cropRect == null) {
+ mIntrinsicWidth = width;
+ mIntrinsicHeight = height;
+ } else {
+ cropRect.set(Bitmap.scaleFromDensity(cropRect.left, srcDensity, dstDensity),
+ Bitmap.scaleFromDensity(cropRect.top, srcDensity, dstDensity),
+ Bitmap.scaleFromDensity(cropRect.right, srcDensity, dstDensity),
+ Bitmap.scaleFromDensity(cropRect.bottom, srcDensity, dstDensity));
+ mIntrinsicWidth = cropRect.width();
+ mIntrinsicHeight = cropRect.height();
+ }
+
+ mState = new State(nCreate(nativeImageDecoder, decoder, width, height, cropRect),
+ inputStream, afd);
+
+ // FIXME: Use the right size for the native allocation.
+ long nativeSize = 200;
+ NativeAllocationRegistry registry = new NativeAllocationRegistry(
+ AnimatedImageDrawable.class.getClassLoader(), nGetNativeFinalizer(), nativeSize);
+ registry.registerNativeAllocation(mState, mState.mNativePtr);
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mIntrinsicWidth;
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mIntrinsicHeight;
+ }
+
+ // nDraw returns -1 if the animation has finished.
+ private static final int FINISHED = -1;
+
+ @Override
+ public void draw(@NonNull Canvas canvas) {
+ if (mState == null) {
+ throw new IllegalStateException("called draw on empty AnimatedImageDrawable");
+ }
+
+ if (mStarting) {
+ mStarting = false;
+
+ postOnAnimationStart();
+ }
+
+ long nextUpdate = nDraw(mState.mNativePtr, canvas.getNativeCanvasWrapper());
+ // a value <= 0 indicates that the drawable is stopped or that renderThread
+ // will manage the animation
+ if (nextUpdate > 0) {
+ if (mRunnable == null) {
+ mRunnable = this::invalidateSelf;
+ }
+ scheduleSelf(mRunnable, nextUpdate);
+ } else if (nextUpdate == FINISHED) {
+ // This means the animation was drawn in software mode and ended.
+ postOnAnimationEnd();
+ }
+ }
+
+ @Override
+ public void setAlpha(@IntRange(from=0,to=255) int alpha) {
+ if (alpha < 0 || alpha > 255) {
+ throw new IllegalArgumentException("Alpha must be between 0 and"
+ + " 255! provided " + alpha);
+ }
+
+ if (mState == null) {
+ throw new IllegalStateException("called setAlpha on empty AnimatedImageDrawable");
+ }
+
+ nSetAlpha(mState.mNativePtr, alpha);
+ invalidateSelf();
+ }
+
+ @Override
+ public int getAlpha() {
+ if (mState == null) {
+ throw new IllegalStateException("called getAlpha on empty AnimatedImageDrawable");
+ }
+ return nGetAlpha(mState.mNativePtr);
+ }
+
+ @Override
+ public void setColorFilter(@Nullable ColorFilter colorFilter) {
+ if (mState == null) {
+ throw new IllegalStateException("called setColorFilter on empty AnimatedImageDrawable");
+ }
+
+ long nativeFilter = colorFilter == null ? 0 : colorFilter.getNativeInstance();
+ nSetColorFilter(mState.mNativePtr, nativeFilter);
+ invalidateSelf();
+ }
+
+ @Override
+ public @PixelFormat.Opacity int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+
+ @Override
+ public boolean setVisible(boolean visible, boolean restart) {
+ if (!super.setVisible(visible, restart)) {
+ return false;
+ }
+
+ if (!visible) {
+ nMarkInvisible(mState.mNativePtr);
+ }
+
+ return true;
+ }
+
+ // Animatable overrides
+ /**
+ * Return whether the animation is currently running.
+ *
+ * <p>When this drawable is created, this will return {@code false}. A client
+ * needs to call {@link #start} to start the animation.</p>
+ */
+ @Override
+ public boolean isRunning() {
+ if (mState == null) {
+ throw new IllegalStateException("called isRunning on empty AnimatedImageDrawable");
+ }
+ return nIsRunning(mState.mNativePtr);
+ }
+
+ /**
+ * Start the animation.
+ *
+ * <p>Does nothing if the animation is already running. If the animation is stopped,
+ * this will reset it.</p>
+ *
+ * <p>If the animation starts, this will call
+ * {@link Animatable2.AnimationCallback#onAnimationStart}.</p>
+ */
+ @Override
+ public void start() {
+ if (mState == null) {
+ throw new IllegalStateException("called start on empty AnimatedImageDrawable");
+ }
+
+ if (nStart(mState.mNativePtr)) {
+ mStarting = true;
+ invalidateSelf();
+ }
+ }
+
+ /**
+ * Stop the animation.
+ *
+ * <p>If the animation is stopped, it will continue to display the frame
+ * it was displaying when stopped.</p>
+ */
+ @Override
+ public void stop() {
+ if (mState == null) {
+ throw new IllegalStateException("called stop on empty AnimatedImageDrawable");
+ }
+ if (nStop(mState.mNativePtr)) {
+ postOnAnimationEnd();
+ }
+ }
+
+ // Animatable2 overrides
+ private ArrayList<Animatable2.AnimationCallback> mAnimationCallbacks = null;
+
+ @Override
+ public void registerAnimationCallback(@NonNull AnimationCallback callback) {
+ if (callback == null) {
+ return;
+ }
+
+ if (mAnimationCallbacks == null) {
+ mAnimationCallbacks = new ArrayList<Animatable2.AnimationCallback>();
+ nSetOnAnimationEndListener(mState.mNativePtr, this);
+ }
+
+ if (!mAnimationCallbacks.contains(callback)) {
+ mAnimationCallbacks.add(callback);
+ }
+ }
+
+ @Override
+ public boolean unregisterAnimationCallback(@NonNull AnimationCallback callback) {
+ if (callback == null || mAnimationCallbacks == null
+ || !mAnimationCallbacks.remove(callback)) {
+ return false;
+ }
+
+ if (mAnimationCallbacks.isEmpty()) {
+ clearAnimationCallbacks();
+ }
+
+ return true;
+ }
+
+ @Override
+ public void clearAnimationCallbacks() {
+ if (mAnimationCallbacks != null) {
+ mAnimationCallbacks = null;
+ nSetOnAnimationEndListener(mState.mNativePtr, null);
+ }
+ }
+
+ private void postOnAnimationStart() {
+ if (mAnimationCallbacks == null) {
+ return;
+ }
+
+ getHandler().post(() -> {
+ for (Animatable2.AnimationCallback callback : mAnimationCallbacks) {
+ callback.onAnimationStart(this);
+ }
+ });
+ }
+
+ private void postOnAnimationEnd() {
+ if (mAnimationCallbacks == null) {
+ return;
+ }
+
+ getHandler().post(() -> {
+ for (Animatable2.AnimationCallback callback : mAnimationCallbacks) {
+ callback.onAnimationEnd(this);
+ }
+ });
+ }
+
+ private Handler getHandler() {
+ if (mHandler == null) {
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+ return mHandler;
+ }
+
+ /**
+ * Called by JNI.
+ *
+ * The JNI code has already posted this to the thread that created the
+ * callback, so no need to post.
+ */
+ @SuppressWarnings("unused")
+ private void onAnimationEnd() {
+ if (mAnimationCallbacks != null) {
+ for (Animatable2.AnimationCallback callback : mAnimationCallbacks) {
+ callback.onAnimationEnd(this);
+ }
+ }
+ }
+
+
+ private static native long nCreate(long nativeImageDecoder,
+ @Nullable ImageDecoder decoder, int width, int height, Rect cropRect)
+ throws IOException;
+ @FastNative
+ private static native long nGetNativeFinalizer();
+ private static native long nDraw(long nativePtr, long canvasNativePtr);
+ @FastNative
+ private static native void nSetAlpha(long nativePtr, int alpha);
+ @FastNative
+ private static native int nGetAlpha(long nativePtr);
+ @FastNative
+ private static native void nSetColorFilter(long nativePtr, long nativeFilter);
+ @FastNative
+ private static native boolean nIsRunning(long nativePtr);
+ // Return whether the animation started.
+ @FastNative
+ private static native boolean nStart(long nativePtr);
+ @FastNative
+ private static native boolean nStop(long nativePtr);
+ @FastNative
+ private static native void nSetLoopCount(long nativePtr, int loopCount);
+ // Pass the drawable down to native so it can call onAnimationEnd.
+ private static native void nSetOnAnimationEndListener(long nativePtr,
+ @Nullable AnimatedImageDrawable drawable);
+ @FastNative
+ private static native long nNativeByteSize(long nativePtr);
+ @FastNative
+ private static native void nMarkInvisible(long nativePtr);
+}
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index 90d6ab867fe1..e74dc6dc9671 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -132,7 +132,7 @@ import dalvik.annotation.optimization.FastNative;
* <td>translateY</td>
* </tr>
* <tr>
- * <td rowspan="8">&lt;path&gt;</td>
+ * <td rowspan="9">&lt;path&gt;</td>
* <td>pathData</td>
* </tr>
* <tr>
@@ -154,6 +154,9 @@ import dalvik.annotation.optimization.FastNative;
* <td>trimPathStart</td>
* </tr>
* <tr>
+ * <td>trimPathEnd</td>
+ * </tr>
+ * <tr>
* <td>trimPathOffset</td>
* </tr>
* <tr>
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index f17cd768c386..05533d787aa1 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -1168,9 +1168,13 @@ public abstract class Drawable {
/**
* Create a drawable from an inputstream, using the given resources and
* value to determine density information.
+ *
+ * @deprecated Prefer the version without an Options object.
*/
- public static Drawable createFromResourceStream(Resources res, TypedValue value,
- InputStream is, String srcName, BitmapFactory.Options opts) {
+ @Nullable
+ public static Drawable createFromResourceStream(@Nullable Resources res,
+ @Nullable TypedValue value, @Nullable InputStream is, @Nullable String srcName,
+ @Nullable BitmapFactory.Options opts) {
if (is == null) {
return null;
}
diff --git a/graphics/java/android/graphics/drawable/DrawableInflater.java b/graphics/java/android/graphics/drawable/DrawableInflater.java
index eea7048ca534..0ee9071f4d06 100644
--- a/graphics/java/android/graphics/drawable/DrawableInflater.java
+++ b/graphics/java/android/graphics/drawable/DrawableInflater.java
@@ -185,6 +185,8 @@ public final class DrawableInflater {
return new BitmapDrawable();
case "nine-patch":
return new NinePatchDrawable();
+ case "animated-image":
+ return new AnimatedImageDrawable();
default:
return null;
}
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index 6c3aea2202a2..8b5114c50581 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -42,6 +42,7 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.SweepGradient;
+import android.graphics.Xfermode;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -814,6 +815,24 @@ public class GradientDrawable extends Drawable {
}
}
+ /**
+ * @param mode to draw this drawable with
+ * @hide
+ */
+ @Override
+ public void setXfermode(@Nullable Xfermode mode) {
+ super.setXfermode(mode);
+ mFillPaint.setXfermode(mode);
+ }
+
+ /**
+ * @param aa to draw this drawable with
+ * @hide
+ */
+ public void setAntiAlias(boolean aa) {
+ mFillPaint.setAntiAlias(aa);
+ }
+
private void buildPathIfDirty() {
final GradientState st = mGradientState;
if (mPathIsDirty) {
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index c329918afc27..749b75941ef9 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -819,8 +819,10 @@ public final class Icon implements Parcelable {
if (bitmapWidth > maxWidth || bitmapHeight > maxHeight) {
float scale = Math.min((float) maxWidth / bitmapWidth,
(float) maxHeight / bitmapHeight);
- bitmap = Bitmap.createScaledBitmap(bitmap, (int) (scale * bitmapWidth),
- (int) (scale * bitmapHeight), true /* filter */);
+ bitmap = Bitmap.createScaledBitmap(bitmap,
+ Math.max(1, (int) (scale * bitmapWidth)),
+ Math.max(1, (int) (scale * bitmapHeight)),
+ true /* filter */);
}
return bitmap;
}
diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java
index 6bd2646f9299..41d36986dfe2 100644
--- a/graphics/java/android/graphics/drawable/RippleBackground.java
+++ b/graphics/java/android/graphics/drawable/RippleBackground.java
@@ -16,17 +16,12 @@
package android.graphics.drawable;
-import android.animation.Animator;
-import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.graphics.Canvas;
-import android.graphics.CanvasProperty;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.FloatProperty;
-import android.view.DisplayListCanvas;
-import android.view.RenderNodeAnimator;
import android.view.animation.LinearInterpolator;
/**
@@ -36,138 +31,71 @@ class RippleBackground extends RippleComponent {
private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
- private static final int OPACITY_ENTER_DURATION = 600;
- private static final int OPACITY_ENTER_DURATION_FAST = 120;
- private static final int OPACITY_EXIT_DURATION = 480;
+ private static final int OPACITY_DURATION = 80;
- // Hardware rendering properties.
- private CanvasProperty<Paint> mPropPaint;
- private CanvasProperty<Float> mPropRadius;
- private CanvasProperty<Float> mPropX;
- private CanvasProperty<Float> mPropY;
+ private ObjectAnimator mAnimator;
- // Software rendering properties.
private float mOpacity = 0;
/** Whether this ripple is bounded. */
private boolean mIsBounded;
- public RippleBackground(RippleDrawable owner, Rect bounds, boolean isBounded,
- boolean forceSoftware) {
- super(owner, bounds, forceSoftware);
+ private boolean mFocused = false;
+ private boolean mHovered = false;
+
+ public RippleBackground(RippleDrawable owner, Rect bounds, boolean isBounded) {
+ super(owner, bounds);
mIsBounded = isBounded;
}
public boolean isVisible() {
- return mOpacity > 0 || isHardwareAnimating();
+ return mOpacity > 0;
}
- @Override
- protected boolean drawSoftware(Canvas c, Paint p) {
- boolean hasContent = false;
-
+ public void draw(Canvas c, Paint p) {
final int origAlpha = p.getAlpha();
- final int alpha = (int) (origAlpha * mOpacity + 0.5f);
+ final int alpha = Math.min((int) (origAlpha * mOpacity + 0.5f), 255);
if (alpha > 0) {
p.setAlpha(alpha);
c.drawCircle(0, 0, mTargetRadius, p);
p.setAlpha(origAlpha);
- hasContent = true;
}
-
- return hasContent;
- }
-
- @Override
- protected boolean drawHardware(DisplayListCanvas c) {
- c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
- return true;
}
- @Override
- protected Animator createSoftwareEnter(boolean fast) {
- // Linear enter based on current opacity.
- final int maxDuration = fast ? OPACITY_ENTER_DURATION_FAST : OPACITY_ENTER_DURATION;
- final int duration = (int) ((1 - mOpacity) * maxDuration);
-
- final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 1);
- opacity.setAutoCancel(true);
- opacity.setDuration(duration);
- opacity.setInterpolator(LINEAR_INTERPOLATOR);
-
- return opacity;
- }
-
- @Override
- protected Animator createSoftwareExit() {
- final AnimatorSet set = new AnimatorSet();
-
- // Linear exit after enter is completed.
- final ObjectAnimator exit = ObjectAnimator.ofFloat(this, RippleBackground.OPACITY, 0);
- exit.setInterpolator(LINEAR_INTERPOLATOR);
- exit.setDuration(OPACITY_EXIT_DURATION);
- exit.setAutoCancel(true);
-
- final AnimatorSet.Builder builder = set.play(exit);
-
- // Linear "fast" enter based on current opacity.
- final int fastEnterDuration = mIsBounded ?
- (int) ((1 - mOpacity) * OPACITY_ENTER_DURATION_FAST) : 0;
- if (fastEnterDuration > 0) {
- final ObjectAnimator enter = ObjectAnimator.ofFloat(this, RippleBackground.OPACITY, 1);
- enter.setInterpolator(LINEAR_INTERPOLATOR);
- enter.setDuration(fastEnterDuration);
- enter.setAutoCancel(true);
-
- builder.after(enter);
+ public void setState(boolean focused, boolean hovered, boolean pressed) {
+ if (!mFocused) {
+ focused = focused && !pressed;
}
-
- return set;
- }
-
- @Override
- protected RenderNodeAnimatorSet createHardwareExit(Paint p) {
- final RenderNodeAnimatorSet set = new RenderNodeAnimatorSet();
-
- final int targetAlpha = p.getAlpha();
- final int currentAlpha = (int) (mOpacity * targetAlpha + 0.5f);
- p.setAlpha(currentAlpha);
-
- mPropPaint = CanvasProperty.createPaint(p);
- mPropRadius = CanvasProperty.createFloat(mTargetRadius);
- mPropX = CanvasProperty.createFloat(0);
- mPropY = CanvasProperty.createFloat(0);
-
- final int fastEnterDuration = mIsBounded ?
- (int) ((1 - mOpacity) * OPACITY_ENTER_DURATION_FAST) : 0;
-
- // Linear exit after enter is completed.
- final RenderNodeAnimator exit = new RenderNodeAnimator(
- mPropPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
- exit.setInterpolator(LINEAR_INTERPOLATOR);
- exit.setDuration(OPACITY_EXIT_DURATION);
- if (fastEnterDuration > 0) {
- exit.setStartDelay(fastEnterDuration);
- exit.setStartValue(targetAlpha);
+ if (!mHovered) {
+ hovered = hovered && !pressed;
}
- set.add(exit);
-
- // Linear "fast" enter based on current opacity.
- if (fastEnterDuration > 0) {
- final RenderNodeAnimator enter = new RenderNodeAnimator(
- mPropPaint, RenderNodeAnimator.PAINT_ALPHA, targetAlpha);
- enter.setInterpolator(LINEAR_INTERPOLATOR);
- enter.setDuration(fastEnterDuration);
- set.add(enter);
+ if (mHovered != hovered || mFocused != focused) {
+ mHovered = hovered;
+ mFocused = focused;
+ onStateChanged();
}
+ }
- return set;
+ private void onStateChanged() {
+ float newOpacity = 0.0f;
+ if (mHovered) newOpacity += .25f;
+ if (mFocused) newOpacity += .75f;
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ mAnimator = null;
+ }
+ mAnimator = ObjectAnimator.ofFloat(this, OPACITY, newOpacity);
+ mAnimator.setDuration(OPACITY_DURATION);
+ mAnimator.setInterpolator(LINEAR_INTERPOLATOR);
+ mAnimator.start();
}
- @Override
- protected void jumpValuesToExit() {
- mOpacity = 0;
+ public void jumpToFinal() {
+ if (mAnimator != null) {
+ mAnimator.end();
+ mAnimator = null;
+ }
}
private static abstract class BackgroundProperty extends FloatProperty<RippleBackground> {
diff --git a/graphics/java/android/graphics/drawable/RippleComponent.java b/graphics/java/android/graphics/drawable/RippleComponent.java
index e83513c644db..626bcee9454b 100644
--- a/graphics/java/android/graphics/drawable/RippleComponent.java
+++ b/graphics/java/android/graphics/drawable/RippleComponent.java
@@ -27,23 +27,14 @@ import android.view.RenderNodeAnimator;
import java.util.ArrayList;
/**
- * Abstract class that handles hardware/software hand-off and lifecycle for
- * animated ripple foreground and background components.
+ * Abstract class that handles size & positioning common to the ripple & focus states.
*/
abstract class RippleComponent {
- private final RippleDrawable mOwner;
+ protected final RippleDrawable mOwner;
/** Bounds used for computing max radius. May be modified by the owner. */
protected final Rect mBounds;
- /** Whether we can use hardware acceleration for the exit animation. */
- private boolean mHasDisplayListCanvas;
-
- private boolean mHasPendingHardwareAnimator;
- private RenderNodeAnimatorSet mHardwareAnimator;
-
- private Animator mSoftwareAnimator;
-
/** Whether we have an explicit maximum radius. */
private boolean mHasMaxRadius;
@@ -53,16 +44,9 @@ abstract class RippleComponent {
/** Screen density used to adjust pixel-based constants. */
protected float mDensityScale;
- /**
- * If set, force all ripple animations to not run on RenderThread, even if it would be
- * available.
- */
- private final boolean mForceSoftware;
-
- public RippleComponent(RippleDrawable owner, Rect bounds, boolean forceSoftware) {
+ public RippleComponent(RippleDrawable owner, Rect bounds) {
mOwner = owner;
mBounds = bounds;
- mForceSoftware = forceSoftware;
}
public void onBoundsChange() {
@@ -92,89 +76,6 @@ abstract class RippleComponent {
}
/**
- * Starts a ripple enter animation.
- *
- * @param fast whether the ripple should enter quickly
- */
- public final void enter(boolean fast) {
- cancel();
-
- mSoftwareAnimator = createSoftwareEnter(fast);
-
- if (mSoftwareAnimator != null) {
- mSoftwareAnimator.start();
- }
- }
-
- /**
- * Starts a ripple exit animation.
- */
- public final void exit() {
- cancel();
-
- if (mHasDisplayListCanvas) {
- // We don't have access to a canvas here, but we expect one on the
- // next frame. We'll start the render thread animation then.
- mHasPendingHardwareAnimator = true;
-
- // Request another frame.
- invalidateSelf();
- } else {
- mSoftwareAnimator = createSoftwareExit();
- mSoftwareAnimator.start();
- }
- }
-
- /**
- * Cancels all animations. Software animation values are left in the
- * current state, while hardware animation values jump to the end state.
- */
- public void cancel() {
- cancelSoftwareAnimations();
- endHardwareAnimations();
- }
-
- /**
- * Ends all animations, jumping values to the end state.
- */
- public void end() {
- endSoftwareAnimations();
- endHardwareAnimations();
- }
-
- /**
- * Draws the ripple to the canvas, inheriting the paint's color and alpha
- * properties.
- *
- * @param c the canvas to which the ripple should be drawn
- * @param p the paint used to draw the ripple
- * @return {@code true} if something was drawn, {@code false} otherwise
- */
- public boolean draw(Canvas c, Paint p) {
- final boolean hasDisplayListCanvas = !mForceSoftware && c.isHardwareAccelerated()
- && c instanceof DisplayListCanvas;
- if (mHasDisplayListCanvas != hasDisplayListCanvas) {
- mHasDisplayListCanvas = hasDisplayListCanvas;
-
- if (!hasDisplayListCanvas) {
- // We've switched from hardware to non-hardware mode. Panic.
- endHardwareAnimations();
- }
- }
-
- if (hasDisplayListCanvas) {
- final DisplayListCanvas hw = (DisplayListCanvas) c;
- startPendingAnimation(hw, p);
-
- if (mHardwareAnimator != null) {
- return drawHardware(hw);
- }
- }
-
- return drawSoftware(c, p);
- }
-
- /**
* Populates {@code bounds} with the maximum drawing bounds of the ripple
* relative to its center. The resulting bounds should be translated into
* parent drawable coordinates before use.
@@ -186,85 +87,14 @@ abstract class RippleComponent {
bounds.set(-r, -r, r, r);
}
- /**
- * Starts the pending hardware animation, if available.
- *
- * @param hw hardware canvas on which the animation should draw
- * @param p paint whose properties the hardware canvas should use
- */
- private void startPendingAnimation(DisplayListCanvas hw, Paint p) {
- if (mHasPendingHardwareAnimator) {
- mHasPendingHardwareAnimator = false;
-
- mHardwareAnimator = createHardwareExit(new Paint(p));
- mHardwareAnimator.start(hw);
-
- // Preemptively jump the software values to the end state now that
- // the hardware exit has read whatever values it needs.
- jumpValuesToExit();
- }
- }
-
- /**
- * Cancels any current software animations, leaving the values in their
- * current state.
- */
- private void cancelSoftwareAnimations() {
- if (mSoftwareAnimator != null) {
- mSoftwareAnimator.cancel();
- mSoftwareAnimator = null;
- }
- }
-
- /**
- * Ends any current software animations, jumping the values to their end
- * state.
- */
- private void endSoftwareAnimations() {
- if (mSoftwareAnimator != null) {
- mSoftwareAnimator.end();
- mSoftwareAnimator = null;
- }
- }
-
- /**
- * Ends any pending or current hardware animations.
- * <p>
- * Hardware animations can't synchronize values back to the software
- * thread, so there is no "cancel" equivalent.
- */
- private void endHardwareAnimations() {
- if (mHardwareAnimator != null) {
- mHardwareAnimator.end();
- mHardwareAnimator = null;
- }
-
- if (mHasPendingHardwareAnimator) {
- mHasPendingHardwareAnimator = false;
-
- // Manually jump values to their exited state. Normally we'd do that
- // later when starting the hardware exit, but we're aborting early.
- jumpValuesToExit();
- }
- }
-
protected final void invalidateSelf() {
mOwner.invalidateSelf(false);
}
- protected final boolean isHardwareAnimating() {
- return mHardwareAnimator != null && mHardwareAnimator.isRunning()
- || mHasPendingHardwareAnimator;
- }
-
protected final void onHotspotBoundsChanged() {
if (!mHasMaxRadius) {
- final float halfWidth = mBounds.width() / 2.0f;
- final float halfHeight = mBounds.height() / 2.0f;
- final float targetRadius = (float) Math.sqrt(halfWidth * halfWidth
- + halfHeight * halfHeight);
-
- onTargetRadiusChanged(targetRadius);
+ mTargetRadius = getTargetRadius(mBounds);
+ onTargetRadiusChanged(mTargetRadius);
}
}
@@ -276,76 +106,4 @@ abstract class RippleComponent {
protected void onTargetRadiusChanged(float targetRadius) {
// Stub.
}
-
- protected abstract Animator createSoftwareEnter(boolean fast);
-
- protected abstract Animator createSoftwareExit();
-
- protected abstract RenderNodeAnimatorSet createHardwareExit(Paint p);
-
- protected abstract boolean drawHardware(DisplayListCanvas c);
-
- protected abstract boolean drawSoftware(Canvas c, Paint p);
-
- /**
- * Called when the hardware exit is cancelled. Jumps software values to end
- * state to ensure that software and hardware values are synchronized.
- */
- protected abstract void jumpValuesToExit();
-
- public static class RenderNodeAnimatorSet {
- private final ArrayList<RenderNodeAnimator> mAnimators = new ArrayList<>();
-
- public void add(RenderNodeAnimator anim) {
- mAnimators.add(anim);
- }
-
- public void clear() {
- mAnimators.clear();
- }
-
- public void start(DisplayListCanvas target) {
- if (target == null) {
- throw new IllegalArgumentException("Hardware canvas must be non-null");
- }
-
- final ArrayList<RenderNodeAnimator> animators = mAnimators;
- final int N = animators.size();
- for (int i = 0; i < N; i++) {
- final RenderNodeAnimator anim = animators.get(i);
- anim.setTarget(target);
- anim.start();
- }
- }
-
- public void cancel() {
- final ArrayList<RenderNodeAnimator> animators = mAnimators;
- final int N = animators.size();
- for (int i = 0; i < N; i++) {
- final RenderNodeAnimator anim = animators.get(i);
- anim.cancel();
- }
- }
-
- public void end() {
- final ArrayList<RenderNodeAnimator> animators = mAnimators;
- final int N = animators.size();
- for (int i = 0; i < N; i++) {
- final RenderNodeAnimator anim = animators.get(i);
- anim.end();
- }
- }
-
- public boolean isRunning() {
- final ArrayList<RenderNodeAnimator> animators = mAnimators;
- final int N = animators.size();
- for (int i = 0; i < N; i++) {
- final RenderNodeAnimator anim = animators.get(i);
- if (anim.isRunning()) {
- return true;
- }
- }
- return false;
- }
- }
}
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 8f314c9c36aa..0da61c29bd8d 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.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.content.pm.ActivityInfo.Config;
@@ -42,6 +37,11 @@ import android.graphics.Rect;
import android.graphics.Shader;
import android.util.AttributeSet;
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.IOException;
import java.util.Arrays;
@@ -135,9 +135,6 @@ public class RippleDrawable extends LayerDrawable {
private PorterDuffColorFilter mMaskColorFilter;
private boolean mHasValidMask;
- /** Whether we expect to draw a background when visible. */
- private boolean mBackgroundActive;
-
/** The current ripple. May be actively animating or pending entry. */
private RippleForeground mRipple;
@@ -217,7 +214,7 @@ public class RippleDrawable extends LayerDrawable {
}
if (mBackground != null) {
- mBackground.end();
+ mBackground.jumpToFinal();
}
cancelExitingRipples();
@@ -267,7 +264,7 @@ public class RippleDrawable extends LayerDrawable {
}
setRippleActive(enabled && pressed);
- setBackgroundActive(hovered || focused || (enabled && pressed), focused || hovered);
+ setBackgroundActive(hovered, focused, pressed);
return changed;
}
@@ -283,14 +280,13 @@ public class RippleDrawable extends LayerDrawable {
}
}
- private void setBackgroundActive(boolean active, boolean focused) {
- if (mBackgroundActive != active) {
- mBackgroundActive = active;
- if (active) {
- tryBackgroundEnter(focused);
- } else {
- tryBackgroundExit();
- }
+ private void setBackgroundActive(boolean hovered, boolean focused, boolean pressed) {
+ if (mBackground == null && (hovered || focused)) {
+ mBackground = new RippleBackground(this, mHotspotBounds, isBounded());
+ mBackground.setup(mState.mMaxRadius, mDensity);
+ }
+ if (mBackground != null) {
+ mBackground.setState(focused, hovered, pressed);
}
}
@@ -303,6 +299,12 @@ public class RippleDrawable extends LayerDrawable {
onHotspotBoundsChanged();
}
+ final int count = mExitingRipplesCount;
+ final RippleForeground[] ripples = mExitingRipples;
+ for (int i = 0; i < count; i++) {
+ ripples[i].onBoundsChange();
+ }
+
if (mBackground != null) {
mBackground.onBoundsChange();
}
@@ -327,10 +329,6 @@ public class RippleDrawable extends LayerDrawable {
tryRippleEnter();
}
- if (mBackgroundActive) {
- tryBackgroundEnter(false);
- }
-
// Skip animations, just show the correct final states.
jumpToCurrentState();
}
@@ -546,26 +544,6 @@ public class RippleDrawable extends LayerDrawable {
}
/**
- * Creates an active hotspot at the specified location.
- */
- private void tryBackgroundEnter(boolean focused) {
- if (mBackground == null) {
- final boolean isBounded = isBounded();
- mBackground = new RippleBackground(this, mHotspotBounds, isBounded, mForceSoftware);
- }
-
- mBackground.setup(mState.mMaxRadius, mDensity);
- mBackground.enter(focused);
- }
-
- private void tryBackgroundExit() {
- if (mBackground != null) {
- // Don't null out the background, we need it to draw!
- mBackground.exit();
- }
- }
-
- /**
* Attempts to start an enter animation for the active hotspot. Fails if
* there are too many animating ripples.
*/
@@ -588,12 +566,11 @@ public class RippleDrawable extends LayerDrawable {
y = mHotspotBounds.exactCenterY();
}
- final boolean isBounded = isBounded();
- mRipple = new RippleForeground(this, mHotspotBounds, x, y, isBounded, mForceSoftware);
+ mRipple = new RippleForeground(this, mHotspotBounds, x, y, mForceSoftware);
}
mRipple.setup(mState.mMaxRadius, mDensity);
- mRipple.enter(false);
+ mRipple.enter();
}
/**
@@ -623,9 +600,7 @@ public class RippleDrawable extends LayerDrawable {
}
if (mBackground != null) {
- mBackground.end();
- mBackground = null;
- mBackgroundActive = false;
+ mBackground.setState(false, false, false);
}
cancelExitingRipples();
@@ -693,7 +668,9 @@ public class RippleDrawable extends LayerDrawable {
// have a mask or content and the ripple bounds if we're projecting.
final Rect bounds = getDirtyBounds();
final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
- canvas.clipRect(bounds);
+ if (isBounded()) {
+ canvas.clipRect(bounds);
+ }
drawContent(canvas);
drawBackgroundAndRipples(canvas);
@@ -856,38 +833,8 @@ public class RippleDrawable extends LayerDrawable {
final float y = mHotspotBounds.exactCenterY();
canvas.translate(x, y);
- updateMaskShaderIfNeeded();
-
- // Position the shader to account for canvas translation.
- if (mMaskShader != null) {
- final Rect bounds = getBounds();
- mMaskMatrix.setTranslate(bounds.left - x, bounds.top - y);
- mMaskShader.setLocalMatrix(mMaskMatrix);
- }
-
- // Grab the color for the current state and cut the alpha channel in
- // half so that the ripple and background together yield full alpha.
- final int color = mState.mColor.getColorForState(getState(), Color.BLACK);
- final int halfAlpha = (Color.alpha(color) / 2) << 24;
final Paint p = getRipplePaint();
- if (mMaskColorFilter != null) {
- // The ripple timing depends on the paint's alpha value, so we need
- // to push just the alpha channel into the paint and let the filter
- // handle the full-alpha color.
- final int fullAlphaColor = color | (0xFF << 24);
- mMaskColorFilter.setColor(fullAlphaColor);
-
- p.setColor(halfAlpha);
- p.setColorFilter(mMaskColorFilter);
- p.setShader(mMaskShader);
- } else {
- final int halfAlphaColor = (color & 0xFFFFFF) | halfAlpha;
- p.setColor(halfAlphaColor);
- p.setColorFilter(null);
- p.setShader(null);
- }
-
if (background != null && background.isVisible()) {
background.draw(canvas, p);
}
@@ -910,13 +857,48 @@ public class RippleDrawable extends LayerDrawable {
mMask.draw(canvas);
}
- private Paint getRipplePaint() {
+ Paint getRipplePaint() {
if (mRipplePaint == null) {
mRipplePaint = new Paint();
mRipplePaint.setAntiAlias(true);
mRipplePaint.setStyle(Paint.Style.FILL);
}
- return mRipplePaint;
+
+ final float x = mHotspotBounds.exactCenterX();
+ final float y = mHotspotBounds.exactCenterY();
+
+ updateMaskShaderIfNeeded();
+
+ // Position the shader to account for canvas translation.
+ if (mMaskShader != null) {
+ final Rect bounds = getBounds();
+ mMaskMatrix.setTranslate(bounds.left - x, bounds.top - y);
+ mMaskShader.setLocalMatrix(mMaskMatrix);
+ }
+
+ // Grab the color for the current state and cut the alpha channel in
+ // half so that the ripple and background together yield full alpha.
+ int color = mState.mColor.getColorForState(getState(), Color.BLACK);
+ if (Color.alpha(color) > 128) {
+ color = (color & 0x00FFFFFF) | 0x80000000;
+ }
+ final Paint p = mRipplePaint;
+
+ if (mMaskColorFilter != null) {
+ // The ripple timing depends on the paint's alpha value, so we need
+ // to push just the alpha channel into the paint and let the filter
+ // handle the full-alpha color.
+ mMaskColorFilter.setColor(color | 0xFF000000);
+ p.setColor(color & 0xFF000000);
+ p.setColorFilter(mMaskColorFilter);
+ p.setShader(mMaskShader);
+ } else {
+ p.setColor(color);
+ p.setColorFilter(null);
+ p.setShader(null);
+ }
+
+ return p;
}
@Override
diff --git a/graphics/java/android/graphics/drawable/RippleForeground.java b/graphics/java/android/graphics/drawable/RippleForeground.java
index 829733e9b097..a8dc34af292b 100644
--- a/graphics/java/android/graphics/drawable/RippleForeground.java
+++ b/graphics/java/android/graphics/drawable/RippleForeground.java
@@ -18,7 +18,6 @@ package android.graphics.drawable;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.graphics.Canvas;
@@ -29,29 +28,29 @@ 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;
+import android.view.animation.PathInterpolator;
+
+import java.util.ArrayList;
/**
* Draws a ripple foreground.
*/
class RippleForeground extends RippleComponent {
private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
- private static final TimeInterpolator DECELERATE_INTERPOLATOR = new LogDecelerateInterpolator(
- 400f, 1.4f, 0);
-
- // Pixel-based accelerations and velocities.
- private static final float WAVE_TOUCH_DOWN_ACCELERATION = 1024;
- private static final float WAVE_TOUCH_UP_ACCELERATION = 3400;
- private static final float WAVE_OPACITY_DECAY_VELOCITY = 3;
+ // Matches R.interpolator.fast_out_slow_in but as we have no context we can't just import that
+ private static final TimeInterpolator DECELERATE_INTERPOLATOR =
+ new PathInterpolator(0.4f, 0f, 0.2f, 1f);
- // Bounded ripple animation properties.
- private static final int BOUNDED_ORIGIN_EXIT_DURATION = 300;
- private static final int BOUNDED_RADIUS_EXIT_DURATION = 800;
- private static final int BOUNDED_OPACITY_EXIT_DURATION = 400;
- private static final float MAX_BOUNDED_RADIUS = 350;
+ // Time it takes for the ripple to expand
+ private static final int RIPPLE_ENTER_DURATION = 225;
+ // Time it takes for the ripple to slide from the touch to the center point
+ private static final int RIPPLE_ORIGIN_DURATION = 225;
- private static final int RIPPLE_ENTER_DELAY = 80;
- private static final int OPACITY_ENTER_DURATION_FAST = 120;
+ private static final int OPACITY_ENTER_DURATION = 75;
+ private static final int OPACITY_EXIT_DURATION = 150;
+ private static final int OPACITY_HOLD_DURATION = OPACITY_ENTER_DURATION + 150;
// Parent-relative values for starting position.
private float mStartingX;
@@ -69,48 +68,58 @@ class RippleForeground extends RippleComponent {
private float mTargetX = 0;
private float mTargetY = 0;
- /** Ripple target radius used when bounded. Not used for clamping. */
- private float mBoundedRadius = 0;
-
// Software rendering properties.
- private float mOpacity = 1;
+ private float mOpacity = 0;
// Values used to tween between the start and end positions.
private float mTweenRadius = 0;
private float mTweenX = 0;
private float mTweenY = 0;
- /** Whether this ripple is bounded. */
- private boolean mIsBounded;
-
/** Whether this ripple has finished its exit animation. */
private boolean mHasFinishedExit;
+ /** Whether we can use hardware acceleration for the exit animation. */
+ private boolean mUsingProperties;
+
+ private long mEnterStartedAtMillis;
+
+ private ArrayList<RenderNodeAnimator> mPendingHwAnimators = new ArrayList<>();
+ private ArrayList<RenderNodeAnimator> mRunningHwAnimators = new ArrayList<>();
+
+ private ArrayList<Animator> mRunningSwAnimators = new ArrayList<>();
+
+ /**
+ * If set, force all ripple animations to not run on RenderThread, even if it would be
+ * available.
+ */
+ private final boolean mForceSoftware;
+
+ /**
+ * If we have a bound, don't start from 0. Start from 60% of the max out of width and height.
+ */
+ private float mStartRadius = 0;
+
public RippleForeground(RippleDrawable owner, Rect bounds, float startingX, float startingY,
- boolean isBounded, boolean forceSoftware) {
- super(owner, bounds, forceSoftware);
+ boolean forceSoftware) {
+ super(owner, bounds);
- mIsBounded = isBounded;
+ mForceSoftware = forceSoftware;
mStartingX = startingX;
mStartingY = startingY;
- if (isBounded) {
- mBoundedRadius = MAX_BOUNDED_RADIUS * 0.9f
- + (float) (MAX_BOUNDED_RADIUS * Math.random() * 0.1);
- } else {
- mBoundedRadius = 0;
- }
+ // Take 60% of the maximum of the width and height, then divided half to get the radius.
+ mStartRadius = Math.max(bounds.width(), bounds.height()) * 0.3f;
+ clampStartingPosition();
}
@Override
protected void onTargetRadiusChanged(float targetRadius) {
clampStartingPosition();
+ switchToUiThreadAnimation();
}
- @Override
- protected boolean drawSoftware(Canvas c, Paint p) {
- boolean hasContent = false;
-
+ private void drawSoftware(Canvas c, Paint p) {
final int origAlpha = p.getAlpha();
final int alpha = (int) (origAlpha * mOpacity + 0.5f);
final float radius = getCurrentRadius();
@@ -120,16 +129,51 @@ class RippleForeground extends RippleComponent {
p.setAlpha(alpha);
c.drawCircle(x, y, radius, p);
p.setAlpha(origAlpha);
- hasContent = true;
}
+ }
- return hasContent;
+ private void startPending(DisplayListCanvas c) {
+ if (!mPendingHwAnimators.isEmpty()) {
+ for (int i = 0; i < mPendingHwAnimators.size(); i++) {
+ RenderNodeAnimator animator = mPendingHwAnimators.get(i);
+ animator.setTarget(c);
+ animator.start();
+ mRunningHwAnimators.add(animator);
+ }
+ mPendingHwAnimators.clear();
+ }
}
- @Override
- protected boolean drawHardware(DisplayListCanvas c) {
- c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
- return true;
+ private void pruneHwFinished() {
+ if (!mRunningHwAnimators.isEmpty()) {
+ for (int i = mRunningHwAnimators.size() - 1; i >= 0; i--) {
+ if (!mRunningHwAnimators.get(i).isRunning()) {
+ mRunningHwAnimators.remove(i);
+ }
+ }
+ }
+ }
+
+ private void pruneSwFinished() {
+ if (!mRunningSwAnimators.isEmpty()) {
+ for (int i = mRunningSwAnimators.size() - 1; i >= 0; i--) {
+ if (!mRunningSwAnimators.get(i).isRunning()) {
+ mRunningSwAnimators.remove(i);
+ }
+ }
+ }
+ }
+
+ private void drawHardware(DisplayListCanvas c, Paint p) {
+ startPending(c);
+ pruneHwFinished();
+ if (mPropPaint != null) {
+ mUsingProperties = true;
+ c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
+ } else {
+ mUsingProperties = false;
+ drawSoftware(c, p);
+ }
}
/**
@@ -160,170 +204,143 @@ class RippleForeground extends RippleComponent {
return mHasFinishedExit;
}
- @Override
- protected Animator createSoftwareEnter(boolean fast) {
- // Bounded ripples don't have enter animations.
- if (mIsBounded) {
- return null;
+ private long computeFadeOutDelay() {
+ long timeSinceEnter = AnimationUtils.currentAnimationTimeMillis() - mEnterStartedAtMillis;
+ if (timeSinceEnter > 0 && timeSinceEnter < OPACITY_HOLD_DURATION) {
+ return OPACITY_HOLD_DURATION - timeSinceEnter;
}
-
- final int duration = (int)
- (1000 * Math.sqrt(mTargetRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensityScale) + 0.5);
-
- final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1);
- tweenRadius.setAutoCancel(true);
- tweenRadius.setDuration(duration);
- tweenRadius.setInterpolator(LINEAR_INTERPOLATOR);
- tweenRadius.setStartDelay(RIPPLE_ENTER_DELAY);
-
- final ObjectAnimator tweenOrigin = ObjectAnimator.ofFloat(this, TWEEN_ORIGIN, 1);
- tweenOrigin.setAutoCancel(true);
- tweenOrigin.setDuration(duration);
- tweenOrigin.setInterpolator(LINEAR_INTERPOLATOR);
- tweenOrigin.setStartDelay(RIPPLE_ENTER_DELAY);
-
- final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 1);
- opacity.setAutoCancel(true);
- opacity.setDuration(OPACITY_ENTER_DURATION_FAST);
- opacity.setInterpolator(LINEAR_INTERPOLATOR);
-
- final AnimatorSet set = new AnimatorSet();
- set.play(tweenOrigin).with(tweenRadius).with(opacity);
-
- return set;
- }
-
- private float getCurrentX() {
- return MathUtils.lerp(mClampedStartingX - mBounds.exactCenterX(), mTargetX, mTweenX);
- }
-
- private float getCurrentY() {
- return MathUtils.lerp(mClampedStartingY - mBounds.exactCenterY(), mTargetY, mTweenY);
+ return 0;
}
- private int getRadiusExitDuration() {
- final float remainingRadius = mTargetRadius - getCurrentRadius();
- return (int) (1000 * Math.sqrt(remainingRadius / (WAVE_TOUCH_UP_ACCELERATION
- + WAVE_TOUCH_DOWN_ACCELERATION) * mDensityScale) + 0.5);
- }
-
- private float getCurrentRadius() {
- return MathUtils.lerp(0, mTargetRadius, mTweenRadius);
- }
-
- private int getOpacityExitDuration() {
- return (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
- }
-
- /**
- * Compute target values that are dependent on bounding.
- */
- private void computeBoundedTargetValues() {
- mTargetX = (mClampedStartingX - mBounds.exactCenterX()) * .7f;
- mTargetY = (mClampedStartingY - mBounds.exactCenterY()) * .7f;
- mTargetRadius = mBoundedRadius;
- }
-
- @Override
- protected Animator createSoftwareExit() {
- final int radiusDuration;
- final int originDuration;
- final int opacityDuration;
- if (mIsBounded) {
- computeBoundedTargetValues();
-
- radiusDuration = BOUNDED_RADIUS_EXIT_DURATION;
- originDuration = BOUNDED_ORIGIN_EXIT_DURATION;
- opacityDuration = BOUNDED_OPACITY_EXIT_DURATION;
- } else {
- radiusDuration = getRadiusExitDuration();
- originDuration = radiusDuration;
- opacityDuration = getOpacityExitDuration();
+ private void startSoftwareEnter() {
+ for (int i = 0; i < mRunningSwAnimators.size(); i++) {
+ mRunningSwAnimators.get(i).cancel();
}
+ mRunningSwAnimators.clear();
final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1);
- tweenRadius.setAutoCancel(true);
- tweenRadius.setDuration(radiusDuration);
+ tweenRadius.setDuration(RIPPLE_ENTER_DURATION);
tweenRadius.setInterpolator(DECELERATE_INTERPOLATOR);
+ tweenRadius.start();
+ mRunningSwAnimators.add(tweenRadius);
final ObjectAnimator tweenOrigin = ObjectAnimator.ofFloat(this, TWEEN_ORIGIN, 1);
- tweenOrigin.setAutoCancel(true);
- tweenOrigin.setDuration(originDuration);
+ tweenOrigin.setDuration(RIPPLE_ORIGIN_DURATION);
tweenOrigin.setInterpolator(DECELERATE_INTERPOLATOR);
+ tweenOrigin.start();
+ mRunningSwAnimators.add(tweenOrigin);
- final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 0);
- opacity.setAutoCancel(true);
- opacity.setDuration(opacityDuration);
+ final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 1);
+ opacity.setDuration(OPACITY_ENTER_DURATION);
opacity.setInterpolator(LINEAR_INTERPOLATOR);
-
- final AnimatorSet set = new AnimatorSet();
- set.play(tweenOrigin).with(tweenRadius).with(opacity);
- set.addListener(mAnimationListener);
-
- return set;
+ opacity.start();
+ mRunningSwAnimators.add(opacity);
}
- @Override
- protected RenderNodeAnimatorSet createHardwareExit(Paint p) {
- final int radiusDuration;
- final int originDuration;
- final int opacityDuration;
- if (mIsBounded) {
- computeBoundedTargetValues();
-
- radiusDuration = BOUNDED_RADIUS_EXIT_DURATION;
- originDuration = BOUNDED_ORIGIN_EXIT_DURATION;
- opacityDuration = BOUNDED_OPACITY_EXIT_DURATION;
- } else {
- radiusDuration = getRadiusExitDuration();
- originDuration = radiusDuration;
- opacityDuration = getOpacityExitDuration();
- }
-
- final float startX = getCurrentX();
- final float startY = getCurrentY();
- final float startRadius = getCurrentRadius();
-
- p.setAlpha((int) (p.getAlpha() * mOpacity + 0.5f));
+ private void startSoftwareExit() {
+ final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 0);
+ opacity.setDuration(OPACITY_EXIT_DURATION);
+ opacity.setInterpolator(LINEAR_INTERPOLATOR);
+ opacity.addListener(mAnimationListener);
+ opacity.setStartDelay(computeFadeOutDelay());
+ opacity.start();
+ mRunningSwAnimators.add(opacity);
+ }
- mPropPaint = CanvasProperty.createPaint(p);
- mPropRadius = CanvasProperty.createFloat(startRadius);
- mPropX = CanvasProperty.createFloat(startX);
- mPropY = CanvasProperty.createFloat(startY);
+ private void startHardwareEnter() {
+ if (mForceSoftware) { return; }
+ mPropX = CanvasProperty.createFloat(getCurrentX());
+ mPropY = CanvasProperty.createFloat(getCurrentY());
+ mPropRadius = CanvasProperty.createFloat(getCurrentRadius());
+ final Paint paint = mOwner.getRipplePaint();
+ mPropPaint = CanvasProperty.createPaint(paint);
final RenderNodeAnimator radius = new RenderNodeAnimator(mPropRadius, mTargetRadius);
- radius.setDuration(radiusDuration);
+ radius.setDuration(RIPPLE_ORIGIN_DURATION);
radius.setInterpolator(DECELERATE_INTERPOLATOR);
+ mPendingHwAnimators.add(radius);
final RenderNodeAnimator x = new RenderNodeAnimator(mPropX, mTargetX);
- x.setDuration(originDuration);
+ x.setDuration(RIPPLE_ORIGIN_DURATION);
x.setInterpolator(DECELERATE_INTERPOLATOR);
+ mPendingHwAnimators.add(x);
final RenderNodeAnimator y = new RenderNodeAnimator(mPropY, mTargetY);
- y.setDuration(originDuration);
+ y.setDuration(RIPPLE_ORIGIN_DURATION);
y.setInterpolator(DECELERATE_INTERPOLATOR);
+ mPendingHwAnimators.add(y);
+
+ final RenderNodeAnimator opacity = new RenderNodeAnimator(mPropPaint,
+ RenderNodeAnimator.PAINT_ALPHA, paint.getAlpha());
+ opacity.setDuration(OPACITY_ENTER_DURATION);
+ opacity.setInterpolator(LINEAR_INTERPOLATOR);
+ opacity.setStartValue(0);
+ mPendingHwAnimators.add(opacity);
+
+ invalidateSelf();
+ }
+
+ private void startHardwareExit() {
+ // Only run a hardware exit if we had a hardware enter to continue from
+ if (mForceSoftware || mPropPaint == null) return;
final RenderNodeAnimator opacity = new RenderNodeAnimator(mPropPaint,
RenderNodeAnimator.PAINT_ALPHA, 0);
- opacity.setDuration(opacityDuration);
+ opacity.setDuration(OPACITY_EXIT_DURATION);
opacity.setInterpolator(LINEAR_INTERPOLATOR);
opacity.addListener(mAnimationListener);
+ opacity.setStartDelay(computeFadeOutDelay());
+ opacity.setStartValue(mOwner.getRipplePaint().getAlpha());
+ mPendingHwAnimators.add(opacity);
+ invalidateSelf();
+ }
- final RenderNodeAnimatorSet set = new RenderNodeAnimatorSet();
- set.add(radius);
- set.add(opacity);
- set.add(x);
- set.add(y);
+ /**
+ * Starts a ripple enter animation.
+ */
+ public final void enter() {
+ mEnterStartedAtMillis = AnimationUtils.currentAnimationTimeMillis();
+ startSoftwareEnter();
+ startHardwareEnter();
+ }
- return set;
+ /**
+ * Starts a ripple exit animation.
+ */
+ public final void exit() {
+ startSoftwareExit();
+ startHardwareExit();
}
- @Override
- protected void jumpValuesToExit() {
- mOpacity = 0;
- mTweenX = 1;
- mTweenY = 1;
- mTweenRadius = 1;
+ private float getCurrentX() {
+ return MathUtils.lerp(mClampedStartingX - mBounds.exactCenterX(), mTargetX, mTweenX);
+ }
+
+ private float getCurrentY() {
+ return MathUtils.lerp(mClampedStartingY - mBounds.exactCenterY(), mTargetY, mTweenY);
+ }
+
+ private float getCurrentRadius() {
+ return MathUtils.lerp(mStartRadius, mTargetRadius, mTweenRadius);
+ }
+
+ /**
+ * Draws the ripple to the canvas, inheriting the paint's color and alpha
+ * properties.
+ *
+ * @param c the canvas to which the ripple should be drawn
+ * @param p the paint used to draw the ripple
+ */
+ public void draw(Canvas c, Paint p) {
+ final boolean hasDisplayListCanvas = !mForceSoftware && c instanceof DisplayListCanvas;
+
+ pruneSwFinished();
+ if (hasDisplayListCanvas) {
+ final DisplayListCanvas hw = (DisplayListCanvas) c;
+ drawHardware(hw, p);
+ } else {
+ drawSoftware(c, p);
+ }
}
/**
@@ -334,7 +351,7 @@ class RippleForeground extends RippleComponent {
final float cY = mBounds.exactCenterY();
final float dX = mStartingX - cX;
final float dY = mStartingY - cY;
- final float r = mTargetRadius;
+ final float r = mTargetRadius - mStartRadius;
if (dX * dX + dY * dY > r * r) {
// Point is outside the circle, clamp to the perimeter.
final double angle = Math.atan2(dY, dX);
@@ -346,38 +363,56 @@ class RippleForeground extends RippleComponent {
}
}
+ /**
+ * Ends all animations, jumping values to the end state.
+ */
+ public void end() {
+ for (int i = 0; i < mRunningSwAnimators.size(); i++) {
+ mRunningSwAnimators.get(i).end();
+ }
+ mRunningSwAnimators.clear();
+ for (int i = 0; i < mRunningHwAnimators.size(); i++) {
+ mRunningHwAnimators.get(i).end();
+ }
+ mRunningHwAnimators.clear();
+ }
+
+ private void onAnimationPropertyChanged() {
+ if (!mUsingProperties) {
+ invalidateSelf();
+ }
+ }
+
+ private void clearHwProps() {
+ mPropPaint = null;
+ mPropRadius = null;
+ mPropX = null;
+ mPropY = null;
+ mUsingProperties = false;
+ }
+
private final AnimatorListenerAdapter mAnimationListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animator) {
mHasFinishedExit = true;
- }
- };
-
- /**
- * Interpolator with a smooth log deceleration.
- */
- private static final class LogDecelerateInterpolator implements TimeInterpolator {
- private final float mBase;
- private final float mDrift;
- private final float mTimeScale;
- private final float mOutputScale;
-
- public LogDecelerateInterpolator(float base, float timeScale, float drift) {
- mBase = base;
- mDrift = drift;
- mTimeScale = 1f / timeScale;
-
- mOutputScale = 1f / computeLog(1f);
- }
+ pruneHwFinished();
+ pruneSwFinished();
- private float computeLog(float t) {
- return 1f - (float) Math.pow(mBase, -t * mTimeScale) + (mDrift * t);
+ if (mRunningHwAnimators.isEmpty()) {
+ clearHwProps();
+ }
}
+ };
- @Override
- public float getInterpolation(float t) {
- return computeLog(t) * mOutputScale;
+ private void switchToUiThreadAnimation() {
+ for (int i = 0; i < mRunningHwAnimators.size(); i++) {
+ Animator animator = mRunningHwAnimators.get(i);
+ animator.removeListener(mAnimationListener);
+ animator.end();
}
+ mRunningHwAnimators.clear();
+ clearHwProps();
+ invalidateSelf();
}
/**
@@ -388,7 +423,7 @@ class RippleForeground extends RippleComponent {
@Override
public void setValue(RippleForeground object, float value) {
object.mTweenRadius = value;
- object.invalidateSelf();
+ object.onAnimationPropertyChanged();
}
@Override
@@ -402,18 +437,18 @@ class RippleForeground extends RippleComponent {
*/
private static final FloatProperty<RippleForeground> TWEEN_ORIGIN =
new FloatProperty<RippleForeground>("tweenOrigin") {
- @Override
- public void setValue(RippleForeground object, float value) {
- object.mTweenX = value;
- object.mTweenY = value;
- object.invalidateSelf();
- }
+ @Override
+ public void setValue(RippleForeground object, float value) {
+ object.mTweenX = value;
+ object.mTweenY = value;
+ object.onAnimationPropertyChanged();
+ }
- @Override
- public Float get(RippleForeground object) {
- return object.mTweenX;
- }
- };
+ @Override
+ public Float get(RippleForeground object) {
+ return object.mTweenX;
+ }
+ };
/**
* Property for animating opacity between 0 and its target value.
@@ -423,7 +458,7 @@ class RippleForeground extends RippleComponent {
@Override
public void setValue(RippleForeground object, float value) {
object.mOpacity = value;
- object.invalidateSelf();
+ object.onAnimationPropertyChanged();
}
@Override
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index ceac3253e178..c71585f32155 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -213,12 +213,79 @@ import dalvik.system.VMRuntime;
* &lt;/vector&gt;
* </pre>
* </li>
- * <li>And here is an example of linear gradient color, which is supported in SDK 24+.
+ * <h4>Gradient support</h4>
+ * We support 3 types of gradients: {@link android.graphics.LinearGradient},
+ * {@link android.graphics.RadialGradient}, or {@link android.graphics.SweepGradient}.
+ * <p/>
+ * And we support all of 3 types of tile modes {@link android.graphics.Shader.TileMode}:
+ * CLAMP, REPEAT, MIRROR.
+ * <p/>
+ * All of the attributes are listed in {@link android.R.styleable#GradientColor}.
+ * Note that different attributes are relevant for different types of gradient.
+ * <table border="2" align="center" cellpadding="5">
+ * <thead>
+ * <tr>
+ * <th>LinearGradient</th>
+ * <th>RadialGradient</th>
+ * <th>SweepGradient</th>
+ * </tr>
+ * </thead>
+ * <tr>
+ * <td>startColor </td>
+ * <td>startColor</td>
+ * <td>startColor</td>
+ * </tr>
+ * <tr>
+ * <td>centerColor</td>
+ * <td>centerColor</td>
+ * <td>centerColor</td>
+ * </tr>
+ * <tr>
+ * <td>endColor</td>
+ * <td>endColor</td>
+ * <td>endColor</td>
+ * </tr>
+ * <tr>
+ * <td>type</td>
+ * <td>type</td>
+ * <td>type</td>
+ * </tr>
+ * <tr>
+ * <td>tileMode</td>
+ * <td>tileMode</td>
+ * <td>tileMode</td>
+ * </tr>
+ * <tr>
+ * <td>startX</td>
+ * <td>centerX</td>
+ * <td>centerX</td>
+ * </tr>
+ * <tr>
+ * <td>startY</td>
+ * <td>centerY</td>
+ * <td>centerY</td>
+ * </tr>
+ * <tr>
+ * <td>endX</td>
+ * <td>gradientRadius</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>endY</td>
+ * <td></td>
+ * <td></td>
+ * </tr>
+ * </table>
+ * <p/>
+ * Also note that if any color item {@link android.R.styleable#GradientColorItem} is defined, then
+ * startColor, centerColor and endColor will be ignored.
+ * <p/>
* See more details in {@link android.R.styleable#GradientColor} and
* {@link android.R.styleable#GradientColorItem}.
+ * <p/>
+ * Here is a simple example that defines a linear gradient.
* <pre>
* &lt;gradient xmlns:android="http://schemas.android.com/apk/res/android"
- * android:angle="90"
* android:startColor="?android:attr/colorPrimary"
* android:endColor="?android:attr/colorControlActivated"
* android:centerColor="#f00"
@@ -229,7 +296,18 @@ import dalvik.system.VMRuntime;
* android:type="linear"&gt;
* &lt;/gradient&gt;
* </pre>
- * </li>
+ * And here is a simple example that defines a radial gradient using color items.
+ * <pre>
+ * &lt;gradient xmlns:android="http://schemas.android.com/apk/res/android"
+ * android:centerX="300"
+ * android:centerY="300"
+ * android:gradientRadius="100"
+ * android:type="radial"&gt;
+ * &lt;item android:offset="0.1" android:color="#0ff"/&gt;
+ * &lt;item android:offset="0.4" android:color="#fff"/&gt;
+ * &lt;item android:offset="0.9" android:color="#ff0"/&gt;
+ * &lt;/gradient&gt;
+ * </pre>
*
*/
@@ -818,6 +896,13 @@ public class VectorDrawable extends Drawable {
return mVectorState.getNativeRenderer();
}
+ /**
+ * @hide
+ */
+ public void setAntiAlias(boolean aa) {
+ nSetAntiAlias(mVectorState.mNativeTree.get(), aa);
+ }
+
static class VectorDrawableState extends ConstantState {
// Variables below need to be copied (deep copy if applicable) for mutation.
int[] mThemeAttrs;
@@ -2191,6 +2276,8 @@ public class VectorDrawable extends Drawable {
@FastNative
private static native float nGetRootAlpha(long rendererPtr);
@FastNative
+ private static native void nSetAntiAlias(long rendererPtr, boolean aa);
+ @FastNative
private static native void nSetAllowCaching(long rendererPtr, boolean allowCaching);
@FastNative
diff --git a/graphics/java/android/graphics/fonts/FontVariationAxis.java b/graphics/java/android/graphics/fonts/FontVariationAxis.java
index 99564fab94cd..1b7408a03294 100644
--- a/graphics/java/android/graphics/fonts/FontVariationAxis.java
+++ b/graphics/java/android/graphics/fonts/FontVariationAxis.java
@@ -178,7 +178,7 @@ public final class FontVariationAxis {
* @return String a valid font variation settings string.
*/
public static @NonNull String toFontVariationSettings(@Nullable FontVariationAxis[] axes) {
- if (axes == null || axes.length == 0) {
+ if (axes == null) {
return "";
}
return TextUtils.join(",", axes);
diff --git a/graphics/java/android/graphics/pdf/PdfEditor.java b/graphics/java/android/graphics/pdf/PdfEditor.java
index dc4b11791c1e..3821bc7ab063 100644
--- a/graphics/java/android/graphics/pdf/PdfEditor.java
+++ b/graphics/java/android/graphics/pdf/PdfEditor.java
@@ -40,7 +40,7 @@ public final class PdfEditor {
private final CloseGuard mCloseGuard = CloseGuard.get();
- private final long mNativeDocument;
+ private long mNativeDocument;
private int mPageCount;
@@ -78,12 +78,17 @@ public final class PdfEditor {
} catch (ErrnoException ee) {
throw new IllegalArgumentException("file descriptor not seekable");
}
-
mInput = input;
synchronized (PdfRenderer.sPdfiumLock) {
mNativeDocument = nativeOpen(mInput.getFd(), size);
- mPageCount = nativeGetPageCount(mNativeDocument);
+ try {
+ mPageCount = nativeGetPageCount(mNativeDocument);
+ } catch (Throwable t) {
+ nativeClose(mNativeDocument);
+ mNativeDocument = 0;
+ throw t;
+ }
}
mCloseGuard.open("close");
@@ -275,20 +280,24 @@ public final class PdfEditor {
mCloseGuard.warnIfOpen();
}
- if (mInput != null) {
- doClose();
- }
+ doClose();
} finally {
super.finalize();
}
}
private void doClose() {
- synchronized (PdfRenderer.sPdfiumLock) {
- nativeClose(mNativeDocument);
+ if (mNativeDocument != 0) {
+ synchronized (PdfRenderer.sPdfiumLock) {
+ nativeClose(mNativeDocument);
+ }
+ mNativeDocument = 0;
+ }
+
+ if (mInput != null) {
+ IoUtils.closeQuietly(mInput);
+ mInput = null;
}
- IoUtils.closeQuietly(mInput);
- mInput = null;
mCloseGuard.close();
}
diff --git a/graphics/java/android/graphics/pdf/PdfRenderer.java b/graphics/java/android/graphics/pdf/PdfRenderer.java
index 29e1ea0fee5d..4a91705239c1 100644
--- a/graphics/java/android/graphics/pdf/PdfRenderer.java
+++ b/graphics/java/android/graphics/pdf/PdfRenderer.java
@@ -31,6 +31,8 @@ import android.system.OsConstants;
import com.android.internal.util.Preconditions;
import dalvik.system.CloseGuard;
+import libcore.io.IoUtils;
+
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -110,7 +112,7 @@ public final class PdfRenderer implements AutoCloseable {
private final Point mTempPoint = new Point();
- private final long mNativeDocument;
+ private long mNativeDocument;
private final int mPageCount;
@@ -159,7 +161,6 @@ public final class PdfRenderer implements AutoCloseable {
} catch (ErrnoException ee) {
throw new IllegalArgumentException("file descriptor not seekable");
}
-
mInput = input;
synchronized (sPdfiumLock) {
@@ -168,6 +169,7 @@ public final class PdfRenderer implements AutoCloseable {
mPageCount = nativeGetPageCount(mNativeDocument);
} catch (Throwable t) {
nativeClose(mNativeDocument);
+ mNativeDocument = 0;
throw t;
}
}
@@ -234,9 +236,7 @@ public final class PdfRenderer implements AutoCloseable {
mCloseGuard.warnIfOpen();
}
- if (mInput != null) {
- doClose();
- }
+ doClose();
} finally {
super.finalize();
}
@@ -245,16 +245,20 @@ public final class PdfRenderer implements AutoCloseable {
private void doClose() {
if (mCurrentPage != null) {
mCurrentPage.close();
+ mCurrentPage = null;
}
- synchronized (sPdfiumLock) {
- nativeClose(mNativeDocument);
+
+ if (mNativeDocument != 0) {
+ synchronized (sPdfiumLock) {
+ nativeClose(mNativeDocument);
+ }
+ mNativeDocument = 0;
}
- try {
- mInput.close();
- } catch (IOException ioe) {
- /* ignore - best effort */
+
+ if (mInput != null) {
+ IoUtils.closeQuietly(mInput);
+ mInput = null;
}
- mInput = null;
mCloseGuard.close();
}
@@ -451,19 +455,20 @@ public final class PdfRenderer implements AutoCloseable {
mCloseGuard.warnIfOpen();
}
- if (mNativePage != 0) {
- doClose();
- }
+ doClose();
} finally {
super.finalize();
}
}
private void doClose() {
- synchronized (sPdfiumLock) {
- nativeClosePage(mNativePage);
+ if (mNativePage != 0) {
+ synchronized (sPdfiumLock) {
+ nativeClosePage(mNativePage);
+ }
+ mNativePage = 0;
}
- mNativePage = 0;
+
mCloseGuard.close();
mCurrentPage = null;
}