Home | History | Annotate | Download | only in shadows
      1 package org.robolectric.shadows;
      2 
      3 import static android.os.Build.VERSION_CODES.LOLLIPOP;
      4 import static android.os.Build.VERSION_CODES.P;
      5 import static org.robolectric.RuntimeEnvironment.getApiLevel;
      6 import static org.robolectric.Shadows.shadowOf;
      7 
      8 import android.content.res.AssetManager;
      9 import android.graphics.FontFamily;
     10 import android.graphics.Typeface;
     11 import android.util.ArrayMap;
     12 import java.io.File;
     13 import java.util.Collection;
     14 import java.util.HashMap;
     15 import java.util.Map;
     16 import org.robolectric.annotation.HiddenApi;
     17 import org.robolectric.annotation.Implementation;
     18 import org.robolectric.annotation.Implements;
     19 import org.robolectric.annotation.RealObject;
     20 import org.robolectric.annotation.Resetter;
     21 import org.robolectric.res.FsFile;
     22 import org.robolectric.util.ReflectionHelpers;
     23 import org.robolectric.util.ReflectionHelpers.ClassParameter;
     24 
     25 @Implements(value = Typeface.class, looseSignatures = true)
     26 public class ShadowTypeface {
     27   private static Map<Long, FontDesc> FONTS = new HashMap<>();
     28   private static long nextFontId = 1;
     29   private FontDesc description;
     30   @RealObject private Typeface realTypeface;
     31 
     32   @HiddenApi
     33   @Implementation
     34   public void __constructor__(int fontId) {
     35     description = findById((long) fontId);
     36   }
     37 
     38   @HiddenApi
     39   @Implementation
     40   public void __constructor__(long fontId) {
     41     description = findById(fontId);
     42   }
     43 
     44   @Implementation
     45   public static Typeface create(String familyName, int style) {
     46     return createUnderlyingTypeface(familyName, style);
     47   }
     48 
     49   @Implementation
     50   public static Typeface create(Typeface family, int style) {
     51     if (family == null) {
     52       return createUnderlyingTypeface(null, style);
     53     } else {
     54       return createUnderlyingTypeface(shadowOf(family).getFontDescription().getFamilyName(), style);
     55     }
     56   }
     57 
     58   @Implementation
     59   public static Typeface createFromAsset(AssetManager mgr, String path) {
     60     Collection<FsFile> assetDirs = shadowOf(mgr).getAllAssetsDirectories();
     61     for (FsFile assetDir : assetDirs) {
     62       // check if in zip file too?
     63       FsFile[] files = assetDir.listFiles(new StartsWith(path));
     64       FsFile assetFile = assetDir.join(path);
     65       if (assetFile.exists() || files.length != 0) {
     66         return createUnderlyingTypeface(path, Typeface.NORMAL);
     67       }
     68     }
     69 
     70     throw new RuntimeException("Font not found at " + assetDirs);
     71   }
     72 
     73   @Implementation
     74   public static Typeface createFromFile(File path) {
     75     String familyName = path.toPath().getFileName().toString();
     76     return createUnderlyingTypeface(familyName, Typeface.NORMAL);
     77   }
     78 
     79   @Implementation
     80   public static Typeface createFromFile(String path) {
     81     return createFromFile(new File(path));
     82   }
     83 
     84   @Implementation
     85   public int getStyle() {
     86     return description.getStyle();
     87   }
     88 
     89   @HiddenApi
     90   @Implementation(minSdk = LOLLIPOP)
     91   public static Typeface createFromFamilies(Object /*FontFamily[]*/ families) {
     92     return null;
     93   }
     94 
     95   @HiddenApi
     96   @Implementation(minSdk = LOLLIPOP)
     97   public static Typeface createFromFamiliesWithDefault(Object /*FontFamily[]*/ families) {
     98     return null;
     99   }
    100 
    101   // BEGIN-INTERNAL
    102   @Implementation(minSdk = P)
    103   public static void buildSystemFallback(String xmlPath, String fontDir,
    104       ArrayMap<String, Typeface> fontMap, ArrayMap<String, FontFamily[]> fallbackMap) {
    105     fontMap.put("sans-serif", create("sans-serif", 0));
    106   }
    107   // END-INTERNAL
    108 
    109   @Resetter
    110   synchronized public static void reset() {
    111     FONTS.clear();
    112   }
    113 
    114   private static Typeface createUnderlyingTypeface(String familyName, int style) {
    115     long thisFontId = nextFontId++;
    116     FONTS.put(thisFontId, new FontDesc(familyName, style));
    117     if (getApiLevel() >= LOLLIPOP) {
    118       return ReflectionHelpers.callConstructor(Typeface.class, ClassParameter.from(long.class, thisFontId));
    119     } else {
    120       return ReflectionHelpers.callConstructor(Typeface.class, ClassParameter.from(int.class, (int) thisFontId));
    121     }
    122   }
    123 
    124   private synchronized static FontDesc findById(long fontId) {
    125     if (FONTS.containsKey(fontId)) {
    126       return FONTS.get(fontId);
    127     }
    128     throw new RuntimeException("Unknown font id: " + fontId);
    129   }
    130 
    131   /**
    132    * Returns the font description.
    133    *
    134    * @return Font description.
    135    */
    136   public FontDesc getFontDescription() {
    137     return description;
    138   }
    139 
    140   public static class FontDesc {
    141     public final String familyName;
    142     public final int style;
    143 
    144     public FontDesc(String familyName, int style) {
    145       this.familyName = familyName;
    146       this.style = style;
    147     }
    148 
    149     @Override
    150     public boolean equals(Object o) {
    151       if (this == o) return true;
    152       if (o == null || getClass() != o.getClass()) return false;
    153 
    154       FontDesc fontDesc = (FontDesc) o;
    155 
    156       if (style != fontDesc.style) return false;
    157       if (familyName != null ? !familyName.equals(fontDesc.familyName) : fontDesc.familyName != null)
    158         return false;
    159 
    160       return true;
    161     }
    162 
    163     @Override
    164     public int hashCode() {
    165       int result = familyName != null ? familyName.hashCode() : 0;
    166       result = 31 * result + style;
    167       return result;
    168     }
    169 
    170     public String getFamilyName() {
    171       return familyName;
    172     }
    173 
    174     public int getStyle() {
    175       return style;
    176     }
    177   }
    178 
    179   private static class StartsWith implements FsFile.Filter {
    180     private final String contains;
    181 
    182     public StartsWith(String contains) {
    183       this.contains = contains;
    184     }
    185 
    186     @Override
    187     public boolean accept(FsFile file) {
    188       return file.getName().startsWith(contains);
    189     }
    190   }
    191 }
    192