• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.N_MR1;
4 import static android.os.Build.VERSION_CODES.O;
5 import static android.os.Build.VERSION_CODES.O_MR1;
6 import static android.os.Build.VERSION_CODES.P;
7 import static android.os.Build.VERSION_CODES.Q;
8 import static android.os.Build.VERSION_CODES.R;
9 import static android.os.Build.VERSION_CODES.S;
10 import static org.robolectric.Shadows.shadowOf;
11 import static org.robolectric.util.reflector.Reflector.reflector;
12 
13 import android.annotation.SuppressLint;
14 import android.content.res.AssetManager;
15 import android.graphics.Typeface;
16 import android.graphics.fonts.FontStyle;
17 import android.util.ArrayMap;
18 import java.io.File;
19 import java.io.IOException;
20 import java.nio.file.Files;
21 import java.nio.file.Path;
22 import java.util.Collection;
23 import java.util.Map;
24 import java.util.Objects;
25 import java.util.concurrent.atomic.AtomicLong;
26 import org.robolectric.RuntimeEnvironment;
27 import org.robolectric.annotation.ClassName;
28 import org.robolectric.annotation.HiddenApi;
29 import org.robolectric.annotation.Implementation;
30 import org.robolectric.annotation.Implements;
31 import org.robolectric.annotation.RealObject;
32 import org.robolectric.res.Fs;
33 import org.robolectric.shadow.api.Shadow;
34 import org.robolectric.util.ReflectionHelpers;
35 import org.robolectric.util.ReflectionHelpers.ClassParameter;
36 import org.robolectric.util.reflector.Accessor;
37 import org.robolectric.util.reflector.Direct;
38 import org.robolectric.util.reflector.ForType;
39 
40 /** Shadow for {@link Typeface}. */
41 @Implements(value = Typeface.class, isInAndroidSdk = false)
42 @SuppressLint("NewApi")
43 public class ShadowLegacyTypeface extends ShadowTypeface {
44   private static final AtomicLong nextFontId = new AtomicLong(1);
45   private FontDesc description;
46 
47   @RealObject Typeface realTypeface;
48 
49   @Implementation
__staticInitializer__()50   protected static void __staticInitializer__() {
51     Shadow.directInitialize(Typeface.class);
52     if (RuntimeEnvironment.getApiLevel() > R) {
53       Typeface.loadPreinstalledSystemFontMap();
54     }
55   }
56 
57   @Implementation(minSdk = P)
create(Typeface family, int weight, boolean italic)58   protected static Typeface create(Typeface family, int weight, boolean italic) {
59     if (family == null) {
60       return createUnderlyingTypeface(null, weight);
61     } else {
62       ShadowTypeface shadowTypeface = Shadow.extract(family);
63       return createUnderlyingTypeface(shadowTypeface.getFontDescription().getFamilyName(), weight);
64     }
65   }
66 
67   @Implementation
create(String familyName, int style)68   protected static Typeface create(String familyName, int style) {
69     return createUnderlyingTypeface(familyName, style);
70   }
71 
72   @Implementation
create(Typeface family, int style)73   protected static Typeface create(Typeface family, int style) {
74     if (family == null) {
75       return createUnderlyingTypeface(null, style);
76     } else {
77       ShadowTypeface shadowTypeface = Shadow.extract(family);
78       return createUnderlyingTypeface(shadowTypeface.getFontDescription().getFamilyName(), style);
79     }
80   }
81 
82   @Implementation
createFromAsset(AssetManager mgr, String path)83   protected static Typeface createFromAsset(AssetManager mgr, String path) {
84     ShadowAssetManager shadowAssetManager = Shadow.extract(mgr);
85     Collection<Path> assetDirs = shadowAssetManager.getAllAssetDirs();
86     for (Path assetDir : assetDirs) {
87       Path assetFile = assetDir.resolve(path);
88       if (Files.exists(assetFile)) {
89         return createUnderlyingTypeface(path, Typeface.NORMAL);
90       }
91 
92       // maybe path is e.g. "myFont", but we should match "myFont.ttf" too?
93       Path[] files;
94       try {
95         files = Fs.listFiles(assetDir, f -> f.getFileName().toString().startsWith(path));
96       } catch (IOException e) {
97         throw new RuntimeException(e);
98       }
99       if (files.length != 0) {
100         return createUnderlyingTypeface(path, Typeface.NORMAL);
101       }
102     }
103 
104     throw new RuntimeException("Font asset not found " + path);
105   }
106 
107   @Implementation(minSdk = O, maxSdk = P)
createFromResources(AssetManager mgr, String path, int cookie)108   protected static Typeface createFromResources(AssetManager mgr, String path, int cookie) {
109     return createUnderlyingTypeface(path, Typeface.NORMAL);
110   }
111 
112   @Implementation(minSdk = O)
createFromResources( @lassName"android.content.res.FontResourcesParser$FamilyResourceEntry") Object entry, AssetManager mgr, String path)113   protected static Typeface createFromResources(
114       @ClassName("android.content.res.FontResourcesParser$FamilyResourceEntry") Object entry,
115       AssetManager mgr,
116       String path) {
117     return createUnderlyingTypeface(path, Typeface.NORMAL);
118   }
119 
120   @Implementation
createFromFile(File path)121   protected static Typeface createFromFile(File path) {
122     String familyName = path.toPath().getFileName().toString();
123     return createUnderlyingTypeface(familyName, Typeface.NORMAL);
124   }
125 
126   @Implementation
createFromFile(String path)127   protected static Typeface createFromFile(String path) {
128     return createFromFile(new File(path));
129   }
130 
131   @Implementation
getStyle()132   protected int getStyle() {
133     return description.getStyle();
134   }
135 
136   @Override
137   @Implementation
equals(Object o)138   public boolean equals(Object o) {
139     if (o instanceof Typeface) {
140       Typeface other = ((Typeface) o);
141       return Objects.equals(getFontDescription(), shadowOf(other).getFontDescription());
142     }
143     return false;
144   }
145 
146   @Override
147   @Implementation
hashCode()148   public int hashCode() {
149     return getFontDescription().hashCode();
150   }
151 
152   @HiddenApi
153   @Implementation
createFromFamilies( @lassName"[Landroid.graphics.FontFamily;") Object families)154   protected static Typeface createFromFamilies(
155       @ClassName("[Landroid.graphics.FontFamily;") Object families) {
156     return null;
157   }
158 
159   @HiddenApi
160   @Implementation(maxSdk = N_MR1)
createFromFamiliesWithDefault( @lassName"[Landroid.graphics.FontFamily;") Object families)161   protected static Typeface createFromFamiliesWithDefault(
162       @ClassName("[Landroid.graphics.FontFamily;") Object families) {
163     return null;
164   }
165 
166   @Implementation(minSdk = O, maxSdk = O_MR1)
createFromFamiliesWithDefault( @lassName"[Landroid.graphics.FontFamily;") Object families, int weight, int italic)167   protected static Typeface createFromFamiliesWithDefault(
168       @ClassName("[Landroid.graphics.FontFamily;") Object families, int weight, int italic) {
169     return createUnderlyingTypeface("fake-font", Typeface.NORMAL);
170   }
171 
172   @Implementation(minSdk = P)
createFromFamiliesWithDefault( @lassName"[Landroid.graphics.FontFamily;") Object families, String fallbackName, int weight, int italic)173   protected static Typeface createFromFamiliesWithDefault(
174       @ClassName("[Landroid.graphics.FontFamily;") Object families,
175       String fallbackName,
176       int weight,
177       int italic) {
178     return createUnderlyingTypeface(fallbackName, Typeface.NORMAL);
179   }
180 
181   @Implementation(minSdk = P, maxSdk = P)
buildSystemFallback( String xmlPath, String fontDir, ArrayMap<String, Typeface> fontMap, ArrayMap<String, ?> fallbackMap)182   protected static void buildSystemFallback(
183       String xmlPath,
184       String fontDir,
185       ArrayMap<String, Typeface> fontMap,
186       ArrayMap<String, /*android.graphics.FontFamily[]*/ ?> fallbackMap) {
187     fontMap.put("sans-serif", createUnderlyingTypeface("sans-serif", 0));
188   }
189 
190   /** Avoid spurious error message about /system/etc/fonts.xml */
191   @Implementation(maxSdk = O_MR1)
init()192   protected static void init() {}
193 
194   @HiddenApi
195   @Implementation(minSdk = Q, maxSdk = R)
initSystemDefaultTypefaces( Map<String, Typeface> systemFontMap, Map<String, ?> fallbacks, @ClassName("[Landroid.text.FontConfig$Alias;") Object aliases)196   protected static void initSystemDefaultTypefaces(
197       Map<String, Typeface> systemFontMap,
198       Map<String, /*android.graphics.FontFamily[]*/ ?> fallbacks,
199       @ClassName("[Landroid.text.FontConfig$Alias;") Object aliases) {}
200 
createUnderlyingTypeface(String familyName, int style)201   protected static Typeface createUnderlyingTypeface(String familyName, int style) {
202     long thisFontId = nextFontId.getAndIncrement();
203     Typeface result =
204         ReflectionHelpers.callConstructor(
205             Typeface.class, ClassParameter.from(long.class, thisFontId));
206     ((ShadowLegacyTypeface) Shadow.extract(result)).description = new FontDesc(familyName, style);
207     return result;
208   }
209 
210 
211   @Implementation(minSdk = O, maxSdk = R)
nativeCreateFromArray(long[] familyArray, int weight, int italic)212   protected static long nativeCreateFromArray(long[] familyArray, int weight, int italic) {
213     // TODO: implement this properly
214     return nextFontId.incrementAndGet();
215   }
216 
217   /**
218    * Returns the font description.
219    *
220    * @return Font description.
221    */
222   @Override
getFontDescription()223   public FontDesc getFontDescription() {
224     return description;
225   }
226 
227   @Implementation(minSdk = S)
nativeForceSetStaticFinalField(String fieldname, Typeface typeface)228   protected static void nativeForceSetStaticFinalField(String fieldname, Typeface typeface) {
229     ReflectionHelpers.setStaticField(Typeface.class, fieldname, typeface);
230   }
231 
232   @Implementation(minSdk = S)
nativeCreateFromArray( long[] familyArray, long fallbackTypeface, int weight, int italic)233   protected static long nativeCreateFromArray(
234       long[] familyArray, long fallbackTypeface, int weight, int italic) {
235     return ShadowLegacyTypeface.nativeCreateFromArray(familyArray, weight, italic);
236   }
237 
238   /** Shadow for {@link Typeface.Builder} */
239   @Implements(value = Typeface.Builder.class, minSdk = Q)
240   public static class ShadowBuilder {
241     @RealObject Typeface.Builder realBuilder;
242 
243     @Implementation
build()244     protected Typeface build() {
245       String path = ReflectionHelpers.getField(realBuilder, "mPath");
246       return createUnderlyingTypeface(path, Typeface.NORMAL);
247     }
248   }
249 
250   /** Shadow for {@link Typeface.CustomFallbackBuilder} that populates {@link #description} */
251   @Implements(
252       value = Typeface.CustomFallbackBuilder.class,
253       minSdk = Q,
254       shadowPicker = CustomFallbackBuilderPicker.class)
255   public static class ShadowCustomFallbackBuilder {
256     @RealObject Typeface.CustomFallbackBuilder realBuilder;
257 
258     @Implementation
build()259     protected Typeface build() {
260       Typeface result = reflector(CustomFallbackBuilderReflector.class, realBuilder).build();
261       FontStyle style = reflector(CustomFallbackBuilderReflector.class, realBuilder).getStyle();
262       ((ShadowLegacyTypeface) Shadow.extract(result)).description =
263           new FontDesc(null, style.getWeight());
264       return result;
265     }
266   }
267 
268   /** Shadow picker for {@link Typeface.CustomFallbackBuilder}. */
269   public static final class CustomFallbackBuilderPicker extends GraphicsShadowPicker<Object> {
CustomFallbackBuilderPicker()270     public CustomFallbackBuilderPicker() {
271       super(ShadowLegacyTypeface.ShadowCustomFallbackBuilder.class, null);
272     }
273   }
274 
275   @ForType(Typeface.CustomFallbackBuilder.class)
276   interface CustomFallbackBuilderReflector {
277     @Direct
build()278     Typeface build();
279 
280     @Accessor("mStyle")
getStyle()281     FontStyle getStyle();
282   }
283 }
284