Merge "Zygote: Add support for explicit preloading of resources."
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 5bbbcc2..4480b41 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -1886,7 +1886,7 @@
                 ContentProvider.getUriWithoutUserId(uri),
                 notifyForDescendants,
                 observer,
-                ContentProvider.getUserIdFromUri(uri, mContext.getUserId()));
+                ContentProvider.getUserIdFromUri(uri, UserHandle.myUserId()));
     }
 
     /** @hide - designated user version */
@@ -1956,7 +1956,7 @@
                 ContentProvider.getUriWithoutUserId(uri),
                 observer,
                 syncToNetwork,
-                ContentProvider.getUserIdFromUri(uri, mContext.getUserId()));
+                ContentProvider.getUserIdFromUri(uri, UserHandle.myUserId()));
     }
 
     /**
@@ -1982,7 +1982,7 @@
                 ContentProvider.getUriWithoutUserId(uri),
                 observer,
                 flags,
-                ContentProvider.getUserIdFromUri(uri, mContext.getUserId()));
+                ContentProvider.getUserIdFromUri(uri, UserHandle.myUserId()));
     }
 
     /**
diff --git a/core/java/android/text/method/LinkMovementMethod.java b/core/java/android/text/method/LinkMovementMethod.java
index 24c119f..31ed549 100644
--- a/core/java/android/text/method/LinkMovementMethod.java
+++ b/core/java/android/text/method/LinkMovementMethod.java
@@ -29,9 +29,6 @@
 /**
  * A movement method that traverses links in the text buffer and scrolls if necessary.
  * Supports clicking on links with DPad Center or Enter.
- *
- * <p>Note: Starting from Android 8.0 (API level 25) this class no longer handles the touch
- * clicks.
  */
 public class LinkMovementMethod extends ScrollingMovementMethod {
     private static final int CLICK = 1;
@@ -198,7 +195,7 @@
                                 MotionEvent event) {
         int action = event.getAction();
 
-        if (action == MotionEvent.ACTION_DOWN) {
+        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
             int x = (int) event.getX();
             int y = (int) event.getY();
 
@@ -215,9 +212,13 @@
             ClickableSpan[] links = buffer.getSpans(off, off, ClickableSpan.class);
 
             if (links.length != 0) {
-                Selection.setSelection(buffer,
+                if (action == MotionEvent.ACTION_UP) {
+                    links[0].onClick(widget);
+                } else if (action == MotionEvent.ACTION_DOWN) {
+                    Selection.setSelection(buffer,
                         buffer.getSpanStart(links[0]),
                         buffer.getSpanEnd(links[0]));
+                }
                 return true;
             } else {
                 Selection.removeSelection(buffer);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 17cd446..5572cbb 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -121,7 +121,6 @@
 import android.view.Choreographer;
 import android.view.ContextMenu;
 import android.view.DragEvent;
-import android.view.GestureDetector;
 import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyCharacterMap;
@@ -682,8 +681,6 @@
      */
     private Editor mEditor;
 
-    private GestureDetector mClickableSpanOnClickGestureDetector;
-
     private static final int DEVICE_PROVISIONED_UNKNOWN = 0;
     private static final int DEVICE_PROVISIONED_NO = 1;
     private static final int DEVICE_PROVISIONED_YES = 2;
@@ -9319,24 +9316,21 @@
                 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
             }
 
-            // Lazily create the clickable span gesture detector only if it looks like it
-            // might be useful.
-            if (action == MotionEvent.ACTION_DOWN && mClickableSpanOnClickGestureDetector == null
-                    && shouldUseClickableSpanOnClickGestureDetector()) {
-                ClickableSpan[] links = ((Spannable) mText).getSpans(
-                        getSelectionStart(), getSelectionEnd(),
-                        ClickableSpan.class);
+            final boolean textIsSelectable = isTextSelectable();
+            if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
+                // The LinkMovementMethod which should handle taps on links has not been installed
+                // on non editable text that support text selection.
+                // We reproduce its behavior here to open links for these.
+                ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
+                    getSelectionEnd(), ClickableSpan.class);
+
                 if (links.length > 0) {
-                    mClickableSpanOnClickGestureDetector =
-                            createClickableSpanOnClickGestureDetector();
+                    links[0].onClick(this);
+                    handled = true;
                 }
             }
 
-            if (mClickableSpanOnClickGestureDetector != null) {
-                handled |= mClickableSpanOnClickGestureDetector.onTouchEvent(event);
-            }
-
-            if (touchIsFinished && (isTextEditable() || isTextSelectable())) {
+            if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
                 // Show the IME, except when selecting in read-only text.
                 final InputMethodManager imm = InputMethodManager.peekInstance();
                 viewClicked(imm);
@@ -9754,31 +9748,6 @@
         mEditor.onLocaleChanged();
     }
 
-    private GestureDetector createClickableSpanOnClickGestureDetector() {
-        return new GestureDetector(mContext,
-                new GestureDetector.SimpleOnGestureListener() {
-                    @Override
-                    public boolean onSingleTapUp(MotionEvent e) {
-                        if (shouldUseClickableSpanOnClickGestureDetector()) {
-                            ClickableSpan[] links = ((Spannable) mText).getSpans(
-                                    getSelectionStart(), getSelectionEnd(),
-                                    ClickableSpan.class);
-                            if (links.length > 0) {
-                                links[0].onClick(TextView.this);
-                                return true;
-                            }
-                        }
-                        return false;
-                    }
-                });
-    }
-
-    private boolean shouldUseClickableSpanOnClickGestureDetector() {
-        return mLinksClickable && (mMovement != null) &&
-                (mMovement instanceof LinkMovementMethod
-                        || (mAutoLinkMask != 0 && isTextSelectable()));
-    }
-
     /**
      * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
      * Made available to achieve a consistent behavior.
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 7b8c229..91ce7a4 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1357,9 +1357,6 @@
             </intent-filter>
         </service>
 
-        <service android:name="android.content.CrossUserContentService"
-                android:exported="true" />
-
     </application>
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/core/tests/coretests/src/android/app/activity/LocalProvider.java b/core/tests/coretests/src/android/app/activity/LocalProvider.java
index b7a63ce..d6a10c2 100644
--- a/core/tests/coretests/src/android/app/activity/LocalProvider.java
+++ b/core/tests/coretests/src/android/app/activity/LocalProvider.java
@@ -29,19 +29,6 @@
 public class LocalProvider extends ContentProvider {
     private static final String TAG = "LocalProvider";
 
-    private static final String AUTHORITY = "com.android.frameworks.coretests.LocalProvider";
-    private static final String TABLE_DATA_NAME = "data";
-    public static final Uri TABLE_DATA_URI =
-            Uri.parse("content://" + AUTHORITY + "/" + TABLE_DATA_NAME);
-
-    public static final String COLUMN_TEXT_NAME = "text";
-    public static final String COLUMN_INTEGER_NAME = "integer";
-
-    public static final String TEXT1 = "first data";
-    public static final String TEXT2 = "second data";
-    public static final int INTEGER1 = 100;
-    public static final int INTEGER2 = 101;
-
     private SQLiteOpenHelper mOpenHelper;
 
     private static final int DATA = 1;
@@ -64,20 +51,13 @@
 
         @Override
         public void onCreate(SQLiteDatabase db) {
-            db.execSQL("CREATE TABLE " + TABLE_DATA_NAME + " (" +
+            db.execSQL("CREATE TABLE data (" +
                        "_id INTEGER PRIMARY KEY," +
-                       COLUMN_TEXT_NAME + " TEXT, " +
-                       COLUMN_INTEGER_NAME + " INTEGER);");
+                       "text TEXT, " +
+                       "integer INTEGER);");
 
             // insert alarms
-            db.execSQL(getInsertCommand(TEXT1, INTEGER1));
-            db.execSQL(getInsertCommand(TEXT2, INTEGER2));
-        }
-
-        private String getInsertCommand(String textValue, int integerValue) {
-            return "INSERT INTO " + TABLE_DATA_NAME
-                    + " (" + COLUMN_TEXT_NAME + ", " + COLUMN_INTEGER_NAME + ") "
-                    + "VALUES ('" + textValue + "', " + integerValue + ");";
+            db.execSQL("INSERT INTO data (text, integer) VALUES ('first data', 100);");
         }
 
         @Override
@@ -94,10 +74,6 @@
     public LocalProvider() {
     }
 
-    static public Uri getTableDataUriForRow(int rowId) {
-        return Uri.parse("content://" + AUTHORITY + "/" + TABLE_DATA_NAME + "/" + rowId);
-    }
-
     @Override
     public boolean onCreate() {
         mOpenHelper = new DatabaseHelper(getContext());
diff --git a/core/tests/coretests/src/android/content/CrossUserContentResolverTest.java b/core/tests/coretests/src/android/content/CrossUserContentResolverTest.java
deleted file mode 100644
index 027ba8e..0000000
--- a/core/tests/coretests/src/android/content/CrossUserContentResolverTest.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content;
-
-
-import static org.junit.Assert.fail;
-
-import android.app.ActivityManager;
-import android.app.activity.LocalProvider;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.UserInfo;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.IBinder;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class CrossUserContentResolverTest {
-    private final static int TIMEOUT_SERVICE_CONNECTION_SEC = 4;
-    private final static int TIMEOUT_CONTENT_CHANGE_SEC = 4;
-
-    private Context mContext;
-    private UserManager mUm;
-    private int mSecondaryUserId = -1;
-    private CrossUserContentServiceConnection mServiceConnection;
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getContext();
-        mUm = UserManager.get(mContext);
-        final UserInfo userInfo = mUm.createUser("Test user", 0);
-        mSecondaryUserId = userInfo.id;
-        final PackageManager pm = mContext.getPackageManager();
-        pm.installExistingPackageAsUser(mContext.getPackageName(), mSecondaryUserId);
-        ActivityManager.getService().startUserInBackground(mSecondaryUserId);
-
-        final CountDownLatch connectionLatch = new CountDownLatch(1);
-        mServiceConnection = new CrossUserContentServiceConnection(connectionLatch);
-        mContext.bindServiceAsUser(
-                new Intent(mContext, CrossUserContentService.class),
-                mServiceConnection,
-                Context.BIND_AUTO_CREATE,
-                UserHandle.of(mSecondaryUserId));
-        if (!connectionLatch.await(TIMEOUT_SERVICE_CONNECTION_SEC, TimeUnit.SECONDS)) {
-            fail("Timed out waiting for service connection to establish");
-        }
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (mSecondaryUserId != -1) {
-            mUm.removeUser(mSecondaryUserId);
-        }
-        if (mServiceConnection != null) {
-            mContext.unbindService(mServiceConnection);
-        }
-    }
-
-    /**
-     * Register an observer for an URI in the secondary user and verify that it receives
-     * onChange callback when data at the URI changes.
-     */
-    @Test
-    public void testRegisterContentObserver() throws Exception {
-        Context secondaryUserContext = null;
-        String packageName = null;
-        try {
-            packageName = InstrumentationRegistry.getContext().getPackageName();
-            secondaryUserContext =
-                    InstrumentationRegistry.getContext().createPackageContextAsUser(
-                            packageName, 0 /* flags */, UserHandle.of(mSecondaryUserId));
-        } catch (NameNotFoundException e) {
-            fail("Couldn't find package " + packageName + " in u" + mSecondaryUserId);
-        }
-
-        final CountDownLatch updateLatch = new CountDownLatch(1);
-        final Uri uriToUpdate = LocalProvider.getTableDataUriForRow(2);
-        final TestContentObserver observer = new TestContentObserver(updateLatch,
-                uriToUpdate, mSecondaryUserId);
-        secondaryUserContext.getContentResolver().registerContentObserver(
-                LocalProvider.TABLE_DATA_URI, true, observer, mSecondaryUserId);
-        mServiceConnection.getService().updateContent(uriToUpdate, "New Text", 42);
-        if (!updateLatch.await(TIMEOUT_CONTENT_CHANGE_SEC, TimeUnit.SECONDS)) {
-            fail("Timed out waiting for the content change callback");
-        }
-    }
-
-    /**
-     * Register an observer for an URI in the current user and verify that secondary user can
-     * notify changes for this URI.
-     */
-    @Test
-    public void testNotifyChange() throws Exception {
-        final CountDownLatch notifyLatch = new CountDownLatch(1);
-        final Uri notifyUri = LocalProvider.TABLE_DATA_URI;
-        final TestContentObserver observer = new TestContentObserver(notifyLatch,
-                notifyUri, UserHandle.myUserId());
-        mContext.getContentResolver().registerContentObserver(notifyUri, true, observer);
-        mServiceConnection.getService().notifyForUriAsUser(notifyUri, UserHandle.myUserId());
-        if (!notifyLatch.await(TIMEOUT_CONTENT_CHANGE_SEC, TimeUnit.SECONDS)) {
-            fail("Timed out waiting for the notify callback");
-        }
-    }
-
-    private static final class CrossUserContentServiceConnection implements ServiceConnection {
-        private ICrossUserContentService mService;
-        private final CountDownLatch mLatch;
-
-        public CrossUserContentServiceConnection(CountDownLatch latch) {
-            mLatch = latch;
-        }
-
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            mService = ICrossUserContentService.Stub.asInterface(service);
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-        }
-
-        public ICrossUserContentService getService() {
-            return mService;
-        }
-    }
-
-    private static final class TestContentObserver extends ContentObserver {
-        private final CountDownLatch mLatch;
-        private final Uri mExpectedUri;
-        private final int mExpectedUserId;
-
-        public TestContentObserver(CountDownLatch latch, Uri exptectedUri, int expectedUserId) {
-            super(null);
-            mLatch = latch;
-            mExpectedUri = exptectedUri;
-            mExpectedUserId = expectedUserId;
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri, int userId) {
-            if (mExpectedUri.equals(uri) && mExpectedUserId == userId) {
-                mLatch.countDown();
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/content/CrossUserContentService.java b/core/tests/coretests/src/android/content/CrossUserContentService.java
deleted file mode 100644
index 9cbe549..0000000
--- a/core/tests/coretests/src/android/content/CrossUserContentService.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.content;
-
-import android.app.Service;
-import android.app.activity.LocalProvider;
-import android.net.Uri;
-import android.os.IBinder;
-
-public class CrossUserContentService extends Service {
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        return mLocalService.asBinder();
-    }
-
-    private ICrossUserContentService mLocalService = new ICrossUserContentService.Stub() {
-        @Override
-        public void updateContent(Uri uri, String key, int value) {
-            final ContentValues values = new ContentValues();
-            values.put(LocalProvider.COLUMN_TEXT_NAME, key);
-            values.put(LocalProvider.COLUMN_INTEGER_NAME, value);
-            getContentResolver().update(uri, values, null, null);
-        }
-
-        @Override
-        public void notifyForUriAsUser(Uri uri, int userId) {
-            getContentResolver().notifyChange(uri, null, false, userId);
-        }
-    };
-}
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
index 35cf903..9bc8e18 100644
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
@@ -31,6 +31,7 @@
 import android.annotation.Nullable;
 import android.content.res.Resources.NotFoundException;
 import android.content.res.Resources.Theme;
+import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
@@ -697,6 +698,22 @@
 
 
     /**
+     * Retrieve the Typeface for the attribute at <var>index</var>.
+     * @param index Index of attribute to retrieve.
+     *
+     * @return Typeface for the attribute, or null if not defined.
+     */
+    @Override
+    public Typeface getFont(int index) {
+        if (!hasValue(index)) {
+            return null;
+        }
+
+        ResourceValue value = mResourceData[index];
+        return ResourceHelper.getFont(value, mContext, mTheme);
+    }
+
+    /**
      * Retrieve the CharSequence[] for the attribute at <var>index</var>.
      * This gets the resource ID of the selected attribute, and uses
      * {@link Resources#getTextArray Resources.getTextArray} of the owning
diff --git a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
index e0f8e1c..d71cc6f 100644
--- a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
@@ -43,6 +43,7 @@
 import android.annotation.Nullable;
 import android.content.res.Resources.NotFoundException;
 import android.content.res.Resources.Theme;
+import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.icu.text.PluralRules;
 import android.util.AttributeSet;
@@ -779,6 +780,35 @@
     }
 
     @LayoutlibDelegate
+    static Typeface getFont(Resources resources, int id) throws
+            NotFoundException {
+        Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+        if (value != null) {
+            return ResourceHelper.getFont(value.getSecond(), resources.mContext, null);
+        }
+
+        throwException(resources, id);
+
+        // this is not used since the method above always throws
+        return null;
+    }
+
+    @LayoutlibDelegate
+    static Typeface getFont(Resources resources, TypedValue outValue, int id) throws
+            NotFoundException {
+        Resources_Delegate.getValue(resources, id, outValue, true);
+        if (outValue.string != null) {
+            return ResourceHelper.getFont(outValue.string.toString(), resources.mContext, null,
+                    mPlatformResourceFlag[0]);
+        }
+
+        throwException(resources, id);
+
+        // this is not used since the method above always throws
+        return null;
+    }
+
+    @LayoutlibDelegate
     static void getValue(Resources resources, int id, TypedValue outValue, boolean resolveRefs)
             throws NotFoundException {
         Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
index a43e545..fb24c01 100644
--- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
@@ -344,7 +344,9 @@
                     ffd.addFont(fontInfo);
                     return true;
                 }
-                fontStream = assetRepository.openAsset(path, AssetManager.ACCESS_STREAMING);
+                fontStream = isAsset ?
+                        assetRepository.openAsset(path, AssetManager.ACCESS_STREAMING) :
+                        assetRepository.openNonAsset(cookie, path, AssetManager.ACCESS_STREAMING);
                 if (fontStream == null) {
                     Bridge.getLog().error(LayoutLog.TAG_MISSING_ASSET, "Asset not found: " + path,
                             path);
diff --git a/core/tests/coretests/src/android/content/ICrossUserContentService.aidl b/tools/layoutlib/bridge/src/android/graphics/Typeface_Accessor.java
similarity index 63%
rename from core/tests/coretests/src/android/content/ICrossUserContentService.aidl
rename to tools/layoutlib/bridge/src/android/graphics/Typeface_Accessor.java
index 2c5cde4..ce669cb 100644
--- a/core/tests/coretests/src/android/content/ICrossUserContentService.aidl
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Accessor.java
@@ -11,14 +11,18 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
-package android.content;
+package android.graphics;
 
-import android.net.Uri;
+import android.annotation.NonNull;
 
-interface ICrossUserContentService {
-    void updateContent(in Uri uri, String key, int value);
-    void notifyForUriAsUser(in Uri uri, int userId);
-}
\ No newline at end of file
+/**
+ * Class allowing access to package-protected methods/fields.
+ */
+public class Typeface_Accessor {
+    public static boolean isSystemFont(@NonNull String fontName) {
+        return Typeface.sSystemFontMap.containsKey(fontName);
+    }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
index c197e40..b3a2d3e 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
@@ -38,16 +38,20 @@
 import android.content.res.ColorStateList;
 import android.content.res.ComplexColor;
 import android.content.res.ComplexColor_Accessor;
+import android.content.res.FontResourcesParser;
 import android.content.res.GradientColor;
 import android.content.res.Resources.Theme;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap_Delegate;
 import android.graphics.NinePatch_Delegate;
 import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.Typeface_Accessor;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.NinePatchDrawable;
+import android.text.FontConfig;
 import android.util.TypedValue;
 
 import java.io.File;
@@ -367,6 +371,89 @@
         return null;
     }
 
+    /**
+     * Returns a {@link Typeface} given a font name. The font name, can be a system font family
+     * (like sans-serif) or a full path if the font is to be loaded from resources.
+     */
+    public static Typeface getFont(String fontName, BridgeContext context, Theme theme, boolean
+            isFramework) {
+        if (fontName == null) {
+            return null;
+        }
+
+        if (Typeface_Accessor.isSystemFont(fontName)) {
+            // Shortcut for the case where we are asking for a system font name. Those are not
+            // loaded using external resources.
+            return null;
+        }
+
+        // Check if this is an asset that we've already loaded dynamically
+        Typeface typeface = Typeface.findFromCache(context.getAssets(), fontName);
+        if (typeface != null) {
+            return typeface;
+        }
+
+        String lowerCaseValue = fontName.toLowerCase();
+        if (lowerCaseValue.endsWith(".xml")) {
+            // create a block parser for the file
+            Boolean psiParserSupport = context.getLayoutlibCallback().getFlag(
+                    RenderParamsFlags.FLAG_KEY_XML_FILE_PARSER_SUPPORT);
+            XmlPullParser parser = null;
+            if (psiParserSupport != null && psiParserSupport) {
+                parser = context.getLayoutlibCallback().getXmlFileParser(fontName);
+            }
+            else {
+                File f = new File(fontName);
+                if (f.isFile()) {
+                    try {
+                        parser = ParserFactory.create(f);
+                    } catch (XmlPullParserException | FileNotFoundException e) {
+                        // this is an error and not warning since the file existence is checked before
+                        // attempting to parse it.
+                        Bridge.getLog().error(null, "Failed to parse file " + fontName,
+                                e, null /*data*/);
+                    }
+                }
+            }
+
+            if (parser != null) {
+                BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
+                        parser, context, isFramework);
+                try {
+                    FontConfig config = FontResourcesParser.parse(blockParser, context
+                            .getResources());
+                    typeface = Typeface.createFromResources(config, context.getAssets(),
+                            fontName);
+                } catch (XmlPullParserException | IOException e) {
+                    Bridge.getLog().error(null, "Failed to parse file " + fontName,
+                            e, null /*data*/);
+                } finally {
+                    blockParser.ensurePopped();
+                }
+            } else {
+                Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+                        String.format("File %s does not exist (or is not a file)", fontName),
+                        null /*data*/);
+            }
+        } else {
+            typeface = Typeface.createFromResources(context.getAssets(), fontName, 0);
+        }
+
+        return typeface;
+    }
+
+    /**
+     * Returns a {@link Typeface} given a font name. The font name, can be a system font family
+     * (like sans-serif) or a full path if the font is to be loaded from resources.
+     */
+    public static Typeface getFont(ResourceValue value, BridgeContext context, Theme theme) {
+        if (value == null) {
+            return null;
+        }
+
+        return getFont(value.getValue(), context, theme, value.isFramework());
+    }
+
     private static Drawable getNinePatchDrawable(InputStream inputStream, Density density,
             boolean isFramework, String cacheKey, BridgeContext context) throws IOException {
         // see if we still have both the chunk and the bitmap in the caches
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png
new file mode 100644
index 0000000..b2baa98
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/font/testfamily.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/font/testfamily.xml
new file mode 100644
index 0000000..b1e9206
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/font/testfamily.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:android="http://schemas.android.com/apk/res/android">
+    <font android:fontStyle="normal" android:fontWeight="400" android:font="@font/testfont" />
+    <font android:fontStyle="italic" android:fontWeight="400" android:font="@font/testfont2" />
+</font-family>
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/font/testfont.ttf b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/font/testfont.ttf
new file mode 100644
index 0000000..2852302
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/font/testfont.ttf
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/font/testfont2.ttf b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/font/testfont2.ttf
new file mode 100644
index 0000000..b7bf5b4
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/font/testfont2.ttf
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/fonts_test.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/fonts_test.xml
new file mode 100644
index 0000000..c63b211
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/fonts_test.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical" android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="CONDENSED"
+        android:textSize="50sp"
+        android:fontFamily="sans-serif-condensed"
+        />
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="CONDENSED ITALIC"
+        android:textSize="30sp"
+        android:fontFamily="sans-serif-condensed"
+        android:textStyle="italic"
+        />
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="MONOSPACE"
+        android:textSize="50sp"
+        android:fontFamily="monospace"/>
+
+    <Space
+        android:layout_width="wrap_content"
+        android:layout_height="30dp" />
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Custom"
+        android:textSize="20sp"
+        android:fontFamily="@font/testfont"/>
+
+    <Space
+        android:layout_width="wrap_content"
+        android:layout_height="30dp" />
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Custom family"
+        android:textSize="20sp"
+        android:fontFamily="@font/testfamily"/>
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Custom family"
+        android:textSize="20sp"
+        android:fontFamily="@font/testfamily"
+        android:textStyle="italic"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
index 3e5f9e0..67b42a7 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
@@ -35,6 +35,7 @@
 import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser;
 import com.android.layoutlib.bridge.intensive.util.ImageUtils;
 import com.android.layoutlib.bridge.intensive.util.ModuleClassLoader;
+import com.android.layoutlib.bridge.intensive.util.TestAssetRepository;
 import com.android.layoutlib.bridge.intensive.util.TestUtils;
 import com.android.tools.layoutlib.java.System_Delegate;
 import com.android.utils.ILogger;
@@ -537,6 +538,7 @@
                         configGenerator.getHardwareConfig(), resourceResolver, layoutLibCallback, 0,
                         targetSdk, getLayoutLog());
         sessionParams.setFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE, true);
+        sessionParams.setAssetRepository(new TestAssetRepository());
         return sessionParams;
     }
 }
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java
index 73e51ec..913519c 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java
@@ -384,4 +384,10 @@
                 strings);
         assertTrue(sRenderMessages.isEmpty());
     }
+
+    @Test
+    public void testFonts() throws ClassNotFoundException {
+        // TODO: styles seem to be broken in TextView
+        renderAndVerify("fonts_test.xml", "font_test.png");
+    }
 }
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/TestAssetRepository.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/TestAssetRepository.java
new file mode 100644
index 0000000..0856ac9
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/TestAssetRepository.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.intensive.util;
+
+import com.android.ide.common.rendering.api.AssetRepository;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * {@link AssetRepository} used for render tests.
+ */
+public class TestAssetRepository extends AssetRepository {
+    private static InputStream open(String path) throws FileNotFoundException {
+        File asset = new File(path);
+        if (asset.isFile()) {
+            return new FileInputStream(asset);
+        }
+
+        return null;
+    }
+
+    @Override
+    public boolean isSupported() {
+        return true;
+    }
+
+    @Override
+    public InputStream openAsset(String path, int mode) throws IOException {
+        return open(path);
+    }
+
+    @Override
+    public InputStream openNonAsset(int cookie, String path, int mode) throws IOException {
+        return open(path);
+    }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 94302d3..a8582c6 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -156,6 +156,7 @@
         "android.content.res.Resources#getDimensionPixelOffset",
         "android.content.res.Resources#getDimensionPixelSize",
         "android.content.res.Resources#getDrawable",
+        "android.content.res.Resources#getFont",
         "android.content.res.Resources#getIntArray",
         "android.content.res.Resources#getInteger",
         "android.content.res.Resources#getLayout",