• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.O;
4 import static android.os.Build.VERSION_CODES.O_MR1;
5 import static android.os.Build.VERSION_CODES.P;
6 import static android.os.Build.VERSION_CODES.Q;
7 import static android.os.Build.VERSION_CODES.R;
8 import static android.os.Build.VERSION_CODES.S;
9 import static android.os.Build.VERSION_CODES.TIRAMISU;
10 import static org.robolectric.util.reflector.Reflector.reflector;
11 
12 import android.graphics.FontFamily;
13 import android.graphics.Typeface;
14 import android.graphics.fonts.FontVariationAxis;
15 import android.text.FontConfig;
16 import android.util.ArrayMap;
17 import android.util.Log;
18 import com.google.common.base.Preconditions;
19 import com.google.errorprone.annotations.CanIgnoreReturnValue;
20 import java.io.File;
21 import java.io.FileInputStream;
22 import java.io.IOException;
23 import java.nio.ByteBuffer;
24 import java.nio.channels.FileChannel;
25 import java.util.List;
26 import java.util.Map;
27 import org.robolectric.RuntimeEnvironment;
28 import org.robolectric.annotation.ClassName;
29 import org.robolectric.annotation.Implementation;
30 import org.robolectric.annotation.Implements;
31 import org.robolectric.annotation.InDevelopment;
32 import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
33 import org.robolectric.nativeruntime.TypefaceNatives;
34 import org.robolectric.shadow.api.Shadow;
35 import org.robolectric.util.reflector.Direct;
36 import org.robolectric.util.reflector.ForType;
37 import org.robolectric.util.reflector.Static;
38 import org.robolectric.versioning.AndroidVersions;
39 import org.robolectric.versioning.AndroidVersions.U;
40 
41 /** Shadow for {@link Typeface} that is backed by native code */
42 @Implements(
43     value = Typeface.class,
44     minSdk = O,
45     isInAndroidSdk = false,
46     callNativeMethodsByDefault = true)
47 public class ShadowNativeTypeface extends ShadowTypeface {
48 
49   private static final String TAG = "ShadowNativeTypeface";
50 
51   // Style value for building typeface.
52   private static final int STYLE_NORMAL = 0;
53   private static final int STYLE_ITALIC = 1;
54 
55   @Implementation(minSdk = S)
__staticInitializer__()56   protected static void __staticInitializer__() {
57     if (RuntimeEnvironment.getApiLevel() <= U.SDK_INT) {
58       Shadow.directInitialize(Typeface.class);
59       // Initialize the system font map. In real Android this is done as part of Application startup
60       // and uses a more complex SharedMemory system not supported in Robolectric.
61       Typeface.loadPreinstalledSystemFontMap();
62     }
63     // The Typeface static initializer invokes its own native methods. This has to be deferred
64     // starting in Android V.
65   }
66 
67   @Implementation(minSdk = P, maxSdk = P)
buildSystemFallback( String xmlPath, String systemFontDir, ArrayMap<String, Typeface> fontMap, ArrayMap<String, FontFamily[]> fallbackMap)68   protected static void buildSystemFallback(
69       String xmlPath,
70       String systemFontDir,
71       ArrayMap<String, Typeface> fontMap,
72       ArrayMap<String, FontFamily[]> fallbackMap) {
73     String fontDir = System.getProperty("robolectric.nativeruntime.fontdir");
74     Preconditions.checkNotNull(fontDir);
75     Preconditions.checkState(new File(fontDir).isDirectory(), "Missing fonts directory");
76     Preconditions.checkState(
77         fontDir.endsWith(File.separator), "Fonts directory must end with a slash");
78     reflector(TypefaceReflector.class)
79         .buildSystemFallback(fontDir + "fonts.xml", fontDir, fontMap, fallbackMap);
80   }
81 
82   @Implementation(minSdk = O, maxSdk = O_MR1)
getSystemFontConfigLocation()83   protected static File getSystemFontConfigLocation() {
84     // Ensure that the Robolectric native runtime is loaded in ordere to ensure that the
85     // `robolectric.nativeruntime.fontdir` system property is valid.
86     DefaultNativeRuntimeLoader.injectAndLoad();
87     String fontDir = System.getProperty("robolectric.nativeruntime.fontdir");
88     Preconditions.checkNotNull(fontDir);
89     Preconditions.checkState(new File(fontDir).isDirectory(), "Missing fonts directory");
90     Preconditions.checkState(
91         fontDir.endsWith(File.separator), "Fonts directory must end with a slash");
92     return new File(fontDir);
93   }
94 
95   @SuppressWarnings("unchecked")
96   @Implementation(minSdk = O, maxSdk = O_MR1)
makeFamilyFromParsed( @lassName"android.text.FontConfig$Family") Object family, Map<String, ByteBuffer> bufferForPath)97   protected static @ClassName("android.graphics.FontFamily") Object makeFamilyFromParsed(
98       @ClassName("android.text.FontConfig$Family") Object family,
99       Map<String, ByteBuffer> bufferForPath) {
100     FontConfigFamilyReflector reflector = reflector(FontConfigFamilyReflector.class, family);
101 
102     FontFamily fontFamily =
103         Shadow.newInstance(
104             FontFamily.class,
105             new Class<?>[] {String.class, int.class},
106             new Object[] {reflector.getLanguage(), reflector.getVariant()});
107     for (FontConfig.Font font : reflector.getFonts()) {
108       String fullPathName =
109           System.getProperty("robolectric.nativeruntime.fontdir")
110               + reflector(FontConfigFontReflector.class, font).getFontName();
111       ByteBuffer fontBuffer = bufferForPath.get(fullPathName);
112       if (fontBuffer == null) {
113         try (FileInputStream file = new FileInputStream(fullPathName)) {
114           FileChannel fileChannel = file.getChannel();
115           long fontSize = fileChannel.size();
116           fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
117           bufferForPath.put(fullPathName, fontBuffer);
118         } catch (IOException e) {
119           Log.w(TAG, "Error mapping font file " + fullPathName);
120           continue;
121         }
122       }
123       if (!fontFamily.addFontFromBuffer(
124           fontBuffer,
125           font.getTtcIndex(),
126           font.getAxes(),
127           font.getWeight(),
128           font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL)) {
129         Log.e(TAG, "Error creating font " + fullPathName + "#" + font.getTtcIndex());
130       }
131     }
132     if (!fontFamily.freeze()) {
133       // Treat as system error since reaching here means that a system pre-installed font
134       // can't be used by our font stack.
135       Log.w(TAG, "Unable to load Family: " + reflector.getName() + ":" + reflector.getLanguage());
136       return null;
137     }
138     return fontFamily;
139   }
140 
141   @Implementation(maxSdk = U.SDK_INT)
nativeCreateFromTypeface(long nativeInstance, int style)142   protected static long nativeCreateFromTypeface(long nativeInstance, int style) {
143     return TypefaceNatives.nativeCreateFromTypeface(nativeInstance, style);
144   }
145 
146   @Implementation(minSdk = O, maxSdk = U.SDK_INT)
nativeCreateFromTypefaceWithExactStyle( long nativeInstance, int weight, boolean italic)147   protected static long nativeCreateFromTypefaceWithExactStyle(
148       long nativeInstance, int weight, boolean italic) {
149     return TypefaceNatives.nativeCreateFromTypefaceWithExactStyle(nativeInstance, weight, italic);
150   }
151 
152   @Implementation(minSdk = O, maxSdk = U.SDK_INT)
nativeCreateFromTypefaceWithVariation( long nativeInstance, List<FontVariationAxis> axes)153   protected static long nativeCreateFromTypefaceWithVariation(
154       long nativeInstance, List<FontVariationAxis> axes) {
155     return TypefaceNatives.nativeCreateFromTypefaceWithVariation(nativeInstance, axes);
156   }
157 
158   @Implementation(maxSdk = U.SDK_INT)
nativeCreateWeightAlias(long nativeInstance, int weight)159   protected static long nativeCreateWeightAlias(long nativeInstance, int weight) {
160     return TypefaceNatives.nativeCreateWeightAlias(nativeInstance, weight);
161   }
162 
163   @Implementation(minSdk = O, maxSdk = R)
nativeCreateFromArray(long[] familyArray, int weight, int italic)164   protected static long nativeCreateFromArray(long[] familyArray, int weight, int italic) {
165     return TypefaceNatives.nativeCreateFromArray(familyArray, 0, weight, italic);
166   }
167 
168   @Implementation(minSdk = S, maxSdk = U.SDK_INT)
nativeCreateFromArray( long[] familyArray, long fallbackTypeface, int weight, int italic)169   protected static long nativeCreateFromArray(
170       long[] familyArray, long fallbackTypeface, int weight, int italic) {
171     return TypefaceNatives.nativeCreateFromArray(familyArray, fallbackTypeface, weight, italic);
172   }
173 
174   @Implementation(minSdk = O, maxSdk = U.SDK_INT)
nativeGetSupportedAxes(long nativeInstance)175   protected static int[] nativeGetSupportedAxes(long nativeInstance) {
176     return TypefaceNatives.nativeGetSupportedAxes(nativeInstance);
177   }
178 
179   @Implementation(maxSdk = U.SDK_INT)
nativeSetDefault(long nativePtr)180   protected static void nativeSetDefault(long nativePtr) {
181     TypefaceNatives.nativeSetDefault(nativePtr);
182   }
183 
184   @Implementation(maxSdk = U.SDK_INT)
nativeGetStyle(long nativePtr)185   protected static int nativeGetStyle(long nativePtr) {
186     return TypefaceNatives.nativeGetStyle(nativePtr);
187   }
188 
189   @Implementation(minSdk = O, maxSdk = U.SDK_INT)
nativeGetWeight(long nativePtr)190   protected static int nativeGetWeight(long nativePtr) {
191     return TypefaceNatives.nativeGetWeight(nativePtr);
192   }
193 
194   @Implementation(minSdk = P, maxSdk = U.SDK_INT)
nativeGetReleaseFunc()195   protected static long nativeGetReleaseFunc() {
196     DefaultNativeRuntimeLoader.injectAndLoad();
197     return TypefaceNatives.nativeGetReleaseFunc();
198   }
199 
200   @Implementation(minSdk = S, maxSdk = TIRAMISU)
nativeGetFamilySize(long nativePtr)201   protected static int nativeGetFamilySize(long nativePtr) {
202     return TypefaceNatives.nativeGetFamilySize(nativePtr);
203   }
204 
205   @Implementation(minSdk = S, maxSdk = TIRAMISU)
nativeGetFamily(long nativePtr, int index)206   protected static long nativeGetFamily(long nativePtr, int index) {
207     return TypefaceNatives.nativeGetFamily(nativePtr, index);
208   }
209 
210   @Implementation(minSdk = AndroidVersions.Baklava.SDK_INT)
211   @InDevelopment
nativeIsVariationInstance(long nativePtr)212   protected static boolean nativeIsVariationInstance(long nativePtr) {
213     return false;
214     // TODO: call the real impl when it's finally available in native binaries
215     // return TypefaceNatives.nativeIsVariationInstance(nativePtr);
216   }
217 
218   @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
nativeRegisterGenericFamily(String str, long nativePtr)219   protected static void nativeRegisterGenericFamily(String str, long nativePtr) {
220     TypefaceNatives.nativeRegisterGenericFamily(str, nativePtr);
221   }
222 
223   @Implementation(minSdk = S, maxSdk = TIRAMISU)
nativeWriteTypefaces(ByteBuffer buffer, long[] nativePtrs)224   protected static int nativeWriteTypefaces(ByteBuffer buffer, long[] nativePtrs) {
225     return TypefaceNatives.nativeWriteTypefaces(buffer, nativePtrs);
226   }
227 
228   @Implementation(minSdk = U.SDK_INT, maxSdk = U.SDK_INT)
nativeWriteTypefaces(ByteBuffer buffer, int position, long[] nativePtrs)229   protected static int nativeWriteTypefaces(ByteBuffer buffer, int position, long[] nativePtrs) {
230     return nativeWriteTypefaces(buffer, nativePtrs);
231   }
232 
233   @Implementation(minSdk = S, maxSdk = TIRAMISU)
nativeReadTypefaces(ByteBuffer buffer)234   protected static long[] nativeReadTypefaces(ByteBuffer buffer) {
235     return TypefaceNatives.nativeReadTypefaces(buffer);
236   }
237 
238   @Implementation(minSdk = U.SDK_INT, maxSdk = U.SDK_INT)
nativeReadTypefaces(ByteBuffer buffer, int position)239   protected static long[] nativeReadTypefaces(ByteBuffer buffer, int position) {
240     return nativeReadTypefaces(buffer);
241   }
242 
243   @Implementation(minSdk = S, maxSdk = U.SDK_INT)
nativeForceSetStaticFinalField(String fieldName, Typeface typeface)244   protected static void nativeForceSetStaticFinalField(String fieldName, Typeface typeface) {
245     TypefaceNatives.nativeForceSetStaticFinalField(fieldName, typeface);
246   }
247 
248   @Implementation(minSdk = S, maxSdk = U.SDK_INT)
nativeAddFontCollections(long nativePtr)249   protected static void nativeAddFontCollections(long nativePtr) {
250     TypefaceNatives.nativeAddFontCollections(nativePtr);
251   }
252 
253   @Implementation(minSdk = U.SDK_INT, maxSdk = U.SDK_INT)
nativeRegisterLocaleList(String locales)254   protected static void nativeRegisterLocaleList(String locales) {
255     // no-op
256   }
257 
ensureInitialized()258   static void ensureInitialized() {
259     try {
260       // Forces static initialization. This should be called before any native code that calls
261       // Typeface::resolveDefault.
262       Class.forName("android.graphics.Typeface");
263     } catch (ClassNotFoundException e) {
264       throw new LinkageError("Unable to load Typeface", e);
265     }
266   }
267 
268   @Override
getFontDescription()269   public FontDesc getFontDescription() {
270     throw new UnsupportedOperationException(
271         "Legacy ShadowTypeface description APIs are not supported");
272   }
273 
274   /**
275    * Shadow for {@link Typeface.Builder}. It is empty to avoid using the legacy {@link
276    * Typeface.Builder} shadow.
277    */
278   @Implements(
279       value = Typeface.Builder.class,
280       minSdk = P,
281       shadowPicker = ShadowNativeTypefaceBuilder.Picker.class,
282       isInAndroidSdk = false)
283   public static class ShadowNativeTypefaceBuilder {
284     /** Shadow picker for {@link Typeface.Builder}. */
285     public static final class Picker extends GraphicsShadowPicker<Object> {
Picker()286       public Picker() {
287         super(ShadowLegacyTypeface.ShadowBuilder.class, ShadowNativeTypefaceBuilder.class);
288       }
289     }
290   }
291 
292   @ForType(Typeface.class)
293   interface TypefaceReflector {
294     @CanIgnoreReturnValue
295     @Static
296     @Direct
buildSystemFallback( String xmlPath, String fontDir, ArrayMap<String, Typeface> fontMap, ArrayMap<String, FontFamily[]> fallbackMap)297     FontConfig.Alias[] buildSystemFallback(
298         String xmlPath,
299         String fontDir,
300         ArrayMap<String, Typeface> fontMap,
301         ArrayMap<String, FontFamily[]> fallbackMap);
302   }
303 
304   @ForType(className = "android.text.FontConfig$Family")
305   interface FontConfigFamilyReflector {
getLanguage()306     String getLanguage();
307 
getVariant()308     int getVariant();
309 
getFonts()310     FontConfig.Font[] getFonts();
311 
getName()312     String getName();
313   }
314 
315   @ForType(className = "android.text.FontConfig$Font")
316   interface FontConfigFontReflector {
getFontName()317     String getFontName();
318   }
319 }
320