diff options
Diffstat (limited to 'graphics/java')
19 files changed, 1071 insertions, 1030 deletions
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java index 1f339f7aaa54..2d8c71794e7d 100644 --- a/graphics/java/android/graphics/BaseCanvas.java +++ b/graphics/java/android/graphics/BaseCanvas.java @@ -375,7 +375,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 +387,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 +395,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 +403,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 +413,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 +424,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 +432,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 +453,7 @@ 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()); } public void drawTextRun(@NonNull CharSequence text, int start, int end, int contextStart, @@ -474,7 +474,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); @@ -484,7 +484,7 @@ public abstract class BaseCanvas { char[] buf = TemporaryBuffer.obtain(contextLen); TextUtils.getChars(text, contextStart, contextEnd, buf, 0); nDrawTextRun(mNativeCanvasWrapper, buf, start - contextStart, len, - 0, contextLen, x, y, isRtl, paint.getNativeInstance(), paint.mNativeTypeface); + 0, contextLen, x, y, isRtl, paint.getNativeInstance()); TemporaryBuffer.recycle(buf); } } @@ -614,23 +614,20 @@ 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); + int contextStart, int contextCount, float x, float y, boolean isRtl, long nativePaint); 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..0072012f69ad 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; @@ -1233,6 +1234,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 diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java index ffb39e339119..f5bf754ae6ef 100644 --- a/graphics/java/android/graphics/BitmapFactory.java +++ b/graphics/java/android/graphics/BitmapFactory.java @@ -354,6 +354,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 +413,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 +428,7 @@ public class BitmapFactory { * or if inJustDecodeBounds is true, will set outWidth/outHeight * to -1 */ + @Deprecated public void requestCancelDecode() { mCancel = true; } diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index 0301f2e6b555..f5e863305766 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 */ @@ -1242,8 +1237,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..d77e6012fb46 100644 --- a/graphics/java/android/graphics/FontFamily.java +++ b/graphics/java/android/graphics/FontFamily.java @@ -16,9 +16,11 @@ 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; @@ -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); } /** @@ -174,7 +184,7 @@ public class FontFamily { 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); diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index 7c07a302dfe9..9f672e30e05b 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -74,13 +74,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 +96,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 +112,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 +128,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/Paint.java b/graphics/java/android/graphics/Paint.java index aa9227c9bb08..317144a21d32 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -60,11 +60,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 +88,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 +518,6 @@ public class Paint { mShader = null; mNativeShader = 0; mTypeface = null; - mNativeTypeface = 0; mXfermode = null; mHasCompatScaling = false; @@ -566,7 +560,6 @@ public class Paint { mShader = paint.mShader; mNativeShader = paint.mNativeShader; mTypeface = paint.mTypeface; - mNativeTypeface = paint.mNativeTypeface; mXfermode = paint.mXfermode; mHasCompatScaling = paint.mHasCompatScaling; @@ -822,14 +815,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 +851,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 +1267,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 +1445,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 +1729,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 +1789,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 +1853,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 +1872,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 +1902,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 +1934,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 +2023,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 +2111,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 +2148,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 +2231,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 +2284,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 +2410,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 +2464,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 +2550,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 +2571,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 +2591,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 +2610,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 +2652,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 +2673,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 +2726,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); } /** @@ -2747,7 +2742,7 @@ public class Paint { * @param offset index of caret position * @return width measurement between start and offset */ - public float getRunAdvance(CharSequence text, int start, int end, int contextStart, + public float getRunAdvance(@NonNull CharSequence text, int start, int end, int contextStart, int contextEnd, boolean isRtl, int offset) { if (text == null) { throw new IllegalArgumentException("text cannot be null"); @@ -2808,8 +2803,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); } /** @@ -2847,38 +2842,31 @@ public class Paint { 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 +2876,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 +2912,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 +2992,17 @@ 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); } 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/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..3d65bd226faf 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -38,6 +38,7 @@ 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,6 +46,7 @@ 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; @@ -95,21 +97,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 +135,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 +144,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 +158,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 +186,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 +219,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 +277,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; @@ -498,7 +517,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 +532,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 +547,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 +564,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 +588,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 +610,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 +625,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 +652,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 +673,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 +682,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; } @@ -751,34 +804,33 @@ public class Typeface { 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; + synchronized (sDynamicCacheLock) { + 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; + final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, + null /* axes */, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE, + DEFAULT_FAMILY); + typeface = sDynamicTypefaceCache.get(key); + if (typeface != null) return typeface; - final FontFamily fontFamily = new FontFamily(); - if (fontFamily.addFontFromAssetManager(mgr, path, 0, true /* isAsset */, - 0 /* ttc index */, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE, - null /* axes */)) { - // Due to backward compatibility, even if the font is not supported by our font - // stack, we need to place the empty font at the first place. The typeface with - // empty font behaves different from default typeface especially in fallback - // font selection. - fontFamily.allowUnsupportedFont(); - fontFamily.freeze(); - final FontFamily[] families = { fontFamily }; - typeface = createFromFamiliesWithDefault(families, RESOLVE_BY_FONT_TABLE, - RESOLVE_BY_FONT_TABLE); - sDynamicTypefaceCache.put(key, typeface); - return typeface; - } else { - fontFamily.abortCreation(); - } + final FontFamily fontFamily = new FontFamily(); + if (fontFamily.addFontFromAssetManager(mgr, path, 0, true /* isAsset */, + 0 /* ttc index */, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE, + null /* axes */)) { + // Due to backward compatibility, even if the font is not supported by our font + // stack, we need to place the empty font at the first place. The typeface with + // empty font behaves different from default typeface especially in fallback + // font selection. + fontFamily.allowUnsupportedFont(); + fontFamily.freeze(); + final FontFamily[] families = { fontFamily }; + typeface = createFromFamiliesWithDefault(families, DEFAULT_FAMILY, + RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE); + sDynamicTypefaceCache.put(key, typeface); + return typeface; + } else { + fontFamily.abortCreation(); } } throw new RuntimeException("Font asset not found " + path); @@ -815,22 +867,20 @@ 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(); - } + final FontFamily fontFamily = new FontFamily(); + if (fontFamily.addFont(path, 0 /* ttcIndex */, null /* axes */, + RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)) { + // Due to backward compatibility, even if the font is not supported by our font + // stack, we need to place the empty font at the first place. The typeface with + // empty font behaves different from default typeface especially in fallback font + // selection. + fontFamily.allowUnsupportedFont(); + fontFamily.freeze(); + FontFamily[] families = { fontFamily }; + return createFromFamiliesWithDefault(families, DEFAULT_FAMILY, + RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE); + } else { + fontFamily.abortCreation(); } throw new RuntimeException("Font not found " + path); } @@ -852,6 +902,8 @@ public class Typeface { /** * Create a new typeface from an array of font families, including * also the font families in the fallback list. + * @param fallbackName the family name. If given families don't support characters, the + * characters will be rendered with this family. * @param weight the weight for this family. {@link RESOLVE_BY_FONT_TABLE} can be used. In that * case, the table information in the first family's font is used. If the first * family has multiple fonts, the closest to the regular weight and upright font @@ -863,13 +915,17 @@ public class Typeface { * @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 +941,190 @@ 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); + } + } + } + } + + /** + * 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); + } + } + + // 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))); } - for (FontConfig.Alias alias : fontConfig.getAliases()) { - Typeface base = systemFonts.get(alias.getToName()); + + // 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 +1141,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/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"><path></td> + * <td rowspan="9"><path></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/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java index 6bd2646f9299..dea194e4ffde 100644 --- a/graphics/java/android/graphics/drawable/RippleBackground.java +++ b/graphics/java/android/graphics/drawable/RippleBackground.java @@ -36,138 +36,69 @@ 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 animateChanged) { + if (mHovered != hovered || mFocused != focused) { + mHovered = hovered; + mFocused = focused; + onStateChanged(animateChanged); } - - 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); + private void onStateChanged(boolean animateChanged) { + float newOpacity = 0.0f; + if (mHovered) newOpacity += 1.0f; + if (mFocused) newOpacity += 1.0f; + if (mAnimator != null) { + mAnimator.cancel(); + mAnimator = null; } - 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 (animateChanged) { + mAnimator = ObjectAnimator.ofFloat(this, OPACITY, newOpacity); + mAnimator.setDuration(OPACITY_DURATION); + mAnimator.setInterpolator(LINEAR_INTERPOLATOR); + mAnimator.start(); + } else { + mOpacity = newOpacity; } - - return set; } - @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..0e38826eb34b 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,77 +87,10 @@ 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; @@ -276,76 +110,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..8b185f2b8903 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,8 +264,8 @@ public class RippleDrawable extends LayerDrawable { } setRippleActive(enabled && pressed); - setBackgroundActive(hovered || focused || (enabled && pressed), focused || hovered); + setBackgroundActive(hovered, focused); 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) { + if (mBackground == null && (hovered || focused)) { + mBackground = new RippleBackground(this, mHotspotBounds, isBounded()); + mBackground.setup(mState.mMaxRadius, mDensity); + } + if (mBackground != null) { + mBackground.setState(focused, hovered, true); } } @@ -327,10 +323,6 @@ public class RippleDrawable extends LayerDrawable { tryRippleEnter(); } - if (mBackgroundActive) { - tryBackgroundEnter(false); - } - // Skip animations, just show the correct final states. jumpToCurrentState(); } @@ -546,26 +538,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. */ @@ -593,7 +565,7 @@ public class RippleDrawable extends LayerDrawable { } mRipple.setup(mState.mMaxRadius, mDensity); - mRipple.enter(false); + mRipple.enter(); } /** @@ -623,9 +595,7 @@ public class RippleDrawable extends LayerDrawable { } if (mBackground != null) { - mBackground.end(); - mBackground = null; - mBackgroundActive = false; + mBackground.setState(false, false, false); } cancelExitingRipples(); @@ -693,7 +663,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,6 +828,40 @@ public class RippleDrawable extends LayerDrawable { final float y = mHotspotBounds.exactCenterY(); canvas.translate(x, y); + final Paint p = getRipplePaint(); + + if (background != null && background.isVisible()) { + background.draw(canvas, p); + } + + if (count > 0) { + final RippleForeground[] ripples = mExitingRipples; + for (int i = 0; i < count; i++) { + ripples[i].draw(canvas, p); + } + } + + if (active != null) { + active.draw(canvas, p); + } + + canvas.translate(-x, -y); + } + + private void drawMask(Canvas canvas) { + mMask.draw(canvas); + } + + Paint getRipplePaint() { + if (mRipplePaint == null) { + mRipplePaint = new Paint(); + mRipplePaint.setAntiAlias(true); + mRipplePaint.setStyle(Paint.Style.FILL); + } + + final float x = mHotspotBounds.exactCenterX(); + final float y = mHotspotBounds.exactCenterY(); + updateMaskShaderIfNeeded(); // Position the shader to account for canvas translation. @@ -869,7 +875,7 @@ public class RippleDrawable extends LayerDrawable { // 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(); + final Paint p = mRipplePaint; if (mMaskColorFilter != null) { // The ripple timing depends on the paint's alpha value, so we need @@ -888,35 +894,7 @@ public class RippleDrawable extends LayerDrawable { p.setShader(null); } - if (background != null && background.isVisible()) { - background.draw(canvas, p); - } - - if (count > 0) { - final RippleForeground[] ripples = mExitingRipples; - for (int i = 0; i < count; i++) { - ripples[i].draw(canvas, p); - } - } - - if (active != null) { - active.draw(canvas, p); - } - - canvas.translate(-x, -y); - } - - private void drawMask(Canvas canvas) { - mMask.draw(canvas); - } - - private Paint getRipplePaint() { - if (mRipplePaint == null) { - mRipplePaint = new Paint(); - mRipplePaint.setAntiAlias(true); - mRipplePaint.setStyle(Paint.Style.FILL); - } - return mRipplePaint; + return p; } @Override diff --git a/graphics/java/android/graphics/drawable/RippleForeground.java b/graphics/java/android/graphics/drawable/RippleForeground.java index 829733e9b097..0b5020cbe55c 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,8 +28,11 @@ 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 java.util.ArrayList; + /** * Draws a ripple foreground. */ @@ -40,8 +42,7 @@ class RippleForeground extends RippleComponent { 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_TOUCH_DOWN_ACCELERATION = 2048; private static final float WAVE_OPACITY_DECAY_VELOCITY = 3; // Bounded ripple animation properties. @@ -50,8 +51,9 @@ class RippleForeground extends RippleComponent { private static final int BOUNDED_OPACITY_EXIT_DURATION = 400; private static final float MAX_BOUNDED_RADIUS = 350; - 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; @@ -73,24 +75,42 @@ class RippleForeground extends RippleComponent { 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); + super(owner, bounds); - mIsBounded = isBounded; + mForceSoftware = forceSoftware; mStartingX = startingX; mStartingY = startingY; @@ -100,6 +120,8 @@ class RippleForeground extends RippleComponent { } 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; } @Override @@ -107,10 +129,7 @@ class RippleForeground extends RippleComponent { clampStartingPosition(); } - @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 +139,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 +214,152 @@ 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; + } + return 0; + } + + private void startSoftwareEnter() { + for (int i = 0; i < mRunningSwAnimators.size(); i++) { + mRunningSwAnimators.get(i).cancel(); } + mRunningSwAnimators.clear(); - final int duration = (int) - (1000 * Math.sqrt(mTargetRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensityScale) + 0.5); + final int duration = getRadiusDuration(); final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1); - tweenRadius.setAutoCancel(true); tweenRadius.setDuration(duration); - tweenRadius.setInterpolator(LINEAR_INTERPOLATOR); - tweenRadius.setStartDelay(RIPPLE_ENTER_DELAY); + tweenRadius.setInterpolator(DECELERATE_INTERPOLATOR); + tweenRadius.start(); + mRunningSwAnimators.add(tweenRadius); final ObjectAnimator tweenOrigin = ObjectAnimator.ofFloat(this, TWEEN_ORIGIN, 1); - tweenOrigin.setAutoCancel(true); tweenOrigin.setDuration(duration); - tweenOrigin.setInterpolator(LINEAR_INTERPOLATOR); - tweenOrigin.setStartDelay(RIPPLE_ENTER_DELAY); + tweenOrigin.setInterpolator(DECELERATE_INTERPOLATOR); + tweenOrigin.start(); + mRunningSwAnimators.add(tweenOrigin); final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 1); - opacity.setAutoCancel(true); - opacity.setDuration(OPACITY_ENTER_DURATION_FAST); + opacity.setDuration(OPACITY_ENTER_DURATION); 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); + opacity.start(); + mRunningSwAnimators.add(opacity); } - private float getCurrentY() { - return MathUtils.lerp(mClampedStartingY - mBounds.exactCenterY(), mTargetY, mTweenY); - } - - 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(); - } - - final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1); - tweenRadius.setAutoCancel(true); - tweenRadius.setDuration(radiusDuration); - tweenRadius.setInterpolator(DECELERATE_INTERPOLATOR); - - final ObjectAnimator tweenOrigin = ObjectAnimator.ofFloat(this, TWEEN_ORIGIN, 1); - tweenOrigin.setAutoCancel(true); - tweenOrigin.setDuration(originDuration); - tweenOrigin.setInterpolator(DECELERATE_INTERPOLATOR); - + private void startSoftwareExit() { final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 0); - opacity.setAutoCancel(true); - opacity.setDuration(opacityDuration); + opacity.setDuration(OPACITY_EXIT_DURATION); opacity.setInterpolator(LINEAR_INTERPOLATOR); - - final AnimatorSet set = new AnimatorSet(); - set.play(tweenOrigin).with(tweenRadius).with(opacity); - set.addListener(mAnimationListener); - - return set; + opacity.addListener(mAnimationListener); + opacity.setStartDelay(computeFadeOutDelay()); + 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(); + 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); - p.setAlpha((int) (p.getAlpha() * mOpacity + 0.5f)); - - mPropPaint = CanvasProperty.createPaint(p); - mPropRadius = CanvasProperty.createFloat(startRadius); - mPropX = CanvasProperty.createFloat(startX); - mPropY = CanvasProperty.createFloat(startY); + final int radiusDuration = getRadiusDuration(); final RenderNodeAnimator radius = new RenderNodeAnimator(mPropRadius, mTargetRadius); radius.setDuration(radiusDuration); radius.setInterpolator(DECELERATE_INTERPOLATOR); + mPendingHwAnimators.add(radius); final RenderNodeAnimator x = new RenderNodeAnimator(mPropX, mTargetX); - x.setDuration(originDuration); + x.setDuration(radiusDuration); x.setInterpolator(DECELERATE_INTERPOLATOR); + mPendingHwAnimators.add(x); final RenderNodeAnimator y = new RenderNodeAnimator(mPropY, mTargetY); - y.setDuration(originDuration); + y.setDuration(radiusDuration); 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()); + 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 int getRadiusDuration() { + final float remainingRadius = mTargetRadius - getCurrentRadius(); + return (int) (1000 * Math.sqrt(remainingRadius / WAVE_TOUCH_DOWN_ACCELERATION * + mDensityScale) + 0.5); + } + + 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); + } } /** @@ -346,10 +382,39 @@ 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 final AnimatorListenerAdapter mAnimationListener = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animator) { mHasFinishedExit = true; + pruneHwFinished(); + pruneSwFinished(); + + if (mRunningHwAnimators.isEmpty()) { + mPropPaint = null; + mPropRadius = null; + mPropX = null; + mPropY = null; + } } }; @@ -388,7 +453,7 @@ class RippleForeground extends RippleComponent { @Override public void setValue(RippleForeground object, float value) { object.mTweenRadius = value; - object.invalidateSelf(); + object.onAnimationPropertyChanged(); } @Override @@ -402,18 +467,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 +488,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; * </vector> * </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> * <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"> * </gradient> * </pre> - * </li> + * And here is a simple example that defines a radial gradient using color items. + * <pre> + * <gradient xmlns:android="http://schemas.android.com/apk/res/android" + * android:centerX="300" + * android:centerY="300" + * android:gradientRadius="100" + * android:type="radial"> + * <item android:offset="0.1" android:color="#0ff"/> + * <item android:offset="0.4" android:color="#fff"/> + * <item android:offset="0.9" android:color="#ff0"/> + * </gradient> + * </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; } |