aboutsummaryrefslogtreecommitdiff
path: root/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyTypeface.java
diff options
context:
space:
mode:
Diffstat (limited to 'shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyTypeface.java')
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyTypeface.java273
1 files changed, 273 insertions, 0 deletions
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyTypeface.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyTypeface.java
new file mode 100644
index 000000000..378bbb03f
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyTypeface.java
@@ -0,0 +1,273 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.KITKAT;
+import static android.os.Build.VERSION_CODES.LOLLIPOP;
+import static android.os.Build.VERSION_CODES.N_MR1;
+import static android.os.Build.VERSION_CODES.O;
+import static android.os.Build.VERSION_CODES.O_MR1;
+import static android.os.Build.VERSION_CODES.P;
+import static android.os.Build.VERSION_CODES.Q;
+import static android.os.Build.VERSION_CODES.R;
+import static android.os.Build.VERSION_CODES.S;
+import static org.robolectric.RuntimeEnvironment.getApiLevel;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.annotation.SuppressLint;
+import android.content.res.AssetManager;
+import android.graphics.FontFamily;
+import android.graphics.Typeface;
+import android.util.ArrayMap;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicLong;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.HiddenApi;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+import org.robolectric.annotation.Resetter;
+import org.robolectric.res.Fs;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.util.ReflectionHelpers;
+import org.robolectric.util.ReflectionHelpers.ClassParameter;
+
+/** Shadow for {@link Typeface}. */
+@Implements(value = Typeface.class, looseSignatures = true, isInAndroidSdk = false)
+@SuppressLint("NewApi")
+public class ShadowLegacyTypeface extends ShadowTypeface {
+ private static final Map<Long, FontDesc> FONTS = Collections.synchronizedMap(new HashMap<>());
+ private static final AtomicLong nextFontId = new AtomicLong(1);
+ private FontDesc description;
+
+ @HiddenApi
+ @Implementation(maxSdk = KITKAT)
+ protected void __constructor__(int fontId) {
+ description = findById((long) fontId);
+ }
+
+ @HiddenApi
+ @Implementation(minSdk = LOLLIPOP)
+ protected void __constructor__(long fontId) {
+ description = findById(fontId);
+ }
+
+ @Implementation
+ protected static void __staticInitializer__() {
+ Shadow.directInitialize(Typeface.class);
+ if (RuntimeEnvironment.getApiLevel() > R) {
+ Typeface.loadPreinstalledSystemFontMap();
+ }
+ }
+
+ @Implementation(minSdk = P)
+ protected static Typeface create(Typeface family, int weight, boolean italic) {
+ if (family == null) {
+ return createUnderlyingTypeface(null, weight);
+ } else {
+ ShadowTypeface shadowTypeface = Shadow.extract(family);
+ return createUnderlyingTypeface(shadowTypeface.getFontDescription().getFamilyName(), weight);
+ }
+ }
+
+ @Implementation
+ protected static Typeface create(String familyName, int style) {
+ return createUnderlyingTypeface(familyName, style);
+ }
+
+ @Implementation
+ protected static Typeface create(Typeface family, int style) {
+ if (family == null) {
+ return createUnderlyingTypeface(null, style);
+ } else {
+ ShadowTypeface shadowTypeface = Shadow.extract(family);
+ return createUnderlyingTypeface(shadowTypeface.getFontDescription().getFamilyName(), style);
+ }
+ }
+
+ @Implementation
+ protected static Typeface createFromAsset(AssetManager mgr, String path) {
+ ShadowAssetManager shadowAssetManager = Shadow.extract(mgr);
+ Collection<Path> assetDirs = shadowAssetManager.getAllAssetDirs();
+ for (Path assetDir : assetDirs) {
+ Path assetFile = assetDir.resolve(path);
+ if (Files.exists(assetFile)) {
+ return createUnderlyingTypeface(path, Typeface.NORMAL);
+ }
+
+ // maybe path is e.g. "myFont", but we should match "myFont.ttf" too?
+ Path[] files;
+ try {
+ files = Fs.listFiles(assetDir, f -> f.getFileName().toString().startsWith(path));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ if (files.length != 0) {
+ return createUnderlyingTypeface(path, Typeface.NORMAL);
+ }
+ }
+
+ throw new RuntimeException("Font asset not found " + path);
+ }
+
+ @Implementation(minSdk = O, maxSdk = P)
+ protected static Typeface createFromResources(AssetManager mgr, String path, int cookie) {
+ return createUnderlyingTypeface(path, Typeface.NORMAL);
+ }
+
+ @Implementation(minSdk = O)
+ protected static Typeface createFromResources(
+ Object /* FamilyResourceEntry */ entry,
+ Object /* AssetManager */ mgr,
+ Object /* String */ path) {
+ return createUnderlyingTypeface((String) path, Typeface.NORMAL);
+ }
+
+ @Implementation
+ protected static Typeface createFromFile(File path) {
+ String familyName = path.toPath().getFileName().toString();
+ return createUnderlyingTypeface(familyName, Typeface.NORMAL);
+ }
+
+ @Implementation
+ protected static Typeface createFromFile(String path) {
+ return createFromFile(new File(path));
+ }
+
+ @Implementation
+ protected int getStyle() {
+ return description.getStyle();
+ }
+
+ @Override
+ @Implementation
+ public boolean equals(Object o) {
+ if (o instanceof Typeface) {
+ Typeface other = ((Typeface) o);
+ return Objects.equals(getFontDescription(), shadowOf(other).getFontDescription());
+ }
+ return false;
+ }
+
+ @Override
+ @Implementation
+ public int hashCode() {
+ return getFontDescription().hashCode();
+ }
+
+ @HiddenApi
+ @Implementation(minSdk = LOLLIPOP)
+ protected static Typeface createFromFamilies(Object /*FontFamily[]*/ families) {
+ return null;
+ }
+
+ @HiddenApi
+ @Implementation(minSdk = LOLLIPOP, maxSdk = N_MR1)
+ protected static Typeface createFromFamiliesWithDefault(Object /*FontFamily[]*/ families) {
+ return null;
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static Typeface createFromFamiliesWithDefault(
+ Object /*FontFamily[]*/ families, Object /* int */ weight, Object /* int */ italic) {
+ return createUnderlyingTypeface("fake-font", Typeface.NORMAL);
+ }
+
+ @Implementation(minSdk = P)
+ protected static Typeface createFromFamiliesWithDefault(
+ Object /*FontFamily[]*/ families,
+ Object /* String */ fallbackName,
+ Object /* int */ weight,
+ Object /* int */ italic) {
+ return createUnderlyingTypeface((String) fallbackName, Typeface.NORMAL);
+ }
+
+ @Implementation(minSdk = P, maxSdk = P)
+ protected static void buildSystemFallback(
+ String xmlPath,
+ String fontDir,
+ ArrayMap<String, Typeface> fontMap,
+ ArrayMap<String, FontFamily[]> fallbackMap) {
+ fontMap.put("sans-serif", createUnderlyingTypeface("sans-serif", 0));
+ }
+
+ /** Avoid spurious error message about /system/etc/fonts.xml */
+ @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1)
+ protected static void init() {}
+
+ @HiddenApi
+ @Implementation(minSdk = Q, maxSdk = R)
+ protected static void initSystemDefaultTypefaces(
+ Object systemFontMap, Object fallbacks, Object aliases) {}
+
+ @Resetter
+ public static synchronized void reset() {
+ FONTS.clear();
+ }
+
+ protected static Typeface createUnderlyingTypeface(String familyName, int style) {
+ long thisFontId = nextFontId.getAndIncrement();
+ FONTS.put(thisFontId, new FontDesc(familyName, style));
+ if (getApiLevel() >= LOLLIPOP) {
+ return ReflectionHelpers.callConstructor(
+ Typeface.class, ClassParameter.from(long.class, thisFontId));
+ } else {
+ return ReflectionHelpers.callConstructor(
+ Typeface.class, ClassParameter.from(int.class, (int) thisFontId));
+ }
+ }
+
+ private static synchronized FontDesc findById(long fontId) {
+ if (FONTS.containsKey(fontId)) {
+ return FONTS.get(fontId);
+ }
+ throw new RuntimeException("Unknown font id: " + fontId);
+ }
+
+ @Implementation(minSdk = O, maxSdk = R)
+ protected static long nativeCreateFromArray(long[] familyArray, int weight, int italic) {
+ // TODO: implement this properly
+ long thisFontId = nextFontId.getAndIncrement();
+ FONTS.put(thisFontId, new FontDesc(null, weight));
+ return thisFontId;
+ }
+
+ /**
+ * Returns the font description.
+ *
+ * @return Font description.
+ */
+ @Override
+ public FontDesc getFontDescription() {
+ return description;
+ }
+
+ @Implementation(minSdk = S)
+ protected static void nativeForceSetStaticFinalField(String fieldname, Typeface typeface) {
+ ReflectionHelpers.setStaticField(Typeface.class, fieldname, typeface);
+ }
+
+ @Implementation(minSdk = S)
+ protected static long nativeCreateFromArray(
+ long[] familyArray, long fallbackTypeface, int weight, int italic) {
+ return ShadowLegacyTypeface.nativeCreateFromArray(familyArray, weight, italic);
+ }
+
+ /** Shadow for {@link Typeface.Builder} */
+ @Implements(value = Typeface.Builder.class, minSdk = Q)
+ public static class ShadowBuilder {
+ @RealObject Typeface.Builder realBuilder;
+
+ @Implementation
+ protected Typeface build() {
+ String path = ReflectionHelpers.getField(realBuilder, "mPath");
+ return createUnderlyingTypeface(path, Typeface.NORMAL);
+ }
+ }
+}