Add Asset management support for fonts.

Change-Id: I10ca67dcffe244667d4ae0bda65dbc1aa6691d50
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java b/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java
index c41a4ee..2691e56 100644
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java
@@ -16,12 +16,13 @@
 
 package android.content.res;
 
+import com.android.ide.common.rendering.api.AssetRepository;
 import com.android.layoutlib.bridge.Bridge;
 
-import android.content.res.AssetManager;
-
 public class BridgeAssetManager extends AssetManager {
 
+    private AssetRepository mAssetRepository;
+
     /**
      * This initializes the static field {@link AssetManager#sSystem} which is used
      * by methods who get a global asset manager using {@link AssetManager#getSystem()}.
@@ -48,6 +49,14 @@
         AssetManager.sSystem = null;
     }
 
-    private BridgeAssetManager() {
+    public void setAssetRepository(AssetRepository assetRepository) {
+        mAssetRepository = assetRepository;
+    }
+
+    public AssetRepository getAssetRepository() {
+        return mAssetRepository;
+    }
+
+    public BridgeAssetManager() {
     }
 }
diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
index ab79664..42de4ec 100644
--- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
@@ -18,21 +18,28 @@
 
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
+import com.android.ide.common.rendering.api.AssetRepository;
 import com.android.ide.common.rendering.api.LayoutLog;
 import com.android.layoutlib.bridge.Bridge;
 import com.android.layoutlib.bridge.impl.DelegateManager;
 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
 
 import android.content.res.AssetManager;
+import android.content.res.BridgeAssetManager;
 
 import java.awt.Font;
 import java.awt.FontFormatException;
 import java.io.File;
 import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Scanner;
 import java.util.Set;
 
@@ -56,10 +63,28 @@
     public static final int BOLD_FONT_WEIGHT_DELTA = 300;
     public static final int BOLD_FONT_WEIGHT = 700;
 
-    // FONT_SUFFIX_ITALIC will always match FONT_SUFFIX_BOLDITALIC and hence it must be checked
-    // separately.
     private static final String FONT_SUFFIX_ITALIC = "Italic.ttf";
     private static final String FN_ALL_FONTS_LIST = "fontsInSdk.txt";
+    private static final String EXTENSION_OTF = ".otf";
+
+    private static final int CACHE_SIZE = 10;
+    // The cache has a drawback that if the font file changed after the font object was created,
+    // we will not update it.
+    private static final Map<String, FontInfo> sCache =
+            new LinkedHashMap<String, FontInfo>(CACHE_SIZE) {
+        @Override
+        protected boolean removeEldestEntry(Entry<String, FontInfo> eldest) {
+            return size() > CACHE_SIZE;
+        }
+
+        @Override
+        public FontInfo put(String key, FontInfo value) {
+            // renew this entry.
+            FontInfo removed = remove(key);
+            super.put(key, value);
+            return removed;
+        }
+    };
 
     /**
      * A class associating {@link Font} with its metadata.
@@ -194,7 +219,7 @@
             try {
                 return Font.createFont(Font.TRUETYPE_FONT, f);
             } catch (Exception e) {
-                if (path.endsWith(".otf") && e instanceof FontFormatException) {
+                if (path.endsWith(EXTENSION_OTF) && e instanceof FontFormatException) {
                     // If we aren't able to load an Open Type font, don't log a warning just yet.
                     // We wait for a case where font is being used. Only then we try to log the
                     // warning.
@@ -281,8 +306,74 @@
 
     @LayoutlibDelegate
     /*package*/ static boolean nAddFontFromAsset(long nativeFamily, AssetManager mgr, String path) {
-        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
-                "Typeface.createFromAsset is not supported.", null, null);
+        FontFamily_Delegate ffd = sManager.getDelegate(nativeFamily);
+        ffd.mValid = true;
+        if (mgr == null) {
+            return false;
+        }
+        if (mgr instanceof BridgeAssetManager) {
+            InputStream fontStream = null;
+            try {
+                AssetRepository assetRepository = ((BridgeAssetManager) mgr).getAssetRepository();
+                if (assetRepository == null) {
+                    Bridge.getLog().error(LayoutLog.TAG_MISSING_ASSET, "Asset not found: " + path,
+                            null);
+                    return false;
+                }
+                if (!assetRepository.isSupported()) {
+                    // Don't log any warnings on unsupported IDEs.
+                    return false;
+                }
+                // Check cache
+                FontInfo fontInfo = sCache.get(path);
+                if (fontInfo != null) {
+                    // renew the font's lease.
+                    sCache.put(path, fontInfo);
+                    ffd.addFont(fontInfo);
+                    return true;
+                }
+                fontStream = assetRepository.openAsset(path, AssetManager.ACCESS_STREAMING);
+                if (fontStream == null) {
+                    Bridge.getLog().error(LayoutLog.TAG_MISSING_ASSET, "Asset not found: " + path,
+                            path);
+                    return false;
+                }
+                Font font = Font.createFont(Font.TRUETYPE_FONT, fontStream);
+                fontInfo = new FontInfo();
+                fontInfo.mFont = font;
+                fontInfo.mWeight = font.isBold() ? BOLD_FONT_WEIGHT : DEFAULT_FONT_WEIGHT;
+                fontInfo.mIsItalic = font.isItalic();
+                ffd.addFont(fontInfo);
+                return true;
+            } catch (IOException e) {
+                Bridge.getLog().error(LayoutLog.TAG_MISSING_ASSET, "Unable to load font " + path, e,
+                        path);
+            } catch (FontFormatException e) {
+                if (path.endsWith(EXTENSION_OTF)) {
+                    // otf fonts are not supported on the user's config (JRE version + OS)
+                    Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                            "OpenType fonts are not supported yet: " + path, null, path);
+                } else {
+                    Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+                            "Unable to load font " + path, e, path);
+                }
+            } finally {
+                if (fontStream != null) {
+                    try {
+                        fontStream.close();
+                    } catch (IOException ignored) {
+                    }
+                }
+            }
+            return false;
+        }
+        // This should never happen. AssetManager is a final class (from user's perspective), and
+        // we've replaced every creation of AssetManager with our implementation. We create an
+        // exception and log it, but continue with rest of the rendering, without loading this font.
+        Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+                "You have found a bug in the rendering library. Please file a bug at b.android.com.",
+                new RuntimeException("Asset Manager is not an instance of BridgeAssetManager"),
+                null);
         return false;
     }
 
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 8523f1a..4c30f58 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -17,6 +17,7 @@
 package com.android.layoutlib.bridge.android;
 
 import com.android.annotations.Nullable;
+import com.android.ide.common.rendering.api.AssetRepository;
 import com.android.ide.common.rendering.api.ILayoutPullParser;
 import com.android.ide.common.rendering.api.IProjectCallback;
 import com.android.ide.common.rendering.api.LayoutLog;
@@ -47,6 +48,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.AssetManager;
+import android.content.res.BridgeAssetManager;
 import android.content.res.BridgeResources;
 import android.content.res.BridgeTypedArray;
 import android.content.res.Configuration;
@@ -101,6 +103,7 @@
      * used to populate the mViewKeyMap.
      */
     private final HashMap<Object, Object> mViewKeyHelpMap = new HashMap<Object, Object>();
+    private final BridgeAssetManager mAssets;
     private Resources mSystemResources;
     private final Object mProjectKey;
     private final DisplayMetrics mMetrics;
@@ -140,6 +143,7 @@
      */
     public BridgeContext(Object projectKey, DisplayMetrics metrics,
             RenderResources renderResources,
+            AssetRepository assets,
             IProjectCallback projectCallback,
             Configuration config,
             int targetSdkVersion,
@@ -150,6 +154,8 @@
 
         mRenderResources = renderResources;
         mConfig = config;
+        mAssets = new BridgeAssetManager();
+        mAssets.setAssetRepository(assets);
 
         mApplicationInfo = new ApplicationInfo();
         mApplicationInfo.targetSdkVersion = targetSdkVersion;
@@ -1071,9 +1077,8 @@
     }
 
     @Override
-    public AssetManager getAssets() {
-        // pass
-        return null;
+    public BridgeAssetManager getAssets() {
+        return mAssets;
     }
 
     @Override
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
index 60f5331..127cb72 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
@@ -35,7 +35,6 @@
 
 import android.content.res.Configuration;
 import android.os.HandlerThread_Delegate;
-import android.os.Looper;
 import android.util.DisplayMetrics;
 import android.view.ViewConfiguration_Accessor;
 import android.view.inputmethod.InputMethodManager;
@@ -71,7 +70,7 @@
     /**
      * Creates a renderAction.
      * <p>
-     * This <b>must</b> be followed by a call to {@link RenderAction#init()}, which act as a
+     * This <b>must</b> be followed by a call to {@link RenderAction#init(long)}, which act as a
      * call to {@link RenderAction#acquire(long)}
      *
      * @param params the RenderParams. This must be a copy that the action can keep
@@ -121,8 +120,8 @@
 
         // build the context
         mContext = new BridgeContext(mParams.getProjectKey(), metrics, resources,
-                mParams.getProjectCallback(), getConfiguration(), mParams.getTargetSdkVersion(),
-                mParams.isRtlSupported());
+                mParams.getAssets(), mParams.getProjectCallback(), getConfiguration(),
+                mParams.getTargetSdkVersion(), mParams.isRtlSupported());
 
         setUp();
 
@@ -139,7 +138,7 @@
      * The preparation can fail if another rendering took too long and the timeout was elapsed.
      *
      * More than one call to this from the same thread will have no effect and will return
-     * {@link Result#SUCCESS}.
+     * {@link Result.Status#SUCCESS}.
      *
      * After scene actions have taken place, only one call to {@link #release()} must be
      * done.
@@ -173,7 +172,7 @@
      * Acquire the lock so that the scene can be acted upon.
      * <p>
      * This returns null if the lock was just acquired, otherwise it returns
-     * {@link Result#SUCCESS} if the lock already belonged to that thread, or another
+     * {@link Result.Status#SUCCESS} if the lock already belonged to that thread, or another
      * instance (see {@link Result#getStatus()}) if an error occurred.
      *
      * @param timeout the time to wait if another rendering is happening.
@@ -184,11 +183,11 @@
      */
     private Result acquireLock(long timeout) {
         ReentrantLock lock = Bridge.getLock();
-        if (lock.isHeldByCurrentThread() == false) {
+        if (!lock.isHeldByCurrentThread()) {
             try {
                 boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
 
-                if (acquired == false) {
+                if (!acquired) {
                     return ERROR_TIMEOUT.createResult();
                 }
             } catch (InterruptedException e) {
@@ -308,7 +307,7 @@
      */
     protected void checkLock() {
         ReentrantLock lock = Bridge.getLock();
-        if (lock.isHeldByCurrentThread() == false) {
+        if (!lock.isHeldByCurrentThread()) {
             throw new IllegalStateException("scene must be acquired first. see #acquire(long)");
         }
         if (sCurrentContext != mContext) {
@@ -347,6 +346,7 @@
         config.screenWidthDp = hardwareConfig.getScreenWidth() / density.getDpiValue();
         config.screenHeightDp = hardwareConfig.getScreenHeight() / density.getDpiValue();
         if (config.screenHeightDp < config.screenWidthDp) {
+            //noinspection SuspiciousNameCombination
             config.smallestScreenWidthDp = config.screenHeightDp;
         } else {
             config.smallestScreenWidthDp = config.screenWidthDp;
@@ -367,6 +367,7 @@
                 config.orientation = Configuration.ORIENTATION_LANDSCAPE;
                 break;
             case SQUARE:
+                //noinspection deprecation
                 config.orientation = Configuration.ORIENTATION_SQUARE;
                 break;
             }