summaryrefslogtreecommitdiff
path: root/graphics
diff options
context:
space:
mode:
Diffstat (limited to 'graphics')
-rw-r--r--graphics/TEST_MAPPING12
-rw-r--r--graphics/java/Android.bp6
-rw-r--r--graphics/java/android/framework_graphics.aconfig8
-rw-r--r--graphics/java/android/graphics/Bitmap.java21
-rw-r--r--graphics/java/android/graphics/BitmapShader.java40
-rw-r--r--graphics/java/android/graphics/Canvas.java3
-rw-r--r--graphics/java/android/graphics/ColorFilter.java13
-rw-r--r--graphics/java/android/graphics/ColorMatrixColorFilter.java6
-rw-r--r--graphics/java/android/graphics/ColorSpace.java19
-rw-r--r--graphics/java/android/graphics/FontListParser.java45
-rw-r--r--graphics/java/android/graphics/ForceDarkType.java60
-rw-r--r--graphics/java/android/graphics/Gainmap.java6
-rw-r--r--graphics/java/android/graphics/HardwareRenderer.java19
-rw-r--r--graphics/java/android/graphics/LightingColorFilter.java8
-rw-r--r--graphics/java/android/graphics/Mesh.java3
-rw-r--r--graphics/java/android/graphics/Paint.java228
-rw-r--r--graphics/java/android/graphics/Path.java18
-rw-r--r--graphics/java/android/graphics/RecordingCanvas.java2
-rw-r--r--graphics/java/android/graphics/RenderNode.java57
-rw-r--r--graphics/java/android/graphics/RuntimeShader.java16
-rw-r--r--graphics/java/android/graphics/SurfaceTexture.java95
-rw-r--r--graphics/java/android/graphics/TEST_MAPPING21
-rw-r--r--graphics/java/android/graphics/Typeface.java5
-rw-r--r--graphics/java/android/graphics/drawable/Icon.java49
-rw-r--r--graphics/java/android/graphics/fonts/FontCustomizationParser.java46
-rw-r--r--graphics/java/android/graphics/fonts/FontFamily.java173
-rw-r--r--graphics/java/android/graphics/fonts/FontFileUtil.java71
-rw-r--r--graphics/java/android/graphics/fonts/SystemFonts.java150
-rw-r--r--graphics/java/android/graphics/fonts/TEST_MAPPING15
-rw-r--r--graphics/java/android/graphics/text/LineBreakConfig.java459
-rw-r--r--graphics/java/android/graphics/text/LineBreaker.java35
-rw-r--r--graphics/java/android/graphics/text/MeasuredText.java35
-rw-r--r--graphics/java/android/graphics/text/PositionedGlyphs.java78
-rw-r--r--graphics/java/android/graphics/text/TEST_MAPPING15
-rw-r--r--graphics/java/android/view/PixelCopy.java2
35 files changed, 1709 insertions, 130 deletions
diff --git a/graphics/TEST_MAPPING b/graphics/TEST_MAPPING
index abeaf1996ca7..8afc30d54a53 100644
--- a/graphics/TEST_MAPPING
+++ b/graphics/TEST_MAPPING
@@ -7,6 +7,18 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
+ },
+ {
+ "name": "CtsTextTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.LargeTest"
+ }
+ ],
+ "file_patterns": ["(/|^)Typeface\\.java", "(/|^)Paint\\.java"]
}
]
}
diff --git a/graphics/java/Android.bp b/graphics/java/Android.bp
index 63d1f6d6f2d6..db37a38756d2 100644
--- a/graphics/java/Android.bp
+++ b/graphics/java/Android.bp
@@ -7,6 +7,12 @@ package {
default_applicable_licenses: ["frameworks_base_license"],
}
+aconfig_declarations {
+ name: "framework_graphics_flags",
+ package: "com.android.graphics.flags",
+ srcs: ["android/framework_graphics.aconfig"],
+}
+
filegroup {
name: "framework-graphics-nonupdatable-sources",
srcs: [
diff --git a/graphics/java/android/framework_graphics.aconfig b/graphics/java/android/framework_graphics.aconfig
new file mode 100644
index 000000000000..9a0a22a08a0c
--- /dev/null
+++ b/graphics/java/android/framework_graphics.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.graphics.flags"
+
+flag {
+ name: "exact_compute_bounds"
+ namespace: "core_graphics"
+ description: "Add a function without unused exact param for computeBounds."
+ bug: "304478551"
+} \ No newline at end of file
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index b9d3756ac6d2..250362b1e1e3 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -479,7 +479,8 @@ public final class Bitmap implements Parcelable {
* This configuration may be useful when using opaque bitmaps
* that do not require high color fidelity.
*
- * <p>Use this formula to pack into 16 bits:</p>
+ * <p>When accessing directly via #copyPixelsFromBuffer or #copyPixelsToBuffer,
+ * use this formula to pack into 16 bits:</p>
* <pre class="prettyprint">
* short color = (R & 0x1f) << 11 | (G & 0x3f) << 5 | (B & 0x1f);
* </pre>
@@ -516,7 +517,8 @@ public final class Bitmap implements Parcelable {
* This configuration is very flexible and offers the best
* quality. It should be used whenever possible.
*
- * <p>Use this formula to pack into 32 bits:</p>
+ * <p>When accessing directly via #copyPixelsFromBuffer or #copyPixelsToBuffer,
+ * use this formula to pack into 32 bits:</p>
* <pre class="prettyprint">
* int color = (A & 0xff) << 24 | (B & 0xff) << 16 | (G & 0xff) << 8 | (R & 0xff);
* </pre>
@@ -531,7 +533,8 @@ public final class Bitmap implements Parcelable {
* This configuration is particularly suited for wide-gamut and
* HDR content.
*
- * <p>Use this formula to pack into 64 bits:</p>
+ * <p>When accessing directly via #copyPixelsFromBuffer or #copyPixelsToBuffer,
+ * use this formula to pack into 64 bits:</p>
* <pre class="prettyprint">
* long color = (A & 0xffff) << 48 | (B & 0xffff) << 32 | (G & 0xffff) << 16 | (R & 0xffff);
* </pre>
@@ -556,7 +559,8 @@ public final class Bitmap implements Parcelable {
* blending, such that the memory cost is the same as ARGB_8888 while enabling higher color
* precision.
*
- * <p>Use this formula to pack into 32 bits:</p>
+ * <p>When accessing directly via #copyPixelsFromBuffer or #copyPixelsToBuffer,
+ * use this formula to pack into 32 bits:</p>
* <pre class="prettyprint">
* int color = (A & 0x3) << 30 | (B & 0x3ff) << 20 | (G & 0x3ff) << 10 | (R & 0x3ff);
* </pre>
@@ -782,10 +786,13 @@ public final class Bitmap implements Parcelable {
@Nullable
public static Bitmap wrapHardwareBuffer(@NonNull HardwareBuffer hardwareBuffer,
@Nullable ColorSpace colorSpace) {
- if ((hardwareBuffer.getUsage() & HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE) == 0) {
+ final long usage = hardwareBuffer.getUsage();
+ if ((usage & HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE) == 0) {
throw new IllegalArgumentException("usage flags must contain USAGE_GPU_SAMPLED_IMAGE.");
}
- int format = hardwareBuffer.getFormat();
+ if ((usage & HardwareBuffer.USAGE_PROTECTED_CONTENT) != 0) {
+ throw new IllegalArgumentException("Bitmap is not compatible with protected buffers");
+ }
if (colorSpace == null) {
colorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
}
@@ -1776,7 +1783,7 @@ public final class Bitmap implements Parcelable {
* If the bitmap's internal config is in one of the public formats, return
* that config, otherwise return null.
*/
- @NonNull
+ @Nullable
public final Config getConfig() {
if (mRecycled) {
Log.w(TAG, "Called getConfig() on a recycle()'d bitmap! This is undefined behavior!");
diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java
index 5c065775eea2..dcfff62459ab 100644
--- a/graphics/java/android/graphics/BitmapShader.java
+++ b/graphics/java/android/graphics/BitmapShader.java
@@ -16,9 +16,13 @@
package android.graphics;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.graphics.hwui.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -32,6 +36,7 @@ public class BitmapShader extends Shader {
* Prevent garbage collection.
*/
/*package*/ Bitmap mBitmap;
+ private Gainmap mOverrideGainmap;
private int mTileX;
private int mTileY;
@@ -173,6 +178,24 @@ public class BitmapShader extends Shader {
}
/**
+ * Draws the BitmapShader with a copy of the given gainmap instead of the gainmap on the Bitmap
+ * the shader was constructed from
+ *
+ * @param overrideGainmap The gainmap to draw instead, null to use any gainmap on the Bitmap
+ */
+ @FlaggedApi(Flags.FLAG_GAINMAP_ANIMATIONS)
+ public void setOverrideGainmap(@Nullable Gainmap overrideGainmap) {
+ if (!Flags.gainmapAnimations()) throw new IllegalStateException("API not available");
+
+ if (overrideGainmap == null) {
+ mOverrideGainmap = null;
+ } else {
+ mOverrideGainmap = new Gainmap(overrideGainmap, overrideGainmap.getGainmapContents());
+ }
+ discardNativeInstance();
+ }
+
+ /**
* Returns the current max anisotropic filtering value configured by
* {@link #setFilterMode(int)}. If {@link #setFilterMode(int)} is invoked this returns zero.
*/
@@ -199,14 +222,9 @@ public class BitmapShader extends Shader {
mIsDirectSampled = mRequestDirectSampling;
mRequestDirectSampling = false;
-
- if (mMaxAniso > 0) {
- return nativeCreateWithMaxAniso(nativeMatrix, mBitmap.getNativeInstance(), mTileX,
- mTileY, mMaxAniso, mIsDirectSampled);
- } else {
- return nativeCreate(nativeMatrix, mBitmap.getNativeInstance(), mTileX, mTileY,
- enableLinearFilter, mIsDirectSampled);
- }
+ return nativeCreate(nativeMatrix, mBitmap.getNativeInstance(), mTileX,
+ mTileY, mMaxAniso, enableLinearFilter, mIsDirectSampled,
+ mOverrideGainmap != null ? mOverrideGainmap.mNativePtr : 0);
}
/** @hide */
@@ -217,9 +235,7 @@ public class BitmapShader extends Shader {
}
private static native long nativeCreate(long nativeMatrix, long bitmapHandle,
- int shaderTileModeX, int shaderTileModeY, boolean filter, boolean isDirectSampled);
-
- private static native long nativeCreateWithMaxAniso(long nativeMatrix, long bitmapHandle,
- int shaderTileModeX, int shaderTileModeY, int maxAniso, boolean isDirectSampled);
+ int shaderTileModeX, int shaderTileModeY, int maxAniso, boolean filter,
+ boolean isDirectSampled, long overrideGainmapHandle);
}
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index e7814cbd67e7..d1aceafc4e2c 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -114,6 +114,7 @@ public class Canvas extends BaseCanvas {
throw new IllegalStateException("Immutable bitmap passed to Canvas constructor");
}
throwIfCannotDraw(bitmap);
+ bitmap.setGainmap(null);
mNativeCanvasWrapper = nInitRaster(bitmap.getNativeInstance());
mFinalizer = NoImagePreloadHolder.sRegistry.registerNativeAllocation(
this, mNativeCanvasWrapper);
@@ -178,7 +179,7 @@ public class Canvas extends BaseCanvas {
throw new IllegalStateException();
}
throwIfCannotDraw(bitmap);
-
+ bitmap.setGainmap(null);
nSetBitmap(mNativeCanvasWrapper, bitmap.getNativeInstance());
mDensity = bitmap.mDensity;
}
diff --git a/graphics/java/android/graphics/ColorFilter.java b/graphics/java/android/graphics/ColorFilter.java
index 8fd6f7f609c6..7050325997b6 100644
--- a/graphics/java/android/graphics/ColorFilter.java
+++ b/graphics/java/android/graphics/ColorFilter.java
@@ -41,21 +41,11 @@ public class ColorFilter {
* Current native SkColorFilter instance.
*/
private long mNativeInstance;
- // Runnable to do immediate destruction
- private Runnable mCleaner;
long createNativeInstance() {
return 0;
}
- synchronized final void discardNativeInstance() {
- if (mNativeInstance != 0) {
- mCleaner.run();
- mCleaner = null;
- mNativeInstance = 0;
- }
- }
-
/** @hide */
public synchronized final long getNativeInstance() {
if (mNativeInstance == 0) {
@@ -65,8 +55,7 @@ public class ColorFilter {
// Note: we must check for null here, since it's possible for createNativeInstance()
// to return nullptr if the native SkColorFilter would be a no-op at draw time.
// See native implementations of subclass create methods for more info.
- mCleaner = NoImagePreloadHolder.sRegistry.registerNativeAllocation(
- this, mNativeInstance);
+ NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativeInstance);
}
}
return mNativeInstance;
diff --git a/graphics/java/android/graphics/ColorMatrixColorFilter.java b/graphics/java/android/graphics/ColorMatrixColorFilter.java
index 90ff1899f34d..bfdf3187c575 100644
--- a/graphics/java/android/graphics/ColorMatrixColorFilter.java
+++ b/graphics/java/android/graphics/ColorMatrixColorFilter.java
@@ -81,12 +81,12 @@ public class ColorMatrixColorFilter extends ColorFilter {
*/
@UnsupportedAppUsage
public void setColorMatrix(@Nullable ColorMatrix matrix) {
- discardNativeInstance();
if (matrix == null) {
mMatrix.reset();
} else {
mMatrix.set(matrix);
}
+ nativeSetColorMatrix(getNativeInstance(), mMatrix.getArray());
}
/**
@@ -111,7 +111,6 @@ public class ColorMatrixColorFilter extends ColorFilter {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public void setColorMatrixArray(@Nullable float[] array) {
// called '...Array' so that passing null isn't ambiguous
- discardNativeInstance();
if (array == null) {
mMatrix.reset();
} else {
@@ -120,6 +119,7 @@ public class ColorMatrixColorFilter extends ColorFilter {
}
mMatrix.set(array);
}
+ nativeSetColorMatrix(getNativeInstance(), mMatrix.getArray());
}
@Override
@@ -128,4 +128,6 @@ public class ColorMatrixColorFilter extends ColorFilter {
}
private static native long nativeColorMatrixFilter(float[] array);
+
+ private static native void nativeSetColorMatrix(long colorMatrixColorFilter, float[] array);
}
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 99bebb8b9812..a2319a53a659 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -199,6 +199,8 @@ public abstract class ColorSpace {
private static final float[] SRGB_PRIMARIES = { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f };
private static final float[] NTSC_1953_PRIMARIES = { 0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f };
+ private static final float[] DCI_P3_PRIMARIES =
+ { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f };
private static final float[] BT2020_PRIMARIES =
{ 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f };
/**
@@ -211,6 +213,9 @@ public abstract class ColorSpace {
private static final Rgb.TransferParameters SRGB_TRANSFER_PARAMETERS =
new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4);
+ private static final Rgb.TransferParameters SMPTE_170M_TRANSFER_PARAMETERS =
+ new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45);
+
// HLG transfer with an SDR whitepoint of 203 nits
private static final Rgb.TransferParameters BT2020_HLG_TRANSFER_PARAMETERS =
new Rgb.TransferParameters(2.0, 2.0, 1 / 0.17883277, 0.28466892, 0.55991073,
@@ -1559,10 +1564,10 @@ public abstract class ColorSpace {
DataSpace.DATASPACE_SCRGB_LINEAR, Named.LINEAR_EXTENDED_SRGB.ordinal());
sNamedColorSpaces[Named.BT709.ordinal()] = new ColorSpace.Rgb(
"Rec. ITU-R BT.709-5",
- new float[] { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f },
+ SRGB_PRIMARIES,
ILLUMINANT_D65,
null,
- new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45),
+ SMPTE_170M_TRANSFER_PARAMETERS,
Named.BT709.ordinal()
);
sDataToColorSpaces.put(DataSpace.DATASPACE_BT709, Named.BT709.ordinal());
@@ -1577,7 +1582,7 @@ public abstract class ColorSpace {
sDataToColorSpaces.put(DataSpace.DATASPACE_BT2020, Named.BT2020.ordinal());
sNamedColorSpaces[Named.DCI_P3.ordinal()] = new ColorSpace.Rgb(
"SMPTE RP 431-2-2007 DCI (P3)",
- new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
+ DCI_P3_PRIMARIES,
new float[] { 0.314f, 0.351f },
2.6,
0.0f, 1.0f,
@@ -1586,7 +1591,7 @@ public abstract class ColorSpace {
sDataToColorSpaces.put(DataSpace.DATASPACE_DCI_P3, Named.DCI_P3.ordinal());
sNamedColorSpaces[Named.DISPLAY_P3.ordinal()] = new ColorSpace.Rgb(
"Display P3",
- new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
+ DCI_P3_PRIMARIES,
ILLUMINANT_D65,
null,
SRGB_TRANSFER_PARAMETERS,
@@ -1598,7 +1603,7 @@ public abstract class ColorSpace {
NTSC_1953_PRIMARIES,
ILLUMINANT_C,
null,
- new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45),
+ SMPTE_170M_TRANSFER_PARAMETERS,
Named.NTSC_1953.ordinal()
);
sNamedColorSpaces[Named.SMPTE_C.ordinal()] = new ColorSpace.Rgb(
@@ -1606,7 +1611,7 @@ public abstract class ColorSpace {
new float[] { 0.630f, 0.340f, 0.310f, 0.595f, 0.155f, 0.070f },
ILLUMINANT_D65,
null,
- new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45),
+ SMPTE_170M_TRANSFER_PARAMETERS,
Named.SMPTE_C.ordinal()
);
sNamedColorSpaces[Named.ADOBE_RGB.ordinal()] = new ColorSpace.Rgb(
@@ -3057,7 +3062,7 @@ public abstract class ColorSpace {
* primaries for such a ColorSpace does not make sense, so we use a special
* set of primaries that are all 1s.</p>
*
- * @return A new non-null array of 2 floats
+ * @return A new non-null array of 6 floats
*
* @see #getPrimaries(float[])
*/
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 674246acafef..52b0b95d3e76 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -16,6 +16,10 @@
package android.graphics;
+import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE;
+import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL;
+import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY;
+import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT;
import static android.text.FontConfig.NamedFamilyList;
import android.annotation.NonNull;
@@ -28,6 +32,7 @@ import android.os.Build;
import android.os.LocaleList;
import android.text.FontConfig;
import android.util.ArraySet;
+import android.util.Log;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
@@ -231,7 +236,9 @@ public class FontListParser {
}
}
- return new FontConfig(families, filtered, resultNamedFamilies, lastModifiedDate,
+ return new FontConfig(families, filtered, resultNamedFamilies,
+ customization.getLocaleFamilyCustomizations(),
+ lastModifiedDate,
configVersion);
}
@@ -256,6 +263,7 @@ public class FontListParser {
final String lang = parser.getAttributeValue("", "lang");
final String variant = parser.getAttributeValue(null, "variant");
final String ignore = parser.getAttributeValue(null, "ignore");
+ final String varFamilyTypeStr = parser.getAttributeValue(null, "varFamilyType");
final List<FontConfig.Font> fonts = new ArrayList<>();
while (keepReading(parser)) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
@@ -278,12 +286,45 @@ public class FontListParser {
intVariant = FontConfig.FontFamily.VARIANT_ELEGANT;
}
}
+ int varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE;
+ if (varFamilyTypeStr != null) {
+ varFamilyType = Integer.parseInt(varFamilyTypeStr);
+ if (varFamilyType <= -1 || varFamilyType > 3) {
+ Log.e(TAG, "Error: unexpected varFamilyType value: " + varFamilyTypeStr);
+ varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE;
+ }
+
+ // validation but don't read font content for performance reasons.
+ switch (varFamilyType) {
+ case VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY:
+ if (fonts.size() != 1) {
+ Log.e(TAG, "Error: Single font support wght axis, but two or more fonts are"
+ + " included in the font family.");
+ varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE;
+ }
+ break;
+ case VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL:
+ if (fonts.size() != 1) {
+ Log.e(TAG, "Error: Single font support both ital and wght axes, but two or"
+ + " more fonts are included in the font family.");
+ varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE;
+ }
+ break;
+ case VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT:
+ if (fonts.size() != 2) {
+ Log.e(TAG, "Error: two fonts that support wght axis, but one or three or"
+ + " more fonts are included in the font family.");
+ varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE;
+ }
+ }
+ }
boolean skip = (ignore != null && (ignore.equals("true") || ignore.equals("1")));
if (skip || fonts.isEmpty()) {
return null;
}
- return new FontConfig.FontFamily(fonts, LocaleList.forLanguageTags(lang), intVariant);
+ return new FontConfig.FontFamily(fonts, LocaleList.forLanguageTags(lang), intVariant,
+ varFamilyType);
}
private static void throwIfAttributeExists(String attrName, XmlPullParser parser) {
diff --git a/graphics/java/android/graphics/ForceDarkType.java b/graphics/java/android/graphics/ForceDarkType.java
new file mode 100644
index 000000000000..396b03703bb9
--- /dev/null
+++ b/graphics/java/android/graphics/ForceDarkType.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * The style of force dark to use in {@link HardwareRenderer}.
+ *
+ * You must keep this in sync with the C++ enum ForceDarkType in
+ * frameworks/base/libs/hwui/utils/ForceDark.h
+ *
+ * @hide
+ */
+public class ForceDarkType {
+ /**
+ * Force dark disabled: normal, default operation.
+ *
+ * @hide
+ */
+ public static final int NONE = 0;
+
+ /**
+ * Use force dark
+ * @hide
+ */
+ public static final int FORCE_DARK = 1;
+
+ /**
+ * Force force-dark. {@see Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED}
+ * @hide */
+ public static final int FORCE_INVERT_COLOR_DARK = 2;
+
+ /** @hide */
+ @IntDef({
+ NONE,
+ FORCE_DARK,
+ FORCE_INVERT_COLOR_DARK,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ForceDarkTypeDef {}
+
+}
diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java
index f639521ff250..0a6fb8424094 100644
--- a/graphics/java/android/graphics/Gainmap.java
+++ b/graphics/java/android/graphics/Gainmap.java
@@ -16,11 +16,14 @@
package android.graphics;
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.graphics.hwui.flags.Flags;
+
import libcore.util.NativeAllocationRegistry;
/**
@@ -124,9 +127,8 @@ public final class Gainmap implements Parcelable {
/**
* Creates a new gainmap using the provided gainmap as the metadata source and the provided
* bitmap as the replacement for the gainmapContents
- * TODO: Make public, it's useful
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_GAINMAP_CONSTRUCTOR_WITH_METADATA)
public Gainmap(@NonNull Gainmap gainmap, @NonNull Bitmap gainmapContents) {
this(gainmapContents, nCreateCopy(gainmap.mNativePtr));
}
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index 9cde1878d9d8..20e393eaee6d 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -182,7 +182,7 @@ public class HardwareRenderer {
/** @hide */
protected RenderNode mRootNode;
private boolean mOpaque = true;
- private boolean mForceDark = false;
+ private int mForceDark = ForceDarkType.NONE;
private @ActivityInfo.ColorMode int mColorMode = ActivityInfo.COLOR_MODE_DEFAULT;
private float mDesiredSdrHdrRatio = 1f;
@@ -571,10 +571,10 @@ public class HardwareRenderer {
* Whether or not the force-dark feature should be used for this renderer.
* @hide
*/
- public boolean setForceDark(boolean enable) {
- if (mForceDark != enable) {
- mForceDark = enable;
- nSetForceDark(mNativeProxy, enable);
+ public boolean setForceDark(@ForceDarkType.ForceDarkTypeDef int type) {
+ if (mForceDark != type) {
+ mForceDark = type;
+ nSetForceDark(mNativeProxy, type);
return true;
}
return false;
@@ -1390,10 +1390,6 @@ public class HardwareRenderer {
int largestWidth = activeMode.getPhysicalWidth();
int largestHeight = activeMode.getPhysicalHeight();
final OverlayProperties overlayProperties = defaultDisplay.getOverlaySupport();
- boolean supportFp16ForHdr = overlayProperties != null
- ? overlayProperties.supportFp16ForHdr() : false;
- boolean supportMixedColorSpaces = overlayProperties != null
- ? overlayProperties.supportMixedColorSpaces() : false;
for (int i = 0; i < allDisplays.length; i++) {
final Display display = allDisplays[i];
@@ -1421,7 +1417,8 @@ public class HardwareRenderer {
nInitDisplayInfo(largestWidth, largestHeight, defaultDisplay.getRefreshRate(),
wideColorDataspace, defaultDisplay.getAppVsyncOffsetNanos(),
defaultDisplay.getPresentationDeadlineNanos(),
- supportFp16ForHdr, supportMixedColorSpaces);
+ overlayProperties.isFp16SupportedForHdr(),
+ overlayProperties.isMixedColorSpacesSupported());
mDisplayInitialized = true;
}
@@ -1600,7 +1597,7 @@ public class HardwareRenderer {
private static native void nAllocateBuffers(long nativeProxy);
- private static native void nSetForceDark(long nativeProxy, boolean enabled);
+ private static native void nSetForceDark(long nativeProxy, int type);
private static native void nSetDisplayDensityDpi(int densityDpi);
diff --git a/graphics/java/android/graphics/LightingColorFilter.java b/graphics/java/android/graphics/LightingColorFilter.java
index df91c5d492bd..0aa6f1282c1a 100644
--- a/graphics/java/android/graphics/LightingColorFilter.java
+++ b/graphics/java/android/graphics/LightingColorFilter.java
@@ -78,7 +78,7 @@ public class LightingColorFilter extends ColorFilter {
public void setColorMultiply(@ColorInt int mul) {
if (mMul != mul) {
mMul = mul;
- discardNativeInstance();
+ native_SetLightingFilterMul(getNativeInstance(), mul);
}
}
@@ -104,7 +104,7 @@ public class LightingColorFilter extends ColorFilter {
public void setColorAdd(@ColorInt int add) {
if (mAdd != add) {
mAdd = add;
- discardNativeInstance();
+ native_SetLightingFilterAdd(getNativeInstance(), add);
}
}
@@ -114,4 +114,8 @@ public class LightingColorFilter extends ColorFilter {
}
private static native long native_CreateLightingFilter(int mul, int add);
+
+ private static native void native_SetLightingFilterAdd(long lightingFilter, int add);
+
+ private static native void native_SetLightingFilterMul(long lightingFilter, int mul);
}
diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java
index 66fabec91924..a4bce9eb5e88 100644
--- a/graphics/java/android/graphics/Mesh.java
+++ b/graphics/java/android/graphics/Mesh.java
@@ -23,6 +23,8 @@ import android.annotation.NonNull;
import libcore.util.NativeAllocationRegistry;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.nio.Buffer;
import java.nio.ShortBuffer;
@@ -43,6 +45,7 @@ public class Mesh {
* Determines how the mesh is represented and will be drawn.
*/
@IntDef({TRIANGLES, TRIANGLE_STRIP})
+ @Retention(RetentionPolicy.SOURCE)
private @interface Mode {}
/**
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index d35dcab11f49..92c4de6490a3 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -16,14 +16,20 @@
package android.graphics;
+import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
+
import android.annotation.ColorInt;
import android.annotation.ColorLong;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Px;
import android.annotation.Size;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.fonts.FontVariationAxis;
import android.os.Build;
@@ -611,6 +617,7 @@ public class Paint {
mCompatScaling = mInvCompatScaling = 1;
setTextLocales(LocaleList.getAdjustedDefault());
mColor = Color.pack(Color.BLACK);
+ resetElegantTextHeight();
}
/**
@@ -651,7 +658,7 @@ public class Paint {
mBidiFlags = BIDI_DEFAULT_LTR;
setTextLocales(LocaleList.getAdjustedDefault());
- setElegantTextHeight(false);
+ resetElegantTextHeight();
mFontFeatureSettings = null;
mFontVariationSettings = null;
@@ -1606,7 +1613,7 @@ public class Paint {
/**
* Returns the color of the shadow layer.
*
- * @return the shadow layer's color encoded as a {@link ColorLong}.
+ * @return the shadow layer's color encoded as a {@code ColorLong}.
* @see #setShadowLayer(float,float,float,int)
* @see #setShadowLayer(float,float,float,long)
* @see Color
@@ -1732,12 +1739,30 @@ public class Paint {
/**
* Get the elegant metrics flag.
*
+ * From API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, the default value will be true by
+ * default if the app has a target SDK of API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or
+ * later.
+ *
* @return true if elegant metrics are enabled for text drawing.
*/
public boolean isElegantTextHeight() {
- return nIsElegantTextHeight(mNativePaint);
+ int rawValue = nGetElegantTextHeight(mNativePaint);
+ switch (rawValue) {
+ case ELEGANT_TEXT_HEIGHT_DISABLED:
+ return false;
+ case ELEGANT_TEXT_HEIGHT_ENABLED:
+ return true;
+ case ELEGANT_TEXT_HEIGHT_UNSET:
+ default:
+ return com.android.text.flags.Flags.deprecateUiFonts();
+ }
}
+ // Note: the following three values must be equal to the ones in the JNI file: Paint.cpp
+ private static final int ELEGANT_TEXT_HEIGHT_UNSET = -1;
+ private static final int ELEGANT_TEXT_HEIGHT_ENABLED = 0;
+ private static final int ELEGANT_TEXT_HEIGHT_DISABLED = 1;
+
/**
* Set the paint's elegant height metrics flag. This setting selects font
* variants that have not been compacted to fit Latin-based vertical
@@ -1746,7 +1771,29 @@ public class Paint {
* @param elegant set the paint's elegant metrics flag for drawing text.
*/
public void setElegantTextHeight(boolean elegant) {
- nSetElegantTextHeight(mNativePaint, elegant);
+ nSetElegantTextHeight(mNativePaint,
+ elegant ? ELEGANT_TEXT_HEIGHT_ENABLED : ELEGANT_TEXT_HEIGHT_DISABLED);
+ }
+
+ /**
+ * A change ID for deprecating UI fonts.
+ *
+ * From API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, the default value will be true by
+ * default if the app has a target SDK of API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or
+ * later.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public static final long DEPRECATE_UI_FONT = 279646685L;
+
+ private void resetElegantTextHeight() {
+ if (CompatChanges.isChangeEnabled(DEPRECATE_UI_FONT)) {
+ nSetElegantTextHeight(mNativePaint, ELEGANT_TEXT_HEIGHT_UNSET);
+ } else {
+ nSetElegantTextHeight(mNativePaint, ELEGANT_TEXT_HEIGHT_DISABLED);
+ }
}
/**
@@ -2110,6 +2157,31 @@ public class Paint {
* The recommended additional space to add between lines of text.
*/
public float leading;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || !(o instanceof FontMetrics)) return false;
+ FontMetrics that = (FontMetrics) o;
+ return that.top == top && that.ascent == ascent && that.descent == descent
+ && that.bottom == bottom && that.leading == leading;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(top, ascent, descent, bottom, leading);
+ }
+
+ @Override
+ public String toString() {
+ return "FontMetrics{"
+ + "top=" + top
+ + ", ascent=" + ascent
+ + ", descent=" + descent
+ + ", bottom=" + bottom
+ + ", leading=" + leading
+ + '}';
+ }
}
/**
@@ -2125,7 +2197,7 @@ public class Paint {
* @return the font's recommended interline spacing.
*/
public float getFontMetrics(FontMetrics metrics) {
- return nGetFontMetrics(mNativePaint, metrics);
+ return nGetFontMetrics(mNativePaint, metrics, false);
}
/**
@@ -2139,6 +2211,32 @@ public class Paint {
}
/**
+ * Get the font metrics used for the locale
+ *
+ * Obtain the metrics of the font that is used for the specified locale by
+ * {@link #setTextLocales(LocaleList)}. If multiple locales are specified, the minimum ascent
+ * and maximum descent will be set.
+ *
+ * This API is useful for determining the default line height of the empty text field, e.g.
+ * {@link android.widget.EditText}.
+ *
+ * Note, if the {@link android.graphics.Typeface} is created from the custom font files, its
+ * metrics are reserved, i.e. the ascent will be the custom font's ascent or smaller, the
+ * descent will be the custom font's descent or larger.
+ *
+ * Note, if the {@link android.graphics.Typeface} is a system fallback, e.g.
+ * {@link android.graphics.Typeface#SERIF}, the default font's metrics are reserved, i.e. the
+ * ascent will be the serif font's ascent or smaller, the descent will be the serif font's
+ * descent or larger.
+ *
+ * @param metrics an output parameter. All members will be set by calling this function.
+ */
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public void getFontMetricsForLocale(@NonNull FontMetrics metrics) {
+ nGetFontMetrics(mNativePaint, metrics, true);
+ }
+
+ /**
* Returns the font metrics value for the given text.
*
* If the text is rendered with multiple font files, this function returns the large ascent and
@@ -2280,6 +2378,33 @@ public class Paint {
*/
public int leading;
+ /**
+ * Set values from {@link FontMetricsInt}.
+ * @param fontMetricsInt a font metrics.
+ */
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public void set(@NonNull FontMetricsInt fontMetricsInt) {
+ top = fontMetricsInt.top;
+ ascent = fontMetricsInt.ascent;
+ descent = fontMetricsInt.descent;
+ bottom = fontMetricsInt.bottom;
+ leading = fontMetricsInt.leading;
+ }
+
+ /**
+ * Set values from {@link FontMetrics} with rounding accordingly.
+ * @param fontMetrics a font metrics.
+ */
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public void set(@NonNull FontMetrics fontMetrics) {
+ // See GraphicsJNI::set_metrics_int method for consistency.
+ top = (int) Math.floor(fontMetrics.top);
+ ascent = Math.round(fontMetrics.ascent);
+ descent = Math.round(fontMetrics.descent);
+ bottom = (int) Math.ceil(fontMetrics.bottom);
+ leading = Math.round(fontMetrics.leading);
+ }
+
@Override public String toString() {
return "FontMetricsInt: top=" + top + " ascent=" + ascent +
" descent=" + descent + " bottom=" + bottom +
@@ -2318,7 +2443,7 @@ public class Paint {
* @return the font's interline spacing.
*/
public int getFontMetricsInt(FontMetricsInt fmi) {
- return nGetFontMetricsInt(mNativePaint, fmi);
+ return nGetFontMetricsInt(mNativePaint, fmi, false);
}
public FontMetricsInt getFontMetricsInt() {
@@ -2328,6 +2453,32 @@ public class Paint {
}
/**
+ * Get the font metrics used for the locale
+ *
+ * Obtain the metrics of the font that is used for the specified locale by
+ * {@link #setTextLocales(LocaleList)}. If multiple locales are specified, the minimum ascent
+ * and maximum descent will be set.
+ *
+ * This API is useful for determining the default line height of the empty text field, e.g.
+ * {@link android.widget.EditText}.
+ *
+ * Note, if the {@link android.graphics.Typeface} is created from the custom font files, its
+ * metrics are reserved, i.e. the ascent will be the custom font's ascent or smaller, the
+ * descent will be the custom font's descent or larger.
+ *
+ * Note, if the {@link android.graphics.Typeface} is a system fallback, e.g.
+ * {@link android.graphics.Typeface#SERIF}, the default font's metrics are reserved, i.e. the
+ * ascent will be the serif font's ascent or smaller, the descent will be the serif font's
+ * descent or larger.
+ *
+ * @param metrics an output parameter. All members will be set by calling this function.
+ */
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public void getFontMetricsIntForLocale(@NonNull FontMetricsInt metrics) {
+ nGetFontMetricsInt(mNativePaint, metrics, true);
+ }
+
+ /**
* Return the recommend line spacing based on the current typeface and
* text size.
*
@@ -3172,6 +3323,32 @@ public class Paint {
public float getRunCharacterAdvance(@NonNull char[] text, int start, int end, int contextStart,
int contextEnd, boolean isRtl, int offset,
@Nullable float[] advances, int advancesIndex) {
+ return getRunCharacterAdvance(text, start, end, contextStart, contextEnd, isRtl, offset,
+ advances, advancesIndex, null);
+ }
+
+ /**
+ * Measure the advance of each character within a run of text and also return the cursor
+ * position within the run.
+ *
+ * @see #getRunAdvance(char[], int, int, int, int, boolean, int) for more details.
+ *
+ * @param text the text to measure. Cannot be null.
+ * @param start the start index of the range to measure, inclusive
+ * @param end the end index of the range to measure, exclusive
+ * @param contextStart the start index of the shaping context, inclusive
+ * @param contextEnd the end index of the shaping context, exclusive
+ * @param isRtl whether the run is in RTL direction
+ * @param offset index of caret position
+ * @param advances the array that receives the computed character advances
+ * @param advancesIndex the start index from which the advances array is filled
+ * @param drawBounds the output parameter for the bounding box of drawing text, optional
+ * @return width measurement between start and offset
+ * @hide
+ */
+ public float getRunCharacterAdvance(@NonNull char[] text, int start, int end, int contextStart,
+ int contextEnd, boolean isRtl, int offset,
+ @Nullable float[] advances, int advancesIndex, @Nullable RectF drawBounds) {
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -3201,7 +3378,7 @@ public class Paint {
}
return nGetRunCharacterAdvance(mNativePaint, text, start, end, contextStart, contextEnd,
- isRtl, offset, advances, advancesIndex);
+ isRtl, offset, advances, advancesIndex, drawBounds);
}
/**
@@ -3228,6 +3405,29 @@ public class Paint {
public float getRunCharacterAdvance(@NonNull CharSequence text, int start, int end,
int contextStart, int contextEnd, boolean isRtl, int offset,
@Nullable float[] advances, int advancesIndex) {
+ return getRunCharacterAdvance(text, start, end, contextStart, contextEnd, isRtl, offset,
+ advances, advancesIndex, null);
+ }
+
+ /**
+ * @see #getRunCharacterAdvance(char[], int, int, int, int, boolean, int, float[], int)
+ *
+ * @param text the text to measure. Cannot be null.
+ * @param start the index of the start of the range to measure
+ * @param end the index + 1 of the end of the range to measure
+ * @param contextStart the index of the start of the shaping context
+ * @param contextEnd the index + 1 of the end of the shaping context
+ * @param isRtl whether the run is in RTL direction
+ * @param offset index of caret position
+ * @param advances the array that receives the computed character advances
+ * @param advancesIndex the start index from which the advances array is filled
+ * @param drawBounds the output parameter for the bounding box of drawing text, optional
+ * @return width measurement between start and offset
+ * @hide
+ */
+ public float getRunCharacterAdvance(@NonNull CharSequence text, int start, int end,
+ int contextStart, int contextEnd, boolean isRtl, int offset,
+ @Nullable float[] advances, int advancesIndex, @Nullable RectF drawBounds) {
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -3260,7 +3460,7 @@ public class Paint {
TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
final float result = getRunCharacterAdvance(buf, start - contextStart, end - contextStart,
0, contextEnd - contextStart, isRtl, offset - contextStart,
- advances, advancesIndex);
+ advances, advancesIndex, drawBounds);
TemporaryBuffer.recycle(buf);
return result;
}
@@ -3378,7 +3578,7 @@ public class Paint {
int contextStart, int contextEnd, boolean isRtl, int offset);
private static native float nGetRunCharacterAdvance(long paintPtr, char[] text, int start,
int end, int contextStart, int contextEnd, boolean isRtl, int offset, float[] advances,
- int advancesIndex);
+ int advancesIndex, RectF drawingBounds);
private static native int nGetOffsetForAdvance(long paintPtr, char[] text, int start, int end,
int contextStart, int contextEnd, boolean isRtl, float advance);
private static native void nGetFontMetricsIntForText(long paintPtr, char[] text,
@@ -3397,9 +3597,11 @@ public class Paint {
@FastNative
private static native void nSetFontFeatureSettings(long paintPtr, String settings);
@FastNative
- private static native float nGetFontMetrics(long paintPtr, FontMetrics metrics);
+ private static native float nGetFontMetrics(long paintPtr, FontMetrics metrics,
+ boolean useLocale);
@FastNative
- private static native int nGetFontMetricsInt(long paintPtr, FontMetricsInt fmi);
+ private static native int nGetFontMetricsInt(long paintPtr, FontMetricsInt fmi,
+ boolean useLocale);
// ---------------- @CriticalNative ------------------------
@@ -3502,9 +3704,9 @@ public class Paint {
@CriticalNative
private static native void nSetStrikeThruText(long paintPtr, boolean strikeThruText);
@CriticalNative
- private static native boolean nIsElegantTextHeight(long paintPtr);
+ private static native int nGetElegantTextHeight(long paintPtr);
@CriticalNative
- private static native void nSetElegantTextHeight(long paintPtr, boolean elegant);
+ private static native void nSetElegantTextHeight(long paintPtr, int elegant);
@CriticalNative
private static native float nGetTextSize(long paintPtr);
@CriticalNative
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index 81b8542c20f7..deb89faf3419 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -16,11 +16,14 @@
package android.graphics;
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
+import com.android.graphics.flags.Flags;
+
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
@@ -293,9 +296,24 @@ public class Path {
*
* @param bounds Returns the computed bounds of the path's control points.
* @param exact This parameter is no longer used.
+ *
+ * @deprecated use computeBounds(RectF) instead
*/
+ @Deprecated
@SuppressWarnings({"UnusedDeclaration"})
public void computeBounds(@NonNull RectF bounds, boolean exact) {
+ computeBounds(bounds);
+ }
+
+ /**
+ * Compute the bounds of the control points of the path, and write the
+ * answer into bounds. If the path contains 0 or 1 points, the bounds is
+ * set to (0,0,0,0)
+ *
+ * @param bounds Returns the computed bounds of the path's control points.
+ */
+ @FlaggedApi(Flags.FLAG_EXACT_COMPUTE_BOUNDS)
+ public void computeBounds(@NonNull RectF bounds) {
nComputeBounds(mNativePath, bounds);
}
diff --git a/graphics/java/android/graphics/RecordingCanvas.java b/graphics/java/android/graphics/RecordingCanvas.java
index a5184f28dbc1..635e78e674be 100644
--- a/graphics/java/android/graphics/RecordingCanvas.java
+++ b/graphics/java/android/graphics/RecordingCanvas.java
@@ -276,9 +276,7 @@ public final class RecordingCanvas extends BaseRecordingCanvas {
@CriticalNative
private static native void nResetDisplayListCanvas(long canvas, long node,
int width, int height);
- @CriticalNative
private static native int nGetMaximumTextureWidth();
- @CriticalNative
private static native int nGetMaximumTextureHeight();
@CriticalNative
private static native void nEnableZ(long renderer, boolean enableZ);
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index 2e91c240d71b..27325694073c 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -272,6 +272,17 @@ public final class RenderNode {
void positionChanged(long frameNumber, int left, int top, int right, int bottom);
/**
+ * Called by native by a Rendering Worker thread to update window position; includes
+ * the local rect that represents the clipped area of the RenderNode's bounds.
+ *
+ * @hide
+ */
+ default void positionChanged(long frameNumber, int left, int top, int right, int bottom,
+ int clipLeft, int clipTop, int clipRight, int clipBottom) {
+ positionChanged(frameNumber, left, top, right, bottom);
+ }
+
+ /**
* Called by JNI
*
* @hide */
@@ -287,6 +298,23 @@ public final class RenderNode {
}
/**
+ * Called by JNI
+ *
+ * @hide */
+ static boolean callPositionChanged2(WeakReference<PositionUpdateListener> weakListener,
+ long frameNumber, int left, int top, int right, int bottom,
+ int clipLeft, int clipTop, int clipRight, int clipBottom) {
+ final PositionUpdateListener listener = weakListener.get();
+ if (listener != null) {
+ listener.positionChanged(frameNumber, left, top, right, bottom, clipLeft,
+ clipTop, clipRight, clipBottom);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
* Call to apply a stretch effect to any child SurfaceControl layers
*
* TODO: Fold this into positionChanged & have HWUI do the ASurfaceControl calls?
@@ -371,6 +399,15 @@ public final class RenderNode {
}
@Override
+ public void positionChanged(long frameNumber, int left, int top, int right, int bottom,
+ int clipLeft, int clipTop, int clipRight, int clipBottom) {
+ for (PositionUpdateListener pul : mListeners) {
+ pul.positionChanged(frameNumber, left, top, right, bottom, clipLeft, clipTop,
+ clipRight, clipBottom);
+ }
+ }
+
+ @Override
public void positionLost(long frameNumber) {
for (PositionUpdateListener pul : mListeners) {
pul.positionLost(frameNumber);
@@ -971,6 +1008,23 @@ public final class RenderNode {
}
/**
+ * Configure the {@link android.graphics.RenderEffect} to apply to the backdrop contents of
+ * this RenderNode. This will apply a visual effect to the result of the backdrop contents
+ * of this RenderNode before the RenderNode is drawn into the destination. For example if
+ * {@link RenderEffect#createBlurEffect(float, float, RenderEffect, Shader.TileMode)}
+ * is provided, the previous content behind this RenderNode will be blurred before the
+ * RenderNode is drawn in to the destination.
+ * @param renderEffect to be applied to the backdrop contents of this RenderNode. Passing
+ * null clears all previously configured RenderEffects
+ * @return True if the value changed, false if the new value was the same as the previous value.
+ * @hide
+ */
+ public boolean setBackdropRenderEffect(@Nullable RenderEffect renderEffect) {
+ return nSetBackdropRenderEffect(mNativeRenderNode,
+ renderEffect != null ? renderEffect.getNativeInstance() : 0);
+ }
+
+ /**
* Returns the translucency level of this display list.
*
* @return A value between 0.0f and 1.0f
@@ -1797,6 +1851,9 @@ public final class RenderNode {
private static native boolean nSetRenderEffect(long renderNode, long renderEffect);
@CriticalNative
+ private static native boolean nSetBackdropRenderEffect(long renderNode, long renderEffect);
+
+ @CriticalNative
private static native boolean nSetHasOverlappingRendering(long renderNode,
boolean hasOverlappingRendering);
diff --git a/graphics/java/android/graphics/RuntimeShader.java b/graphics/java/android/graphics/RuntimeShader.java
index 3e6457919031..78d257f86613 100644
--- a/graphics/java/android/graphics/RuntimeShader.java
+++ b/graphics/java/android/graphics/RuntimeShader.java
@@ -49,11 +49,11 @@ import libcore.util.NativeAllocationRegistry;
* possible antialiasing logic for border pixels).</li>
* <li>Logic for the {@link Shader}, {@link ColorFilter}, and {@link BlendMode} on the
* {@link Paint}.</li>
- * <li>Color space conversion code, as part of Android’s color management.</li>
+ * <li>Color space conversion code, as part of Android's color management.</li>
* </ul>
*
* <p>A {@link RuntimeShader}, like other {@link Shader} types, effectively contributes a function
- * to the GPU’s fragment shader.</p>
+ * to the GPU's fragment shader.</p>
*
* <h3>AGSL Shader Execution</h3>
* <p>Just like a GLSL shader, an AGSL shader begins execution in a main function. Unlike GLSL, the
@@ -78,10 +78,10 @@ import libcore.util.NativeAllocationRegistry;
* {@link ColorSpace} for an AGSL shader is defined to be the color space of the destination, which
* in most cases is determined by {@link Window#setColorMode(int)}.</p>
*
- * <p>When authoring an AGSL shader, you won’t know what the working color space is. For many
+ * <p>When authoring an AGSL shader, you won't know what the working color space is. For many
* effects, this is fine because by default color inputs are automatically converted into the
* working color space. For certain effects, it may be important to do some math in a fixed, known
- * color space. A common example is lighting – to get physically accurate lighting, math should be
+ * color space. A common example is lighting - to get physically accurate lighting, math should be
* done in a linear color space. To help with this, AGSL provides two intrinsic functions that
* convert colors between the working color space and the
* {@link ColorSpace.Named#LINEAR_EXTENDED_SRGB} color space:
@@ -93,7 +93,7 @@ import libcore.util.NativeAllocationRegistry;
* <h3>AGSL and Premultiplied Alpha</h3>
* <p>When dealing with transparent colors, there are two (common) possible representations:
* straight (unassociated) alpha and premultiplied (associated) alpha. In ASGL the color returned
- * by the main function is expected to be premultiplied. AGSL’s use of premultiplied alpha
+ * by the main function is expected to be premultiplied. AGSL's use of premultiplied alpha
* implies:
* </p>
*
@@ -101,7 +101,7 @@ import libcore.util.NativeAllocationRegistry;
* <li>If your AGSL shader will return transparent colors, be sure to multiply the RGB by A. The
* resulting color should be [R*A, G*A, B*A, A], not [R, G, B, A].</li>
* <li>For more complex shaders, you must understand which of your colors are premultiplied vs.
- * straight. Many operations don’t make sense if you mix both kinds of color together.</li>
+ * straight. Many operations don't make sense if you mix both kinds of color together.</li>
* </ul>
*
* <h3>Uniforms</h3>
@@ -224,7 +224,7 @@ import libcore.util.NativeAllocationRegistry;
* shader uniform is undefined if it is declared in the AGSL shader but not initialized.</p>
*
* <p>Although most {@link BitmapShader}s contain colors that should be color managed, some contain
- * data that isn’t actually colors. This includes bitmaps storing normals, material properties
+ * data that isn't actually colors. This includes bitmaps storing normals, material properties
* (e.g. roughness), heightmaps, or any other purely mathematical data that happens to be stored in
* a bitmap. When using these kinds of shaders in AGSL, you probably want to initialize them with
* {@link #setInputBuffer(String, BitmapShader)}. Shaders initialized this way work much like
@@ -237,7 +237,7 @@ import libcore.util.NativeAllocationRegistry;
*
* <p>In addition, when sampling from a {@link BitmapShader} be aware that the shader does not use
* normalized coordinates (like a texture in GLSL). It uses (0, 0) in the upper-left corner, and
- * (width, height) in the bottom-right corner. Normally, this is exactly what you want. If you’re
+ * (width, height) in the bottom-right corner. Normally, this is exactly what you want. If you're
* evaluating the shader with coordinates based on the ones passed to your AGSL program, the scale
* is correct. However, if you want to adjust those coordinates (to do some kind of re-mapping of
* the bitmap), remember that the coordinates are local to the canvas.</p>
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index ee0d64727cff..dd82fed03087 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -16,6 +16,7 @@
package android.graphics;
+import android.annotation.FloatRange;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.compat.annotation.UnsupportedAppUsage;
@@ -24,7 +25,10 @@ import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.Trace;
import android.view.Surface;
+import android.view.TextureView;
+import android.view.flags.Flags;
import java.lang.ref.WeakReference;
@@ -44,6 +48,10 @@ import java.lang.ref.WeakReference;
* frames from the image stream to be sent to the SurfaceTexture object rather than to the device's
* display.
*
+ * <p>A typical pattern is to use SurfaceTexture to render frames to a {@link TextureView}; however,
+ * a TextureView is not <i>required</i> for using the texture object. The texture object may be used
+ * as part of an OpenGL ES shader.
+ *
* <p>When sampling from the texture one should first transform the texture coordinates using the
* matrix queried via {@link #getTransformMatrix(float[])}. The transform matrix may change each
* time {@link #updateTexImage} is called, so it should be re-queried each time the texture image
@@ -74,6 +82,7 @@ public class SurfaceTexture {
private final Looper mCreatorLooper;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private Handler mOnFrameAvailableHandler;
+ private Handler mOnSetFrameRateHandler;
/**
* These fields are used by native code, do not access or modify.
@@ -95,6 +104,21 @@ public class SurfaceTexture {
}
/**
+ * Callback interface for being notified that a producer set a frame rate
+ * @hide
+ */
+ public interface OnSetFrameRateListener {
+ /**
+ * Called when the producer sets a frame rate
+ * @hide
+ */
+ void onSetFrameRate(SurfaceTexture surfaceTexture,
+ @FloatRange(from = 0.0) float frameRate,
+ @Surface.FrameRateCompatibility int compatibility,
+ @Surface.ChangeFrameRateStrategy int changeFrameRateStrategy);
+ }
+
+ /**
* Exception thrown when a SurfaceTexture couldn't be created or resized.
*
* @deprecated No longer thrown. {@link android.view.Surface.OutOfResourcesException}
@@ -219,6 +243,48 @@ public class SurfaceTexture {
}
}
+ private static class SetFrameRateArgs {
+ SetFrameRateArgs(@FloatRange(from = 0.0) float frameRate,
+ @Surface.FrameRateCompatibility int compatibility,
+ @Surface.ChangeFrameRateStrategy int changeFrameRateStrategy) {
+ this.mFrameRate = frameRate;
+ this.mCompatibility = compatibility;
+ this.mChangeFrameRateStrategy = changeFrameRateStrategy;
+ }
+ final float mFrameRate;
+ final int mCompatibility;
+ final int mChangeFrameRateStrategy;
+ }
+
+ /**
+ * Register a callback to be invoked when the producer sets a frame rate using
+ * Surface.setFrameRate.
+ * @hide
+ */
+ public void setOnSetFrameRateListener(@Nullable final OnSetFrameRateListener listener,
+ @Nullable Handler handler) {
+ if (listener != null) {
+ Looper looper = handler != null ? handler.getLooper() :
+ mCreatorLooper != null ? mCreatorLooper : Looper.getMainLooper();
+ mOnSetFrameRateHandler = new Handler(looper, null, true /*async*/) {
+ @Override
+ public void handleMessage(Message msg) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "onSetFrameRateHandler");
+ try {
+ SetFrameRateArgs args = (SetFrameRateArgs) msg.obj;
+ listener.onSetFrameRate(SurfaceTexture.this,
+ args.mFrameRate, args.mCompatibility,
+ args.mChangeFrameRateStrategy);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ }
+ };
+ } else {
+ mOnSetFrameRateHandler = null;
+ }
+ }
+
/**
* Set the default size of the image buffers. The image producer may override the buffer size,
* in which case the producer-set buffer size will be used, not the default size set by this
@@ -413,6 +479,35 @@ public class SurfaceTexture {
}
/**
+ * This method is invoked from native code only.
+ * @hide
+ */
+ @SuppressWarnings({"UnusedDeclaration"})
+ private static void postOnSetFrameRateEventFromNative(WeakReference<SurfaceTexture> weakSelf,
+ @FloatRange(from = 0.0) float frameRate,
+ @Surface.FrameRateCompatibility int compatibility,
+ @Surface.ChangeFrameRateStrategy int changeFrameRateStrategy) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "postOnSetFrameRateEventFromNative");
+ try {
+ if (Flags.toolkitSetFrameRate()) {
+ SurfaceTexture st = weakSelf.get();
+ if (st != null) {
+ Handler handler = st.mOnSetFrameRateHandler;
+ if (handler != null) {
+ Message msg = new Message();
+ msg.obj = new SetFrameRateArgs(frameRate, compatibility,
+ changeFrameRateStrategy);
+ handler.sendMessage(msg);
+ }
+ }
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+
+ }
+
+ /**
* Returns {@code true} if the SurfaceTexture is single-buffered.
* @hide
*/
diff --git a/graphics/java/android/graphics/TEST_MAPPING b/graphics/java/android/graphics/TEST_MAPPING
new file mode 100644
index 000000000000..df912222909a
--- /dev/null
+++ b/graphics/java/android/graphics/TEST_MAPPING
@@ -0,0 +1,21 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsTextTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.LargeTest"
+ }
+ ],
+ "file_patterns": [
+ "Typeface\\.java",
+ "Paint\\.java",
+ "[^/]*Canvas\\.java",
+ "[^/]*Font[^/]*\\.java"
+ ]
+ }
+ ]
+}
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 9fb627fcc501..4c4e8fa9c088 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -1475,7 +1475,10 @@ public class Typeface {
String locale = SystemProperties.get("persist.sys.locale", "en-US");
String script = ULocale.addLikelySubtags(ULocale.forLanguageTag(locale)).getScript();
- FontConfig config = SystemFonts.getSystemPreinstalledFontConfig();
+ // The feature flag cannot be referred from Zygote. Use legacy fonts.xml for preloading font
+ // files.
+ // TODO(nona): Use new XML file once the feature is fully launched.
+ FontConfig config = SystemFonts.getSystemPreinstalledFontConfigFromLegacyXml();
for (int i = 0; i < config.getFontFamilies().size(); ++i) {
FontConfig.FontFamily family = config.getFontFamilies().get(i);
if (!family.getLocaleList().isEmpty()) {
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index 708feeb9e421..45e29a88c7db 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -24,9 +24,12 @@ import android.annotation.DrawableRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.IUriGrantsManager;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
@@ -44,10 +47,13 @@ import android.os.Message;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.RequiresPermission;
+
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
@@ -56,6 +62,8 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Objects;
@@ -110,6 +118,7 @@ public final class Icon implements Parcelable {
*/
@IntDef({TYPE_BITMAP, TYPE_RESOURCE, TYPE_DATA, TYPE_URI, TYPE_ADAPTIVE_BITMAP,
TYPE_URI_ADAPTIVE_BITMAP})
+ @Retention(RetentionPolicy.SOURCE)
public @interface IconType {
}
@@ -530,6 +539,46 @@ public final class Icon implements Parcelable {
return loadDrawable(context);
}
+ /**
+ * Load a drawable, but in the case of URI types, it will check if the passed uid has a grant
+ * to load the resource. The check will be performed using the permissions of the passed uid,
+ * and not those of the caller.
+ * <p>
+ * This should be called for {@link Icon} objects that come from a not trusted source and may
+ * contain a URI.
+ *
+ * After the check, if passed, {@link #loadDrawable} will be called. If failed, this will
+ * return {@code null}.
+ *
+ * @see #loadDrawable
+ *
+ * @hide
+ */
+ @Nullable
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ public Drawable loadDrawableCheckingUriGrant(
+ Context context,
+ IUriGrantsManager iugm,
+ int callingUid,
+ String packageName
+ ) {
+ if (getType() == TYPE_URI || getType() == TYPE_URI_ADAPTIVE_BITMAP) {
+ try {
+ iugm.checkGrantUriPermission_ignoreNonSystem(
+ callingUid,
+ packageName,
+ ContentProvider.getUriWithoutUserId(getUri()),
+ Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ ContentProvider.getUserIdFromUri(getUri())
+ );
+ } catch (SecurityException | RemoteException e) {
+ Log.e(TAG, "Failed to get URI permission for: " + getUri(), e);
+ return null;
+ }
+ }
+ return loadDrawable(context);
+ }
+
/** @hide */
public static final int MIN_ASHMEM_ICON_SIZE = 128 * (1 << 10);
diff --git a/graphics/java/android/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
index b458dd9021d0..ba5628cd2bc1 100644
--- a/graphics/java/android/graphics/fonts/FontCustomizationParser.java
+++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
@@ -22,6 +22,7 @@ import static android.text.FontConfig.NamedFamilyList;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.FontListParser;
+import android.text.FontConfig;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
@@ -34,6 +35,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
/**
@@ -52,14 +54,19 @@ public class FontCustomizationParser {
private final List<Alias> mAdditionalAliases;
+ private final List<FontConfig.Customization.LocaleFallback> mLocaleFamilyCustomizations;
+
public Result() {
mAdditionalNamedFamilies = Collections.emptyMap();
+ mLocaleFamilyCustomizations = Collections.emptyList();
mAdditionalAliases = Collections.emptyList();
}
public Result(Map<String, NamedFamilyList> additionalNamedFamilies,
+ List<FontConfig.Customization.LocaleFallback> localeFamilyCustomizations,
List<Alias> additionalAliases) {
mAdditionalNamedFamilies = additionalNamedFamilies;
+ mLocaleFamilyCustomizations = localeFamilyCustomizations;
mAdditionalAliases = additionalAliases;
}
@@ -70,6 +77,10 @@ public class FontCustomizationParser {
public List<Alias> getAdditionalAliases() {
return mAdditionalAliases;
}
+
+ public List<FontConfig.Customization.LocaleFallback> getLocaleFamilyCustomizations() {
+ return mLocaleFamilyCustomizations;
+ }
}
/**
@@ -89,7 +100,9 @@ public class FontCustomizationParser {
}
private static Result validateAndTransformToResult(
- List<NamedFamilyList> families, List<Alias> aliases) {
+ List<NamedFamilyList> families,
+ List<FontConfig.Customization.LocaleFallback> outLocaleFamilies,
+ List<Alias> aliases) {
HashMap<String, NamedFamilyList> namedFamily = new HashMap<>();
for (int i = 0; i < families.size(); ++i) {
final NamedFamilyList family = families.get(i);
@@ -105,7 +118,7 @@ public class FontCustomizationParser {
+ "requires fallackTarget attribute");
}
}
- return new Result(namedFamily, aliases);
+ return new Result(namedFamily, outLocaleFamilies, aliases);
}
private static Result readFamilies(
@@ -115,12 +128,13 @@ public class FontCustomizationParser {
) throws XmlPullParserException, IOException {
List<NamedFamilyList> families = new ArrayList<>();
List<Alias> aliases = new ArrayList<>();
+ List<FontConfig.Customization.LocaleFallback> outLocaleFamilies = new ArrayList<>();
parser.require(XmlPullParser.START_TAG, null, "fonts-modification");
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
String tag = parser.getName();
if (tag.equals("family")) {
- readFamily(parser, fontDir, families, updatableFontMap);
+ readFamily(parser, fontDir, families, outLocaleFamilies, updatableFontMap);
} else if (tag.equals("family-list")) {
readFamilyList(parser, fontDir, families, updatableFontMap);
} else if (tag.equals("alias")) {
@@ -129,13 +143,14 @@ public class FontCustomizationParser {
FontListParser.skip(parser);
}
}
- return validateAndTransformToResult(families, aliases);
+ return validateAndTransformToResult(families, outLocaleFamilies, aliases);
}
private static void readFamily(
@NonNull XmlPullParser parser,
@NonNull String fontDir,
@NonNull List<NamedFamilyList> out,
+ @NonNull List<FontConfig.Customization.LocaleFallback> outCustomization,
@Nullable Map<String, File> updatableFontMap)
throws XmlPullParserException, IOException {
final String customizationType = parser.getAttributeValue(null, "customizationType");
@@ -148,6 +163,29 @@ public class FontCustomizationParser {
if (fontFamily != null) {
out.add(fontFamily);
}
+ } else if (customizationType.equals("new-locale-family")) {
+ final String lang = parser.getAttributeValue(null, "lang");
+ final String op = parser.getAttributeValue(null, "operation");
+ final int intOp;
+ if (op.equals("append")) {
+ intOp = FontConfig.Customization.LocaleFallback.OPERATION_APPEND;
+ } else if (op.equals("prepend")) {
+ intOp = FontConfig.Customization.LocaleFallback.OPERATION_PREPEND;
+ } else if (op.equals("replace")) {
+ intOp = FontConfig.Customization.LocaleFallback.OPERATION_REPLACE;
+ } else {
+ throw new IllegalArgumentException("Unknown operation=" + op);
+ }
+
+ final FontConfig.FontFamily family = FontListParser.readFamily(
+ parser, fontDir, updatableFontMap, false);
+
+ // For ignoring the customization, consume the new-locale-family element but don't
+ // register any customizations.
+ if (com.android.text.flags.Flags.vendorCustomLocaleFallback()) {
+ outCustomization.add(new FontConfig.Customization.LocaleFallback(
+ Locale.forLanguageTag(lang), intOp, family));
+ }
} else {
throw new IllegalArgumentException("Unknown customizationType=" + customizationType);
}
diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java
index bf79b1bedd8e..685fd825d43e 100644
--- a/graphics/java/android/graphics/fonts/FontFamily.java
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -16,6 +16,12 @@
package android.graphics.fonts;
+import static com.android.text.flags.Flags.FLAG_NEW_FONTS_FALLBACK_XML;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -29,7 +35,9 @@ import dalvik.annotation.optimization.FastNative;
import libcore.util.NativeAllocationRegistry;
+import java.lang.annotation.Retention;
import java.util.ArrayList;
+import java.util.Set;
/**
* A font family class can be used for creating Typeface.
@@ -58,6 +66,7 @@ import java.util.ArrayList;
*
*/
public final class FontFamily {
+
private static final String TAG = "FontFamily";
/**
@@ -73,6 +82,7 @@ public final class FontFamily {
// initial capacity.
private final SparseIntArray mStyles = new SparseIntArray(4);
+
/**
* Constructs a builder.
*
@@ -110,23 +120,64 @@ public final class FontFamily {
}
/**
+ * Build a variable font family that automatically adjust the `wght` and `ital` axes value
+ * for the requested weight/italic style values.
+ *
+ * To build a variable font family, added fonts must meet one of following conditions.
+ *
+ * If two font files are added, both font files must support `wght` axis and one font must
+ * support {@link FontStyle#FONT_SLANT_UPRIGHT} and another font must support
+ * {@link FontStyle#FONT_SLANT_ITALIC}. If the requested weight value is lower than minimum
+ * value of the supported `wght` axis, the minimum supported `wght` value is used. If the
+ * requested weight value is larger than maximum value of the supported `wght` axis, the
+ * maximum supported `wght` value is used. The weight values of the fonts are ignored.
+ *
+ * If one font file is added, that font must support the `wght` axis. If that font support
+ * `ital` axis, that `ital` value is set to 1 when the italic style is requested. If that
+ * font doesn't support `ital` axis, synthetic italic may be used. If the requested
+ * weight value is lower than minimum value of the supported `wght` axis, the minimum
+ * supported `wght` value is used. If the requested weight value is larger than maximum
+ * value of the supported `wght`axis, the maximum supported `wght` value is used. The weight
+ * value of the font is ignored.
+ *
+ * If none of the above conditions are met, this function return {@code null}.
+ *
+ * @return A variable font family. null if a variable font cannot be built from the given
+ * fonts.
+ */
+ @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
+ public @Nullable FontFamily buildVariableFamily() {
+ int variableFamilyType = analyzeAndResolveVariableType(mFonts);
+ if (variableFamilyType == VARIABLE_FONT_FAMILY_TYPE_UNKNOWN) {
+ return null;
+ }
+ return build("", FontConfig.FontFamily.VARIANT_DEFAULT,
+ true /* isCustomFallback */,
+ false /* isDefaultFallback */,
+ variableFamilyType);
+ }
+
+ /**
* Build the font family
* @return a font family
*/
public @NonNull FontFamily build() {
- return build("", FontConfig.FontFamily.VARIANT_DEFAULT, true /* isCustomFallback */,
- false /* isDefaultFallback */);
+ return build("", FontConfig.FontFamily.VARIANT_DEFAULT,
+ true /* isCustomFallback */,
+ false /* isDefaultFallback */,
+ VARIABLE_FONT_FAMILY_TYPE_NONE);
}
/** @hide */
public @NonNull FontFamily build(@NonNull String langTags, int variant,
- boolean isCustomFallback, boolean isDefaultFallback) {
+ boolean isCustomFallback, boolean isDefaultFallback, int variableFamilyType) {
+
final long builderPtr = nInitBuilder();
for (int i = 0; i < mFonts.size(); ++i) {
nAddFont(builderPtr, mFonts.get(i).getNativePtr());
}
final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback,
- isDefaultFallback);
+ isDefaultFallback, variableFamilyType);
final FontFamily family = new FontFamily(ptr);
sFamilyRegistory.registerNativeAllocation(family, ptr);
return family;
@@ -136,11 +187,123 @@ public final class FontFamily {
return font.getStyle().getWeight() | (font.getStyle().getSlant() << 16);
}
+ /**
+ * A special variable font family type that indicates `analyzeAndResolveVariableType` could
+ * not be identified the variable font family type.
+ *
+ * @see #buildVariableFamily()
+ * @hide
+ */
+ public static final int VARIABLE_FONT_FAMILY_TYPE_UNKNOWN = -1;
+
+ /**
+ * A variable font family type that indicates no variable font family can be used.
+ *
+ * The font family is used as bundle of static fonts.
+ * @see #buildVariableFamily()
+ * @hide
+ */
+ public static final int VARIABLE_FONT_FAMILY_TYPE_NONE = 0;
+ /**
+ * A variable font family type that indicates single font file can be used for multiple
+ * weight. For the italic style, fake italic may be applied.
+ *
+ * @see #buildVariableFamily()
+ * @hide
+ */
+ public static final int VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY = 1;
+ /**
+ * A variable font family type that indicates single font file can be used for multiple
+ * weight and italic.
+ *
+ * @see #buildVariableFamily()
+ * @hide
+ */
+ public static final int VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL = 2;
+ /**
+ * A variable font family type that indicates two font files are included in the family:
+ * one can be used for upright with various weights, the other one can be used for italic
+ * with various weights.
+ *
+ * @see #buildVariableFamily()
+ * @hide
+ */
+ public static final int VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT = 3;
+
+ /** @hide */
+ @Retention(SOURCE)
+ @IntDef(prefix = { "VARIABLE_FONT_FAMILY_TYPE_" }, value = {
+ VARIABLE_FONT_FAMILY_TYPE_UNKNOWN,
+ VARIABLE_FONT_FAMILY_TYPE_NONE,
+ VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY,
+ VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL,
+ VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT
+ })
+ public @interface VariableFontFamilyType {}
+
+ /**
+ * The registered italic axis used for adjusting requested style.
+ * https://learn.microsoft.com/en-us/typography/opentype/spec/dvaraxistag_ital
+ */
+ private static final int TAG_ital = 0x6974616C; // i(0x69), t(0x74), a(0x61), l(0x6c)
+
+ /**
+ * The registered weight axis used for adjusting requested style.
+ * https://learn.microsoft.com/en-us/typography/opentype/spec/dvaraxistag_wght
+ */
+ private static final int TAG_wght = 0x77676874; // w(0x77), g(0x67), h(0x68), t(0x74)
+
+ /** @hide */
+ public static @VariableFontFamilyType int analyzeAndResolveVariableType(
+ ArrayList<Font> fonts) {
+ if (fonts.size() > 2) {
+ return VARIABLE_FONT_FAMILY_TYPE_UNKNOWN;
+ }
+
+ if (fonts.size() == 1) {
+ Font font = fonts.get(0);
+ Set<Integer> supportedAxes =
+ FontFileUtil.getSupportedAxes(font.getBuffer(), font.getTtcIndex());
+ if (supportedAxes.contains(TAG_wght)) {
+ if (supportedAxes.contains(TAG_ital)) {
+ return VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL;
+ } else {
+ return VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY;
+ }
+ } else {
+ return VARIABLE_FONT_FAMILY_TYPE_UNKNOWN;
+ }
+ } else {
+ for (int i = 0; i < fonts.size(); ++i) {
+ Font font = fonts.get(i);
+ Set<Integer> supportedAxes =
+ FontFileUtil.getSupportedAxes(font.getBuffer(), font.getTtcIndex());
+ if (!supportedAxes.contains(TAG_wght)) {
+ return VARIABLE_FONT_FAMILY_TYPE_UNKNOWN;
+ }
+ }
+ boolean italic1 = fonts.get(0).getStyle().getSlant() == FontStyle.FONT_SLANT_ITALIC;
+ boolean italic2 = fonts.get(1).getStyle().getSlant() == FontStyle.FONT_SLANT_ITALIC;
+
+ if (italic1 == italic2) {
+ return VARIABLE_FONT_FAMILY_TYPE_UNKNOWN;
+ } else {
+ if (italic1) {
+ // Swap fonts to make the first font upright, second font italic.
+ Font firstFont = fonts.get(0);
+ fonts.set(0, fonts.get(1));
+ fonts.set(1, firstFont);
+ }
+ return VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT;
+ }
+ }
+ }
+
private static native long nInitBuilder();
@CriticalNative
private static native void nAddFont(long builderPtr, long fontPtr);
private static native long nBuild(long builderPtr, String langTags, int variant,
- boolean isCustomFallback, boolean isDefaultFallback);
+ boolean isCustomFallback, boolean isDefaultFallback, int variableFamilyType);
@CriticalNative
private static native long nGetReleaseNativeFamily();
}
diff --git a/graphics/java/android/graphics/fonts/FontFileUtil.java b/graphics/java/android/graphics/fonts/FontFileUtil.java
index 917eef2ffede..ff38282255f2 100644
--- a/graphics/java/android/graphics/fonts/FontFileUtil.java
+++ b/graphics/java/android/graphics/fonts/FontFileUtil.java
@@ -19,11 +19,14 @@ package android.graphics.fonts;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.util.ArraySet;
import dalvik.annotation.optimization.FastNative;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.util.Collections;
+import java.util.Set;
/**
* Provides a utility for font file operations.
@@ -62,6 +65,7 @@ public class FontFileUtil {
private static final int SFNT_VERSION_OTTO = 0x4F54544F;
private static final int TTC_TAG = 0x74746366;
private static final int OS2_TABLE_TAG = 0x4F532F32;
+ private static final int FVAR_TABLE_TAG = 0x66766172;
private static final int ANALYZE_ERROR = 0xFFFFFFFF;
@@ -200,6 +204,73 @@ public class FontFileUtil {
}
}
+ private static int getUInt16(ByteBuffer buffer, int offset) {
+ return ((int) buffer.getShort(offset)) & 0xFFFF;
+ }
+
+ /**
+ * Returns supported axes of font
+ *
+ * @param buffer A buffer of the entire font file.
+ * @param index A font index in case of font collection. Must be 0 otherwise.
+ * @return set of supported axes tag. Returns empty set on error.
+ */
+ public static Set<Integer> getSupportedAxes(@NonNull ByteBuffer buffer, int index) {
+ ByteOrder originalOrder = buffer.order();
+ buffer.order(ByteOrder.BIG_ENDIAN);
+ try {
+ int fontFileOffset = 0;
+ int magicNumber = buffer.getInt(0);
+ if (magicNumber == TTC_TAG) {
+ // TTC file.
+ if (index >= buffer.getInt(8 /* offset to number of fonts in TTC */)) {
+ return Collections.EMPTY_SET;
+ }
+ fontFileOffset = buffer.getInt(
+ 12 /* offset to array of offsets of font files */ + 4 * index);
+ }
+ int sfntVersion = buffer.getInt(fontFileOffset);
+
+ if (sfntVersion != SFNT_VERSION_1 && sfntVersion != SFNT_VERSION_OTTO) {
+ return Collections.EMPTY_SET;
+ }
+
+ int numTables = buffer.getShort(fontFileOffset + 4 /* offset to number of tables */);
+ int fvarTableOffset = -1;
+ for (int i = 0; i < numTables; ++i) {
+ int tableOffset = fontFileOffset + 12 /* size of offset table */
+ + i * 16 /* size of table record */;
+ if (buffer.getInt(tableOffset) == FVAR_TABLE_TAG) {
+ fvarTableOffset = buffer.getInt(tableOffset + 8 /* offset to the table */);
+ break;
+ }
+ }
+
+ if (fvarTableOffset == -1) {
+ // Couldn't find OS/2 table. use regular style
+ return Collections.EMPTY_SET;
+ }
+
+ if (buffer.getShort(fvarTableOffset) != 1
+ || buffer.getShort(fvarTableOffset + 2) != 0) {
+ return Collections.EMPTY_SET;
+ }
+
+ int axesArrayOffset = getUInt16(buffer, fvarTableOffset + 4);
+ int axisCount = getUInt16(buffer, fvarTableOffset + 8);
+ int axisSize = getUInt16(buffer, fvarTableOffset + 10);
+
+ ArraySet<Integer> axes = new ArraySet<>();
+ for (int i = 0; i < axisCount; ++i) {
+ axes.add(buffer.getInt(fvarTableOffset + axesArrayOffset + axisSize * i));
+ }
+
+ return axes;
+ } finally {
+ buffer.order(originalOrder);
+ }
+ }
+
@FastNative
private static native long nGetFontRevision(@NonNull ByteBuffer buffer,
@IntRange(from = 0) int index);
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index acc4da6e1527..3ef714ed8bdf 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -16,10 +16,15 @@
package android.graphics.fonts;
+import static android.text.FontConfig.Customization.LocaleFallback.OPERATION_APPEND;
+import static android.text.FontConfig.Customization.LocaleFallback.OPERATION_PREPEND;
+import static android.text.FontConfig.Customization.LocaleFallback.OPERATION_REPLACE;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.FontListParser;
import android.graphics.Typeface;
+import android.os.LocaleList;
import android.text.FontConfig;
import android.util.ArrayMap;
import android.util.Log;
@@ -38,6 +43,7 @@ import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -48,6 +54,8 @@ public final class SystemFonts {
private static final String TAG = "SystemFonts";
private static final String FONTS_XML = "/system/etc/font_fallback.xml";
+ private static final String LEGACY_FONTS_XML = "/system/etc/fonts.xml";
+
/** @hide */
public static final String SYSTEM_FONT_DIR = "/system/fonts/";
private static final String OEM_XML = "/product/etc/fonts_customization.xml";
@@ -117,9 +125,9 @@ public final class SystemFonts {
}
}
-
final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily(
- defaultFonts, languageTags, variant, false, cache);
+ defaultFonts, languageTags, variant, xmlFamily.getVariableFontFamilyType(), false,
+ cache);
// Insert family into fallback map.
for (int i = 0; i < fallbackMap.size(); i++) {
final String name = fallbackMap.keyAt(i);
@@ -136,8 +144,8 @@ public final class SystemFonts {
familyListSet.familyList.add(defaultFamily);
}
} else {
- final FontFamily family = createFontFamily(fallback, languageTags, variant, false,
- cache);
+ final FontFamily family = createFontFamily(fallback, languageTags, variant,
+ xmlFamily.getVariableFontFamilyType(), false, cache);
if (family != null) {
familyListSet.familyList.add(family);
} else if (defaultFamily != null) {
@@ -153,6 +161,7 @@ public final class SystemFonts {
@NonNull List<FontConfig.Font> fonts,
@NonNull String languageTags,
@FontConfig.FontFamily.Variant int variant,
+ int varFamilyType,
boolean isDefaultFallback,
@NonNull Map<String, ByteBuffer> cache) {
if (fonts.size() == 0) {
@@ -194,7 +203,7 @@ public final class SystemFonts {
}
}
return b == null ? null : b.build(languageTags, variant, false /* isCustomFallback */,
- isDefaultFallback);
+ isDefaultFallback, varFamilyType);
}
private static void appendNamedFamilyList(@NonNull FontConfig.NamedFamilyList namedFamilyList,
@@ -208,6 +217,7 @@ public final class SystemFonts {
final FontFamily family = createFontFamily(
xmlFamily.getFontList(),
xmlFamily.getLocaleList().toLanguageTags(), xmlFamily.getVariant(),
+ xmlFamily.getVariableFontFamilyType(),
true, // named family is always default
bufferCache);
if (family == null) {
@@ -220,15 +230,23 @@ public final class SystemFonts {
}
/**
- * Get the updated FontConfig for testing purposes.
+ * Get the updated FontConfig.
+ *
+ * @param updatableFontMap a font mapping of updated font files.
* @hide
*/
- public static @NonNull FontConfig getSystemFontConfigForTesting(
- @NonNull String fontsXmlPath,
+ public static @NonNull FontConfig getSystemFontConfig(
@Nullable Map<String, File> updatableFontMap,
long lastModifiedDate,
- int configVersion) {
- return getSystemFontConfigInternal(fontsXmlPath, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR,
+ int configVersion
+ ) {
+ final String fontsXml;
+ if (com.android.text.flags.Flags.newFontsFallbackXml()) {
+ fontsXml = FONTS_XML;
+ } else {
+ fontsXml = LEGACY_FONTS_XML;
+ }
+ return getSystemFontConfigInternal(fontsXml, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR,
updatableFontMap, lastModifiedDate, configVersion);
}
@@ -238,12 +256,13 @@ public final class SystemFonts {
* @param updatableFontMap a font mapping of updated font files.
* @hide
*/
- public static @NonNull FontConfig getSystemFontConfig(
+ public static @NonNull FontConfig getSystemFontConfigForTesting(
+ @NonNull String fontsXml,
@Nullable Map<String, File> updatableFontMap,
long lastModifiedDate,
int configVersion
) {
- return getSystemFontConfigInternal(FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR,
+ return getSystemFontConfigInternal(fontsXml, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR,
updatableFontMap, lastModifiedDate, configVersion);
}
@@ -252,10 +271,24 @@ public final class SystemFonts {
* @hide
*/
public static @NonNull FontConfig getSystemPreinstalledFontConfig() {
- return getSystemFontConfigInternal(FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, null,
+ final String fontsXml;
+ if (com.android.text.flags.Flags.newFontsFallbackXml()) {
+ fontsXml = FONTS_XML;
+ } else {
+ fontsXml = LEGACY_FONTS_XML;
+ }
+ return getSystemFontConfigInternal(fontsXml, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, null,
0, 0);
}
+ /**
+ * @hide
+ */
+ public static @NonNull FontConfig getSystemPreinstalledFontConfigFromLegacyXml() {
+ return getSystemFontConfigInternal(LEGACY_FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR,
+ null, 0, 0);
+ }
+
/* package */ static @NonNull FontConfig getSystemFontConfigInternal(
@NonNull String fontsXml,
@NonNull String systemFontDir,
@@ -266,16 +299,17 @@ public final class SystemFonts {
int configVersion
) {
try {
+ Log.i(TAG, "Loading font config from " + fontsXml);
return FontListParser.parse(fontsXml, systemFontDir, oemXml, productFontDir,
updatableFontMap, lastModifiedDate, configVersion);
} catch (IOException e) {
Log.e(TAG, "Failed to open/read system font configurations.", e);
return new FontConfig(Collections.emptyList(), Collections.emptyList(),
- Collections.emptyList(), 0, 0);
+ Collections.emptyList(), Collections.emptyList(), 0, 0);
} catch (XmlPullParserException e) {
Log.e(TAG, "Failed to parse the system font configuration.", e);
return new FontConfig(Collections.emptyList(), Collections.emptyList(),
- Collections.emptyList(), 0, 0);
+ Collections.emptyList(), Collections.emptyList(), 0, 0);
}
}
@@ -299,6 +333,8 @@ public final class SystemFonts {
ArrayMap<String, ByteBuffer> outBufferCache) {
final ArrayMap<String, NativeFamilyListSet> fallbackListMap = new ArrayMap<>();
+ final List<FontConfig.Customization.LocaleFallback> localeFallbacks =
+ fontConfig.getLocaleFallbackCustomizations();
final List<FontConfig.NamedFamilyList> namedFamilies = fontConfig.getNamedFamilyLists();
for (int i = 0; i < namedFamilies.size(); ++i) {
@@ -307,10 +343,54 @@ public final class SystemFonts {
}
// Then, add fallback fonts to the fallback map.
+ final List<FontConfig.Customization.LocaleFallback> customizations = new ArrayList<>();
final List<FontConfig.FontFamily> xmlFamilies = fontConfig.getFontFamilies();
+ final SparseIntArray seenCustomization = new SparseIntArray();
for (int i = 0; i < xmlFamilies.size(); i++) {
final FontConfig.FontFamily xmlFamily = xmlFamilies.get(i);
- pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache);
+
+ customizations.clear();
+ for (int j = 0; j < localeFallbacks.size(); ++j) {
+ if (seenCustomization.get(j, -1) != -1) {
+ continue; // The customization is already applied.
+ }
+ FontConfig.Customization.LocaleFallback localeFallback = localeFallbacks.get(j);
+ if (scriptMatch(xmlFamily.getLocaleList(), localeFallback.getScript())) {
+ customizations.add(localeFallback);
+ seenCustomization.put(j, 1);
+ }
+ }
+
+ if (customizations.isEmpty()) {
+ pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache);
+ } else {
+ for (int j = 0; j < customizations.size(); ++j) {
+ FontConfig.Customization.LocaleFallback localeFallback = customizations.get(j);
+ if (localeFallback.getOperation() == OPERATION_PREPEND) {
+ pushFamilyToFallback(localeFallback.getFamily(), fallbackListMap,
+ outBufferCache);
+ }
+ }
+ boolean isReplaced = false;
+ for (int j = 0; j < customizations.size(); ++j) {
+ FontConfig.Customization.LocaleFallback localeFallback = customizations.get(j);
+ if (localeFallback.getOperation() == OPERATION_REPLACE) {
+ pushFamilyToFallback(localeFallback.getFamily(), fallbackListMap,
+ outBufferCache);
+ isReplaced = true;
+ }
+ }
+ if (!isReplaced) { // If nothing is replaced, push the original one.
+ pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache);
+ }
+ for (int j = 0; j < customizations.size(); ++j) {
+ FontConfig.Customization.LocaleFallback localeFallback = customizations.get(j);
+ if (localeFallback.getOperation() == OPERATION_APPEND) {
+ pushFamilyToFallback(localeFallback.getFamily(), fallbackListMap,
+ outBufferCache);
+ }
+ }
+ }
}
// Build the font map and fallback map.
@@ -336,4 +416,42 @@ public final class SystemFonts {
Typeface.initSystemDefaultTypefaces(fallbackMap, fontConfig.getAliases(), result);
return result;
}
+
+ private static boolean scriptMatch(LocaleList localeList, String targetScript) {
+ if (localeList == null || localeList.isEmpty()) {
+ return false;
+ }
+ for (int i = 0; i < localeList.size(); ++i) {
+ Locale locale = localeList.get(i);
+ if (locale == null) {
+ continue;
+ }
+ String baseScript = FontConfig.resolveScript(locale);
+ if (baseScript.equals(targetScript)) {
+ return true;
+ }
+
+ // Subtag match
+ if (targetScript.equals("Bopo") && baseScript.equals("Hanb")) {
+ // Hanb is Han with Bopomofo.
+ return true;
+ } else if (targetScript.equals("Hani")) {
+ if (baseScript.equals("Hanb") || baseScript.equals("Hans")
+ || baseScript.equals("Hant") || baseScript.equals("Kore")
+ || baseScript.equals("Jpan")) {
+ // Han id suppoted by Taiwanese, Traditional Chinese, Simplified Chinese, Korean
+ // and Japanese.
+ return true;
+ }
+ } else if (targetScript.equals("Hira") || targetScript.equals("Hrkt")
+ || targetScript.equals("Kana")) {
+ if (baseScript.equals("Jpan") || baseScript.equals("Hrkt")) {
+ // Hiragana, Hiragana-Katakana, Katakana is supported by Japanese and
+ // Hiragana-Katakana script.
+ return true;
+ }
+ }
+ }
+ return false;
+ }
}
diff --git a/graphics/java/android/graphics/fonts/TEST_MAPPING b/graphics/java/android/graphics/fonts/TEST_MAPPING
new file mode 100644
index 000000000000..99cbfe720c05
--- /dev/null
+++ b/graphics/java/android/graphics/fonts/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsTextTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.LargeTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index 0c493f5a1fdc..c5e451a5ec3a 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -16,8 +16,20 @@
package android.graphics.text;
+import static com.android.text.flags.Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN;
+import static com.android.text.flags.Flags.FLAG_WORD_STYLE_AUTO;
+
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.os.Build;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -30,7 +42,100 @@ import java.util.Objects;
* <a href="https://www.w3.org/TR/css-text-3/#line-break-property" class="external">
* line-break property</a> for more information.
*/
-public final class LineBreakConfig {
+public final class LineBreakConfig implements Parcelable {
+
+ /**
+ * A feature ID for automatic line break word style.
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public static final long WORD_STYLE_AUTO = 280005585L;
+
+ /**
+ * No hyphenation preference is specified.
+ *
+ * <p>
+ * This is a special value of hyphenation preference indicating no hyphenation preference is
+ * specified. When overriding a {@link LineBreakConfig} with another {@link LineBreakConfig}
+ * with {@link Builder#merge(LineBreakConfig)} function, the hyphenation preference of
+ * overridden config will be kept if the hyphenation preference of overriding config is
+ * {@link #HYPHENATION_UNSPECIFIED}.
+ *
+ * <p>
+ * <pre>
+ * val override = LineBreakConfig.Builder()
+ * .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE)
+ * .build(); // UNSPECIFIED if no setHyphenation is called.
+ * val config = LineBreakConfig.Builder()
+ * .setHyphenation(LineBreakConfig.HYPHENATION_DISABLED)
+ * .merge(override)
+ * .build()
+ * // Here, config has HYPHENATION_DISABLED for line break config and
+ * // LINE_BREAK_WORD_STYLE_PHRASE for line break word style.
+ * </pre>
+ *
+ * <p>
+ * This value is resolved to {@link #HYPHENATION_ENABLED} if this value is used for text
+ * layout/rendering.
+ */
+ @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+ public static final int HYPHENATION_UNSPECIFIED = -1;
+
+ /**
+ * The hyphenation is disabled.
+ */
+ @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+ public static final int HYPHENATION_DISABLED = 0;
+
+ /**
+ * The hyphenation is enabled.
+ *
+ * Note: Even if the hyphenation is enabled with a line break strategy
+ * {@link LineBreaker#BREAK_STRATEGY_SIMPLE}, the hyphenation will not be performed unless a
+ * single word cannot meet width constraints.
+ */
+ @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+ public static final int HYPHENATION_ENABLED = 1;
+
+ /** @hide */
+ @IntDef(prefix = { "HYPHENATION_" }, value = {
+ HYPHENATION_UNSPECIFIED, HYPHENATION_ENABLED, HYPHENATION_DISABLED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Hyphenation {}
+
+ /**
+ * No line break style is specified.
+ *
+ * <p>
+ * This is a special value of line break style indicating no style value is specified.
+ * When overriding a {@link LineBreakConfig} with another {@link LineBreakConfig} with
+ * {@link Builder#merge(LineBreakConfig)} function, the line break style of overridden config
+ * will be kept if the line break style of overriding config is
+ * {@link #LINE_BREAK_STYLE_UNSPECIFIED}.
+ *
+ * <pre>
+ * val override = LineBreakConfig.Builder()
+ * .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE)
+ * .build(); // UNSPECIFIED if no setLineBreakStyle is called.
+ * val config = LineBreakConfig.Builder()
+ * .setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT)
+ * .merge(override)
+ * .build()
+ * // Here, config has LINE_BREAK_STYLE_STRICT for line break config and
+ * // LINE_BREAK_WORD_STYLE_PHRASE for line break word style.
+ * </pre>
+ *
+ * <p>
+ * This value is resolved to {@link #LINE_BREAK_STYLE_NONE} if the target SDK version is API
+ * {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or before and this value is used for text
+ * layout/rendering. This value is resolved to {@link #LINE_BREAK_STYLE_AUTO} if the target SDK
+ * version is API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or after and this value is
+ * used for text layout/rendering.
+ */
+ @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+ public static final int LINE_BREAK_STYLE_UNSPECIFIED = -1;
/**
* No line-break rules are used for line breaking.
@@ -53,15 +158,79 @@ public final class LineBreakConfig {
*/
public static final int LINE_BREAK_STYLE_STRICT = 3;
+ /**
+ * The line break style that used for preventing automatic line breaking.
+ *
+ * This is useful when you want to preserve some words in the same line by using
+ * {@link android.text.style.LineBreakConfigSpan} or
+ * {@link android.text.style.LineBreakConfigSpan#createNoBreakSpan()} as a shorthand.
+ * Note that even if this style is specified, the grapheme based line break is still performed
+ * for preventing clipping text.
+ *
+ * @see android.text.style.LineBreakConfigSpan
+ * @see android.text.style.LineBreakConfigSpan#createNoBreakSpan()
+ */
+ @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+ public static final int LINE_BREAK_STYLE_NO_BREAK = 4;
+
+ /**
+ * A special value for the line breaking style option.
+ *
+ * <p>
+ * The auto option for the line break style set the line break style based on the locale of the
+ * text rendering context. You can specify the context locale by
+ * {@link android.widget.TextView#setTextLocales(LocaleList)} or
+ * {@link android.graphics.Paint#setTextLocales(LocaleList)}.
+ *
+ * <p>
+ * In the API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, auto option does followings:
+ * - If at least one locale in the locale list contains Japanese script, this option is
+ * equivalent to {@link #LINE_BREAK_STYLE_STRICT}.
+ * - Otherwise, this option is equivalent to {@link #LINE_BREAK_STYLE_NONE}.
+ */
+ @FlaggedApi(FLAG_WORD_STYLE_AUTO)
+ public static final int LINE_BREAK_STYLE_AUTO = 5;
+
/** @hide */
@IntDef(prefix = { "LINE_BREAK_STYLE_" }, value = {
LINE_BREAK_STYLE_NONE, LINE_BREAK_STYLE_LOOSE, LINE_BREAK_STYLE_NORMAL,
- LINE_BREAK_STYLE_STRICT
+ LINE_BREAK_STYLE_STRICT, LINE_BREAK_STYLE_UNSPECIFIED, LINE_BREAK_STYLE_NO_BREAK,
+ LINE_BREAK_STYLE_AUTO
})
@Retention(RetentionPolicy.SOURCE)
public @interface LineBreakStyle {}
/**
+ * No line break word style is specified.
+ *
+ * This is a special value of line break word style indicating no style value is specified.
+ * When overriding a {@link LineBreakConfig} with another {@link LineBreakConfig} with
+ * {@link Builder#merge(LineBreakConfig)} function, the line break word style of overridden
+ * config will be kept if the line break word style of overriding config is
+ * {@link #LINE_BREAK_WORD_STYLE_UNSPECIFIED}.
+ *
+ * <pre>
+ * val override = LineBreakConfig.Builder()
+ * .setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT)
+ * .build(); // UNSPECIFIED if no setLineBreakWordStyle is called.
+ * val config = LineBreakConfig.Builder()
+ * .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE)
+ * .merge(override)
+ * .build()
+ * // Here, config has LINE_BREAK_STYLE_STRICT for line break config and
+ * // LINE_BREAK_WORD_STYLE_PHRASE for line break word style.
+ * </pre>
+ *
+ * This value is resolved to {@link #LINE_BREAK_WORD_STYLE_NONE} if the target SDK version is
+ * API {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or before and this value is used for text
+ * layout/rendering. This value is resolved to {@link #LINE_BREAK_WORD_STYLE_AUTO} if the target
+ * SDK version is API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or after and this value is
+ * used for text layout/rendering.
+ */
+ @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+ public static final int LINE_BREAK_WORD_STYLE_UNSPECIFIED = -1;
+
+ /**
* No line-break word style is used for line breaking.
*/
public static final int LINE_BREAK_WORD_STYLE_NONE = 0;
@@ -76,9 +245,29 @@ public final class LineBreakConfig {
*/
public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1;
+ /**
+ * A special value for the line breaking word style option.
+ *
+ * <p>
+ * The auto option for the line break word style does some heuristics based on locales and line
+ * count.
+ *
+ * <p>
+ * In the API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, auto option does followings:
+ * - If at least one locale in the locale list contains Korean script, this option is equivalent
+ * to {@link #LINE_BREAK_WORD_STYLE_PHRASE}.
+ * - If not, then if at least one locale in the locale list contains Japanese script, this
+ * option is equivalent to {@link #LINE_BREAK_WORD_STYLE_PHRASE} if the result of its line
+ * count is less than 5 lines.
+ * - Otherwise, this option is equivalent to {@link #LINE_BREAK_WORD_STYLE_NONE}.
+ */
+ @FlaggedApi(FLAG_WORD_STYLE_AUTO)
+ public static final int LINE_BREAK_WORD_STYLE_AUTO = 2;
+
/** @hide */
@IntDef(prefix = { "LINE_BREAK_WORD_STYLE_" }, value = {
- LINE_BREAK_WORD_STYLE_NONE, LINE_BREAK_WORD_STYLE_PHRASE
+ LINE_BREAK_WORD_STYLE_NONE, LINE_BREAK_WORD_STYLE_PHRASE, LINE_BREAK_WORD_STYLE_UNSPECIFIED,
+ LINE_BREAK_WORD_STYLE_AUTO
})
@Retention(RetentionPolicy.SOURCE)
public @interface LineBreakWordStyle {}
@@ -88,21 +277,95 @@ public final class LineBreakConfig {
*/
public static final class Builder {
// The line break style for the LineBreakConfig.
- private @LineBreakStyle int mLineBreakStyle = LineBreakConfig.LINE_BREAK_STYLE_NONE;
+ private @LineBreakStyle int mLineBreakStyle = LineBreakConfig.LINE_BREAK_STYLE_UNSPECIFIED;
// The line break word style for the LineBreakConfig.
private @LineBreakWordStyle int mLineBreakWordStyle =
- LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
+ LineBreakConfig.LINE_BREAK_WORD_STYLE_UNSPECIFIED;
+
+ private @Hyphenation int mHyphenation = LineBreakConfig.HYPHENATION_UNSPECIFIED;
/**
* Builder constructor.
*/
public Builder() {
+ reset(null);
+ }
+
+ /**
+ * Merges line break config with other config
+ *
+ * Update the internal configurations with passed {@code config}. If the config values of
+ * passed {@code config} are unspecified, the original config values are kept. For example,
+ * the following code passes {@code config} that has {@link #LINE_BREAK_STYLE_UNSPECIFIED}.
+ * This code generates {@link LineBreakConfig} that has line break config
+ * {@link #LINE_BREAK_STYLE_STRICT}.
+ *
+ * <pre>
+ * val override = LineBreakConfig.Builder()
+ * .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE)
+ * .build(); // UNSPECIFIED if no setLineBreakStyle is called.
+ * val config = LineBreakConfig.Builder()
+ * .setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT)
+ * .merge(override)
+ * .build()
+ * // Here, config has LINE_BREAK_STYLE_STRICT of line break config and
+ * // LINE_BREAK_WORD_STYLE_PHRASE of line break word style.
+ * </pre>
+ *
+ * @see #LINE_BREAK_STYLE_UNSPECIFIED
+ * @see #LINE_BREAK_WORD_STYLE_UNSPECIFIED
+ *
+ * @param config an override line break config
+ * @return This {@code Builder}.
+ */
+ @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+ public @NonNull Builder merge(@NonNull LineBreakConfig config) {
+ if (config.mLineBreakStyle != LINE_BREAK_STYLE_UNSPECIFIED) {
+ mLineBreakStyle = config.mLineBreakStyle;
+ }
+ if (config.mLineBreakWordStyle != LINE_BREAK_WORD_STYLE_UNSPECIFIED) {
+ mLineBreakWordStyle = config.mLineBreakWordStyle;
+ }
+ if (config.mHyphenation != HYPHENATION_UNSPECIFIED) {
+ mHyphenation = config.mHyphenation;
+ }
+ return this;
+ }
+
+ /**
+ * Resets this builder to the given config state.
+ *
+ * @param config a config value used for resetting. {@code null} is allowed. If {@code null}
+ * is passed, all configs are reset to unspecified.
+ * @return This {@code Builder}.
+ * @hide
+ */
+ public @NonNull Builder reset(@Nullable LineBreakConfig config) {
+ if (config == null) {
+ mLineBreakStyle = LINE_BREAK_STYLE_UNSPECIFIED;
+ mLineBreakWordStyle = LINE_BREAK_WORD_STYLE_UNSPECIFIED;
+ mHyphenation = HYPHENATION_UNSPECIFIED;
+ } else {
+ mLineBreakStyle = config.mLineBreakStyle;
+ mLineBreakWordStyle = config.mLineBreakWordStyle;
+ mHyphenation = config.mHyphenation;
+ }
+ return this;
}
/**
* Sets the line-break style.
*
+ * Note: different from {@link #merge(LineBreakConfig)} if this function is called with
+ * {@link #LINE_BREAK_STYLE_UNSPECIFIED}, the line break style is reset to
+ * {@link #LINE_BREAK_STYLE_UNSPECIFIED}.
+ *
+ * @see <a href="https://unicode.org/reports/tr35/#UnicodeLineBreakStyleIdentifier">
+ * Unicode Line Break Style Identifier</a>
+ * @see <a href="https://drafts.csswg.org/css-text/#line-break-property">
+ * CSS Line Break Property</a>
+ *
* @param lineBreakStyle The new line-break style.
* @return This {@code Builder}.
*/
@@ -114,6 +377,15 @@ public final class LineBreakConfig {
/**
* Sets the line-break word style.
*
+ * Note: different from {@link #merge(LineBreakConfig)} method, if this function is called
+ * with {@link #LINE_BREAK_WORD_STYLE_UNSPECIFIED}, the line break style is reset to
+ * {@link #LINE_BREAK_WORD_STYLE_UNSPECIFIED}.
+ *
+ * @see <a href="https://unicode.org/reports/tr35/#UnicodeLineBreakWordIdentifier">
+ * Unicode Line Break Word Identifier</a>
+ * @see <a href="https://drafts.csswg.org/css-text/#word-break-property">
+ * CSS Word Break Property</a>
+ *
* @param lineBreakWordStyle The new line-break word style.
* @return This {@code Builder}.
*/
@@ -123,12 +395,36 @@ public final class LineBreakConfig {
}
/**
+ * Sets the hyphenation preference
+ *
+ * Note: Even if the {@link LineBreakConfig#HYPHENATION_ENABLED} is specified, the
+ * hyphenation will not be performed if the {@link android.widget.TextView} or underlying
+ * {@link android.text.StaticLayout}, {@link LineBreaker} are configured with
+ * {@link LineBreaker#HYPHENATION_FREQUENCY_NONE}.
+ *
+ * Note: Even if the hyphenation is enabled with a line break strategy
+ * {@link LineBreaker#BREAK_STRATEGY_SIMPLE}, the hyphenation will not be performed unless a
+ * single word cannot meet width constraints.
+ *
+ * @param hyphenation The hyphenation preference.
+ * @return This {@code Builder}.
+ */
+ @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+ public @NonNull Builder setHyphenation(@Hyphenation int hyphenation) {
+ mHyphenation = hyphenation;
+ return this;
+ }
+
+ /**
* Builds a {@link LineBreakConfig} instance.
*
+ * This method can be called multiple times for generating multiple {@link LineBreakConfig}
+ * instances.
+ *
* @return The {@code LineBreakConfig} instance.
*/
public @NonNull LineBreakConfig build() {
- return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle);
+ return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle, mHyphenation);
}
}
@@ -156,17 +452,21 @@ public final class LineBreakConfig {
private final @LineBreakStyle int mLineBreakStyle;
private final @LineBreakWordStyle int mLineBreakWordStyle;
+ private final @Hyphenation int mHyphenation;
/**
* Constructor with line-break parameters.
*
* <p>Use {@link LineBreakConfig.Builder} to create the
* {@code LineBreakConfig} instance.
+ * @hide
*/
- private LineBreakConfig(@LineBreakStyle int lineBreakStyle,
- @LineBreakWordStyle int lineBreakWordStyle) {
+ public LineBreakConfig(@LineBreakStyle int lineBreakStyle,
+ @LineBreakWordStyle int lineBreakWordStyle,
+ @Hyphenation int hyphenation) {
mLineBreakStyle = lineBreakStyle;
mLineBreakWordStyle = lineBreakWordStyle;
+ mHyphenation = hyphenation;
}
/**
@@ -179,6 +479,24 @@ public final class LineBreakConfig {
}
/**
+ * Gets the resolved line break style.
+ *
+ * This method never returns {@link #LINE_BREAK_STYLE_UNSPECIFIED}.
+ *
+ * @return The line break style.
+ * @hide
+ */
+ public static @LineBreakStyle int getResolvedLineBreakStyle(@Nullable LineBreakConfig config) {
+ final int defaultStyle = CompatChanges.isChangeEnabled(WORD_STYLE_AUTO)
+ ? LINE_BREAK_STYLE_AUTO : LINE_BREAK_STYLE_NONE;
+ if (config == null) {
+ return defaultStyle;
+ }
+ return config.mLineBreakStyle == LINE_BREAK_STYLE_UNSPECIFIED
+ ? defaultStyle : config.mLineBreakStyle;
+ }
+
+ /**
* Gets the current line-break word style.
*
* @return The line-break word style to be used for text wrapping.
@@ -187,6 +505,86 @@ public final class LineBreakConfig {
return mLineBreakWordStyle;
}
+ /**
+ * Gets the resolved line break style.
+ *
+ * This method never returns {@link #LINE_BREAK_WORD_STYLE_UNSPECIFIED}.
+ *
+ * @return The line break word style.
+ * @hide
+ */
+ public static @LineBreakWordStyle int getResolvedLineBreakWordStyle(
+ @Nullable LineBreakConfig config) {
+ final int defaultWordStyle = CompatChanges.isChangeEnabled(WORD_STYLE_AUTO)
+ ? LINE_BREAK_WORD_STYLE_AUTO : LINE_BREAK_WORD_STYLE_NONE;
+ if (config == null) {
+ return defaultWordStyle;
+ }
+ return config.mLineBreakWordStyle == LINE_BREAK_WORD_STYLE_UNSPECIFIED
+ ? defaultWordStyle : config.mLineBreakWordStyle;
+ }
+
+ /**
+ * Returns a hyphenation preference.
+ *
+ * @return A hyphenation preference.
+ */
+ @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+ public @Hyphenation int getHyphenation() {
+ return mHyphenation;
+ }
+
+ /**
+ * Returns a hyphenation preference.
+ *
+ * This method never returns {@link #HYPHENATION_UNSPECIFIED}.
+ *
+ * @return A hyphenation preference.
+ * @hide
+ */
+ public static @Hyphenation int getResolvedHyphenation(
+ @Nullable LineBreakConfig config) {
+ if (config == null) {
+ return HYPHENATION_ENABLED;
+ }
+ return config.mHyphenation == HYPHENATION_UNSPECIFIED
+ ? HYPHENATION_ENABLED : config.mHyphenation;
+ }
+
+
+ /**
+ * Generates a new {@link LineBreakConfig} instance merged with given {@code config}.
+ *
+ * If values of passing {@code config} are unspecified, the original values are kept. For
+ * example, the following code shows how line break config is merged.
+ *
+ * <pre>
+ * val override = LineBreakConfig.Builder()
+ * .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE)
+ * .build(); // UNSPECIFIED if no setLineBreakStyle is called.
+ * val config = LineBreakConfig.Builder()
+ * .setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT)
+ * .build();
+ *
+ * val newConfig = config.merge(override)
+ * // newConfig has LINE_BREAK_STYLE_STRICT of line break style and
+ * LINE_BREAK_WORD_STYLE_PHRASE of line break word style.
+ * </pre>
+ *
+ * @param config an overriding config.
+ * @return newly created instance that is current style merged with passed config.
+ */
+ @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+ public @NonNull LineBreakConfig merge(@NonNull LineBreakConfig config) {
+ return new LineBreakConfig(
+ config.mLineBreakStyle == LINE_BREAK_STYLE_UNSPECIFIED
+ ? mLineBreakStyle : config.mLineBreakStyle,
+ config.mLineBreakWordStyle == LINE_BREAK_WORD_STYLE_UNSPECIFIED
+ ? mLineBreakWordStyle : config.mLineBreakWordStyle,
+ config.mHyphenation == HYPHENATION_UNSPECIFIED
+ ? mHyphenation : config.mHyphenation);
+ }
+
@Override
public boolean equals(Object o) {
if (o == null) return false;
@@ -194,11 +592,52 @@ public final class LineBreakConfig {
if (!(o instanceof LineBreakConfig)) return false;
LineBreakConfig that = (LineBreakConfig) o;
return (mLineBreakStyle == that.mLineBreakStyle)
- && (mLineBreakWordStyle == that.mLineBreakWordStyle);
+ && (mLineBreakWordStyle == that.mLineBreakWordStyle)
+ && (mHyphenation == that.mHyphenation);
}
@Override
public int hashCode() {
- return Objects.hash(mLineBreakStyle, mLineBreakWordStyle);
+ return Objects.hash(mLineBreakStyle, mLineBreakWordStyle, mHyphenation);
+ }
+
+ @Override
+ public String toString() {
+ return "LineBreakConfig{"
+ + "mLineBreakStyle=" + mLineBreakStyle
+ + ", mLineBreakWordStyle=" + mLineBreakWordStyle
+ + ", mHyphenation= " + mHyphenation
+ + '}';
+ }
+
+ @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+ @Override
+ public int describeContents() {
+ return 0;
}
+
+ @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mLineBreakStyle);
+ dest.writeInt(mLineBreakWordStyle);
+ dest.writeInt(mHyphenation);
+ }
+
+ @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+ public static final @NonNull Creator<LineBreakConfig> CREATOR = new Creator<>() {
+
+ @Override
+ public LineBreakConfig createFromParcel(Parcel source) {
+ final int lineBreakStyle = source.readInt();
+ final int lineBreakWordStyle = source.readInt();
+ final int hyphenation = source.readInt();
+ return new LineBreakConfig(lineBreakStyle, lineBreakWordStyle, hyphenation);
+ }
+
+ @Override
+ public LineBreakConfig[] newArray(int size) {
+ return new LineBreakConfig[size];
+ }
+ };
}
diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java
index babcfc3815f4..0e3fb163ef75 100644
--- a/graphics/java/android/graphics/text/LineBreaker.java
+++ b/graphics/java/android/graphics/text/LineBreaker.java
@@ -16,12 +16,16 @@
package android.graphics.text;
+import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
+
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Px;
+import android.text.Layout;
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
@@ -182,6 +186,7 @@ public class LineBreaker {
private @HyphenationFrequency int mHyphenationFrequency = HYPHENATION_FREQUENCY_NONE;
private @JustificationMode int mJustificationMode = JUSTIFICATION_MODE_NONE;
private @Nullable int[] mIndents = null;
+ private boolean mUseBoundsForWidth = false;
/**
* Set break strategy.
@@ -231,13 +236,35 @@ public class LineBreaker {
}
/**
+ * Set true for using width of bounding box as a source of automatic line breaking.
+ *
+ * If this value is false, the automatic line breaking uses total amount of advances as text
+ * widths. By setting true, it uses joined all glyph bound's width as a width of the text.
+ *
+ * If the font has glyphs that have negative bearing X or its xMax is greater than advance,
+ * the glyph clipping can happen because the drawing area may be bigger. By setting this to
+ * true, the line breaker will break line based on bounding box, so clipping can be
+ * prevented.
+ *
+ * @param useBoundsForWidth True for using bounding box, false for advances.
+ * @return this builder instance
+ * @see Layout#getUseBoundsForWidth()
+ * @see android.text.StaticLayout.Builder#setUseBoundsForWidth(boolean)
+ */
+ @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
+ public @NonNull Builder setUseBoundsForWidth(boolean useBoundsForWidth) {
+ mUseBoundsForWidth = useBoundsForWidth;
+ return this;
+ }
+
+ /**
* Build a new LineBreaker with given parameters.
*
* You can reuse the Builder instance even after calling this method.
*/
public @NonNull LineBreaker build() {
return new LineBreaker(mBreakStrategy, mHyphenationFrequency, mJustificationMode,
- mIndents);
+ mIndents, mUseBoundsForWidth);
}
}
@@ -456,9 +483,9 @@ public class LineBreaker {
*/
private LineBreaker(@BreakStrategy int breakStrategy,
@HyphenationFrequency int hyphenationFrequency, @JustificationMode int justify,
- @Nullable int[] indents) {
+ @Nullable int[] indents, boolean useBoundsForWidth) {
mNativePtr = nInit(breakStrategy, hyphenationFrequency,
- justify == JUSTIFICATION_MODE_INTER_WORD, indents);
+ justify == JUSTIFICATION_MODE_INTER_WORD, indents, useBoundsForWidth);
sRegistry.registerNativeAllocation(this, mNativePtr);
}
@@ -493,7 +520,7 @@ public class LineBreaker {
@FastNative
private static native long nInit(@BreakStrategy int breakStrategy,
@HyphenationFrequency int hyphenationFrequency, boolean isJustified,
- @Nullable int[] indents);
+ @Nullable int[] indents, boolean useBoundsForWidth);
@CriticalNative
private static native long nGetReleaseFunc();
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
index 3f7f0880d160..2d33e8d24ece 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -60,17 +60,19 @@ public class MeasuredText {
private final long mNativePtr;
private final boolean mComputeHyphenation;
private final boolean mComputeLayout;
+ private final boolean mComputeBounds;
@NonNull private final char[] mChars;
private final int mTop;
private final int mBottom;
// Use builder instead.
private MeasuredText(long ptr, @NonNull char[] chars, boolean computeHyphenation,
- boolean computeLayout, int top, int bottom) {
+ boolean computeLayout, boolean computeBounds, int top, int bottom) {
mNativePtr = ptr;
mChars = chars;
mComputeHyphenation = computeHyphenation;
mComputeLayout = computeLayout;
+ mComputeBounds = computeBounds;
mTop = top;
mBottom = bottom;
}
@@ -217,6 +219,7 @@ public class MeasuredText {
private final @NonNull char[] mText;
private boolean mComputeHyphenation = false;
private boolean mComputeLayout = true;
+ private boolean mComputeBounds = true;
private boolean mFastHyphenation = false;
private int mCurrentOffset = 0;
private @Nullable MeasuredText mHintMt = null;
@@ -296,11 +299,11 @@ public class MeasuredText {
Preconditions.checkArgument(length > 0, "length can not be negative");
final int end = mCurrentOffset + length;
Preconditions.checkArgument(end <= mText.length, "Style exceeds the text length");
- int lbStyle = (lineBreakConfig != null) ? lineBreakConfig.getLineBreakStyle() :
- LineBreakConfig.LINE_BREAK_STYLE_NONE;
- int lbWordStyle = (lineBreakConfig != null) ? lineBreakConfig.getLineBreakWordStyle() :
- LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
- nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, lbWordStyle,
+ int lbStyle = LineBreakConfig.getResolvedLineBreakStyle(lineBreakConfig);
+ int lbWordStyle = LineBreakConfig.getResolvedLineBreakWordStyle(lineBreakConfig);
+ boolean hyphenation = LineBreakConfig.getResolvedHyphenation(lineBreakConfig)
+ == LineBreakConfig.HYPHENATION_ENABLED;
+ nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, lbWordStyle, hyphenation,
mCurrentOffset, end, isRtl);
mCurrentOffset = end;
@@ -436,6 +439,20 @@ public class MeasuredText {
}
/**
+ * Hidden API that tells native to calculate bounding box as well.
+ * Different from {@link #setComputeLayout(boolean)}, the result bounding box is not stored
+ * into MeasuredText instance. Just warm up the global word cache entry.
+ *
+ * @hide
+ * @param computeBounds
+ * @return
+ */
+ public @NonNull Builder setComputeBounds(boolean computeBounds) {
+ mComputeBounds = computeBounds;
+ return this;
+ }
+
+ /**
* Creates a MeasuredText.
*
* Once you called build() method, you can't reuse the Builder class again.
@@ -455,9 +472,9 @@ public class MeasuredText {
try {
long hintPtr = (mHintMt == null) ? 0 : mHintMt.getNativePtr();
long ptr = nBuildMeasuredText(mNativePtr, hintPtr, mText, mComputeHyphenation,
- mComputeLayout, mFastHyphenation);
+ mComputeLayout, mComputeBounds, mFastHyphenation);
final MeasuredText res = new MeasuredText(ptr, mText, mComputeHyphenation,
- mComputeLayout, mTop, mBottom);
+ mComputeLayout, mComputeBounds, mTop, mBottom);
sRegistry.registerNativeAllocation(res, ptr);
return res;
} finally {
@@ -495,6 +512,7 @@ public class MeasuredText {
/* Non Zero */ long paintPtr,
int lineBreakStyle,
int lineBreakWordStyle,
+ boolean hyphenation,
@IntRange(from = 0) int start,
@IntRange(from = 0) int end,
boolean isRtl);
@@ -519,6 +537,7 @@ public class MeasuredText {
@NonNull char[] text,
boolean computeHyphenation,
boolean computeLayout,
+ boolean computeBounds,
boolean fastHyphenationMode);
private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr);
diff --git a/graphics/java/android/graphics/text/PositionedGlyphs.java b/graphics/java/android/graphics/text/PositionedGlyphs.java
index 8d20e9cee7d7..7932e3334063 100644
--- a/graphics/java/android/graphics/text/PositionedGlyphs.java
+++ b/graphics/java/android/graphics/text/PositionedGlyphs.java
@@ -16,6 +16,9 @@
package android.graphics.text;
+import static com.android.text.flags.Flags.FLAG_NEW_FONTS_FALLBACK_XML;
+
+import android.annotation.FlaggedApi;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.graphics.Paint;
@@ -165,6 +168,73 @@ public final class PositionedGlyphs {
}
/**
+ * Returns true if the fake bold option used for drawing, otherwise false.
+ *
+ * @param index the glyph index
+ * @return true if the fake bold option is on, otherwise off.
+ */
+ @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
+ public boolean getFakeBold(@IntRange(from = 0) int index) {
+ Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
+ return nGetFakeBold(mLayoutPtr, index);
+ }
+
+ /**
+ * Returns true if the fake italic option used for drawing, otherwise false.
+ *
+ * @param index the glyph index
+ * @return true if the fake italic option is on, otherwise off.
+ */
+ @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
+ public boolean getFakeItalic(@IntRange(from = 0) int index) {
+ Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
+ return nGetFakeItalic(mLayoutPtr, index);
+ }
+
+ /**
+ * A special value returned by {@link #getWeightOverride(int)} and
+ * {@link #getItalicOverride(int)} that indicates no font variation setting is overridden.
+ */
+ @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
+ public static final float NO_OVERRIDE = Float.MIN_VALUE;
+
+ /**
+ * Returns overridden weight value if the font is variable font and `wght` value is overridden
+ * for drawing. Otherwise returns {@link #NO_OVERRIDE}.
+ *
+ * @param index the glyph index
+ * @return overridden weight value or {@link #NO_OVERRIDE}.
+ */
+ @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
+ public float getWeightOverride(@IntRange(from = 0) int index) {
+ Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
+ float value = nGetWeightOverride(mLayoutPtr, index);
+ if (value == -1) {
+ return NO_OVERRIDE;
+ } else {
+ return value;
+ }
+ }
+
+ /**
+ * Returns overridden italic value if the font is variable font and `ital` value is overridden
+ * for drawing. Otherwise returns {@link #NO_OVERRIDE}.
+ *
+ * @param index the glyph index
+ * @return overridden weight value or {@link #NO_OVERRIDE}.
+ */
+ @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
+ public float getItalicOverride(@IntRange(from = 0) int index) {
+ Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
+ float value = nGetItalicOverride(mLayoutPtr, index);
+ if (value == -1) {
+ return NO_OVERRIDE;
+ } else {
+ return value;
+ }
+ }
+
+ /**
* Create single style layout from native result.
*
* @hide
@@ -210,6 +280,14 @@ public final class PositionedGlyphs {
private static native long nGetFont(long minikinLayout, int i);
@CriticalNative
private static native long nReleaseFunc();
+ @CriticalNative
+ private static native boolean nGetFakeBold(long minikinLayout, int i);
+ @CriticalNative
+ private static native boolean nGetFakeItalic(long minikinLayout, int i);
+ @CriticalNative
+ private static native float nGetWeightOverride(long minikinLayout, int i);
+ @CriticalNative
+ private static native float nGetItalicOverride(long minikinLayout, int i);
@Override
public boolean equals(Object o) {
diff --git a/graphics/java/android/graphics/text/TEST_MAPPING b/graphics/java/android/graphics/text/TEST_MAPPING
new file mode 100644
index 000000000000..99cbfe720c05
--- /dev/null
+++ b/graphics/java/android/graphics/text/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsTextTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.LargeTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java
index 0e198d5c56ec..e6de5978ceb0 100644
--- a/graphics/java/android/view/PixelCopy.java
+++ b/graphics/java/android/view/PixelCopy.java
@@ -322,7 +322,7 @@ public final class PixelCopy {
}
/**
- * Returns the {@link CopyResultStatus} of the copy request.
+ * Returns the status of the copy request.
*/
public @CopyResultStatus int getStatus() {
return mStatus;