resolve merge conflicts of c598b19fda to stage-aosp-master am: e246401220
am: 4cbba085e2  -s ours

Change-Id: Ie7cee929594f27e3e934e0924c8f66bd15723918
diff --git a/.idea/modules.xml b/.idea/modules.xml
index 4654a5c..8d61f6c 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -6,6 +6,10 @@
       <module fileurl="file://$PROJECT_DIR$/common/common.iml" filepath="$PROJECT_DIR$/common/common.iml" />
       <module fileurl="file://$PROJECT_DIR$/create/create.iml" filepath="$PROJECT_DIR$/create/create.iml" />
       <module fileurl="file://$PROJECT_DIR$/legacy/legacy.iml" filepath="$PROJECT_DIR$/legacy/legacy.iml" />
+      <module fileurl="file://$PROJECT_DIR$/remote/client/remote client.iml" filepath="$PROJECT_DIR$/remote/client/remote client.iml" group="remote" />
+      <module fileurl="file://$PROJECT_DIR$/remote/common/remote common.iml" filepath="$PROJECT_DIR$/remote/common/remote common.iml" group="remote" />
+      <module fileurl="file://$PROJECT_DIR$/remote/server/remote server.iml" filepath="$PROJECT_DIR$/remote/server/remote server.iml" group="remote" />
+      <module fileurl="file://$PROJECT_DIR$/remote/tests/remote tests.iml" filepath="$PROJECT_DIR$/remote/tests/remote tests.iml" group="remote" />
       <module fileurl="file://$PROJECT_DIR$/studio-custom-widgets/studio-android-widgets.iml" filepath="$PROJECT_DIR$/studio-custom-widgets/studio-android-widgets.iml" />
     </modules>
   </component>
diff --git a/Android.mk b/Android.mk
index a9f09f4..e9aa138 100644
--- a/Android.mk
+++ b/Android.mk
@@ -13,6 +13,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+ifeq (,$(PRODUCT_MINIMIZE_JAVA_DEBUG_INFO))
+
 LOCAL_PATH := $(my-dir)
 include $(CLEAR_VARS)
 
@@ -88,3 +90,5 @@
 # Include the subdir makefiles.
 #
 include $(call all-makefiles-under,$(LOCAL_PATH))
+
+endif
diff --git a/bridge/Android.mk b/bridge/Android.mk
index 333e6bb..89a34a3 100644
--- a/bridge/Android.mk
+++ b/bridge/Android.mk
@@ -32,6 +32,8 @@
 
 include $(BUILD_HOST_JAVA_LIBRARY)
 
+$(call dist-for-goals, layoutlib, $(LOCAL_INSTALLED_MODULE))
+
 # Build all sub-directories
 include $(call all-makefiles-under,$(LOCAL_PATH))
 
diff --git a/bridge/src/android/content/res/AssetManager_Delegate.java b/bridge/src/android/content/res/AssetManager_Delegate.java
index b4d5288..3b47ea7 100644
--- a/bridge/src/android/content/res/AssetManager_Delegate.java
+++ b/bridge/src/android/content/res/AssetManager_Delegate.java
@@ -20,6 +20,9 @@
 
 import android.util.SparseArray;
 
+import java.io.IOException;
+import java.io.InputStream;
+
 /**
  * Delegate used to provide implementation of a select few native methods of {@link AssetManager}
  * <p/>
@@ -30,6 +33,20 @@
 public class AssetManager_Delegate {
 
     @LayoutlibDelegate
+    public static InputStream open(AssetManager mgr, String fileName) throws IOException {
+        return mgr.open_Original(fileName);
+    }
+
+    @LayoutlibDelegate
+    public static InputStream open(AssetManager mgr, String fileName, int accessMode)
+            throws IOException {
+        if (!(mgr instanceof BridgeAssetManager)) {
+            return mgr.open_Original(fileName, accessMode);
+        }
+        return ((BridgeAssetManager) mgr).getAssetRepository().openAsset(fileName, accessMode);
+    }
+
+    @LayoutlibDelegate
     /*package*/ static long newTheme(AssetManager manager) {
         return Resources_Theme_Delegate.getDelegateManager()
                 .addNewDelegate(new Resources_Theme_Delegate());
diff --git a/bridge/src/android/content/res/Resources_Delegate.java b/bridge/src/android/content/res/Resources_Delegate.java
index c1e9cd3..a32d528 100644
--- a/bridge/src/android/content/res/Resources_Delegate.java
+++ b/bridge/src/android/content/res/Resources_Delegate.java
@@ -651,6 +651,26 @@
     }
 
     @LayoutlibDelegate
+    static float getFloat(Resources resources, int id) {
+        Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+
+        if (value != null) {
+            ResourceValue resValue = value.getSecond();
+
+            if (resValue != null) {
+                String v = resValue.getValue();
+                if (v != null) {
+                    try {
+                        return Float.parseFloat(v);
+                    } catch (NumberFormatException ignore) {
+                    }
+                }
+            }
+        }
+        return 0;
+    }
+
+    @LayoutlibDelegate
     static boolean getBoolean(Resources resources, int id) throws NotFoundException {
         Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
 
@@ -867,30 +887,19 @@
 
     @LayoutlibDelegate
     static XmlResourceParser getXml(Resources resources, int id) throws NotFoundException {
-        Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+        Pair<String, ResourceValue> v = getResourceValue(resources, id, mPlatformResourceFlag);
 
-        if (value != null) {
-            String v = value.getSecond().getValue();
+        if (v != null) {
+            ResourceValue value = v.getSecond();
 
-            if (v != null) {
-                // check this is a file
-                File f = new File(v);
-                if (f.isFile()) {
-                    try {
-                        XmlPullParser parser = ParserFactory.create(f);
-
-                        return new BridgeXmlBlockParser(parser, getContext(resources),
-                                mPlatformResourceFlag[0]);
-                    } catch (XmlPullParserException e) {
-                        NotFoundException newE = new NotFoundException();
-                        newE.initCause(e);
-                        throw newE;
-                    } catch (FileNotFoundException e) {
-                        NotFoundException newE = new NotFoundException();
-                        newE.initCause(e);
-                        throw newE;
-                    }
-                }
+            try {
+                return ResourceHelper.getXmlBlockParser(getContext(resources), value);
+            } catch (XmlPullParserException e) {
+                Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+                        "Failed to configure parser for " + value.getValue(), e, null /*data*/);
+                // we'll return null below.
+            } catch (FileNotFoundException e) {
+                // this shouldn't happen since we check above.
             }
         }
 
diff --git a/bridge/src/android/content/res/Resources_Theme_Delegate.java b/bridge/src/android/content/res/Resources_Theme_Delegate.java
index f1e8fc2..8aa9216 100644
--- a/bridge/src/android/content/res/Resources_Theme_Delegate.java
+++ b/bridge/src/android/content/res/Resources_Theme_Delegate.java
@@ -56,7 +56,8 @@
             Resources thisResources, Theme thisTheme,
             int[] attrs) {
         boolean changed = setupResources(thisTheme);
-        BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(attrs);
+        BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().internalObtainStyledAttributes(
+                0, attrs);
         ta.setTheme(thisTheme);
         restoreResources(changed);
         return ta;
@@ -68,8 +69,8 @@
             int resid, int[] attrs)
             throws NotFoundException {
         boolean changed = setupResources(thisTheme);
-        BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(resid,
-                attrs);
+        BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().internalObtainStyledAttributes(
+                resid, attrs);
         ta.setTheme(thisTheme);
         restoreResources(changed);
         return ta;
@@ -80,7 +81,7 @@
             Resources thisResources, Theme thisTheme,
             AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) {
         boolean changed = setupResources(thisTheme);
-        BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(set,
+        BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().internalObtainStyledAttributes(set,
                 attrs, defStyleAttr, defStyleRes);
         ta.setTheme(thisTheme);
         restoreResources(changed);
diff --git a/bridge/src/android/graphics/BaseCanvas_Delegate.java b/bridge/src/android/graphics/BaseCanvas_Delegate.java
index f1c63e6..89fccc7 100644
--- a/bridge/src/android/graphics/BaseCanvas_Delegate.java
+++ b/bridge/src/android/graphics/BaseCanvas_Delegate.java
@@ -477,40 +477,38 @@
 
     @LayoutlibDelegate
     /*package*/ static void nDrawText(long nativeCanvas, char[] text, int index, int count,
-            float startX, float startY, int flags, long paint, long typeface) {
+            float startX, float startY, int flags, long paint) {
         drawText(nativeCanvas, text, index, count, startX, startY, flags,
-                paint, typeface);
+                paint);
     }
 
     @LayoutlibDelegate
     /*package*/ static void nDrawText(long nativeCanvas, String text,
-            int start, int end, float x, float y, final int flags, long paint,
-            long typeface) {
+            int start, int end, float x, float y, final int flags, long paint) {
         int count = end - start;
         char[] buffer = TemporaryBuffer.obtain(count);
         TextUtils.getChars(text, start, end, buffer, 0);
 
-        nDrawText(nativeCanvas, buffer, 0, count, x, y, flags, paint, typeface);
+        nDrawText(nativeCanvas, buffer, 0, count, x, y, flags, paint);
     }
 
     @LayoutlibDelegate
     /*package*/ static void nDrawTextRun(long nativeCanvas, String text,
             int start, int end, int contextStart, int contextEnd,
-            float x, float y, boolean isRtl, long paint, long typeface) {
+            float x, float y, boolean isRtl, long paint) {
         int count = end - start;
         char[] buffer = TemporaryBuffer.obtain(count);
         TextUtils.getChars(text, start, end, buffer, 0);
 
         drawText(nativeCanvas, buffer, 0, count, x, y, isRtl ? Paint.BIDI_RTL : Paint.BIDI_LTR,
-                paint,
-                typeface);
+                paint);
     }
 
     @LayoutlibDelegate
     /*package*/ static void nDrawTextRun(long nativeCanvas, char[] text,
             int start, int count, int contextStart, int contextCount,
-            float x, float y, boolean isRtl, long paint, long typeface) {
-        drawText(nativeCanvas, text, start, count, x, y, isRtl ? Paint.BIDI_RTL : Paint.BIDI_LTR, paint, typeface);
+            float x, float y, boolean isRtl, long paint) {
+        drawText(nativeCanvas, text, start, count, x, y, isRtl ? Paint.BIDI_RTL : Paint.BIDI_LTR, paint);
     }
 
     @LayoutlibDelegate
@@ -519,7 +517,7 @@
             int count, long path,
             float hOffset,
             float vOffset, int bidiFlags,
-            long paint, long typeface) {
+            long paint) {
         // FIXME
         Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
                 "Canvas.drawTextOnPath is not supported.", null, null /*data*/);
@@ -530,8 +528,7 @@
             String text, long path,
             float hOffset,
             float vOffset,
-            int bidiFlags, long paint,
-            long typeface) {
+            int bidiFlags, long paint) {
         // FIXME
         Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
                 "Canvas.drawTextOnPath is not supported.", null, null /*data*/);
@@ -576,16 +573,13 @@
 
     private static void drawText(long nativeCanvas, final char[] text, final int index,
             final int count, final float startX, final float startY, final int bidiFlags,
-            long paint, final long typeface) {
+            long paint) {
 
         draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
                 (graphics, paintDelegate) -> {
                     // WARNING: the logic in this method is similar to Paint_Delegate.measureText.
                     // Any change to this method should be reflected in Paint.measureText
 
-                    // assert that the typeface passed is actually the one stored in paint.
-                    assert (typeface == paintDelegate.mNativeTypeface);
-
                     // Paint.TextAlign indicates how the text is positioned relative to X.
                     // LEFT is the default and there's nothing to do.
                     float x = startX;
diff --git a/bridge/src/android/graphics/BidiRenderer.java b/bridge/src/android/graphics/BidiRenderer.java
index 63691c3..7b7dfa6 100644
--- a/bridge/src/android/graphics/BidiRenderer.java
+++ b/bridge/src/android/graphics/BidiRenderer.java
@@ -33,6 +33,7 @@
 import java.awt.font.GlyphVector;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.Rectangle2D;
+import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
 
@@ -62,6 +63,8 @@
     // Bounds of the text drawn so far.
     private RectF mBounds;
     private float mBaseline;
+    private final Bidi mBidi = new Bidi();
+
 
     /**
      * @param graphics May be null.
@@ -97,10 +100,10 @@
      */
     public RectF renderText(int start, int limit, int bidiFlags, float[] advances,
             int advancesIndex, boolean draw) {
-        Bidi bidi = new Bidi(mText, start, null, 0, limit - start, getIcuFlags(bidiFlags));
-        mText = bidi.getText();
-        for (int i = 0; i < bidi.countRuns(); i++) {
-            BidiRun visualRun = bidi.getVisualRun(i);
+        mBidi.setPara(Arrays.copyOfRange(mText, start, limit), (byte)getIcuFlags(bidiFlags), null);
+        mText = mBidi.getText();
+        for (int i = 0; i < mBidi.countRuns(); i++) {
+            BidiRun visualRun = mBidi.getVisualRun(i);
             boolean isRtl = visualRun.getDirection() == Bidi.RTL;
             renderText(visualRun.getStart(), visualRun.getLimit(), isRtl, advances,
                     advancesIndex, draw);
@@ -197,8 +200,7 @@
 
     private static void logFontWarning() {
         Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN,
-                "Some fonts could not be loaded. The rendering may not be perfect. " +
-                        "Try running the IDE with JRE 7.", null, null);
+                "Some fonts could not be loaded. The rendering may not be perfect.", null, null);
     }
 
     /**
@@ -285,15 +287,17 @@
     @NonNull
     private static Font getScriptFont(char[] text, int start, int limit, List<FontInfo> fonts) {
         for (FontInfo fontInfo : fonts) {
-            if (fontInfo.mFont == null) {
-                logFontWarning();
-                continue;
-            }
             if (fontInfo.mFont.canDisplayUpTo(text, start, limit) == -1) {
                 return fontInfo.mFont;
             }
         }
 
+        if (fonts.isEmpty()) {
+            logFontWarning();
+            // Fallback font in case no font can be loaded
+            return Font.getFont(Font.SERIF);
+        }
+
         return fonts.get(0).mFont;
     }
 
diff --git a/bridge/src/android/graphics/BitmapShader_Delegate.java b/bridge/src/android/graphics/BitmapShader_Delegate.java
index 4914a48..891b07f 100644
--- a/bridge/src/android/graphics/BitmapShader_Delegate.java
+++ b/bridge/src/android/graphics/BitmapShader_Delegate.java
@@ -31,7 +31,9 @@
 import java.awt.geom.Rectangle2D;
 import java.awt.image.BufferedImage;
 import java.awt.image.ColorModel;
+import java.awt.image.DataBufferInt;
 import java.awt.image.Raster;
+import java.awt.image.SampleModel;
 
 /**
  * Delegate implementing the native methods of android.graphics.BitmapShader
@@ -189,9 +191,9 @@
                     }
                 }
 
-                image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/);
-
-                return image.getRaster();
+                DataBufferInt dataBuffer = new DataBufferInt(data, data.length);
+                SampleModel colorModel = mColorModel.createCompatibleSampleModel(w, h);
+                return Raster.createWritableRaster(colorModel, dataBuffer, null);
             }
         }
 
diff --git a/bridge/src/android/graphics/Canvas_Delegate.java b/bridge/src/android/graphics/Canvas_Delegate.java
index 47216ee..9d8af38 100644
--- a/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/bridge/src/android/graphics/Canvas_Delegate.java
@@ -141,9 +141,6 @@
     }
 
     @LayoutlibDelegate
-    public static void nSetHighContrastText(long nativeCanvas, boolean highContrastText){}
-
-    @LayoutlibDelegate
     public static int nGetWidth(long nativeCanvas) {
         // get the delegate from the native int.
         Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
diff --git a/bridge/src/android/graphics/FontFamily_Delegate.java b/bridge/src/android/graphics/FontFamily_Delegate.java
index d8e049a..1e3ebd7 100644
--- a/bridge/src/android/graphics/FontFamily_Delegate.java
+++ b/bridge/src/android/graphics/FontFamily_Delegate.java
@@ -38,10 +38,12 @@
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Scanner;
 import java.util.Set;
 
@@ -97,6 +99,28 @@
         Font mFont;
         int mWeight;
         boolean mIsItalic;
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            FontInfo fontInfo = (FontInfo) o;
+            return mWeight == fontInfo.mWeight && mIsItalic == fontInfo.mIsItalic;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mWeight, mIsItalic);
+        }
+
+        @Override
+        public String toString() {
+            return "FontInfo{" + "mWeight=" + mWeight + ", mIsItalic=" + mIsItalic + '}';
+        }
     }
 
     // ---- delegate manager ----
@@ -111,7 +135,10 @@
 
 
     // ---- delegate data ----
-    private List<FontInfo> mFonts = new ArrayList<FontInfo>();
+
+    // Order does not really matter but we use a LinkedHashMap to get reproducible results across
+    // render calls
+    private Map<FontInfo, Font> mFonts = new LinkedHashMap<>();
 
     /**
      * The variant of the Font Family - compact or elegant.
@@ -147,7 +174,7 @@
         File allFonts = new File(fontLocation, FN_ALL_FONTS_LIST);
         // Current number of fonts is 103. Use the next round number to leave scope for more fonts
         // in the future.
-        Set<String> allFontsList = new HashSet<String>(128);
+        Set<String> allFontsList = new HashSet<>(128);
         Scanner scanner = null;
         try {
             scanner = new Scanner(allFonts);
@@ -179,23 +206,38 @@
         FontInfo desiredStyle = new FontInfo();
         desiredStyle.mWeight = desiredWeight;
         desiredStyle.mIsItalic = isItalic;
-        FontInfo bestFont = null;
-        int bestMatch = Integer.MAX_VALUE;
-        //noinspection ForLoopReplaceableByForEach (avoid iterator instantiation)
-        for (int i = 0, n = mFonts.size(); i < n; i++) {
-            FontInfo font = mFonts.get(i);
-            int match = computeMatch(font, desiredStyle);
-            if (match < bestMatch) {
-                bestMatch = match;
-                bestFont = font;
-            }
+
+        Font cachedFont = mFonts.get(desiredStyle);
+        if (cachedFont != null) {
+            return cachedFont;
         }
+
+        FontInfo bestFont = null;
+
+        if (mFonts.size() == 1) {
+            // No need to compute the match since we only have one candidate
+            bestFont = mFonts.keySet().iterator().next();
+        } else {
+            int bestMatch = Integer.MAX_VALUE;
+
+            //noinspection ForLoopReplaceableByForEach (avoid iterator instantiation)
+            for (FontInfo font : mFonts.keySet()) {
+                int match = computeMatch(font, desiredStyle);
+                if (match < bestMatch) {
+                    bestMatch = match;
+                    bestFont = font;
+                }
+            }
+
+            // This would mean that we already had the font so it should be in the set
+            assert bestMatch != 0;
+        }
+
         if (bestFont == null) {
             return null;
         }
-        if (bestMatch == 0) {
-            return bestFont.mFont;
-        }
+
+
         // Derive the font as required and add it to the list of Fonts.
         deriveFont(bestFont, desiredStyle);
         addFont(desiredStyle);
@@ -461,19 +503,7 @@
     }
 
     private boolean addFont(@NonNull FontInfo fontInfo) {
-        int weight = fontInfo.mWeight;
-        boolean isItalic = fontInfo.mIsItalic;
-        // The list is usually just two fonts big. So iterating over all isn't as bad as it looks.
-        // It's biggest for roboto where the size is 12.
-        //noinspection ForLoopReplaceableByForEach (avoid iterator instantiation)
-        for (int i = 0, n = mFonts.size(); i < n; i++) {
-            FontInfo font = mFonts.get(i);
-            if (font.mWeight == weight && font.mIsItalic == isItalic) {
-                return false;
-            }
-        }
-        mFonts.add(fontInfo);
-        return true;
+        return mFonts.putIfAbsent(fontInfo, fontInfo.mFont) == null;
     }
 
     /**
@@ -496,28 +526,32 @@
      *                its style
      * @return outFont
      */
-    @NonNull
-    private FontInfo deriveFont(@NonNull FontInfo srcFont, @NonNull FontInfo outFont) {
+    private void deriveFont(@NonNull FontInfo srcFont, @NonNull FontInfo outFont) {
         int desiredWeight = outFont.mWeight;
         int srcWeight = srcFont.mWeight;
         assert srcFont.mFont != null;
         Font derivedFont = srcFont.mFont;
+        int derivedStyle = 0;
         // Embolden the font if required.
         if (desiredWeight >= BOLD_FONT_WEIGHT && desiredWeight - srcWeight > BOLD_FONT_WEIGHT_DELTA / 2) {
-            derivedFont = derivedFont.deriveFont(Font.BOLD);
+            derivedStyle |= Font.BOLD;
             srcWeight += BOLD_FONT_WEIGHT_DELTA;
         }
         // Italicize the font if required.
         if (outFont.mIsItalic && !srcFont.mIsItalic) {
-            derivedFont = derivedFont.deriveFont(Font.ITALIC);
+            derivedStyle |= Font.ITALIC;
         } else if (outFont.mIsItalic != srcFont.mIsItalic) {
             // The desired font is plain, but the src font is italics. We can't convert it back. So
             // we update the value to reflect the true style of the font we're deriving.
             outFont.mIsItalic = srcFont.mIsItalic;
         }
+
+        if (derivedStyle != 0) {
+            derivedFont = derivedFont.deriveFont(derivedStyle);
+        }
+
         outFont.mFont = derivedFont;
         outFont.mWeight = srcWeight;
         // No need to update mIsItalics, as it's already been handled above.
-        return outFont;
     }
 }
diff --git a/bridge/src/android/graphics/LinearGradient_Delegate.java b/bridge/src/android/graphics/LinearGradient_Delegate.java
index cd4393a..477705c 100644
--- a/bridge/src/android/graphics/LinearGradient_Delegate.java
+++ b/bridge/src/android/graphics/LinearGradient_Delegate.java
@@ -24,6 +24,9 @@
 import android.graphics.Shader.TileMode;
 
 import java.awt.image.ColorModel;
+import java.awt.image.DataBufferInt;
+import java.awt.image.Raster;
+import java.awt.image.SampleModel;
 
 /**
  * Delegate implementing the native methods of android.graphics.LinearGradient
@@ -174,10 +177,6 @@
 
             @Override
             public java.awt.image.Raster getRaster(int x, int y, int w, int h) {
-                java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(
-                    mColorModel, mColorModel.createCompatibleWritableRaster(w, h),
-                    mColorModel.isAlphaPremultiplied(), null);
-
                 int[] data = new int[w*h];
 
                 int index = 0;
@@ -199,9 +198,9 @@
                     }
                 }
 
-                image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/);
-
-                return image.getRaster();
+                DataBufferInt dataBuffer = new DataBufferInt(data, data.length);
+                SampleModel colorModel = mColorModel.createCompatibleSampleModel(w, h);
+                return Raster.createWritableRaster(colorModel, dataBuffer, null);
             }
         }
 
diff --git a/bridge/src/android/graphics/NinePatch_Delegate.java b/bridge/src/android/graphics/NinePatch_Delegate.java
index 43e5b0f..ce2c18b 100644
--- a/bridge/src/android/graphics/NinePatch_Delegate.java
+++ b/bridge/src/android/graphics/NinePatch_Delegate.java
@@ -160,8 +160,12 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void nativeFinalize(long chunk) {
-        sManager.removeJavaReferenceFor(chunk);
+    /*package*/ static void nativeFinalize(long nativeNinePatch) {
+        NinePatch_Delegate delegate = sManager.getDelegate(nativeNinePatch);
+        if (delegate != null && delegate.chunk != null) {
+            sChunkCache.remove(delegate.chunk);
+        }
+        sManager.removeJavaReferenceFor(nativeNinePatch);
     }
 
 
diff --git a/bridge/src/android/graphics/Paint_Delegate.java b/bridge/src/android/graphics/Paint_Delegate.java
index 406157e..4f05176 100644
--- a/bridge/src/android/graphics/Paint_Delegate.java
+++ b/bridge/src/android/graphics/Paint_Delegate.java
@@ -34,10 +34,11 @@
 import java.awt.Stroke;
 import java.awt.Toolkit;
 import java.awt.geom.AffineTransform;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
 
 import libcore.util.NativeAllocationRegistry_Delegate;
 
@@ -55,23 +56,32 @@
  *
  */
 public class Paint_Delegate {
+    private static final float DEFAULT_TEXT_SIZE = 20.f;
+    private static final float DEFAULT_TEXT_SCALE_X = 1.f;
+    private static final float DEFAULT_TEXT_SKEW_X = 0.f;
 
     /**
      * Class associating a {@link Font} and its {@link java.awt.FontMetrics}.
      */
     /*package*/ static final class FontInfo {
-        Font mFont;
-        java.awt.FontMetrics mMetrics;
+        final Font mFont;
+        final java.awt.FontMetrics mMetrics;
+
+        FontInfo(@NonNull Font font, @NonNull java.awt.FontMetrics fontMetrics) {
+            this.mFont = font;
+            this.mMetrics = fontMetrics;
+        }
     }
 
     // ---- delegate manager ----
     private static final DelegateManager<Paint_Delegate> sManager =
-            new DelegateManager<Paint_Delegate>(Paint_Delegate.class);
+            new DelegateManager<>(Paint_Delegate.class);
     private static long sFinalizer = -1;
 
     // ---- delegate helper data ----
 
     // This list can contain null elements.
+    @Nullable
     private List<FontInfo> mFonts;
 
     // ---- delegate data ----
@@ -100,11 +110,9 @@
     private PathEffect_Delegate mPathEffect;
     private MaskFilter_Delegate mMaskFilter;
 
+    @SuppressWarnings("FieldCanBeLocal") // Used to store the locale for future use
     private Locale mLocale = Locale.getDefault();
 
-    // Used only to assert invariants.
-    public long mNativeTypeface;
-
     // ---- Public Helper methods ----
 
     @Nullable
@@ -115,7 +123,32 @@
     /**
      * Returns the list of {@link Font} objects.
      */
+    @NonNull
     public List<FontInfo> getFonts() {
+        Typeface_Delegate typeface = mTypeface;
+        if (typeface == null) {
+            if (Typeface.sDefaultTypeface == null) {
+                return Collections.emptyList();
+            }
+
+            typeface = Typeface_Delegate.getDelegate(Typeface.sDefaultTypeface.native_instance);
+        }
+
+        if (mFonts != null) {
+            return mFonts;
+        }
+
+        // Apply an optional transformation for skew and scale
+        AffineTransform affineTransform = mTextScaleX != 1.0 || mTextSkewX != 0 ?
+                new AffineTransform(mTextScaleX, mTextSkewX, 0, 1, 0, 0) :
+                null;
+
+        List<FontInfo> infoList = StreamSupport.stream(typeface.getFonts(mFontVariant).spliterator
+                (), false)
+                .map(font -> getFontInfo(font, mTextSize, affineTransform))
+                .collect(Collectors.toList());
+        mFonts = Collections.unmodifiableList(infoList);
+
         return mFonts;
     }
 
@@ -187,6 +220,7 @@
             if (mPathEffect.isSupported()) {
                 Stroke stroke = mPathEffect.getStroke(this);
                 assert stroke != null;
+                //noinspection ConstantConditions
                 if (stroke != null) {
                     return stroke;
                 }
@@ -483,7 +517,7 @@
 
         if (delegate.mTextSize != textSize) {
             delegate.mTextSize = textSize;
-            delegate.updateFontObject();
+            delegate.invalidateFonts();
         }
     }
 
@@ -508,7 +542,7 @@
 
         if (delegate.mTextScaleX != scaleX) {
             delegate.mTextScaleX = scaleX;
-            delegate.updateFontObject();
+            delegate.invalidateFonts();
         }
     }
 
@@ -533,20 +567,21 @@
 
         if (delegate.mTextSkewX != skewX) {
             delegate.mTextSkewX = skewX;
-            delegate.updateFontObject();
+            delegate.invalidateFonts();
         }
     }
 
     @LayoutlibDelegate
-    /*package*/ static float nAscent(long nativePaint, long nativeTypeface) {
+    /*package*/ static float nAscent(long nativePaint) {
         // get the delegate
         Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return 0;
         }
 
-        if (delegate.mFonts.size() > 0) {
-            java.awt.FontMetrics javaMetrics = delegate.mFonts.get(0).mMetrics;
+        List<FontInfo> fonts = delegate.getFonts();
+        if (fonts.size() > 0) {
+            java.awt.FontMetrics javaMetrics = fonts.get(0).mMetrics;
             // Android expects negative ascent so we invert the value from Java.
             return - javaMetrics.getAscent();
         }
@@ -555,15 +590,16 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static float nDescent(long nativePaint, long nativeTypeface) {
+    /*package*/ static float nDescent(long nativePaint) {
         // get the delegate
         Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return 0;
         }
 
-        if (delegate.mFonts.size() > 0) {
-            java.awt.FontMetrics javaMetrics = delegate.mFonts.get(0).mMetrics;
+        List<FontInfo> fonts = delegate.getFonts();
+        if (fonts.size() > 0) {
+            java.awt.FontMetrics javaMetrics = fonts.get(0).mMetrics;
             return javaMetrics.getDescent();
         }
 
@@ -572,7 +608,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static float nGetFontMetrics(long nativePaint, long nativeTypeface,
+    /*package*/ static float nGetFontMetrics(long nativePaint,
             FontMetrics metrics) {
         // get the delegate
         Paint_Delegate delegate = sManager.getDelegate(nativePaint);
@@ -584,16 +620,16 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static int nGetFontMetricsInt(long nativePaint,
-            long nativeTypeface, FontMetricsInt fmi) {
+    /*package*/ static int nGetFontMetricsInt(long nativePaint, FontMetricsInt fmi) {
         // get the delegate
         Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return 0;
         }
 
-        if (delegate.mFonts.size() > 0) {
-            java.awt.FontMetrics javaMetrics = delegate.mFonts.get(0).mMetrics;
+        List<FontInfo> fonts = delegate.getFonts();
+        if (fonts.size() > 0) {
+            java.awt.FontMetrics javaMetrics = fonts.get(0).mMetrics;
             if (fmi != null) {
                 // Android expects negative ascent so we invert the value from Java.
                 fmi.top = (int)(- javaMetrics.getMaxAscent() * 1.15);
@@ -610,7 +646,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static int nBreakText(long nativePaint, long nativeTypeface, char[] text,
+    /*package*/ static int nBreakText(long nativePaint, char[] text,
             int index, int count, float maxWidth, int bidiFlags, float[] measuredWidth) {
 
         // get the delegate
@@ -652,10 +688,9 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static int nBreakText(long nativePaint, long nativeTypeface, String text,
-            boolean measureForwards,
+    /*package*/ static int nBreakText(long nativePaint, String text, boolean measureForwards,
             float maxWidth, int bidiFlags, float[] measuredWidth) {
-        return nBreakText(nativePaint, nativeTypeface, text.toCharArray(), 0, text.length(),
+        return nBreakText(nativePaint, text.toCharArray(), 0, text.length(),
                 maxWidth, bidiFlags, measuredWidth);
     }
 
@@ -871,20 +906,18 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static long nSetTypeface(long native_object, long typeface) {
+    /*package*/ static void nSetTypeface(long native_object, long typeface) {
         // get the delegate from the native int.
         Paint_Delegate delegate = sManager.getDelegate(native_object);
         if (delegate == null) {
-            return 0;
+            return;
         }
 
         Typeface_Delegate typefaceDelegate = Typeface_Delegate.getDelegate(typeface);
-        if (delegate.mTypeface != typefaceDelegate || delegate.mNativeTypeface != typeface) {
-            delegate.mTypeface = Typeface_Delegate.getDelegate(typeface);
-            delegate.mNativeTypeface = typeface;
-            delegate.updateFontObject();
+        if (delegate.mTypeface != typefaceDelegate) {
+            delegate.mTypeface = typefaceDelegate;
+            delegate.invalidateFonts();
         }
-        return typeface;
     }
 
     @LayoutlibDelegate
@@ -922,14 +955,14 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void nSetTextLocalesByMinikinLangListId(long paintPtr,
+    /*package*/ static void nSetTextLocalesByMinikinLocaleListId(long paintPtr,
             int mMinikinLangListId) {
         // FIXME
     }
 
     @LayoutlibDelegate
-    /*package*/ static float nGetTextAdvances(long native_object, long native_typeface,
-            char[] text, int index, int count, int contextIndex, int contextCount,
+    /*package*/ static float nGetTextAdvances(long native_object, char[] text, int index,
+            int count, int contextIndex, int contextCount,
             int bidiFlags, float[] advances, int advancesIndex) {
 
         if (advances != null)
@@ -941,32 +974,25 @@
             return 0.f;
         }
 
-        // native_typeface is passed here since Framework's old implementation did not have the
-        // typeface object associated with the Paint. Since, we follow the new framework way,
-        // we store the typeface with the paint and use it directly.
-        assert (native_typeface == delegate.mNativeTypeface);
-
         RectF bounds = delegate.measureText(text, index, count, advances, advancesIndex, bidiFlags);
         return bounds.right - bounds.left;
     }
 
     @LayoutlibDelegate
-    /*package*/ static float nGetTextAdvances(long native_object, long native_typeface,
-            String text, int start, int end, int contextStart, int contextEnd,
-            int bidiFlags, float[] advances, int advancesIndex) {
+    /*package*/ static float nGetTextAdvances(long native_object, String text, int start, int end,
+            int contextStart, int contextEnd, int bidiFlags, float[] advances, int advancesIndex) {
         // FIXME: support contextStart and contextEnd
         int count = end - start;
         char[] buffer = TemporaryBuffer.obtain(count);
         TextUtils.getChars(text, start, end, buffer, 0);
 
-        return nGetTextAdvances(native_object, native_typeface, buffer, 0, count,
+        return nGetTextAdvances(native_object, buffer, 0, count,
                 contextStart, contextEnd - contextStart, bidiFlags, advances, advancesIndex);
     }
 
     @LayoutlibDelegate
-    /*package*/ static int nGetTextRunCursor(Paint paint, long native_object, long typefacePtr,
-            char[] text, int contextStart, int contextLength, int flags, int offset,
-            int cursorOpt) {
+    /*package*/ static int nGetTextRunCursor(Paint paint, long native_object, char[] text,
+            int contextStart, int contextLength, int flags, int offset, int cursorOpt) {
         // FIXME
         Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
                 "Paint.getTextRunCursor is not supported.", null, null /*data*/);
@@ -974,8 +1000,8 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static int nGetTextRunCursor(Paint paint, long native_object, long typefacePtr,
-            String text, int contextStart, int contextEnd, int flags, int offset, int cursorOpt) {
+    /*package*/ static int nGetTextRunCursor(Paint paint, long native_object, String text,
+            int contextStart, int contextEnd, int flags, int offset, int cursorOpt) {
         // FIXME
         Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
                 "Paint.getTextRunCursor is not supported.", null, null /*data*/);
@@ -983,31 +1009,31 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void nGetTextPath(long native_object, long native_typeface,
-            int bidiFlags, char[] text, int index, int count, float x, float y, long path) {
+    /*package*/ static void nGetTextPath(long native_object, int bidiFlags, char[] text,
+            int index, int count, float x, float y, long path) {
         // FIXME
         Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
                 "Paint.getTextPath is not supported.", null, null /*data*/);
     }
 
     @LayoutlibDelegate
-    /*package*/ static void nGetTextPath(long native_object, long native_typeface,
-            int bidiFlags, String text, int start, int end, float x, float y, long path) {
+    /*package*/ static void nGetTextPath(long native_object, int bidiFlags, String text, int start,
+            int end, float x, float y, long path) {
         // FIXME
         Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
                 "Paint.getTextPath is not supported.", null, null /*data*/);
     }
 
     @LayoutlibDelegate
-    /*package*/ static void nGetStringBounds(long nativePaint, long native_typeface,
-            String text, int start, int end, int bidiFlags, Rect bounds) {
-        nGetCharArrayBounds(nativePaint, native_typeface, text.toCharArray(), start,
+    /*package*/ static void nGetStringBounds(long nativePaint, String text, int start, int end,
+            int bidiFlags, Rect bounds) {
+        nGetCharArrayBounds(nativePaint, text.toCharArray(), start,
                 end - start, bidiFlags, bounds);
     }
 
     @LayoutlibDelegate
-    /*package*/ static void nGetCharArrayBounds(long nativePaint, long native_typeface,
-            char[] text, int index, int count, int bidiFlags, Rect bounds) {
+    /*package*/ static void nGetCharArrayBounds(long nativePaint, char[] text, int index,
+            int count, int bidiFlags, Rect bounds) {
 
         // get the delegate from the native int.
         Paint_Delegate delegate = sManager.getDelegate(nativePaint);
@@ -1015,9 +1041,6 @@
             return;
         }
 
-        // assert that the typeface passed is actually the one that we had stored.
-        assert (native_typeface == delegate.mNativeTypeface);
-
         delegate.measureText(text, index, count, null, 0, bidiFlags).roundOut(bounds);
     }
 
@@ -1095,8 +1118,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static boolean nHasGlyph(long nativePaint, long nativeTypeface, int bidiFlags,
-            String string) {
+    /*package*/ static boolean nHasGlyph(long nativePaint, int bidiFlags, String string) {
         Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return false;
@@ -1109,11 +1131,9 @@
                     "Paint.hasGlyph() is not supported for ligatures.", null, null);
             return false;
         }
-        assert nativeTypeface == delegate.mNativeTypeface;
-        Typeface_Delegate typeface_delegate = Typeface_Delegate.getDelegate(nativeTypeface);
 
         char c = string.charAt(0);
-        for (Font font : typeface_delegate.getFonts(delegate.mFontVariant)) {
+        for (Font font : delegate.mTypeface.getFonts(delegate.mFontVariant)) {
             if (font.canDisplay(c)) {
                 return true;
             }
@@ -1123,14 +1143,14 @@
 
 
     @LayoutlibDelegate
-    /*package*/ static float nGetRunAdvance(long nativePaint, long nativeTypeface,
-            @NonNull char[] text, int start, int end, int contextStart, int contextEnd,
+    /*package*/ static float nGetRunAdvance(long nativePaint, @NonNull char[] text, int start,
+            int end, int contextStart, int contextEnd,
             boolean isRtl, int offset) {
         int count = end - start;
         float[] advances = new float[count];
         int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
-        nGetTextAdvances(nativePaint, nativeTypeface, text, start, count,
-                contextStart, contextEnd - contextStart, bidiFlags, advances, 0);
+        nGetTextAdvances(nativePaint, text, start, count, contextStart,
+                contextEnd - contextStart, bidiFlags, advances, 0);
         int startOffset = offset - start;  // offset from start.
         float sum = 0;
         for (int i = 0; i < startOffset; i++) {
@@ -1140,14 +1160,13 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static int nGetOffsetForAdvance(long nativePaint, long nativeTypeface,
-            char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl,
-            float advance) {
+    /*package*/ static int nGetOffsetForAdvance(long nativePaint, char[] text, int start,
+            int end, int contextStart, int contextEnd, boolean isRtl, float advance) {
         int count = end - start;
         float[] advances = new float[count];
         int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
-        nGetTextAdvances(nativePaint, nativeTypeface, text, start, count,
-                contextStart, contextEnd - contextStart, bidiFlags, advances, 0);
+        nGetTextAdvances(nativePaint, text, start, count, contextStart,
+                contextEnd - contextStart, bidiFlags, advances, 0);
         float sum = 0;
         int i;
         for (i = 0; i < count && sum < advance; i++) {
@@ -1159,22 +1178,22 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static float nGetUnderlinePosition(long paintPtr, long typefacePtr) {
+    /*package*/ static float nGetUnderlinePosition(long paintPtr) {
         return (1.0f / 9.0f) * nGetTextSize(paintPtr);
     }
 
     @LayoutlibDelegate
-    /*package*/ static float nGetUnderlineThickness(long paintPtr, long typefacePtr) {
+    /*package*/ static float nGetUnderlineThickness(long paintPtr) {
         return (1.0f / 18.0f) * nGetTextSize(paintPtr);
     }
 
     @LayoutlibDelegate
-    /*package*/ static float nGetStrikeThruPosition(long paintPtr, long typefacePtr) {
+    /*package*/ static float nGetStrikeThruPosition(long paintPtr) {
         return (-79.0f / 252.0f) * nGetTextSize(paintPtr);
     }
 
     @LayoutlibDelegate
-    /*package*/ static float nGetStrikeThruThickness(long paintPtr, long typefacePtr) {
+    /*package*/ static float nGetStrikeThruThickness(long paintPtr) {
         return (1.0f / 18.0f) * nGetTextSize(paintPtr);
     }
 
@@ -1196,26 +1215,24 @@
         mJoin = paint.mJoin;
         mTextAlign = paint.mTextAlign;
 
-        boolean needsFontUpdate = false;
-        if (mTypeface != paint.mTypeface || mNativeTypeface != paint.mNativeTypeface) {
+        if (mTypeface != paint.mTypeface) {
             mTypeface = paint.mTypeface;
-            mNativeTypeface = paint.mNativeTypeface;
-            needsFontUpdate = true;
+            invalidateFonts();
         }
 
         if (mTextSize != paint.mTextSize) {
             mTextSize = paint.mTextSize;
-            needsFontUpdate = true;
+            invalidateFonts();
         }
 
         if (mTextScaleX != paint.mTextScaleX) {
             mTextScaleX = paint.mTextScaleX;
-            needsFontUpdate = true;
+            invalidateFonts();
         }
 
         if (mTextSkewX != paint.mTextSkewX) {
             mTextSkewX = paint.mTextSkewX;
-            needsFontUpdate = true;
+            invalidateFonts();
         }
 
         mStrokeWidth = paint.mStrokeWidth;
@@ -1226,77 +1243,70 @@
         mPathEffect = paint.mPathEffect;
         mMaskFilter = paint.mMaskFilter;
         mHintingMode = paint.mHintingMode;
-
-        if (needsFontUpdate) {
-            updateFontObject();
-        }
     }
 
     private void reset() {
+        Typeface_Delegate defaultTypeface =
+                Typeface_Delegate.getDelegate(Typeface.sDefaults[0].native_instance);
+
         mFlags = Paint.HIDDEN_DEFAULT_PAINT_FLAGS;
         mColor = 0xFF000000;
         mStyle = Paint.Style.FILL.nativeInt;
         mCap = Paint.Cap.BUTT.nativeInt;
         mJoin = Paint.Join.MITER.nativeInt;
         mTextAlign = 0;
-        mTypeface = Typeface_Delegate.getDelegate(Typeface.sDefaults[0].native_instance);
-        mNativeTypeface = 0;
+
+        if (mTypeface != defaultTypeface) {
+            mTypeface = defaultTypeface;
+            invalidateFonts();
+        }
+
         mStrokeWidth = 1.f;
         mStrokeMiter = 4.f;
-        mTextSize = 20.f;
-        mTextScaleX = 1.f;
-        mTextSkewX = 0.f;
+
+        if (mTextSize != DEFAULT_TEXT_SIZE) {
+            mTextSize = DEFAULT_TEXT_SIZE;
+            invalidateFonts();
+        }
+
+        if (mTextScaleX != DEFAULT_TEXT_SCALE_X) {
+            mTextScaleX = DEFAULT_TEXT_SCALE_X;
+            invalidateFonts();
+        }
+
+        if (mTextSkewX != DEFAULT_TEXT_SKEW_X) {
+            mTextSkewX = DEFAULT_TEXT_SKEW_X;
+            invalidateFonts();
+        }
+
         mPorterDuffMode = Xfermode.DEFAULT;
         mColorFilter = null;
         mShader = null;
         mPathEffect = null;
         mMaskFilter = null;
-        updateFontObject();
         mHintingMode = Paint.HINTING_ON;
     }
 
-    /**
-     * Update the {@link Font} object from the typeface, text size and scaling
-     */
-    @SuppressWarnings("deprecation")
-    private void updateFontObject() {
-        if (mTypeface != null) {
-            // Get the fonts from the TypeFace object.
-            List<Font> fonts = mTypeface.getFonts(mFontVariant);
+    private void invalidateFonts() {
+        mFonts = null;
+    }
 
-            if (fonts.isEmpty()) {
-                mFonts = Collections.emptyList();
-                return;
-            }
-
-            // create new font objects as well as FontMetrics, based on the current text size
-            // and skew info.
-            int nFonts = fonts.size();
-            ArrayList<FontInfo> infoList = new ArrayList<FontInfo>(nFonts);
-            //noinspection ForLoopReplaceableByForEach (avoid iterator instantiation)
-            for (int i = 0; i < nFonts; i++) {
-                Font font = fonts.get(i);
-                if (font == null) {
-                    // If the font is null, add null to infoList. When rendering the text, if this
-                    // null is reached, a warning will be logged.
-                    infoList.add(null);
-                    continue;
-                }
-                FontInfo info = new FontInfo();
-                info.mFont = font.deriveFont(mTextSize);
-                if (mTextScaleX != 1.0 || mTextSkewX != 0) {
-                    // TODO: support skew
-                    info.mFont = info.mFont.deriveFont(new AffineTransform(
-                            mTextScaleX, mTextSkewX, 0, 1, 0, 0));
-                }
-                // The metrics here don't have anti-aliasing set.
-                info.mMetrics = Toolkit.getDefaultToolkit().getFontMetrics(info.mFont);
-
-                infoList.add(info);
-            }
-
-            mFonts = Collections.unmodifiableList(infoList);
+    @Nullable
+    private static FontInfo getFontInfo(@Nullable Font font, float textSize,
+            @Nullable AffineTransform transform) {
+        if (font == null) {
+            return null;
         }
+
+        Font transformedFont = font.deriveFont(textSize);
+        if (transform != null) {
+            // TODO: support skew
+            transformedFont = transformedFont.deriveFont(transform);
+        }
+
+        // The metrics here don't have anti-aliasing set.
+        return new FontInfo(transformedFont,
+                Toolkit.getDefaultToolkit().getFontMetrics(transformedFont));
     }
 
     /*package*/ RectF measureText(char[] text, int index, int count, float[] advances,
@@ -1312,8 +1322,9 @@
     }
 
     private float getFontMetrics(FontMetrics metrics) {
-        if (mFonts.size() > 0) {
-            java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics;
+        List<FontInfo> fonts = getFonts();
+        if (fonts.size() > 0) {
+            java.awt.FontMetrics javaMetrics = fonts.get(0).mMetrics;
             if (metrics != null) {
                 // Android expects negative ascent so we invert the value from Java.
                 metrics.top = - javaMetrics.getMaxAscent();
diff --git a/bridge/src/android/graphics/RadialGradient_Delegate.java b/bridge/src/android/graphics/RadialGradient_Delegate.java
index b5ba468..25521d2 100644
--- a/bridge/src/android/graphics/RadialGradient_Delegate.java
+++ b/bridge/src/android/graphics/RadialGradient_Delegate.java
@@ -24,6 +24,9 @@
 import android.graphics.Shader.TileMode;
 
 import java.awt.image.ColorModel;
+import java.awt.image.DataBufferInt;
+import java.awt.image.Raster;
+import java.awt.image.SampleModel;
 
 /**
  * Delegate implementing the native methods of android.graphics.RadialGradient
@@ -163,10 +166,6 @@
 
             @Override
             public java.awt.image.Raster getRaster(int x, int y, int w, int h) {
-                java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(
-                    mColorModel, mColorModel.createCompatibleWritableRaster(w, h),
-                    mColorModel.isAlphaPremultiplied(), null);
-
                 int[] data = new int[w*h];
 
                 // compute distance from each point to the center, and figure out the distance from
@@ -174,6 +173,7 @@
                 int index = 0;
                 float[] pt1 = new float[2];
                 float[] pt2 = new float[2];
+
                 for (int iy = 0 ; iy < h ; iy++) {
                     for (int ix = 0 ; ix < w ; ix++) {
                         // handle the canvas transform
@@ -182,21 +182,21 @@
                         mCanvasMatrix.transform(pt1, 0, pt2, 0, 1);
 
                         // handle the local matrix
-                        pt1[0] = pt2[0] - mX;
-                        pt1[1] = pt2[1] - mY;
+                        pt1[0] = pt2[0];
+                        pt1[1] = pt2[1];
                         mLocalMatrix.transform(pt1, 0, pt2, 0, 1);
 
-                        float _x = pt2[0];
-                        float _y = pt2[1];
+                        float _x = pt2[0] - mX;
+                        float _y = pt2[1] - mY;
                         float distance = (float) Math.hypot(_x, _y);
 
                         data[index++] = getGradientColor(distance / mRadius);
                     }
                 }
 
-                image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/);
-
-                return image.getRaster();
+                DataBufferInt dataBuffer = new DataBufferInt(data, data.length);
+                SampleModel colorModel = mColorModel.createCompatibleSampleModel(w, h);
+                return Raster.createWritableRaster(colorModel, dataBuffer, null);
             }
 
         }
diff --git a/bridge/src/android/graphics/Shader_Delegate.java b/bridge/src/android/graphics/Shader_Delegate.java
index d88be47..eefa929 100644
--- a/bridge/src/android/graphics/Shader_Delegate.java
+++ b/bridge/src/android/graphics/Shader_Delegate.java
@@ -92,6 +92,10 @@
     // ---- Private delegate/helper methods ----
 
     protected Shader_Delegate(long nativeMatrix) {
+        setLocalMatrix(nativeMatrix);
+    }
+
+    public void setLocalMatrix(long nativeMatrix) {
         mLocalMatrix = Matrix_Delegate.getDelegate(nativeMatrix);
     }
 
diff --git a/bridge/src/android/graphics/SweepGradient_Delegate.java b/bridge/src/android/graphics/SweepGradient_Delegate.java
index 30152bc..6e8aca3 100644
--- a/bridge/src/android/graphics/SweepGradient_Delegate.java
+++ b/bridge/src/android/graphics/SweepGradient_Delegate.java
@@ -21,6 +21,10 @@
 import com.android.layoutlib.bridge.impl.DelegateManager;
 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
 
+import java.awt.image.DataBufferInt;
+import java.awt.image.Raster;
+import java.awt.image.SampleModel;
+
 /**
  * Delegate implementing the native methods of android.graphics.SweepGradient
  *
@@ -156,9 +160,6 @@
 
             @Override
             public java.awt.image.Raster getRaster(int x, int y, int w, int h) {
-                java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(
-                    mColorModel, mColorModel.createCompatibleWritableRaster(w, h),
-                    mColorModel.isAlphaPremultiplied(), null);
 
                 int[] data = new int[w*h];
 
@@ -203,9 +204,9 @@
                     }
                 }
 
-                image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/);
-
-                return image.getRaster();
+                DataBufferInt dataBuffer = new DataBufferInt(data, data.length);
+                SampleModel colorModel = mColorModel.createCompatibleSampleModel(w, h);
+                return Raster.createWritableRaster(colorModel, dataBuffer, null);
             }
 
         }
diff --git a/bridge/src/android/graphics/Typeface_Delegate.java b/bridge/src/android/graphics/Typeface_Delegate.java
index a04a324..d793ade 100644
--- a/bridge/src/android/graphics/Typeface_Delegate.java
+++ b/bridge/src/android/graphics/Typeface_Delegate.java
@@ -16,37 +16,57 @@
 
 package android.graphics;
 
+import com.android.SdkConstants;
 import com.android.ide.common.rendering.api.LayoutLog;
 import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.layoutlib.bridge.android.RenderParamsFlags;
 import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.impl.ParserFactory;
+import com.android.layoutlib.bridge.impl.RenderAction;
 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.FontResourcesParser;
 import android.graphics.FontFamily_Delegate.FontVariant;
 import android.graphics.fonts.FontVariationAxis;
 import android.text.FontConfig;
+import android.util.ArrayMap;
 
 import java.awt.Font;
 import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.lang.ref.SoftReference;
 import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Spliterator;
+import java.util.Spliterators;
 
 import static android.graphics.FontFamily_Delegate.getFontLocation;
 
 /**
  * Delegate implementing the native methods of android.graphics.Typeface
- *
- * Through the layoutlib_create tool, the original native methods of Typeface have been replaced
- * by calls to methods of the same name in this delegate class.
- *
+ * <p>
+ * Through the layoutlib_create tool, the original native methods of Typeface have been replaced by
+ * calls to methods of the same name in this delegate class.
+ * <p>
  * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original Typeface class.
+ * native data into its own objects and mapping them to int that are sent back and forth between it
+ * and the original Typeface class.
  *
  * @see DelegateManager
- *
  */
 public final class Typeface_Delegate {
 
@@ -54,87 +74,29 @@
 
     // ---- delegate manager ----
     private static final DelegateManager<Typeface_Delegate> sManager =
-            new DelegateManager<Typeface_Delegate>(Typeface_Delegate.class);
+            new DelegateManager<>(Typeface_Delegate.class);
 
 
     // ---- delegate data ----
-
+    private static long sDefaultTypeface;
     @NonNull
     private final FontFamily_Delegate[] mFontFamilies;  // the reference to FontFamily_Delegate.
     /** @see Font#getStyle() */
     private final int mStyle;
     private final int mWeight;
-
-    private static long sDefaultTypeface;
+    private SoftReference<EnumMap<FontVariant, List<Font>>> mFontsCache = new SoftReference<>(null);
 
 
     // ---- Public Helper methods ----
 
-    public static Typeface_Delegate getDelegate(long nativeTypeface) {
-        return sManager.getDelegate(nativeTypeface);
+    public Typeface_Delegate(@NonNull FontFamily_Delegate[] fontFamilies, int style, int weight) {
+        mFontFamilies = fontFamilies;
+        mStyle = style;
+        mWeight = weight;
     }
 
-    /**
-     * Return a list of fonts that match the style and variant. The list is ordered according to
-     * preference of fonts.
-     *
-     * The list may contain null when the font failed to load. If null is reached when trying to
-     * render with this list of fonts, then a warning should be logged letting the user know that
-     * some font failed to load.
-     *
-     * @param variant The variant preferred. Can only be {@link FontVariant#COMPACT} or
-     *                {@link FontVariant#ELEGANT}
-     */
-    @NonNull
-    public List<Font> getFonts(FontVariant variant) {
-        assert variant != FontVariant.NONE;
-
-        // Calculate the required weight based on style and weight of this typeface.
-        int weight = mWeight + 50 +
-                ((mStyle & Font.BOLD) == 0 ? 0 : FontFamily_Delegate.BOLD_FONT_WEIGHT_DELTA);
-        if (weight > 1000) {
-            weight = 1000;
-        } else if (weight < 100) {
-            weight = 100;
-        }
-        final boolean isItalic = (mStyle & Font.ITALIC) != 0;
-        List<Font> fonts = new ArrayList<Font>(mFontFamilies.length);
-        for (int i = 0; i < mFontFamilies.length; i++) {
-            FontFamily_Delegate ffd = mFontFamilies[i];
-            if (ffd != null && ffd.isValid()) {
-                Font font = ffd.getFont(weight, isItalic);
-                if (font != null) {
-                    FontVariant ffdVariant = ffd.getVariant();
-                    if (ffdVariant == FontVariant.NONE) {
-                        fonts.add(font);
-                        continue;
-                    }
-                    // We cannot open each font and get locales supported, etc to match the fonts.
-                    // As a workaround, we hardcode certain assumptions like Elegant and Compact
-                    // always appear in pairs.
-                    assert i < mFontFamilies.length - 1;
-                    FontFamily_Delegate ffd2 = mFontFamilies[++i];
-                    assert ffd2 != null;
-                    FontVariant ffd2Variant = ffd2.getVariant();
-                    Font font2 = ffd2.getFont(weight, isItalic);
-                    assert ffd2Variant != FontVariant.NONE && ffd2Variant != ffdVariant
-                            && font2 != null;
-                    // Add the font with the matching variant to the list.
-                    if (variant == ffd.getVariant()) {
-                        fonts.add(font);
-                    } else {
-                        fonts.add(font2);
-                    }
-                } else {
-                    // The FontFamily is valid but doesn't contain any matching font. This means
-                    // that the font failed to load. We add null to the list of fonts. Don't throw
-                    // the warning just yet. If this is a non-english font, we don't want to warn
-                    // users who are trying to render only english text.
-                    fonts.add(null);
-                }
-            }
-        }
-        return fonts;
+    public static Typeface_Delegate getDelegate(long nativeTypeface) {
+        return sManager.getDelegate(nativeTypeface);
     }
 
     /**
@@ -161,13 +123,13 @@
             return 0;
         }
 
-        return sManager.addNewDelegate(new Typeface_Delegate(delegate.mFontFamilies, style,
-                delegate.mWeight));
+        return sManager.addNewDelegate(
+                new Typeface_Delegate(delegate.mFontFamilies, style, delegate.mWeight));
     }
 
     @LayoutlibDelegate
-    /*package*/ static long nativeCreateFromTypefaceWithExactStyle(long native_instance,
-            int weight, boolean italic) {
+    /*package*/ static long nativeCreateFromTypefaceWithExactStyle(long native_instance, int weight,
+            boolean italic) {
         Typeface_Delegate delegate = sManager.getDelegate(native_instance);
         if (delegate == null) {
             delegate = sManager.getDelegate(sDefaultTypeface);
@@ -178,7 +140,8 @@
 
         int style = weight >= 600 ? (italic ? Typeface.BOLD_ITALIC : Typeface.BOLD) :
                 (italic ? Typeface.ITALIC : Typeface.NORMAL);
-        return sManager.addNewDelegate(new Typeface_Delegate(delegate.mFontFamilies, style, weight));
+        return sManager.addNewDelegate(
+                new Typeface_Delegate(delegate.mFontFamilies, style, weight));
     }
 
     @LayoutlibDelegate
@@ -262,28 +225,250 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static File getSystemFontConfigLocation() {
-        return new File(getFontLocation());
+    /*package*/ static void buildSystemFallback(String xmlPath, String fontDir,
+            ArrayMap<String, Typeface> fontMap, ArrayMap<String, FontFamily[]> fallbackMap) {
+        Typeface.buildSystemFallback_Original(getFontLocation() + "/fonts.xml", fontDir, fontMap,
+                fallbackMap);
     }
 
     @LayoutlibDelegate
-    /*package*/ static FontFamily makeFamilyFromParsed(FontConfig.Family family,
-            Map<String, ByteBuffer> bufferForPath) {
-        FontFamily fontFamily = new FontFamily(family.getLanguage(), family.getVariant());
-        for (FontConfig.Font font : family.getFonts()) {
-            String fullPathName = "/system/fonts/" + font.getFontName();
-            FontFamily_Delegate.addFont(fontFamily.mBuilderPtr, fullPathName,
-                    font.getWeight(), font.isItalic());
+    /*package*/ static FontFamily createFontFamily(String familyName, List<FontConfig.Font> fonts,
+            String[] languageTags, int variant, Map<String, ByteBuffer> cache, String fontDir) {
+        FontFamily fontFamily = new FontFamily(languageTags, variant);
+        for (FontConfig.Font font : fonts) {
+            String fullPathName = fontDir + font.getFontName();
+            FontFamily_Delegate.addFont(fontFamily.mBuilderPtr, fullPathName, font.getWeight(),
+                    font.isItalic());
         }
         fontFamily.freeze();
         return fontFamily;
     }
 
+    /**
+     * Loads a single font or font family from disk
+     */
+    @Nullable
+    public static Typeface createFromDisk(@NonNull BridgeContext context, @NonNull String path,
+            boolean isFramework) {
+        // Check if this is an asset that we've already loaded dynamically
+        Typeface typeface = Typeface.findFromCache(context.getAssets(), path);
+        if (typeface != null) {
+            return typeface;
+        }
+
+        String lowerCaseValue = path.toLowerCase();
+        if (lowerCaseValue.endsWith(SdkConstants.DOT_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(path);
+            } else {
+                File f = new File(path);
+                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 " + path, e,
+                                null /*data*/);
+                    }
+                }
+            }
+
+            if (parser != null) {
+                BridgeXmlBlockParser blockParser =
+                        new BridgeXmlBlockParser(parser, context, isFramework);
+                try {
+                    FontResourcesParser.FamilyResourceEntry entry =
+                            FontResourcesParser.parse(blockParser, context.getResources());
+                    typeface = Typeface.createFromResources(entry, context.getAssets(), path);
+                } catch (XmlPullParserException | IOException e) {
+                    Bridge.getLog().error(null, "Failed to parse file " + path, 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)", path),
+                        null /*data*/);
+            }
+        } else {
+            typeface = Typeface.createFromResources(context.getAssets(), path, 0);
+        }
+
+        return typeface;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static Typeface create(String familyName, int style) {
+        if (familyName != null && Files.exists(Paths.get(familyName))) {
+            // Workaround for b/64137851
+            // Support lib will call this method after failing to create the TypefaceCompat.
+            return Typeface_Delegate.createFromDisk(RenderAction.getCurrentContext(), familyName,
+                    false);
+        }
+        return Typeface.create_Original(familyName, style);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static Typeface create(Typeface family, int style) {
+        return Typeface.create_Original(family, style);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static Typeface create(Typeface family, int style, boolean isItalic) {
+        return Typeface.create_Original(family, style, isItalic);
+    }
+
     // ---- Private delegate/helper methods ----
 
-    public Typeface_Delegate(@NonNull FontFamily_Delegate[] fontFamilies, int style, int weight) {
-        mFontFamilies = fontFamilies;
-        mStyle = style;
-        mWeight = weight;
+    private static List<Font> computeFonts(FontVariant variant, FontFamily_Delegate[] fontFamilies,
+            int inputWeight, int inputStyle) {
+        // Calculate the required weight based on style and weight of this typeface.
+        int weight = inputWeight + 50 +
+                ((inputStyle & Font.BOLD) == 0 ? 0 : FontFamily_Delegate.BOLD_FONT_WEIGHT_DELTA);
+        if (weight > 1000) {
+            weight = 1000;
+        } else if (weight < 100) {
+            weight = 100;
+        }
+        final boolean isItalic = (inputStyle & Font.ITALIC) != 0;
+        List<Font> fonts = new ArrayList<Font>(fontFamilies.length);
+        for (int i = 0; i < fontFamilies.length; i++) {
+            FontFamily_Delegate ffd = fontFamilies[i];
+            if (ffd != null && ffd.isValid()) {
+                Font font = ffd.getFont(weight, isItalic);
+                if (font != null) {
+                    FontVariant ffdVariant = ffd.getVariant();
+                    if (ffdVariant == FontVariant.NONE) {
+                        fonts.add(font);
+                        continue;
+                    }
+                    // We cannot open each font and get locales supported, etc to match the fonts.
+                    // As a workaround, we hardcode certain assumptions like Elegant and Compact
+                    // always appear in pairs.
+                    assert i < fontFamilies.length - 1;
+                    FontFamily_Delegate ffd2 = fontFamilies[++i];
+                    assert ffd2 != null;
+                    FontVariant ffd2Variant = ffd2.getVariant();
+                    Font font2 = ffd2.getFont(weight, isItalic);
+                    assert ffd2Variant != FontVariant.NONE && ffd2Variant != ffdVariant &&
+                            font2 != null;
+                    // Add the font with the matching variant to the list.
+                    if (variant == ffd.getVariant()) {
+                        fonts.add(font);
+                    } else {
+                        fonts.add(font2);
+                    }
+                } else {
+                    // The FontFamily is valid but doesn't contain any matching font. This means
+                    // that the font failed to load. We add null to the list of fonts. Don't throw
+                    // the warning just yet. If this is a non-english font, we don't want to warn
+                    // users who are trying to render only english text.
+                    fonts.add(null);
+                }
+            }
+        }
+
+        return fonts;
+    }
+
+    /**
+     * Return an Iterable of fonts that match the style and variant. The list is ordered
+     * according to preference of fonts.
+     * <p>
+     * The Iterator may contain null when the font failed to load. If null is reached when trying to
+     * render with this list of fonts, then a warning should be logged letting the user know that
+     * some font failed to load.
+     *
+     * @param variant The variant preferred. Can only be {@link FontVariant#COMPACT} or {@link
+     * FontVariant#ELEGANT}
+     */
+    @NonNull
+    public Iterable<Font> getFonts(final FontVariant variant) {
+        assert variant != FontVariant.NONE;
+
+        return new FontsIterator(mFontFamilies, variant, mWeight, mStyle);
+    }
+
+    private static class FontsIterator implements Iterator<Font>, Iterable<Font> {
+        private final FontFamily_Delegate[] fontFamilies;
+        private final int weight;
+        private final boolean isItalic;
+        private final FontVariant variant;
+
+        private int index = 0;
+
+        private FontsIterator(@NonNull FontFamily_Delegate[] fontFamilies,
+                @NonNull FontVariant variant, int weight, int style) {
+            // Calculate the required weight based on style and weight of this typeface.
+            int boldExtraWeight =
+                    ((style & Font.BOLD) == 0 ? 0 : FontFamily_Delegate.BOLD_FONT_WEIGHT_DELTA);
+            this.weight = Math.min(Math.max(100, weight + 50 + boldExtraWeight), 1000);
+            this.isItalic = (style & Font.ITALIC) != 0;
+            this.fontFamilies = fontFamilies;
+            this.variant = variant;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return index < fontFamilies.length;
+        }
+
+        @Override
+        @Nullable
+        public Font next() {
+            FontFamily_Delegate ffd = fontFamilies[index++];
+            if (ffd == null || !ffd.isValid()) {
+                return null;
+            }
+
+            Font font = ffd.getFont(weight, isItalic);
+            if (font == null) {
+                // The FontFamily is valid but doesn't contain any matching font. This means
+                // that the font failed to load. We add null to the list of fonts. Don't throw
+                // the warning just yet. If this is a non-english font, we don't want to warn
+                // users who are trying to render only english text.
+                return null;
+            }
+
+            FontVariant ffdVariant = ffd.getVariant();
+            if (ffdVariant == FontVariant.NONE) {
+                return font;
+            }
+
+            // We cannot open each font and get locales supported, etc to match the fonts.
+            // As a workaround, we hardcode certain assumptions like Elegant and Compact
+            // always appear in pairs.
+            assert index < fontFamilies.length - 1;
+            FontFamily_Delegate ffd2 = fontFamilies[index++];
+            assert ffd2 != null;
+
+            if (ffdVariant == variant) {
+                return font;
+            }
+
+            FontVariant ffd2Variant = ffd2.getVariant();
+            Font font2 = ffd2.getFont(weight, isItalic);
+            assert ffd2Variant != FontVariant.NONE && ffd2Variant != ffdVariant && font2 != null;
+            // Add the font with the matching variant to the list.
+            return variant == ffd.getVariant() ? font : font2;
+        }
+
+        @NonNull
+        @Override
+        public Iterator<Font> iterator() {
+            return this;
+        }
+
+        @Override
+        public Spliterator<Font> spliterator() {
+            return Spliterators.spliterator(iterator(), fontFamilies.length,
+                    Spliterator.IMMUTABLE | Spliterator.SIZED);
+        }
     }
 }
diff --git a/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java b/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java
index 616784c..0063046 100644
--- a/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java
+++ b/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java
@@ -35,7 +35,9 @@
 import android.graphics.PathMeasure;
 import android.graphics.Path_Delegate;
 import android.graphics.Rect;
+import android.graphics.Region;
 import android.graphics.Region.Op;
+import android.graphics.Shader_Delegate;
 import android.util.ArrayMap;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -143,6 +145,9 @@
         VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
 
         Canvas_Delegate.nSave(canvasWrapperPtr, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
+        Canvas_Delegate.nClipRect(canvasWrapperPtr,
+                bounds.left, bounds.top, bounds.right, bounds.bottom,
+                Region.Op.INTERSECT.nativeInt);
         Canvas_Delegate.nTranslate(canvasWrapperPtr, bounds.left, bounds.top);
 
         if (needsMirroring) {
@@ -1175,10 +1180,23 @@
                     // mFillPaint can not be null at this point so we will have a delegate
                     assert fillPaintDelegate != null;
                     fillPaintDelegate.setColorFilter(filterPtr);
+
+                    Shader_Delegate shaderDelegate =
+                            Shader_Delegate.getDelegate(fullPath.mFillGradient);
+                    if (shaderDelegate != null) {
+                        // If there is a shader, apply the local transformation to make sure
+                        // the gradient is transformed to match the viewport
+                        shaderDelegate.setLocalMatrix(mFinalPathMatrix.native_instance);
+                    }
+
                     fillPaintDelegate.setShader(fullPath.mFillGradient);
                     Path_Delegate.nSetFillType(mRenderPath.mNativePath, fullPath.mFillType);
                     BaseCanvas_Delegate.nDrawPath(canvasPtr, mRenderPath.mNativePath, fillPaint
                             .getNativeInstance());
+                    if (shaderDelegate != null) {
+                        // Remove the local matrix
+                        shaderDelegate.setLocalMatrix(0);
+                    }
                 }
 
                 if (fullPath.mStrokeColor != Color.TRANSPARENT) {
diff --git a/bridge/src/android/os/Binder_Delegate.java b/bridge/src/android/os/Binder_Delegate.java
new file mode 100644
index 0000000..03596de
--- /dev/null
+++ b/bridge/src/android/os/Binder_Delegate.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 android.os;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import libcore.util.NativeAllocationRegistry_Delegate;
+
+/**
+ * Delegate overriding selected methods of android.os.Binder
+ *
+ * Through the layoutlib_create tool, selected methods of Binder have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ *
+ */
+public class Binder_Delegate {
+
+    // ---- delegate manager ----
+    private static final DelegateManager<Binder_Delegate> sManager =
+            new DelegateManager<>(Binder_Delegate.class);
+    private static long sFinalizer = -1;
+
+    @LayoutlibDelegate
+    /*package*/ static long getNativeBBinderHolder() {
+        return sManager.addNewDelegate(new Binder_Delegate());
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long getNativeFinalizer() {
+        synchronized (Binder_Delegate.class) {
+            if (sFinalizer == -1) {
+                sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
+                        sManager::removeJavaReferenceFor);
+            }
+        }
+        return sFinalizer;
+    }
+}
diff --git a/bridge/src/android/text/AndroidBidi_Delegate.java b/bridge/src/android/text/AndroidBidi_Delegate.java
deleted file mode 100644
index 38171dc..0000000
--- a/bridge/src/android/text/AndroidBidi_Delegate.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2011 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.text;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.icu.text.Bidi;
-
-/**
- * Delegate used to provide new implementation for the native methods of {@link AndroidBidi}
- *
- * Through the layoutlib_create tool, the original  methods of AndroidBidi have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- */
-public class AndroidBidi_Delegate {
-
-    @LayoutlibDelegate
-    /*package*/ static int runBidi(int dir, char[] chars, byte[] charInfo, int count,
-            boolean haveInfo) {
-
-        switch (dir) {
-        case 0: // Layout.DIR_REQUEST_LTR
-            dir = Bidi.LTR;
-            break;
-        case 1: // Layout.DIR_REQUEST_RTL
-            dir = Bidi.RTL;
-            break;
-        case -1: // Layout.DIR_REQUEST_DEFAULT_RTL
-            dir = Bidi.LEVEL_DEFAULT_RTL;
-            break;
-        case -2: // Layout.DIR_REQUEST_DEFAULT_LTR
-            dir = Bidi.LEVEL_DEFAULT_LTR;
-            break;
-        default:
-            // Invalid code. Log error, assume LEVEL_DEFAULT_LTR and continue.
-            Bridge.getLog().error(LayoutLog.TAG_BROKEN, "Invalid direction flag", null);
-            dir = Bidi.LEVEL_DEFAULT_LTR;
-        }
-        Bidi bidi = new Bidi(chars, 0, null, 0, count, dir);
-        if (charInfo != null) {
-            for (int i = 0; i < count; ++i)
-            charInfo[i] = bidi.getLevelAt(i);
-        }
-        return bidi.getParaLevel();
-    }
-}
diff --git a/bridge/src/android/text/Hyphenator_Delegate.java b/bridge/src/android/text/Hyphenator_Delegate.java
deleted file mode 100644
index 499e58a..0000000
--- a/bridge/src/android/text/Hyphenator_Delegate.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2015 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.text;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import java.io.File;
-import java.nio.ByteBuffer;
-
-/**
- * Delegate that overrides implementation for certain methods in {@link android.text.Hyphenator}
- * <p/>
- * Through the layoutlib_create tool, selected methods of Hyphenator have been replaced
- * by calls to methods of the same name in this delegate class.
- */
-public class Hyphenator_Delegate {
-
-    private static final DelegateManager<Hyphenator_Delegate> sDelegateManager = new
-            DelegateManager<Hyphenator_Delegate>(Hyphenator_Delegate.class);
-
-    @LayoutlibDelegate
-    /*package*/ static File getSystemHyphenatorLocation() {
-        // FIXME
-        return null;
-    }
-
-    /*package*/ @SuppressWarnings("UnusedParameters")  // TODO implement this.
-    static long loadHyphenator(ByteBuffer buffer, int offset, int minPrefix, int minSuffix) {
-        return sDelegateManager.addNewDelegate(new Hyphenator_Delegate());
-    }
-}
diff --git a/bridge/src/android/text/StaticLayout_Delegate.java b/bridge/src/android/text/StaticLayout_Delegate.java
index 7c59f38..ca8743c 100644
--- a/bridge/src/android/text/StaticLayout_Delegate.java
+++ b/bridge/src/android/text/StaticLayout_Delegate.java
@@ -4,16 +4,17 @@
 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.graphics.BidiRenderer;
 import android.graphics.Paint;
 import android.graphics.Paint_Delegate;
 import android.graphics.RectF;
 import android.icu.text.BreakIterator;
-import android.icu.util.ULocale;
+import android.text.Layout.BreakStrategy;
+import android.text.Layout.HyphenationFrequency;
 import android.text.Primitive.PrimitiveType;
 import android.text.StaticLayout.LineBreaks;
 
-import java.nio.ByteBuffer;
 import java.text.CharacterIterator;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -40,107 +41,82 @@
         new DelegateManager<Builder>(Builder.class);
 
     @LayoutlibDelegate
-    /*package*/ static long nNewBuilder() {
-        return sBuilderManager.addNewDelegate(new Builder());
+    /*package*/ static long nInit(
+            @BreakStrategy int breakStrategy,
+            @HyphenationFrequency int hyphenationFrequency,
+            boolean isJustified,
+            @Nullable int[] indents,
+            @Nullable int[] leftPaddings,
+            @Nullable int[] rightPaddings) {
+        Builder builder = new Builder();
+        builder.mBreakStrategy = breakStrategy;
+        return sBuilderManager.addNewDelegate(builder);
     }
 
     @LayoutlibDelegate
-    /*package*/ static void nFreeBuilder(long nativeBuilder) {
-        sBuilderManager.removeJavaReferenceFor(nativeBuilder);
+    /*package*/ static void nFinish(long nativePtr) {
+        sBuilderManager.removeJavaReferenceFor(nativePtr);
     }
 
     @LayoutlibDelegate
-    /*package*/ static void nFinishBuilder(long nativeBuilder) {
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static long nLoadHyphenator(ByteBuffer buf, int offset, int minPrefix,
-            int minSuffix) {
-        return Hyphenator_Delegate.loadHyphenator(buf, offset, minPrefix, minSuffix);
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static void nSetLocales(long nativeBuilder, String locales,
-            long[] nativeHyphenators) {
-        Builder builder = sBuilderManager.getDelegate(nativeBuilder);
-        if (builder != null) {
-            builder.mLocales = locales;
-            builder.mNativeHyphenators = nativeHyphenators;
-        }
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static void nSetIndents(long nativeBuilder, int[] indents) {
-        // TODO.
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static void nSetupParagraph(long nativeBuilder, char[] text, int length,
-            float firstWidth, int firstWidthLineCount, float restWidth,
-            int[] variableTabStops, int defaultTabStop, int breakStrategy,
-            int hyphenationFrequency, boolean isJustified) {
-        // TODO: implement justified alignment
+    /*package*/ static void nAddStyleRun(long nativeBuilder, long nativePaint, int start,
+            int end, boolean isRtl) {
         Builder builder = sBuilderManager.getDelegate(nativeBuilder);
         if (builder == null) {
             return;
         }
+        builder.mRuns.add(new StyleRun(nativePaint, start, end, isRtl));
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nAddReplacementRun(long nativeBuilder, long nativePaint, int start,
+            int end, float width) {
+        Builder builder = sBuilderManager.getDelegate(nativeBuilder);
+        if (builder == null) {
+            return;
+        }
+        builder.mRuns.add(new ReplacementRun(start, end, width));
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static int nComputeLineBreaks(
+            /* non zero */ long nativePtr,
+
+            // Inputs
+            @NonNull char[] text,
+            int length,
+            float firstWidth,
+            int firstWidthLineCount,
+            float restWidth,
+            @Nullable int[] variableTabStops,
+            int defaultTabStop,
+            int indentsOffset,
+
+            // Outputs
+            @NonNull LineBreaks recycle,
+            int recycleLength,
+            @NonNull int[] recycleBreaks,
+            @NonNull float[] recycleWidths,
+            @NonNull float[] recycleAscents,
+            @NonNull float[] recycleDescents,
+            @NonNull int[] recycleFlags,
+            @NonNull float[] charWidths) {
+        Builder builder = sBuilderManager.getDelegate(nativePtr);
+        if (builder == null) {
+            return 0;
+        }
 
         builder.mText = text;
         builder.mWidths = new float[length];
         builder.mLineWidth = new LineWidth(firstWidth, firstWidthLineCount, restWidth);
         builder.mTabStopCalculator = new TabStops(variableTabStops, defaultTabStop);
-    }
 
-    @LayoutlibDelegate
-    /*package*/ static float nAddStyleRun(long nativeBuilder, long nativePaint, long nativeTypeface,
-            int start, int end, boolean isRtl) {
-        Builder builder = sBuilderManager.getDelegate(nativeBuilder);
-
-        int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
-        return builder == null ? 0 :
-                measureText(nativePaint, builder.mText, start, end - start, builder.mWidths,
-                        bidiFlags);
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static void nAddMeasuredRun(long nativeBuilder, int start, int end, float[] widths) {
-        Builder builder = sBuilderManager.getDelegate(nativeBuilder);
-        if (builder != null) {
-            System.arraycopy(widths, start, builder.mWidths, start, end - start);
-        }
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static void nAddReplacementRun(long nativeBuilder, int start, int end, float width) {
-        Builder builder = sBuilderManager.getDelegate(nativeBuilder);
-        if (builder == null) {
-            return;
-        }
-        builder.mWidths[start] = width;
-        Arrays.fill(builder.mWidths, start + 1, end, 0.0f);
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static void nGetWidths(long nativeBuilder, float[] floatsArray) {
-        Builder builder = sBuilderManager.getDelegate(nativeBuilder);
-        if (builder != null) {
-            System.arraycopy(builder.mWidths, 0, floatsArray, 0, builder.mWidths.length);
-        }
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static int nComputeLineBreaks(long nativeBuilder,
-            LineBreaks recycle, int[] recycleBreaks, float[] recycleWidths,
-            int[] recycleFlags, int recycleLength) {
-
-        Builder builder = sBuilderManager.getDelegate(nativeBuilder);
-        if (builder == null) {
-            return 0;
+        for (Run run: builder.mRuns) {
+            run.addTo(builder);
         }
 
         // compute all possible breakpoints.
-        int length = builder.mWidths.length;
-        BreakIterator it = BreakIterator.getLineInstance(new ULocale(builder.mLocales));
+        BreakIterator it = BreakIterator.getLineInstance();
         it.setText((CharacterIterator) new Segment(builder.mText, 0, length));
 
         // average word length in english is 5. So, initialize the possible breaks with a guess.
@@ -171,6 +147,7 @@
                         builder.mTabStopCalculator);
         }
         builder.mLineBreaker.computeBreaks(recycle);
+        System.arraycopy(builder.mWidths, 0, charWidths, 0, builder.mWidths.length);
         return recycle.breaks.length;
     }
 
@@ -228,13 +205,57 @@
      * Java representation of the native Builder class.
      */
     private static class Builder {
-        String mLocales;
         char[] mText;
         float[] mWidths;
-        LineBreaker mLineBreaker;
-        long[] mNativeHyphenators;
-        int mBreakStrategy;
-        LineWidth mLineWidth;
-        TabStops mTabStopCalculator;
+        private LineBreaker mLineBreaker;
+        private int mBreakStrategy;
+        private LineWidth mLineWidth;
+        private TabStops mTabStopCalculator;
+        private ArrayList<Run> mRuns = new ArrayList<>();
+    }
+
+    private abstract static class Run {
+        int mStart;
+        int mEnd;
+
+        Run(int start, int end) {
+            mStart = start;
+            mEnd = end;
+        }
+
+        abstract void addTo(Builder builder);
+    }
+
+    private static class StyleRun extends Run {
+        private long mNativePaint;
+        private boolean mIsRtl;
+
+        private StyleRun(long nativePaint, int start, int end, boolean isRtl) {
+            super(start, end);
+            mNativePaint = nativePaint;
+            mIsRtl = isRtl;
+        }
+
+        @Override
+        void addTo(Builder builder) {
+            int bidiFlags = mIsRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
+            measureText(mNativePaint, builder.mText, mStart, mEnd - mStart, builder.mWidths,
+                    bidiFlags);
+        }
+    }
+
+    private static class ReplacementRun extends Run {
+        private final float mWidth;
+
+        private ReplacementRun(int start, int end, float width) {
+            super(start, end);
+            mWidth = width;
+        }
+
+        @Override
+        void addTo(Builder builder) {
+            builder.mWidths[mStart] = mWidth;
+            Arrays.fill(builder.mWidths, mStart + 1, mEnd, 0.0f);
+        }
     }
 }
diff --git a/bridge/src/android/view/IWindowManagerImpl.java b/bridge/src/android/view/IWindowManagerImpl.java
index b34dfbf..6c006ca 100644
--- a/bridge/src/android/view/IWindowManagerImpl.java
+++ b/bridge/src/android/view/IWindowManagerImpl.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.app.IAssistDataReceiver;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.GraphicBuffer;
@@ -29,7 +30,6 @@
 import android.os.RemoteException;
 import android.util.DisplayMetrics;
 
-import com.android.internal.app.IAssistScreenshotReceiver;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.policy.IShortcutService;
@@ -261,7 +261,7 @@
     }
 
     @Override
-    public boolean requestAssistScreenshot(IAssistScreenshotReceiver receiver)
+    public boolean requestAssistScreenshot(IAssistDataReceiver receiver)
             throws RemoteException {
         // TODO Auto-generated method stub
         return false;
@@ -507,7 +507,7 @@
         throws RemoteException {}
 
     @Override
-    public void createInputConsumer(String name, InputChannel inputChannel)
+    public void createInputConsumer(IBinder token, String name, InputChannel inputChannel)
             throws RemoteException {}
 
     @Override
diff --git a/bridge/src/android/view/MenuInflater_Delegate.java b/bridge/src/android/view/MenuInflater_Delegate.java
index 08a97d6..977a2a7 100644
--- a/bridge/src/android/view/MenuInflater_Delegate.java
+++ b/bridge/src/android/view/MenuInflater_Delegate.java
@@ -42,7 +42,6 @@
  * ViewInfo}, we check the corresponding view key in the menu item for the view and add it
  */
 public class MenuInflater_Delegate {
-
     @LayoutlibDelegate
     /*package*/ static void registerMenu(MenuInflater thisInflater, MenuItem menuItem,
             AttributeSet attrs) {
@@ -56,10 +55,15 @@
                 return;
             }
         }
-        // This means that Bridge did not take over the instantiation of some object properly.
-        // This is most likely a bug in the LayoutLib code.
-        Bridge.getLog().warning(LayoutLog.TAG_BROKEN,
-                "Action Bar Menu rendering may be incorrect.", null);
+
+        if (menuItem == null || !menuItem.getClass().getName().startsWith("android.support.")) {
+            // This means that Bridge did not take over the instantiation of some object properly.
+            // This is most likely a bug in the LayoutLib code.
+            // We suppress this error for AppCompat menus since we do not support them in the menu
+            // editor yet.
+            Bridge.getLog().warning(LayoutLog.TAG_BROKEN,
+                    "Action Bar Menu rendering may be incorrect.", null);
+        }
 
     }
 
diff --git a/bridge/src/android/view/RectShadowPainter.java b/bridge/src/android/view/RectShadowPainter.java
index 5665d4f..88771a7 100644
--- a/bridge/src/android/view/RectShadowPainter.java
+++ b/bridge/src/android/view/RectShadowPainter.java
@@ -19,8 +19,10 @@
 import com.android.layoutlib.bridge.impl.GcSnapshot;
 import com.android.layoutlib.bridge.impl.ResourceHelper;
 
+import android.graphics.BaseCanvas_Delegate;
 import android.graphics.Canvas;
 import android.graphics.Canvas_Delegate;
+import android.graphics.Color;
 import android.graphics.LinearGradient;
 import android.graphics.Outline;
 import android.graphics.Paint;
@@ -46,7 +48,8 @@
     private static final int END_COLOR = ResourceHelper.getColor("#03000000");
     private static final float PERPENDICULAR_ANGLE = 90f;
 
-    public static void paintShadow(Outline viewOutline, float elevation, Canvas canvas) {
+    public static void paintShadow(Outline viewOutline, float elevation, Canvas canvas,
+            float alpha) {
         Rect outline = new Rect();
         if (!viewOutline.getRect(outline)) {
             assert false : "Outline is not a rect shadow";
@@ -74,9 +77,16 @@
             edgePaint.setAntiAlias(false);
             float outerArcRadius = radius + shadowSize;
             int[] colors = {START_COLOR, START_COLOR, END_COLOR};
+            if (alpha != 1f) {
+                // Correct colors using the given component alpha
+                for (int i = 0; i < colors.length; i++) {
+                    colors[i] = Color.argb((int) (Color.alpha(colors[i]) * alpha), Color.red(colors[i]),
+                            Color.green(colors[i]), Color.blue(colors[i]));
+                }
+            }
             cornerPaint.setShader(new RadialGradient(0, 0, outerArcRadius, colors,
                     new float[]{0f, radius / outerArcRadius, 1f}, TileMode.CLAMP));
-            edgePaint.setShader(new LinearGradient(0, 0, -shadowSize, 0, START_COLOR, END_COLOR,
+            edgePaint.setShader(new LinearGradient(0, 0, -shadowSize, 0, colors[0], colors[2],
                     TileMode.CLAMP));
             Path path = new Path();
             path.setFillType(FillType.EVEN_ODD);
@@ -184,7 +194,8 @@
     /**
      * Differs from {@link RectF#isEmpty()} as this first converts the rect to int and then checks.
      * <p/>
-     * This is required because {@link Canvas_Delegate#native_drawRect(long, float, float, float,
+     * This is required because {@link BaseCanvas_Delegate#native_drawRect(long, float, float,
+     * float,
      * float, long)} casts the co-ordinates to int and we want to ensure that it doesn't end up
      * drawing empty rectangles, which results in IllegalArgumentException.
      */
diff --git a/bridge/src/android/view/ShadowPainter.java b/bridge/src/android/view/ShadowPainter.java
index f09fffd..788c6c3 100644
--- a/bridge/src/android/view/ShadowPainter.java
+++ b/bridge/src/android/view/ShadowPainter.java
@@ -41,14 +41,16 @@
      * @param source the source image
      * @param shadowSize the size of the shadow, normally {@link #SHADOW_SIZE or {@link
      * #SMALL_SHADOW_SIZE}}
+     * @param alpha alpha value to apply to the shadow
      *
      * @return an image with the shadow painted in or the source image if shadowSize <= 1
      */
     @NonNull
-    public static BufferedImage createDropShadow(BufferedImage source, int shadowSize) {
+    public static BufferedImage createDropShadow(BufferedImage source, int shadowSize, float
+            alpha) {
         shadowSize /= 2; // make shadow size have the same meaning as in the other shadow paint methods in this class
 
-        return createDropShadow(source, shadowSize, 0.7f, 0);
+        return createDropShadow(source, shadowSize, 0.7f * alpha, 0);
     }
 
     /**
diff --git a/bridge/src/android/view/ViewGroup_Delegate.java b/bridge/src/android/view/ViewGroup_Delegate.java
index 4b760a7..6daae20 100644
--- a/bridge/src/android/view/ViewGroup_Delegate.java
+++ b/bridge/src/android/view/ViewGroup_Delegate.java
@@ -67,12 +67,12 @@
             Outline outline) {
         float elevation = getElevation(child, parent);
         if(outline.mMode == Outline.MODE_ROUND_RECT && outline.mRect != null) {
-            RectShadowPainter.paintShadow(outline, elevation, canvas);
+            RectShadowPainter.paintShadow(outline, elevation, canvas, child.getAlpha());
             return;
         }
         BufferedImage shadow = null;
         if (outline.mPath != null) {
-            shadow = getPathShadow(outline, canvas, elevation);
+            shadow = getPathShadow(outline, canvas, elevation, child.getAlpha());
         }
         if (shadow == null) {
             return;
@@ -91,7 +91,8 @@
         return child.getZ() - parent.getZ();
     }
 
-    private static BufferedImage getPathShadow(Outline outline, Canvas canvas, float elevation) {
+    private static BufferedImage getPathShadow(Outline outline, Canvas canvas, float elevation,
+            float alpha) {
         Rect clipBounds = canvas.getClipBounds();
         if (clipBounds.isEmpty()) {
           return null;
@@ -101,7 +102,7 @@
         Graphics2D graphics = image.createGraphics();
         graphics.draw(Path_Delegate.getDelegate(outline.mPath.mNativePath).getJavaShape());
         graphics.dispose();
-        return ShadowPainter.createDropShadow(image, (int) elevation);
+        return ShadowPainter.createDropShadow(image, (int) elevation, alpha);
     }
 
     // Copied from android.view.View#draw(Canvas, ViewGroup, long) and removed code paths
diff --git a/bridge/src/com/android/layoutlib/bridge/Bridge.java b/bridge/src/com/android/layoutlib/bridge/Bridge.java
index 93fd005..5dca8e7 100644
--- a/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -56,6 +56,7 @@
 import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.WeakHashMap;
 import java.util.concurrent.locks.ReentrantLock;
 
 import libcore.io.MemoryMappedFile_Delegate;
@@ -104,10 +105,9 @@
     private final static DynamicIdMap sDynamicIds = new DynamicIdMap(DYNAMIC_ID_SEED_START);
 
     private final static Map<Object, Map<String, SoftReference<Bitmap>>> sProjectBitmapCache =
-            new HashMap<>();
+            new WeakHashMap<>();
     private final static Map<Object, Map<String, SoftReference<NinePatchChunk>>> sProject9PatchCache =
-
-            new HashMap<>();
+            new WeakHashMap<>();
 
     private final static Map<String, SoftReference<Bitmap>> sFrameworkBitmapCache = new HashMap<>();
     private final static Map<String, SoftReference<NinePatchChunk>> sFramework9PatchCache =
@@ -355,6 +355,8 @@
         // dispose of the default typeface.
         Typeface_Delegate.resetDefaults();
         Typeface.sDynamicTypefaceCache.evictAll();
+        sProject9PatchCache.clear();
+        sProjectBitmapCache.clear();
 
         return true;
     }
@@ -397,7 +399,7 @@
             // get the real cause of the exception.
             Throwable t2 = t;
             while (t2.getCause() != null) {
-                t2 = t.getCause();
+                t2 = t2.getCause();
             }
             return new BridgeRenderSession(null,
                     ERROR_UNKNOWN.createResult(t2.getMessage(), t));
diff --git a/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 4c6c9d4..4a75be9 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -40,7 +40,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.Notification;
 import android.app.SystemServiceRegistry_Accessor;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -86,7 +85,6 @@
 import android.view.BridgeInflater;
 import android.view.Display;
 import android.view.DisplayAdjustments;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
@@ -610,45 +608,35 @@
 
     @Override
     public Object getSystemService(String service) {
-        if (LAYOUT_INFLATER_SERVICE.equals(service)) {
-            return mBridgeInflater;
+        switch (service) {
+            case LAYOUT_INFLATER_SERVICE:
+                return mBridgeInflater;
+
+            case TEXT_SERVICES_MANAGER_SERVICE:
+                // we need to return a valid service to avoid NPE
+                return TextServicesManager.getInstance();
+
+            case WINDOW_SERVICE:
+                return mWindowManager;
+
+            case POWER_SERVICE:
+                return new PowerManager(this, new BridgePowerManager(), new Handler());
+
+            case DISPLAY_SERVICE:
+                return mDisplayManager;
+
+            case ACCESSIBILITY_SERVICE:
+                return AccessibilityManager.getInstance(this);
+
+            case INPUT_METHOD_SERVICE:  // needed by SearchView
+            case AUTOFILL_MANAGER_SERVICE:
+            case AUDIO_SERVICE:
+            case TEXT_CLASSIFICATION_SERVICE:
+                return null;
+            default:
+                assert false : "Unsupported Service: " + service;
         }
 
-        if (TEXT_SERVICES_MANAGER_SERVICE.equals(service)) {
-            // we need to return a valid service to avoid NPE
-            return TextServicesManager.getInstance();
-        }
-
-        if (WINDOW_SERVICE.equals(service)) {
-            return mWindowManager;
-        }
-
-        // needed by SearchView
-        if (INPUT_METHOD_SERVICE.equals(service)) {
-            return null;
-        }
-
-        if (POWER_SERVICE.equals(service)) {
-            return new PowerManager(this, new BridgePowerManager(), new Handler());
-        }
-
-        if (DISPLAY_SERVICE.equals(service)) {
-            return mDisplayManager;
-        }
-
-        if (ACCESSIBILITY_SERVICE.equals(service)) {
-            return AccessibilityManager.getInstance(this);
-        }
-
-        if (AUTOFILL_MANAGER_SERVICE.equals(service)) {
-            return null;
-        }
-
-        if (AUDIO_SERVICE.equals(service)) {
-            return null;
-        }
-
-        assert false : "Unsupported Service: " + service;
         return null;
     }
 
@@ -657,13 +645,13 @@
         return SystemServiceRegistry_Accessor.getSystemServiceName(serviceClass);
     }
 
-    @Override
-    public final BridgeTypedArray obtainStyledAttributes(int[] attrs) {
-        return obtainStyledAttributes(0, attrs);
-    }
 
-    @Override
-    public final BridgeTypedArray obtainStyledAttributes(int resId, int[] attrs)
+    /**
+     * Same as Context#obtainStyledAttributes. We do not override the base method to give the
+     * original Context the chance to override the theme when needed.
+     */
+    @Nullable
+    public final BridgeTypedArray internalObtainStyledAttributes(int resId, int[] attrs)
             throws Resources.NotFoundException {
         StyleResourceValue style = null;
         // get the StyleResourceValue based on the resId;
@@ -715,13 +703,12 @@
         return typeArrayAndPropertiesPair.getFirst();
     }
 
-    @Override
-    public final BridgeTypedArray obtainStyledAttributes(AttributeSet set, int[] attrs) {
-        return obtainStyledAttributes(set, attrs, 0, 0);
-    }
-
-    @Override
-    public BridgeTypedArray obtainStyledAttributes(AttributeSet set, int[] attrs,
+    /**
+     * Same as Context#obtainStyledAttributes. We do not override the base method to give the
+     * original Context the chance to override the theme when needed.
+     */
+    @Nullable
+    public BridgeTypedArray internalObtainStyledAttributes(@Nullable AttributeSet set, int[] attrs,
             int defStyleAttr, int defStyleRes) {
 
         PropertiesMap defaultPropMap = null;
diff --git a/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java b/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
index 98937ef..37dc166 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
@@ -26,12 +26,11 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ChangedPackages;
-import android.content.pm.InstantAppInfo;
 import android.content.pm.FeatureInfo;
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageDeleteObserver;
-import android.content.pm.IPackageInstallObserver;
 import android.content.pm.IPackageStatsObserver;
+import android.content.pm.InstantAppInfo;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.KeySet;
@@ -56,6 +55,7 @@
 import android.os.Handler;
 import android.os.UserHandle;
 import android.os.storage.VolumeInfo;
+
 import java.util.List;
 
 /**
@@ -611,11 +611,6 @@
     }
 
     @Override
-    public void installPackage(Uri packageURI, IPackageInstallObserver observer, int flags,
-            String installerPackageName) {
-    }
-
-    @Override
     public void installPackage(Uri packageURI, PackageInstallObserver observer, int flags,
             String installerPackageName) {
     }
diff --git a/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java b/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
index cdcf0ea..bc77685 100644
--- a/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
+++ b/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
@@ -26,6 +26,7 @@
 import com.android.layoutlib.bridge.android.BridgeContext;
 import com.android.layoutlib.bridge.impl.ResourceHelper;
 import com.android.resources.ResourceType;
+import com.android.tools.layoutlib.annotations.NotNull;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -33,11 +34,18 @@
 import android.graphics.drawable.Drawable;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
 import android.view.View;
 import android.widget.FrameLayout;
 
+import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.util.List;
+
+import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX;
+import static com.android.resources.ResourceType.MENU;
 
 
 /**
@@ -50,6 +58,7 @@
     private static final String WINDOW_ACTION_BAR_CLASS = "android.support.v7.internal.app.WindowDecorActionBar";
     // This is used on v23.1.1 and later.
     private static final String WINDOW_ACTION_BAR_CLASS_NEW = "android.support.v7.app.WindowDecorActionBar";
+
     private Class<?> mWindowActionBarClass;
 
     /**
@@ -90,6 +99,7 @@
                     constructorParams, constructorArgs);
             mWindowActionBarClass = mWindowDecorActionBar == null ? null :
                     mWindowDecorActionBar.getClass();
+            inflateMenus();
             setupActionBar();
         } catch (Exception e) {
             Bridge.getLog().warning(LayoutLog.TAG_BROKEN,
@@ -165,6 +175,51 @@
         }
     }
 
+    private void inflateMenus() {
+        List<String> menuNames = getCallBack().getMenuIdNames();
+        if (menuNames.isEmpty()) {
+            return;
+        }
+
+        if (menuNames.size() > 1) {
+            // Supporting multiple menus means that we would need to instantiate our own supportlib
+            // MenuInflater instances using reflection
+            Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                    "Support Toolbar does not currently support multiple menus in the preview.",
+                    null, null, null);
+        }
+
+        String name = menuNames.get(0);
+        int id;
+        if (name.startsWith(ANDROID_NS_NAME_PREFIX)) {
+            // Framework menu.
+            name = name.substring(ANDROID_NS_NAME_PREFIX.length());
+            id = mBridgeContext.getFrameworkResourceValue(MENU, name, -1);
+        } else {
+            // Project menu.
+            id = mBridgeContext.getProjectResourceValue(MENU, name, -1);
+        }
+        if (id < 1) {
+            return;
+        }
+        // Get toolbar decorator
+        Object mDecorToolbar = getFieldValue(mWindowDecorActionBar, "mDecorToolbar");
+        if (mDecorToolbar == null) {
+            return;
+        }
+
+        Class<?> mDecorToolbarClass = mDecorToolbar.getClass();
+        Context themedContext = (Context)invoke(
+                getMethod(mWindowActionBarClass, "getThemedContext"),
+                mWindowDecorActionBar);
+        MenuInflater inflater = new MenuInflater(themedContext);
+        Menu menuBuilder = (Menu)invoke(getMethod(mDecorToolbarClass, "getMenu"), mDecorToolbar);
+        inflater.inflate(id, menuBuilder);
+
+        // Set the actual menu
+        invoke(findMethod(mDecorToolbarClass, "setMenu"), mDecorToolbar, menuBuilder, null);
+    }
+
     @Override
     public void createMenuPopup() {
         // it's hard to add menus to appcompat's actionbar, since it'll use a lot of reflection.
@@ -181,13 +236,53 @@
         return null;
     }
 
+    /**
+     * Same as getMethod but doesn't require the parameterTypes. This allows us to call methods
+     * without having to get all the types for the parameters when we do not need them
+     */
     @Nullable
-    private static Object invoke(Method method, Object owner, Object... args) {
+    private static Method findMethod(@Nullable Class<?> owner, @NotNull String name) {
+        if (owner == null) {
+            return null;
+        }
+        for (Method method : owner.getMethods()) {
+            if (name.equals(method.getName())) {
+                return method;
+            }
+        }
+
+        return null;
+    }
+
+    @Nullable
+    private static Object getFieldValue(@Nullable Object instance, @NotNull String name) {
+        if (instance == null) {
+            return null;
+        }
+
+        Class<?> instanceClass = instance.getClass();
+        try {
+            Field field = instanceClass.getDeclaredField(name);
+            boolean accesible = field.isAccessible();
+            if (!accesible) {
+                field.setAccessible(true);
+            }
+            try {
+                return field.get(instance);
+            } finally {
+                field.setAccessible(accesible);
+            }
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    @Nullable
+    private static Object invoke(@Nullable Method method, Object owner, Object... args) {
         try {
             return method == null ? null : method.invoke(owner, args);
-        } catch (InvocationTargetException e) {
-            e.printStackTrace();
-        } catch (IllegalAccessException e) {
+        } catch (InvocationTargetException | IllegalAccessException e) {
             e.printStackTrace();
         }
         return null;
diff --git a/bridge/src/com/android/layoutlib/bridge/bars/Config.java b/bridge/src/com/android/layoutlib/bridge/bars/Config.java
index 7f8d992..569d7b6 100644
--- a/bridge/src/com/android/layoutlib/bridge/bars/Config.java
+++ b/bridge/src/com/android/layoutlib/bridge/bars/Config.java
@@ -74,8 +74,8 @@
     }
 
     public static String getTime(int platformVersion) {
-        if (isGreaterOrEqual(platformVersion, N)) {
-            return "7:00";
+        if (isGreaterOrEqual(platformVersion, O)) {
+            return "8:00";
         }
         if (platformVersion < GINGERBREAD) {
             return "2:20";
@@ -101,6 +101,9 @@
         if (platformVersion < N) {
             return "6:00";
         }
+        if (platformVersion < O) {
+            return "7:00";
+        }
         // Should never happen.
         return "4:04";
     }
diff --git a/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java b/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
index c3b9aed..16f92f3 100644
--- a/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
+++ b/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
@@ -49,6 +49,7 @@
 import android.graphics.Rect;
 import android.graphics.Typeface;
 import android.graphics.Typeface_Accessor;
+import android.graphics.Typeface_Delegate;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -159,31 +160,9 @@
         } catch (NumberFormatException ignored) {
         }
 
-        XmlPullParser parser = null;
-        // first check if the value is a file (xml most likely)
-        Boolean psiParserSupport = context.getLayoutlibCallback().getFlag(
-                RenderParamsFlags.FLAG_KEY_XML_FILE_PARSER_SUPPORT);
-        if (psiParserSupport != null && psiParserSupport) {
-            parser = context.getLayoutlibCallback().getXmlFileParser(value);
-        }
-        if (parser == null) {
-            File f = new File(value);
-            if (f.isFile()) {
-                // let the framework inflate the color from the XML file, by
-                // providing an XmlPullParser
-                try {
-                    parser = ParserFactory.create(f);
-                } catch (XmlPullParserException | FileNotFoundException e) {
-                    Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
-                            "Failed to parse file " + value, e, null /*data*/);
-                }
-            }
-        }
-
-        if (parser != null) {
-            try {
-                BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
-                        parser, context, resValue.isFramework());
+        try {
+            BridgeXmlBlockParser blockParser = getXmlBlockParser(context, resValue);
+            if (blockParser != null) {
                 try {
                     // Advance the parser to the first element so we can detect if it's a
                     // color list or a gradient color
@@ -214,18 +193,18 @@
                 } finally {
                     blockParser.ensurePopped();
                 }
-            } catch (XmlPullParserException e) {
-                Bridge.getLog().error(LayoutLog.TAG_BROKEN,
-                        "Failed to configure parser for " + value, e, null /*data*/);
-                // we'll return null below.
-            } catch (Exception e) {
-                // this is an error and not warning since the file existence is
-                // checked before attempting to parse it.
-                Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
-                        "Failed to parse file " + value, e, null /*data*/);
-
-                return null;
             }
+        } catch (XmlPullParserException e) {
+            Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+                    "Failed to configure parser for " + value, e, null /*data*/);
+            // we'll return null below.
+        } catch (Exception e) {
+            // this is an error and not warning since the file existence is
+            // checked before attempting to parse it.
+            Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
+                    "Failed to parse file " + value, e, null /*data*/);
+
+            return null;
         }
 
         return null;
@@ -409,59 +388,8 @@
             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 {
-                    FontResourcesParser.FamilyResourceEntry entry =
-                            FontResourcesParser.parse(blockParser, context.getResources());
-                    typeface = Typeface.createFromResources(entry, 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;
+        return Typeface_Delegate.createFromDisk(context, fontName, isFramework);
     }
 
     /**
diff --git a/bridge/tests/Android.mk b/bridge/tests/Android.mk
index 1b65eee..e51d75d 100644
--- a/bridge/tests/Android.mk
+++ b/bridge/tests/Android.mk
@@ -32,7 +32,8 @@
 			sdk-common \
 			junit-host \
 			guavalib \
-			mockito-host
+			mockito-host \
+			objenesis-host
 
 include $(BUILD_HOST_JAVA_LIBRARY)
 
diff --git a/bridge/tests/res/testApp/MyApplication/build.gradle b/bridge/tests/res/testApp/MyApplication/build.gradle
index 4781660..c9058b5 100644
--- a/bridge/tests/res/testApp/MyApplication/build.gradle
+++ b/bridge/tests/res/testApp/MyApplication/build.gradle
@@ -3,7 +3,7 @@
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:1.2.3'
+        classpath 'com.android.tools.build:gradle:3.0.0-beta5'
 
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
@@ -19,12 +19,12 @@
 apply plugin: 'com.android.application'
 
 android {
-    compileSdkVersion 25
-    buildToolsVersion '25.0.0'
+    compileSdkVersion 26
+    buildToolsVersion '26.0.2'
     defaultConfig {
         applicationId 'com.android.layoutlib.test.myapplication'
         minSdkVersion 21
-        targetSdkVersion 25
+        targetSdkVersion 26
         versionCode 1
         versionName '1.0'
     }
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/androidTest/debug/com/android/layoutlib/test/myapplication/test/R.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/androidTest/debug/com/android/layoutlib/test/myapplication/test/R.class
new file mode 100644
index 0000000..a734a21
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/androidTest/debug/com/android/layoutlib/test/myapplication/test/R.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/ArraysCheckWidget.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/ArraysCheckWidget.class
index f73528a..ea2e30d 100644
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/ArraysCheckWidget.class
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/ArraysCheckWidget.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class
index 5bb04fc..be984f0 100644
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$array.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$array.class
index b87f193..d279a3e 100644
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$array.class
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$array.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$attr.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$attr.class
index e2968d4..2b98809 100644
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$attr.class
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$attr.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$color.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$color.class
index ff699d1..a73fcca 100644
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$color.class
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$color.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$dimen.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$dimen.class
index a3931b8..6f6bd54 100644
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$dimen.class
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$dimen.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$drawable.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$drawable.class
index e293677..93c5977 100644
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$drawable.class
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$drawable.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$font.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$font.class
new file mode 100644
index 0000000..a3e9926
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$font.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class
index d6268bf..e580ef5 100644
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$integer.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$integer.class
index 08b98fb..4f1a852 100644
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$integer.class
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$integer.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class
index f9be1ca..9bdc44f 100644
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class
index 6874b49..11e0686 100644
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class
index a4205a8..331ea4a 100644
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class
index 4fb3b61..3b47ac9 100644
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class
index dba67fd..e91c774 100644
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/ThemableWidget.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/ThemableWidget.class
new file mode 100644
index 0000000..8d96ac1
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/ThemableWidget.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/asset.png b/bridge/tests/res/testApp/MyApplication/golden/asset.png
new file mode 100644
index 0000000..f69b128
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/asset.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/context_theme_wrapper.png b/bridge/tests/res/testApp/MyApplication/golden/context_theme_wrapper.png
new file mode 100644
index 0000000..27bc3d8
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/context_theme_wrapper.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/remote_component_load.png b/bridge/tests/res/testApp/MyApplication/golden/remote_component_load.png
new file mode 100644
index 0000000..2920b7d
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/remote_component_load.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/remote_component_load_fail.png b/bridge/tests/res/testApp/MyApplication/golden/remote_component_load_fail.png
new file mode 100644
index 0000000..0ed85d1
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/remote_component_load_fail.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png b/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png
index 4f3ed60..926883b 100644
--- a/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png
+++ b/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/vector_drawable.png b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable.png
index 7bbae09..eddb5e6 100644
--- a/bridge/tests/res/testApp/MyApplication/golden/vector_drawable.png
+++ b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_gradient.png b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_gradient.png
new file mode 100644
index 0000000..8bb1677
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_gradient.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_multi_line_of_path_data.png b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_multi_line_of_path_data.png
index 5fc6052..ebc8a11 100644
--- a/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_multi_line_of_path_data.png
+++ b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_multi_line_of_path_data.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_radial_gradient.png b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_radial_gradient.png
new file mode 100644
index 0000000..2a3f3dd
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_radial_gradient.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.properties b/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.properties
index 3b51ffe..565238f 100644
--- a/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.properties
+++ b/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/assets/asset.png b/bridge/tests/res/testApp/MyApplication/src/main/assets/asset.png
new file mode 100644
index 0000000..0327e13
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/assets/asset.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/ThemableWidget.java b/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/ThemableWidget.java
new file mode 100644
index 0000000..10189b7
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/ThemableWidget.java
@@ -0,0 +1,40 @@
+package com.android.layoutlib.test.myapplication;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.widget.FrameLayout;
+
+public class ThemableWidget extends FrameLayout {
+    public ThemableWidget(Context context) {
+        super(context);
+
+        init();
+    }
+
+    public ThemableWidget(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        init();
+    }
+
+    public ThemableWidget(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        init();
+    }
+
+    public ThemableWidget(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+
+        init();
+    }
+
+    private void init() {
+        ContextThemeWrapper context = new ContextThemeWrapper(getContext(), getContext().getTheme());
+        context.setTheme(R.style.ThemableWidgetStyle);
+
+        LayoutInflater.from(context).inflate(R.layout.themable_widget_layout, this);
+    }
+}
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/AssetView.java b/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/AssetView.java
new file mode 100644
index 0000000..9e43d8f
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/AssetView.java
@@ -0,0 +1,35 @@
+package com.android.layoutlib.test.myapplication.widgets;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class AssetView extends View {
+    public AssetView(Context context) {
+        super(context);
+        init(context);
+    }
+
+    public AssetView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(context);
+    }
+
+    public AssetView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init(context);
+    }
+
+    private void init(Context context) {
+        try {
+            InputStream istr = context.getAssets().open("asset.png");
+            setBackground(Drawable.createFromStream(istr, null));
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/color/complex_gradient.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/color/complex_gradient.xml
new file mode 100644
index 0000000..1578192
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/color/complex_gradient.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<gradient
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:endX="200"
+    android:endY="200"
+    android:startX="100"
+    android:startY="100"
+    android:type="linear">
+    <item
+        android:color="#123"
+        android:offset="0.0" />
+    <item
+        android:color="#AAA"
+        android:offset="0.5" />
+    <item
+        android:color="#00F"
+        android:offset="1.0" />
+</gradient>
\ No newline at end of file
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/color/complex_radial_gradient.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/color/complex_radial_gradient.xml
new file mode 100644
index 0000000..ce6a3de
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/color/complex_radial_gradient.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<gradient
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:centerX="50"
+    android:centerY="50"
+    android:gradientRadius="20"
+    android:type="radial">
+    <item
+        android:color="#123"
+        android:offset="0.0" />
+    <item
+        android:color="#00F"
+        android:offset="1.0" />
+</gradient>
\ No newline at end of file
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/color/gradient.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/color/gradient.xml
index fc0afa6..371d4fe 100644
--- a/bridge/tests/res/testApp/MyApplication/src/main/res/color/gradient.xml
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/color/gradient.xml
@@ -16,9 +16,9 @@
 
 
 <gradient xmlns:android="http://schemas.android.com/apk/res/android"
-          android:startX="10"
-          android:startY="10"
-          android:endX="50"
-          android:endY="50"
+          android:startX="-20"
+          android:startY="-20"
+          android:endX="-10"
+          android:endY="-10"
           android:startColor="#ffff0000"
           android:endColor="#ff00ff00" />
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/adaptive.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/adaptive.xml
index 8f862c8..cc9c449 100644
--- a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/adaptive.xml
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/adaptive.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
-    <background android:drawable="@android:color/red" />
+    <background android:drawable="@color/red" />
     <foreground android:drawable="@drawable/headset" />
 </adaptive-icon>
\ No newline at end of file
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_line_of_path_data.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_line_of_path_data.xml
index c5b0f01..3afb7b6 100644
--- a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_line_of_path_data.xml
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_line_of_path_data.xml
@@ -1,13 +1,13 @@
 <?xml version="1.0" encoding="utf-8"?>
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
         android:height="16dp"
-        android:viewportHeight="16"
+        android:viewportHeight="20"
         android:viewportWidth="16"
         android:width="16dp">
 
     <group
         android:translateX="-2.000000"
-        android:translateY="-14.000000">
+        android:translateY="-12.000000">
         <group
             android:translateX="2.000000"
             android:translateY="14.000000">
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_path.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_path.xml
index 0998b25..6f4a350 100644
--- a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_path.xml
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_path.xml
@@ -1,22 +1,60 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:height="76dp"
-        android:width="76dp"
-        android:viewportHeight="48"
-        android:viewportWidth="48"
+        android:height="50dp"
+        android:width="50dp"
+        android:viewportHeight="100"
+        android:viewportWidth="100"
         android:alpha="0.6">
 
     <group
+        android:name="clipBackground">
+        <!--
+        Any content outside this box, should not be there as it will be outside the viewport
+        -->
+        <path
+            android:pathData="M0,0h100v100H0z"
+            android:fillColor="#301BB0D3"/>
+
+        <!--
+        So this box shouldn't be visible
+        -->
+        <path
+            android:pathData="M0,-10h10v10H0z"
+            android:strokeWidth="1"
+            android:strokeColor="#000"
+            android:fillColor="#F00"/>
+
+        <!--
+        This one should be partially visible
+        -->
+        <path
+            android:pathData="M80,-10h10v20h-10z"
+            android:strokeWidth="1"
+            android:strokeColor="#000"
+            android:fillColor="#FF0"/>
+
+    </group>
+
+    <group
         android:name="root"
-        android:translateX="24.0"
-        android:translateY="24.0">
+        android:translateX="50.0"
+        android:translateY="50.0"
+        android:scaleX="1.2"
+        android:scaleY="1.2">
+
+        <path
+            android:strokeWidth="2"
+            android:strokeColor="#FFFF00"
+            android:pathData="M-10,-10 m20,0 m0,20 m-20,0 m0,-20z"
+            android:fillColor="#FEBEBE"
+        />
+
         <!--
             This is the same as the material indeterminate progressbar which involves drawing
             several cubic segments
         -->
         <path
-            android:pathData="M0, 0 m 0, -19 a 19,19 0 1,1 0,38 a 19,19 0 1,1 0,-38"
+            android:pathData="M0, 0 m 0, -30 a 10,10 0 1,1 0,38 a 10,10 0 1,1 0,-38"
             android:strokeColor="#00FF00"
             android:strokeLineCap="square"
             android:strokeLineJoin="miter"
@@ -25,13 +63,13 @@
             android:trimPathStart="0.3" />
         <!-- Same figure with reversed end and start -->
         <path
-            android:pathData="M0, 0 m 0, -12 a 19,19 0 1,1 0,38 a 19,19 0 1,1 0,-38"
+            android:pathData="M0, 0 m 0, -12 a 10,10 0 1,1 0,38 a 10,10 0 1,1 0,-38"
             android:strokeColor="#FFFF00"
             android:strokeLineCap="square"
             android:strokeLineJoin="miter"
             android:strokeWidth="1"
             android:trimPathEnd="0.3"
-            android:trimPathStart="0.8" />
+            android:trimPathStart="0.5" />
 
         <!--
             Draw a few partial quadratic segments
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/radial_gradient.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/radial_gradient.xml
new file mode 100644
index 0000000..3ab9d72
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/radial_gradient.xml
@@ -0,0 +1,28 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportHeight="100"
+        android:viewportWidth="100">
+    <path
+        android:pathData="m100,0 0,100 -100,0 0,-100z"
+        android:strokeColor="#F00000"
+        android:strokeWidth="1"
+        android:fillColor="@color/complex_radial_gradient">
+    </path>
+</vector>
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/shadow.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/shadow.xml
new file mode 100644
index 0000000..67824fd
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/shadow.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="300dp"
+        android:height="300dp"
+        android:viewportHeight="200"
+        android:viewportWidth="200">
+    <path
+        android:pathData="m200,0 0,200 -200,0 0,-200z"
+        android:strokeColor="#F00000"
+        android:strokeWidth="1"
+        android:fillColor="@color/complex_gradient">
+    </path>
+</vector>
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/layout/shadows_test.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/layout/shadows_test.xml
index 59dbbec..1d64b23 100644
--- a/bridge/tests/res/testApp/MyApplication/src/main/res/layout/shadows_test.xml
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/layout/shadows_test.xml
@@ -94,4 +94,31 @@
             android:stateListAnimator="@null"/>
 
     </RelativeLayout>
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_weight="1">
+
+        <Button
+            android:layout_marginLeft="40dp"
+            android:layout_width="48dp"
+            android:layout_height="40dp"
+            android:layout_alignParentLeft="true"
+            android:layout_centerVertical="true"
+            android:elevation="12dp"
+            android:stateListAnimator="@null"
+            android:alpha="0.1"/>
+
+        <Button
+            android:layout_marginRight="40dp"
+            android:layout_width="48dp"
+            android:layout_height="40dp"
+            android:layout_alignParentRight="true"
+            android:layout_centerVertical="true"
+            android:elevation="12dp"
+            android:stateListAnimator="@null"
+            android:alpha="0.5"/>
+
+    </RelativeLayout>
 </LinearLayout>
\ No newline at end of file
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/layout/themable_widget_layout.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/layout/themable_widget_layout.xml
new file mode 100644
index 0000000..507bba6
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/layout/themable_widget_layout.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:background="#00F"
+    android:layout_width="50dp"/>
+<!-- Intentionally omitting height since it will be added via themeing -->
\ No newline at end of file
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/values/arrays.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/values/arrays.xml
index 5f58d39..1059d2e 100644
--- a/bridge/tests/res/testApp/MyApplication/src/main/res/values/arrays.xml
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/values/arrays.xml
@@ -17,7 +17,7 @@
         <!-- theme ref in android NS. value = @string/candidates_style = <u>candidates</u> -->
         <item>?android:attr/candidatesTextStyleSpans</item>
         <item>@android:string/unknownName</item>  <!-- value = Unknown -->
-        <item>?EC</item>
+        <item>\?EC</item>
     </string-array>
 
     <!-- resources that the above array can refer to -->
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/values/colors.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/values/colors.xml
new file mode 100644
index 0000000..4754690
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/values/colors.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="red">#F00</color>
+</resources>
\ No newline at end of file
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml
index debe33b..f598fda 100644
--- a/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml
@@ -5,4 +5,8 @@
         <item name="myattr">@integer/ten</item>
     </style>
 
+    <style name="ThemableWidgetStyle">
+        <item name="android:layout_height">150dp</item>
+    </style>
+
 </resources>
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/intensive/PerformanceTests.java b/bridge/tests/src/com/android/layoutlib/bridge/intensive/PerformanceTests.java
index 230e116..4d7438f 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/PerformanceTests.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/PerformanceTests.java
@@ -43,7 +43,7 @@
     private void render(@NonNull String layoutFileName)
             throws ClassNotFoundException, FileNotFoundException {
         SessionParams params = createSessionParams(layoutFileName, ConfigGenerator.NEXUS_5);
-        render(params, 250);
+        render(sBridge, params, 250);
     }
 
     @Test
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderResult.java b/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderResult.java
index 087478f..989d146 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderResult.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderResult.java
@@ -28,7 +28,7 @@
 import java.util.Collections;
 import java.util.List;
 
-class RenderResult {
+public class RenderResult {
     private final List<ViewInfo> mRootViews;
     private final List<ViewInfo> mSystemViews;
     private final Result mRenderResult;
@@ -51,7 +51,7 @@
     }
 
     @Nullable
-    Result getResult() {
+    public Result getResult() {
         return mRenderResult;
     }
 
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java b/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
index 1fc7015..a1047d9 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
@@ -24,8 +24,6 @@
 import com.android.ide.common.resources.FrameworkResources;
 import com.android.ide.common.resources.ResourceItem;
 import com.android.ide.common.resources.ResourceRepository;
-import com.android.ide.common.resources.ResourceResolver;
-import com.android.ide.common.resources.configuration.FolderConfiguration;
 import com.android.io.FolderWrapper;
 import com.android.layoutlib.bridge.Bridge;
 import com.android.layoutlib.bridge.android.RenderParamsFlags;
@@ -35,6 +33,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.SessionParamsBuilder;
 import com.android.layoutlib.bridge.intensive.util.TestAssetRepository;
 import com.android.layoutlib.bridge.intensive.util.TestUtils;
 import com.android.tools.layoutlib.java.System_Delegate;
@@ -91,12 +90,14 @@
     private static final String PLATFORM_DIR_PROPERTY = "platform.dir";
     private static final String RESOURCE_DIR_PROPERTY = "test_res.dir";
 
-    private static final String PLATFORM_DIR;
+    protected static final String PLATFORM_DIR;
     private static final String TEST_RES_DIR;
     /** Location of the app to test inside {@link #TEST_RES_DIR} */
-    private static final String APP_TEST_DIR = "testApp/MyApplication";
+    protected static final String APP_TEST_DIR = "testApp/MyApplication";
     /** Location of the app's res dir inside {@link #TEST_RES_DIR} */
     private static final String APP_TEST_RES = APP_TEST_DIR + "/src/main/res";
+    /** Location of the app's asset dir inside {@link #TEST_RES_DIR} */
+    private static final String APP_TEST_ASSET = APP_TEST_DIR + "/src/main/assets/";
     private static final String APP_CLASSES_LOCATION =
             APP_TEST_DIR + "/build/intermediates/classes/debug/";
     protected static Bridge sBridge;
@@ -133,9 +134,8 @@
             }
         }
     };
-    // Default class loader with access to the app classes
-    protected ClassLoader mDefaultClassLoader =
-            new ModuleClassLoader(APP_CLASSES_LOCATION, getClass().getClassLoader());
+
+    protected ClassLoader mDefaultClassLoader;
 
     private static String getPlatformDir() {
         String platformDir = System.getProperty(PLATFORM_DIR_PROPERTY);
@@ -163,6 +163,18 @@
         if (currentDir.getName().equalsIgnoreCase("bridge")) {
             currentDir = currentDir.getParentFile();
         }
+
+        // Find frameworks/layoutlib
+        while (currentDir != null && !"layoutlib".equals(currentDir.getName())) {
+            currentDir = currentDir.getParentFile();
+        }
+
+        if (currentDir == null ||
+                currentDir.getParentFile() == null ||
+                !"frameworks".equals(currentDir.getParentFile().getName())) {
+            return null;
+        }
+
         // Test if currentDir is  platform/frameworks/layoutlib. That is, root should be
         // workingDir/../../ (2 levels up)
         for (int i = 0; i < 2; i++) {
@@ -186,7 +198,8 @@
             return null;
         }
         File[] hosts = host.listFiles(path -> path.isDirectory() &&
-                (path.getName().startsWith("linux-") || path.getName().startsWith("darwin-")));
+                (path.getName().startsWith("linux-") ||
+                        path.getName().startsWith("darwin-")));
         assert hosts != null;
         for (File hostOut : hosts) {
             String platformDir = getPlatformDirFromHostOut(hostOut);
@@ -194,6 +207,7 @@
                 return platformDir;
             }
         }
+
         return null;
     }
 
@@ -321,12 +335,14 @@
     }
 
     @NonNull
-    protected static RenderResult render(SessionParams params, long frameTimeNanos) {
+    protected static RenderResult render(com.android.ide.common.rendering.api.Bridge bridge,
+            SessionParams params,
+            long frameTimeNanos) {
         // TODO: Set up action bar handler properly to test menu rendering.
         // Create session params.
         System_Delegate.setBootTimeNanos(TimeUnit.MILLISECONDS.toNanos(871732800000L));
         System_Delegate.setNanosTime(TimeUnit.MILLISECONDS.toNanos(871732800000L));
-        RenderSession session = sBridge.createSession(params);
+        RenderSession session = bridge.createSession(params);
 
         try {
             if (frameTimeNanos != -1) {
@@ -362,7 +378,7 @@
     @Nullable
     protected static RenderResult renderAndVerify(SessionParams params, String goldenFileName,
             long frameTimeNanos) throws ClassNotFoundException {
-        RenderResult result = RenderTestBase.render(params, frameTimeNanos);
+        RenderResult result = RenderTestBase.render(sBridge, params, frameTimeNanos);
         try {
             String goldenImagePath = APP_TEST_DIR + "/golden/" + goldenFileName;
             assertNotNull(result.getImage());
@@ -384,7 +400,7 @@
         return RenderTestBase.renderAndVerify(params, goldenFileName, -1);
     }
 
-    private static LayoutLog getLayoutLog() {
+    protected static LayoutLog getLayoutLog() {
         if (sLayoutLibLog == null) {
             sLayoutLibLog = new LayoutLog() {
                 @Override
@@ -480,6 +496,8 @@
 
     @Before
     public void beforeTestCase() {
+        // Default class loader with access to the app classes
+        mDefaultClassLoader = new ModuleClassLoader(APP_CLASSES_LOCATION, getClass().getClassLoader());
         sRenderMessages.clear();
     }
 
@@ -520,28 +538,28 @@
         layoutLibCallback.initResources();
         // TODO: Set up action bar handler properly to test menu rendering.
         // Create session params.
-        return getSessionParams(parser, deviceConfig, layoutLibCallback, "AppTheme", true,
-                RenderingMode.NORMAL, 22);
+        return getSessionParamsBuilder()
+                .setParser(parser)
+                .setConfigGenerator(deviceConfig)
+                .setCallback(layoutLibCallback)
+                .build();
     }
 
     /**
-     * Uses Theme.Material and Target sdk version as 22.
+     * Returns a pre-configured {@link SessionParamsBuilder} for target API 22, Normal rendering
+     * mode, AppTheme as theme and Nexus 5.
      */
-    protected SessionParams getSessionParams(LayoutPullParser layoutParser,
-            ConfigGenerator configGenerator, LayoutLibTestCallback layoutLibCallback,
-            String themeName, boolean isProjectTheme, RenderingMode renderingMode,
-            @SuppressWarnings("SameParameterValue") int targetSdk) {
-        FolderConfiguration config = configGenerator.getFolderConfig();
-        ResourceResolver resourceResolver =
-                ResourceResolver.create(sProjectResources.getConfiguredResources(config),
-                        sFrameworkRepo.getConfiguredResources(config), themeName, isProjectTheme);
-
-        SessionParams sessionParams =
-                new SessionParams(layoutParser, renderingMode, null /*used for caching*/,
-                        configGenerator.getHardwareConfig(), resourceResolver, layoutLibCallback, 0,
-                        targetSdk, getLayoutLog());
-        sessionParams.setFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE, true);
-        sessionParams.setAssetRepository(new TestAssetRepository());
-        return sessionParams;
+    @NonNull
+    protected SessionParamsBuilder getSessionParamsBuilder() {
+        return new SessionParamsBuilder()
+                .setLayoutLog(getLayoutLog())
+                .setFrameworkResources(sFrameworkRepo)
+                .setConfigGenerator(ConfigGenerator.NEXUS_5)
+                .setProjectResources(sProjectResources)
+                .setTheme("AppTheme", true)
+                .setRenderingMode(RenderingMode.NORMAL)
+                .setTargetSdk(22)
+                .setFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE, true)
+                .setAssetRepository(new TestAssetRepository(TEST_RES_DIR + "/" + APP_TEST_ASSET));
     }
 }
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java b/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java
index b09184b..f434c17 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java
@@ -50,7 +50,6 @@
 import android.util.DisplayMetrics;
 import android.util.StateSet;
 import android.util.TypedValue;
-import android.view.ContextThemeWrapper;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -99,9 +98,11 @@
                 "        android:background=\"#FF0000\"\n" +
                 "        android:id=\"@+id/text1\"/>\n" +
                 "</RelativeLayout>");
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.NoTitleBar", false,
-                RenderingMode.NORMAL, 22);
+        SessionParams params = getSessionParamsBuilder()
+                .setParser(parser)
+                .setCallback(layoutLibCallback)
+                .setTheme("Theme.NoTitleBar", false)
+                .build();
 
         renderAndVerify(params, "simple_activity-old-theme.png");
     }
@@ -113,21 +114,28 @@
         layoutLibCallback.initResources();
 
         LayoutPullParser parser = createParserFromPath("four_corners.xml");
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.Light.NoActionBar.TranslucentDecor", false,
-                RenderingMode.NORMAL, 22);
+        SessionParams params = getSessionParamsBuilder()
+                .setParser(parser)
+                .setCallback(layoutLibCallback)
+                .setTheme("Theme.Material.Light.NoActionBar.TranslucentDecor", false)
+                .build();
         renderAndVerify(params, "four_corners_translucent.png");
 
         parser = createParserFromPath("four_corners.xml");
-        params = getSessionParams(parser, ConfigGenerator.NEXUS_5_LAND,
-                layoutLibCallback, "Theme.Material.Light.NoActionBar.TranslucentDecor", false,
-                RenderingMode.NORMAL, 22);
+        params = getSessionParamsBuilder()
+                .setConfigGenerator(ConfigGenerator.NEXUS_5_LAND)
+                .setParser(parser)
+                .setCallback(layoutLibCallback)
+                .setTheme("Theme.Material.Light.NoActionBar.TranslucentDecor", false)
+                .build();
         renderAndVerify(params, "four_corners_translucent_land.png");
 
         parser = createParserFromPath("four_corners.xml");
-        params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
-                RenderingMode.NORMAL, 22);
+        params = getSessionParamsBuilder()
+                    .setParser(parser)
+                    .setCallback(layoutLibCallback)
+                    .setTheme("Theme.Material.Light.NoActionBar", false)
+                    .build();
         renderAndVerify(params, "four_corners.png");
     }
 
@@ -175,25 +183,34 @@
                 new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
         layoutLibCallback.initResources();
 
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
-                RenderingMode.V_SCROLL, 22);
+        SessionParams params = getSessionParamsBuilder()
+                .setParser(parser)
+                .setCallback(layoutLibCallback)
+                .setTheme("Theme.Material.Light.NoActionBar", false)
+                .setRenderingMode(RenderingMode.V_SCROLL)
+                .build();
 
         renderAndVerify(params, "simple_activity_noactionbar.png");
 
         parser = LayoutPullParser.createFromString(simpleActivity);
-        params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.Light", false,
-                RenderingMode.V_SCROLL, 22);
+        params = getSessionParamsBuilder()
+                    .setParser(parser)
+                    .setCallback(layoutLibCallback)
+                    .setTheme("Theme.Material.Light", false)
+                    .setRenderingMode(RenderingMode.V_SCROLL)
+                    .build();
 
         renderAndVerify(params, "simple_activity.png");
 
         // This also tests that a theme with "NoActionBar" DOES HAVE an action bar when we are
         // displaying menus.
         parser = LayoutPullParser.createFromString(simpleActivity);
-        params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
-                RenderingMode.V_SCROLL, 22);
+        params = getSessionParamsBuilder()
+                    .setParser(parser)
+                    .setCallback(layoutLibCallback)
+                    .setTheme("Theme.Material.Light.NoActionBar", false)
+                    .setRenderingMode(RenderingMode.V_SCROLL)
+                    .build();
         params.setFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG, "menu");
         renderAndVerify(params, "simple_activity.png");
     }
@@ -222,11 +239,13 @@
         LayoutLibTestCallback layoutLibCallback =
                 new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
         layoutLibCallback.initResources();
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
-                RenderingMode.NORMAL, 22);
+        SessionParams params = getSessionParamsBuilder()
+                .setParser(parser)
+                .setCallback(layoutLibCallback)
+                .setTheme("Theme.Material.Light.NoActionBar", false)
+                .build();
 
-        render(params, -1);
+        render(sBridge, params, -1);
 
         assertTrue((Boolean)field.get(null));
         field.set(null, false);
@@ -248,9 +267,13 @@
                 .setDensity(Density.XHIGH)
                 .setNavigation(Navigation.NONAV);
 
-        SessionParams params = getSessionParams(parser, customConfigGenerator,
-                layoutLibCallback, "Theme.Material.Light.NoActionBar.Fullscreen", false,
-                RenderingMode.V_SCROLL, 22);
+        SessionParams params = getSessionParamsBuilder()
+                .setParser(parser)
+                .setConfigGenerator(customConfigGenerator)
+                .setCallback(layoutLibCallback)
+                .setTheme("Theme.Material.Light.NoActionBar.Fullscreen", false)
+                .setRenderingMode(RenderingMode.V_SCROLL)
+                .build();
 
         renderAndVerify(params, "expand_vert_layout.png");
 
@@ -260,9 +283,13 @@
                 .setDensity(Density.XHIGH)
                 .setNavigation(Navigation.NONAV);
         parser = createParserFromPath("expand_horz_layout.xml");
-        params = getSessionParams(parser, customConfigGenerator,
-                layoutLibCallback, "Theme.Material.Light.NoActionBar.Fullscreen", false,
-                RenderingMode.H_SCROLL, 22);
+        params = getSessionParamsBuilder()
+                    .setParser(parser)
+                    .setConfigGenerator(customConfigGenerator)
+                    .setCallback(layoutLibCallback)
+                    .setTheme("Theme.Material.Light.NoActionBar.Fullscreen", false)
+                    .setRenderingMode(RenderingMode.H_SCROLL)
+                    .build();
 
         renderAndVerify(params, "expand_horz_layout.png");
     }
@@ -287,16 +314,22 @@
                 new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
         layoutLibCallback.initResources();
 
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
-                RenderingMode.V_SCROLL, 22);
+        SessionParams params = getSessionParamsBuilder()
+                .setParser(parser)
+                .setCallback(layoutLibCallback)
+                .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+                .setRenderingMode(RenderingMode.V_SCROLL)
+                .build();
 
         renderAndVerify(params, "animated_vector.png", TimeUnit.SECONDS.toNanos(2));
 
         parser = LayoutPullParser.createFromString(layout);
-        params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
-                RenderingMode.V_SCROLL, 22);
+        params = getSessionParamsBuilder()
+                    .setParser(parser)
+                    .setCallback(layoutLibCallback)
+                    .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+                    .setRenderingMode(RenderingMode.V_SCROLL)
+                    .build();
         renderAndVerify(params, "animated_vector_1.png", TimeUnit.SECONDS.toNanos(3));
     }
 
@@ -323,9 +356,12 @@
                 new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
         layoutLibCallback.initResources();
 
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
-                RenderingMode.V_SCROLL, 22);
+        SessionParams params = getSessionParamsBuilder()
+                .setParser(parser)
+                .setCallback(layoutLibCallback)
+                .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+                .setRenderingMode(RenderingMode.V_SCROLL)
+                .build();
 
         renderAndVerify(params, "vector_drawable.png", TimeUnit.SECONDS.toNanos(2));
     }
@@ -356,9 +392,12 @@
                 new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
         layoutLibCallback.initResources();
 
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
-                RenderingMode.V_SCROLL, 22);
+        SessionParams params = getSessionParamsBuilder()
+                .setParser(parser)
+                .setCallback(layoutLibCallback)
+                .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+                .setRenderingMode(RenderingMode.V_SCROLL)
+                .build();
 
         renderAndVerify(params, "vector_drawable_91383.png", TimeUnit.SECONDS.toNanos(2));
     }
@@ -386,14 +425,91 @@
                 new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
         layoutLibCallback.initResources();
 
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
-                RenderingMode.V_SCROLL, 22);
+        SessionParams params = getSessionParamsBuilder()
+                .setParser(parser)
+                .setCallback(layoutLibCallback)
+                .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+                .setRenderingMode(RenderingMode.V_SCROLL)
+                .build();
 
         renderAndVerify(params, "vector_drawable_multi_line_of_path_data.png",
                 TimeUnit.SECONDS.toNanos(2));
     }
 
+    /**
+     * Tests that the gradients are correctly transformed using the viewport of the vector drawable.
+     * <p/>
+     * If a vector drawable is 50x50 and the gradient has startX=25 and startY=25, the gradient
+     * will start in the middle of the box.
+     * <p/>
+     * http://b/65495452
+     */
+    @Test
+    public void testVectorDrawableGradient() throws ClassNotFoundException {
+        // Create the layout pull parser.
+        LayoutPullParser parser = LayoutPullParser.createFromString(
+                "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+                        "              android:padding=\"16dp\"\n" +
+                        "              android:orientation=\"horizontal\"\n" +
+                        "              android:layout_width=\"match_parent\"\n" +
+                        "              android:layout_height=\"match_parent\">\n" +
+                        "    <ImageView\n" +
+                        "             android:layout_height=\"match_parent\"\n" +
+                        "             android:layout_width=\"match_parent\"\n" +
+                        "             android:src=\"@drawable/shadow\" />\n\n" +
+                        "</LinearLayout>");
+        // Create LayoutLibCallback.
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+
+        SessionParams params = getSessionParamsBuilder()
+                .setParser(parser)
+                .setCallback(layoutLibCallback)
+                .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+                .setRenderingMode(RenderingMode.V_SCROLL)
+                .build();
+
+        renderAndVerify(params, "vector_drawable_gradient.png",
+                TimeUnit.SECONDS.toNanos(2));
+    }
+
+    /**
+     * Tests that the radial gradients are correctly transformed using the viewport of the vector
+     * drawable.
+     * <p/>
+     * http://b/66168608
+     */
+    @Test
+    public void testVectorDrawableRadialGradient() throws ClassNotFoundException {
+        // Create the layout pull parser.
+        LayoutPullParser parser = LayoutPullParser.createFromString(
+                "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+                        "              android:padding=\"16dp\"\n" +
+                        "              android:orientation=\"horizontal\"\n" +
+                        "              android:layout_width=\"match_parent\"\n" +
+                        "              android:layout_height=\"match_parent\">\n" +
+                        "    <ImageView\n" +
+                        "             android:layout_height=\"match_parent\"\n" +
+                        "             android:layout_width=\"match_parent\"\n" +
+                        "             android:src=\"@drawable/radial_gradient\" />\n\n" +
+                        "</LinearLayout>");
+        // Create LayoutLibCallback.
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+
+        SessionParams params = getSessionParamsBuilder()
+                .setParser(parser)
+                .setCallback(layoutLibCallback)
+                .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+                .setRenderingMode(RenderingMode.V_SCROLL)
+                .build();
+
+        renderAndVerify(params, "vector_drawable_radial_gradient.png",
+                TimeUnit.SECONDS.toNanos(2));
+    }
+
     /** Test activity.xml */
     @Test
     public void testScrollingAndMeasure() throws ClassNotFoundException, FileNotFoundException {
@@ -404,9 +520,12 @@
                 new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
         layoutLibCallback.initResources();
 
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
-                RenderingMode.V_SCROLL, 22);
+        SessionParams params = getSessionParamsBuilder()
+                .setParser(parser)
+                .setCallback(layoutLibCallback)
+                .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+                .setRenderingMode(RenderingMode.V_SCROLL)
+                .build();
         params.setForceNoDecor();
         params.setExtendedViewInfoMode(true);
 
@@ -434,9 +553,12 @@
         // Do a full render pass
         parser = createParserFromPath("scrolled.xml");
 
-        params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
-                RenderingMode.V_SCROLL, 22);
+        params = getSessionParamsBuilder()
+                    .setParser(parser)
+                    .setCallback(layoutLibCallback)
+                    .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+                    .setRenderingMode(RenderingMode.V_SCROLL)
+                    .build();
         params.setForceNoDecor();
         params.setExtendedViewInfoMode(true);
 
@@ -456,8 +578,11 @@
         LayoutLibTestCallback layoutLibCallback =
                 new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
         layoutLibCallback.initResources();
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4,
-                layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
+        SessionParams params = getSessionParamsBuilder()
+                .setParser(parser)
+                .setConfigGenerator(ConfigGenerator.NEXUS_4)
+                .setCallback(layoutLibCallback)
+                .build();
         AssetManager assetManager = AssetManager.getSystem();
         DisplayMetrics metrics = new DisplayMetrics();
         Configuration configuration = RenderAction.getConfiguration(params);
@@ -495,8 +620,11 @@
         LayoutLibTestCallback layoutLibCallback =
                 new LayoutLibTestCallback(RenderTestBase.getLogger(), mDefaultClassLoader);
         layoutLibCallback.initResources();
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4,
-                layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
+        SessionParams params = getSessionParamsBuilder()
+                .setConfigGenerator(ConfigGenerator.NEXUS_4)
+                .setParser(parser)
+                .setCallback(layoutLibCallback)
+                .build();
         AssetManager assetManager = AssetManager.getSystem();
         DisplayMetrics metrics = new DisplayMetrics();
         Configuration configuration = RenderAction.getConfiguration(params);
@@ -544,36 +672,45 @@
                 new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
         layoutLibCallback.initResources();
 
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
-                RenderingMode.V_SCROLL, 22);
+        SessionParams params = getSessionParamsBuilder()
+                .setParser(parser)
+                .setCallback(layoutLibCallback)
+                .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+                .setRenderingMode(RenderingMode.V_SCROLL)
+                .build();
 
         renderAndVerify(params, "adaptive_icon.png");
 
         layoutLibCallback.setAdaptiveIconMaskPath(
                 "M50 0C77.6 0 100 22.4 100 50C100 77.6 77.6 100 50 100C22.4 100 0 77.6 0 50C0 " +
                         "22.4 22.4 0 50 0Z");
-        params =
-                getSessionParams(LayoutPullParser.createFromString(layout), ConfigGenerator.NEXUS_5,
-                        layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
-                        RenderingMode.V_SCROLL, 22);
+        params = getSessionParamsBuilder()
+                    .setParser(LayoutPullParser.createFromString(layout))
+                    .setCallback(layoutLibCallback)
+                    .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+                    .setRenderingMode(RenderingMode.V_SCROLL)
+                    .build();
         renderAndVerify(params, "adaptive_icon_circle.png");
 
         layoutLibCallback.setAdaptiveIconMaskPath(
                 "M50,0L92,0C96.42,0 100,4.58 100 8L100,92C100, 96.42 96.42 100 92 100L8 100C4.58," +
                         " 100 0 96.42 0 92L0 8 C 0 4.42 4.42 0 8 0L50 0Z");
-        params =
-                getSessionParams(LayoutPullParser.createFromString(layout), ConfigGenerator.NEXUS_5,
-                        layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
-                        RenderingMode.V_SCROLL, 22);
+        params = getSessionParamsBuilder()
+                    .setParser(LayoutPullParser.createFromString(layout))
+                    .setCallback(layoutLibCallback)
+                    .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+                    .setRenderingMode(RenderingMode.V_SCROLL)
+                    .build();
         renderAndVerify(params, "adaptive_icon_rounded_corners.png");
 
         layoutLibCallback.setAdaptiveIconMaskPath(
                 "M50,0 C10,0 0,10 0,50 0,90 10,100 50,100 90,100 100,90 100,50 100,10 90,0 50,0 Z");
-        params =
-                getSessionParams(LayoutPullParser.createFromString(layout), ConfigGenerator.NEXUS_5,
-                        layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
-                        RenderingMode.V_SCROLL, 22);
+        params = getSessionParamsBuilder()
+                    .setParser(LayoutPullParser.createFromString(layout))
+                    .setCallback(layoutLibCallback)
+                    .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+                    .setRenderingMode(RenderingMode.V_SCROLL)
+                    .build();
         renderAndVerify(params, "adaptive_icon_squircle.png");
     }
 
@@ -587,8 +724,11 @@
         LayoutLibTestCallback layoutLibCallback =
                 new LayoutLibTestCallback(RenderTestBase.getLogger(), mDefaultClassLoader);
         layoutLibCallback.initResources();
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4,
-                layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
+        SessionParams params = getSessionParamsBuilder()
+                .setConfigGenerator(ConfigGenerator.NEXUS_4)
+                .setParser(parser)
+                .setCallback(layoutLibCallback)
+                .build();
         DisplayMetrics metrics = new DisplayMetrics();
         Configuration configuration = RenderAction.getConfiguration(params);
 
@@ -633,8 +773,12 @@
         LayoutLibTestCallback layoutLibCallback =
                 new LayoutLibTestCallback(RenderTestBase.getLogger(), mDefaultClassLoader);
         layoutLibCallback.initResources();
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4,
-                layoutLibCallback, "Theme.Material", false, RenderingMode.NORMAL, 22);
+        SessionParams params = getSessionParamsBuilder()
+                .setConfigGenerator(ConfigGenerator.NEXUS_4)
+                .setParser(parser)
+                .setCallback(layoutLibCallback)
+                .setTheme("Theme.Material", false)
+                .build();
         DisplayMetrics metrics = new DisplayMetrics();
         Configuration configuration = RenderAction.getConfiguration(params);
 
@@ -697,8 +841,11 @@
         LayoutLibTestCallback layoutLibCallback =
                 new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
         layoutLibCallback.initResources();
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4,
-                layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
+        SessionParams params = getSessionParamsBuilder()
+                .setConfigGenerator(ConfigGenerator.NEXUS_4)
+                .setParser(parser)
+                .setCallback(layoutLibCallback)
+                .build();
         AssetManager assetManager = AssetManager.getSystem();
         DisplayMetrics metrics = new DisplayMetrics();
         Configuration configuration = RenderAction.getConfiguration(params);
@@ -757,9 +904,69 @@
                 new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
         layoutLibCallback.initResources();
 
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5, layoutLibCallback,
-                "Theme.Material.NoActionBar.Fullscreen", false, RenderingMode.V_SCROLL, 22);
+        SessionParams params = getSessionParamsBuilder()
+                .setParser(parser)
+                .setCallback(layoutLibCallback)
+                .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+                .setRenderingMode(RenderingMode.V_SCROLL)
+                .build();
 
         renderAndVerify(params, "ninepatch_background.png");
     }
+
+    @Test
+    public void testAssetManager() throws Exception {
+        String layout =
+                "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+                        "              android:padding=\"16dp\"\n" +
+                        "              android:orientation=\"horizontal\"\n" +
+                        "              android:layout_width=\"fill_parent\"\n" +
+                        "              android:layout_height=\"fill_parent\">\n" +
+                        "    <com.android.layoutlib.test.myapplication.widgets.AssetView\n" +
+                        "             android:layout_height=\"wrap_content\"\n" +
+                        "             android:layout_width=\"wrap_content\" />\n" +
+                        "</LinearLayout>\n";
+        LayoutPullParser parser = LayoutPullParser.createFromString(layout);
+        // Create LayoutLibCallback.
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+
+        SessionParams params = getSessionParamsBuilder()
+                .setParser(parser)
+                .setCallback(layoutLibCallback)
+                .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+                .setRenderingMode(RenderingMode.V_SCROLL)
+                .build();
+
+        renderAndVerify(params, "asset.png");
+    }
+
+    /**
+     * Tests that calling setTheme in a ContextThemeWrapper actually applies the theme
+     *
+     * http://b/66902070
+     */
+    @Test
+    public void testContextThemeWrapper() throws ClassNotFoundException {
+        // Create the layout pull parser.
+        LayoutPullParser parser = LayoutPullParser.createFromString(
+                "<com.android.layoutlib.test.myapplication.ThemableWidget " +
+                        "xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+                        "              android:layout_width=\"wrap_content\"\n" +
+                        "              android:layout_height=\"wrap_content\" />\n");
+        // Create LayoutLibCallback.
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+
+        SessionParams params = getSessionParamsBuilder()
+                .setParser(parser)
+                .setCallback(layoutLibCallback)
+                .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+                .setRenderingMode(RenderingMode.V_SCROLL)
+                .build();
+
+        renderAndVerify(params, "context_theme_wrapper.png", TimeUnit.SECONDS.toNanos(2));
+    }
 }
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/ImageUtils.java b/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/ImageUtils.java
index 45facf5..4866051 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/ImageUtils.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/ImageUtils.java
@@ -86,8 +86,6 @@
 
     public static void assertImageSimilar(String relativePath, BufferedImage goldenImage,
             BufferedImage image, double maxPercentDifferent) throws IOException {
-        assertEquals("Only TYPE_INT_ARGB image types are supported",  TYPE_INT_ARGB, image.getType());
-
         if (goldenImage.getType() != TYPE_INT_ARGB) {
             BufferedImage temp = new BufferedImage(goldenImage.getWidth(), goldenImage.getHeight(),
                     TYPE_INT_ARGB);
@@ -165,7 +163,6 @@
                     "vs" + image.getWidth() + "x" + image.getHeight();
         }
 
-        assertEquals(TYPE_INT_ARGB, image.getType());
         if (error != null) {
             // Expected on the left
             // Golden on the right
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/SessionParamsBuilder.java b/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/SessionParamsBuilder.java
new file mode 100644
index 0000000..4bd3640
--- /dev/null
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/SessionParamsBuilder.java
@@ -0,0 +1,166 @@
+/*
+ * 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.SdkConstants;
+import com.android.ide.common.rendering.api.AssetRepository;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.LayoutlibCallback;
+import com.android.ide.common.rendering.api.SessionParams;
+import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
+import com.android.ide.common.resources.ResourceRepository;
+import com.android.ide.common.resources.ResourceResolver;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator;
+import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser;
+
+import android.annotation.NonNull;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Builder to help setting up {@link SessionParams} objects.
+ */
+public class SessionParamsBuilder {
+
+    private LayoutPullParser mLayoutParser;
+    private RenderingMode mRenderingMode = RenderingMode.NORMAL;
+    private Object mProjectKey = null;
+    private ConfigGenerator mConfigGenerator = ConfigGenerator.NEXUS_5;
+    private ResourceRepository mFrameworkResources;
+    private ResourceRepository mProjectResources;
+    private String mThemeName;
+    private boolean isProjectTheme;
+    private LayoutlibCallback mLayoutlibCallback;
+    private int mTargetSdk;
+    private int mMinSdk = 0;
+    private LayoutLog mLayoutLog;
+    private Map<SessionParams.Key, Object> mFlags = new HashMap<>();
+    private AssetRepository mAssetRepository = null;
+
+    @NonNull
+    public SessionParamsBuilder setParser(@NonNull LayoutPullParser layoutParser) {
+        mLayoutParser = layoutParser;
+
+        return this;
+    }
+
+    @NonNull
+    public SessionParamsBuilder setRenderingMode(@NonNull RenderingMode renderingMode) {
+        mRenderingMode = renderingMode;
+        return this;
+    }
+
+    @NonNull
+    public SessionParamsBuilder setConfigGenerator(@NonNull ConfigGenerator configGenerator) {
+        mConfigGenerator = configGenerator;
+        return this;
+    }
+
+    @NonNull
+    public SessionParamsBuilder setProjectResources(@NonNull ResourceRepository resources) {
+        mProjectResources = resources;
+        return this;
+    }
+
+    @NonNull
+    public SessionParamsBuilder setFrameworkResources(@NonNull ResourceRepository resources) {
+        mFrameworkResources = resources;
+        return this;
+    }
+
+    @NonNull
+    public SessionParamsBuilder setTheme(@NonNull String themeName, boolean isProjectTheme) {
+        mThemeName = themeName;
+        this.isProjectTheme = isProjectTheme;
+        return this;
+    }
+
+    @NonNull
+    public SessionParamsBuilder setTheme(@NonNull String themeName) {
+        boolean isProjectTheme;
+        if (themeName.startsWith(SdkConstants.PREFIX_ANDROID)) {
+            themeName = themeName.substring(SdkConstants.PREFIX_ANDROID.length());
+            isProjectTheme = false;
+        } else {
+            isProjectTheme = true;
+        }
+        return setTheme(themeName, isProjectTheme);
+    }
+
+    @NonNull
+    public SessionParamsBuilder setCallback(@NonNull LayoutlibCallback callback) {
+        mLayoutlibCallback = callback;
+        return this;
+    }
+
+    @NonNull
+    public SessionParamsBuilder setTargetSdk(int targetSdk) {
+        mTargetSdk = targetSdk;
+        return this;
+    }
+
+    @SuppressWarnings("unused")
+    @NonNull
+    public SessionParamsBuilder setMinSdk(int minSdk) {
+        mMinSdk = minSdk;
+        return this;
+    }
+
+    @NonNull
+    public SessionParamsBuilder setLayoutLog(@NonNull LayoutLog layoutLog) {
+        mLayoutLog = layoutLog;
+        return this;
+    }
+
+    @NonNull
+    public SessionParamsBuilder setFlag(@NonNull SessionParams.Key flag, Object value) {
+        mFlags.put(flag, value);
+        return this;
+    }
+
+    @NonNull
+    public SessionParamsBuilder setAssetRepository(@NonNull AssetRepository repository) {
+        mAssetRepository = repository;
+        return this;
+    }
+
+    @NonNull
+    public SessionParams build() {
+        assert mFrameworkResources != null;
+        assert mProjectResources != null;
+        assert mThemeName != null;
+        assert mLayoutLog != null;
+        assert mLayoutlibCallback != null;
+
+        FolderConfiguration config = mConfigGenerator.getFolderConfig();
+        ResourceResolver resourceResolver =
+                ResourceResolver.create(mProjectResources.getConfiguredResources(config),
+                        mFrameworkResources.getConfiguredResources(config), mThemeName,
+                        isProjectTheme);
+
+        SessionParams params = new SessionParams(mLayoutParser, mRenderingMode, mProjectKey /* for
+        caching */, mConfigGenerator.getHardwareConfig(), resourceResolver, mLayoutlibCallback,
+                mMinSdk, mTargetSdk, mLayoutLog);
+
+        mFlags.forEach(params::setFlag);
+        params.setAssetRepository(mAssetRepository);
+
+        return params;
+    }
+}
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/TestAssetRepository.java b/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/TestAssetRepository.java
index 0856ac9..54af92d 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/TestAssetRepository.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/TestAssetRepository.java
@@ -18,6 +18,8 @@
 
 import com.android.ide.common.rendering.api.AssetRepository;
 
+import android.annotation.NonNull;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -28,6 +30,12 @@
  * {@link AssetRepository} used for render tests.
  */
 public class TestAssetRepository extends AssetRepository {
+    private final String mAssetPath;
+
+    public TestAssetRepository(@NonNull String assetPath) {
+        mAssetPath = assetPath;
+    }
+
     private static InputStream open(String path) throws FileNotFoundException {
         File asset = new File(path);
         if (asset.isFile()) {
@@ -44,7 +52,7 @@
 
     @Override
     public InputStream openAsset(String path, int mode) throws IOException {
-        return open(path);
+        return open(mAssetPath + path);
     }
 
     @Override
diff --git a/common/src/com/android/tools/layoutlib/annotations/NotNull.java b/common/src/com/android/tools/layoutlib/annotations/NotNull.java
new file mode 100644
index 0000000..4dcb24b
--- /dev/null
+++ b/common/src/com/android/tools/layoutlib/annotations/NotNull.java
@@ -0,0 +1,34 @@
+/*
+ * 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.tools.layoutlib.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Denotes a parameter or field can not be null.
+ * <p/>
+ * When decorating a method call parameter, this denotes the parameter can
+ * not be null.
+ * <p/>
+ * When decorating a method, this denotes the method can not return null.
+ * <p/>
+ * This is a marker annotation and it has no specific attributes.
+ */
+@Retention(RetentionPolicy.SOURCE)
+public @interface NotNull {
+}
diff --git a/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index f595803..397ddea 100644
--- a/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -132,6 +132,7 @@
         "android.content.res.Resources#getDimensionPixelOffset",
         "android.content.res.Resources#getDimensionPixelSize",
         "android.content.res.Resources#getDrawable",
+        "android.content.res.Resources#getFloat",
         "android.content.res.Resources#getFont",
         "android.content.res.Resources#getIdentifier",
         "android.content.res.Resources#getIntArray",
@@ -157,6 +158,7 @@
         "android.content.res.Resources$Theme#obtainStyledAttributes",
         "android.content.res.Resources$Theme#resolveAttribute",
         "android.content.res.Resources$Theme#resolveAttributes",
+        "android.content.res.AssetManager#open",
         "android.content.res.AssetManager#newTheme",
         "android.content.res.AssetManager#deleteTheme",
         "android.content.res.AssetManager#getAssignedPackageIdentifiers",
@@ -169,13 +171,15 @@
         "android.graphics.drawable.GradientDrawable#buildRing",
         "android.graphics.drawable.AdaptiveIconDrawable#<init>",
         "android.graphics.FontFamily#addFont",
-        "android.graphics.Typeface#getSystemFontConfigLocation",
-        "android.graphics.Typeface#makeFamilyFromParsed",
+        "android.graphics.Typeface#buildSystemFallback",
+        "android.graphics.Typeface#create",
+        "android.graphics.Typeface#createFontFamily",
+        "android.os.Binder#getNativeBBinderHolder",
+        "android.os.Binder#getNativeFinalizer",
         "android.os.Handler#sendMessageAtTime",
         "android.os.HandlerThread#run",
         "android.preference.Preference#getView",
         "android.text.format.DateFormat#is24HourFormat",
-        "android.text.Hyphenator#getSystemHyphenatorLocation",
         "android.util.Xml#newPullParser",
         "android.view.Choreographer#getInstance",
         "android.view.Choreographer#getRefreshRate",
@@ -272,7 +276,6 @@
         "android.graphics.drawable.VectorDrawable",
         "android.os.SystemClock",
         "android.os.SystemProperties",
-        "android.text.AndroidBidi",
         "android.text.StaticLayout",
         "android.util.PathParser",
         "android.view.Display",
diff --git a/remote/client/remote client.iml b/remote/client/remote client.iml
new file mode 100644
index 0000000..051a2c1
--- /dev/null
+++ b/remote/client/remote client.iml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="layoutlib_api-prebuilt" level="project" />
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/../../../../prebuilts/misc/common/kxml2/kxml2-2.3.0.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="file://$MODULE_DIR$/../../../../libcore/xml/src/main/java" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module" module-name="remote common" />
+    <orderEntry type="module" module-name="common" />
+  </component>
+</module>
\ No newline at end of file
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/RemoteBridgeClient.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/RemoteBridgeClient.java
new file mode 100644
index 0000000..99143ad
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/RemoteBridgeClient.java
@@ -0,0 +1,148 @@
+/*
+ * 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.remote.client;
+
+import com.android.ide.common.rendering.api.Bridge;
+import com.android.ide.common.rendering.api.DrawableParams;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.Result;
+import com.android.ide.common.rendering.api.SessionParams;
+import com.android.layout.remote.api.RemoteBridge;
+import com.android.layout.remote.api.RemoteDrawableParams;
+import com.android.layout.remote.api.RemoteSessionParams;
+import com.android.layoutlib.bridge.remote.client.adapters.RemoteDrawableParamsAdapter;
+import com.android.layoutlib.bridge.remote.client.adapters.RemoteLayoutLogAdapter;
+import com.android.layoutlib.bridge.remote.client.adapters.RemoteRenderSessionAdapter;
+import com.android.layoutlib.bridge.remote.client.adapters.RemoteSessionParamsAdapter;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.io.File;
+import java.rmi.NotBoundException;
+import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+import java.util.Map;
+
+public class RemoteBridgeClient extends Bridge {
+    private final RemoteBridge mDelegate;
+
+    private RemoteBridgeClient(@NotNull RemoteBridge delegate) {
+        mDelegate = delegate;
+    }
+
+    @NotNull
+    public static RemoteBridgeClient getRemoteBridge(int registryPort) throws RemoteException,
+            NotBoundException {
+        Registry registry = LocateRegistry.getRegistry(registryPort);
+        RemoteBridge remoteBridge = (RemoteBridge) registry.lookup(RemoteBridge.class.getName());
+
+        return new RemoteBridgeClient(remoteBridge);
+    }
+
+    @Override
+    public int getApiLevel() {
+        try {
+            return mDelegate.getApiLevel();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+
+        }
+    }
+
+    @Override
+    public int getRevision() {
+        try {
+            return mDelegate.getRevision();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public boolean supports(int feature) {
+        try {
+            return mDelegate.supports(feature);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public boolean init(Map<String, String> platformProperties, File fontLocation,
+            Map<String, Map<String, Integer>> enumValueMap, LayoutLog log) {
+        try {
+            return mDelegate.init(platformProperties, fontLocation, enumValueMap,
+                    RemoteLayoutLogAdapter.create(log));
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public boolean dispose() {
+        try {
+            return mDelegate.dispose();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public RenderSession createSession(SessionParams params) {
+        try {
+            RemoteSessionParams remoteParams = RemoteSessionParamsAdapter.create(params);
+
+            return new RemoteRenderSessionAdapter(mDelegate.createSession(remoteParams));
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public Result renderDrawable(DrawableParams params) {
+        try {
+            return mDelegate.renderDrawable(RemoteDrawableParamsAdapter.create(params));
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void clearCaches(Object projectKey) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Result getViewParent(Object viewObject) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Result getViewIndex(Object viewObject) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isRtl(String locale) {
+        try {
+            return mDelegate.isRtl(locale);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteActionBarCallbackAdapter.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteActionBarCallbackAdapter.java
new file mode 100644
index 0000000..e1cfe15
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteActionBarCallbackAdapter.java
@@ -0,0 +1,70 @@
+/*
+ * 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.remote.client.adapters;
+
+import com.android.ide.common.rendering.api.ActionBarCallback;
+import com.android.ide.common.rendering.api.ActionBarCallback.HomeButtonStyle;
+import com.android.layout.remote.api.RemoteActionBarCallback;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.List;
+
+class RemoteActionBarCallbackAdapter implements RemoteActionBarCallback {
+    private final ActionBarCallback mDelegate;
+
+    private RemoteActionBarCallbackAdapter(@NotNull ActionBarCallback delegate) {
+        mDelegate = delegate;
+    }
+
+    public static RemoteActionBarCallback create(@NotNull ActionBarCallback delegate)
+            throws RemoteException {
+        return (RemoteActionBarCallback) UnicastRemoteObject.exportObject(
+                new RemoteActionBarCallbackAdapter(delegate), 0);
+    }
+
+    @Override
+    public List<String> getMenuIdNames() {
+        return mDelegate.getMenuIdNames();
+    }
+
+    @Override
+    public boolean getSplitActionBarWhenNarrow() {
+        return mDelegate.getSplitActionBarWhenNarrow();
+    }
+
+    @Override
+    public int getNavigationMode() {
+        return mDelegate.getNavigationMode();
+    }
+
+    @Override
+    public String getSubTitle() {
+        return mDelegate.getSubTitle();
+    }
+
+    @Override
+    public HomeButtonStyle getHomeButtonStyle() {
+        return mDelegate.getHomeButtonStyle();
+    }
+
+    @Override
+    public boolean isOverflowPopupNeeded() {
+        return mDelegate.isOverflowPopupNeeded();
+    }
+}
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteAssetRepositoryAdapter.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteAssetRepositoryAdapter.java
new file mode 100644
index 0000000..4def740
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteAssetRepositoryAdapter.java
@@ -0,0 +1,52 @@
+/*
+ * 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.remote.client.adapters;
+
+import com.android.ide.common.rendering.api.AssetRepository;
+import com.android.layout.remote.api.RemoteAssetRepository;
+import com.android.layout.remote.util.RemoteInputStream;
+import com.android.layout.remote.util.RemoteInputStreamAdapter;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.io.IOException;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+
+public class RemoteAssetRepositoryAdapter implements RemoteAssetRepository {
+    private final AssetRepository mDelegate;
+
+    private RemoteAssetRepositoryAdapter(@NotNull AssetRepository delegate) {
+        mDelegate = delegate;
+    }
+
+    static RemoteAssetRepository create(@NotNull AssetRepository delegate) throws RemoteException {
+        return (RemoteAssetRepository) UnicastRemoteObject.exportObject(
+                new RemoteAssetRepositoryAdapter(delegate), 0);
+    }
+
+    @Override
+    public RemoteInputStream openAsset(String path, int mode) throws IOException, RemoteException {
+        return RemoteInputStreamAdapter.create(mDelegate.openAsset(path, mode));
+    }
+
+    @Override
+    public RemoteInputStream openNonAsset(int cookie, String path, int mode)
+            throws IOException, RemoteException {
+        return RemoteInputStreamAdapter.create(mDelegate.openNonAsset(cookie, path, mode));
+    }
+
+}
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteDrawableParamsAdapter.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteDrawableParamsAdapter.java
new file mode 100644
index 0000000..d2c2cd8
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteDrawableParamsAdapter.java
@@ -0,0 +1,48 @@
+/*
+ * 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.remote.client.adapters;
+
+import com.android.ide.common.rendering.api.DrawableParams;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.layout.remote.api.RemoteDrawableParams;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+
+public class RemoteDrawableParamsAdapter extends RemoteRenderParamsAdapter implements
+        RemoteDrawableParams {
+    private final DrawableParams mDelegate;
+
+    private RemoteDrawableParamsAdapter(@NotNull DrawableParams drawableParams) {
+        super(drawableParams);
+        mDelegate = drawableParams;
+    }
+
+    @NotNull
+    public static RemoteDrawableParams create(@NotNull DrawableParams drawableParams)
+            throws RemoteException {
+        return (RemoteDrawableParams) UnicastRemoteObject.exportObject(
+                new RemoteDrawableParamsAdapter(drawableParams), 0);
+    }
+
+    @NotNull
+    @Override
+    public ResourceValue getDrawable() throws RemoteException {
+        return mDelegate.getDrawable();
+    }
+}
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteILayoutPullParserAdapter.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteILayoutPullParserAdapter.java
new file mode 100644
index 0000000..451d93e
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteILayoutPullParserAdapter.java
@@ -0,0 +1,42 @@
+/*
+ * 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.remote.client.adapters;
+
+import com.android.ide.common.rendering.api.ILayoutPullParser;
+import com.android.layout.remote.api.RemoteILayoutPullParser;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+
+class RemoteILayoutPullParserAdapter extends RemoteXmlPullParserAdapter
+        implements RemoteILayoutPullParser {
+    private RemoteILayoutPullParserAdapter(@NotNull ILayoutPullParser delegate) {
+        super(delegate);
+    }
+
+    public static RemoteILayoutPullParser create(@NotNull ILayoutPullParser delegate)
+            throws RemoteException {
+        return (RemoteILayoutPullParser) UnicastRemoteObject.exportObject(
+                new RemoteILayoutPullParserAdapter(delegate), 0);
+    }
+
+    @Override
+    public Object getViewCookie() throws RemoteException {
+        return ((ILayoutPullParser) mDelegate).getViewCookie();
+    }
+}
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteLayoutLogAdapter.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteLayoutLogAdapter.java
new file mode 100644
index 0000000..e87a0e0
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteLayoutLogAdapter.java
@@ -0,0 +1,59 @@
+/*
+ * 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.remote.client.adapters;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layout.remote.api.RemoteLayoutLog;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.io.Serializable;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+
+public class RemoteLayoutLogAdapter implements RemoteLayoutLog {
+    private final LayoutLog mLog;
+
+    private RemoteLayoutLogAdapter(@NotNull LayoutLog log) {
+        mLog = log;
+    }
+
+    public static RemoteLayoutLog create(@NotNull LayoutLog log) throws RemoteException {
+        return (RemoteLayoutLog) UnicastRemoteObject.exportObject(new RemoteLayoutLogAdapter(log),
+                0);
+    }
+
+    @Override
+    public void warning(String tag, String message, Serializable data) {
+        mLog.warning(tag, message, null);
+    }
+
+    @Override
+    public void fidelityWarning(String tag, String message, Throwable throwable,
+            Serializable data) {
+        mLog.fidelityWarning(tag, message, throwable, null);
+    }
+
+    @Override
+    public void error(String tag, String message, Serializable data) {
+        mLog.error(tag, message, null);
+    }
+
+    @Override
+    public void error(String tag, String message, Throwable throwable, Serializable data) {
+        mLog.error(tag, message, throwable, null);
+    }
+}
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteLayoutlibCallbackAdapter.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteLayoutlibCallbackAdapter.java
new file mode 100644
index 0000000..b9b1a00
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteLayoutlibCallbackAdapter.java
@@ -0,0 +1,159 @@
+/*
+ * 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.remote.client.adapters;
+
+import com.android.ide.common.rendering.api.AdapterBinding;
+import com.android.ide.common.rendering.api.IProjectCallback.ViewAttribute;
+import com.android.ide.common.rendering.api.LayoutlibCallback;
+import com.android.ide.common.rendering.api.ResourceReference;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.SessionParams.Key;
+import com.android.layout.remote.api.RemoteActionBarCallback;
+import com.android.layout.remote.api.RemoteILayoutPullParser;
+import com.android.layout.remote.api.RemoteLayoutlibCallback;
+import com.android.layout.remote.api.RemoteParserFactory;
+import com.android.layout.remote.api.RemoteXmlPullParser;
+import com.android.resources.ResourceType;
+import com.android.tools.layoutlib.annotations.NotNull;
+import com.android.tools.layoutlib.annotations.Nullable;
+import com.android.util.Pair;
+
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+
+public class RemoteLayoutlibCallbackAdapter implements RemoteLayoutlibCallback {
+    private final LayoutlibCallback mDelegate;
+
+    private RemoteLayoutlibCallbackAdapter(@NotNull LayoutlibCallback delegate) {
+        mDelegate = delegate;
+    }
+
+    public static RemoteLayoutlibCallback create(@NotNull LayoutlibCallback delegate)
+            throws RemoteException {
+        return (RemoteLayoutlibCallback) UnicastRemoteObject.exportObject(
+                new RemoteLayoutlibCallbackAdapter(delegate), 0);
+    }
+
+    @Override
+    public boolean supports(int ideFeature) {
+        return mDelegate.supports(ideFeature);
+    }
+
+    @Override
+    public Object loadView(String name, Class[] constructorSignature, Object[] constructorArgs)
+            throws Exception {
+        throw new UnsupportedOperationException("Not implemented yet");
+    }
+
+    @Override
+    public String getNamespace() {
+        return mDelegate.getNamespace();
+    }
+
+    @Override
+    public RemoteResolveResult resolveResourceId(int id) {
+        Pair<ResourceType, String> result = mDelegate.resolveResourceId(id);
+        return result != null ? new RemoteResolveResult(result.getFirst(), result.getSecond()) :
+                null;
+    }
+
+    @Override
+    public String resolveResourceId(int[] id) {
+        return mDelegate.resolveResourceId(id);
+    }
+
+    @Override
+    public Integer getResourceId(ResourceType type, String name) {
+        return mDelegate.getResourceId(type, name);
+    }
+
+    @Override
+    public RemoteILayoutPullParser getParser(ResourceValue layoutResource) {
+        try {
+            return RemoteILayoutPullParserAdapter.create(mDelegate.getParser(layoutResource));
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public Object getAdapterItemValue(ResourceReference adapterView, Object adapterCookie,
+            ResourceReference itemRef, int fullPosition, int positionPerType,
+            int fullParentPosition, int parentPositionPerType, ResourceReference viewRef,
+            ViewAttribute viewAttribute, Object defaultValue) {
+        throw new UnsupportedOperationException("Not implemented yet");
+    }
+
+    @Override
+    public AdapterBinding getAdapterBinding(ResourceReference adapterViewRef, Object adapterCookie,
+            Object viewObject) {
+        throw new UnsupportedOperationException("Not implemented yet");
+    }
+
+    @Override
+    public RemoteActionBarCallback getActionBarCallback() {
+        try {
+            return RemoteActionBarCallbackAdapter.create(mDelegate.getActionBarCallback());
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public <T> T getFlag(Key<T> key) {
+        return mDelegate.getFlag(key);
+    }
+
+    @Override
+    public RemoteParserFactory getParserFactory() {
+        try {
+            return RemoteParserFactoryAdapter.create(mDelegate.getParserFactory());
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Nullable
+    @Override
+    public Path findClassPath(String name) {
+        try {
+            Class<?> clazz = mDelegate.findClass(name);
+            URL url = clazz.getProtectionDomain().getCodeSource().getLocation();
+            if (url != null) {
+                return Paths.get(url.toURI());
+            }
+        } catch (ClassNotFoundException ignore) {
+        } catch (URISyntaxException e) {
+            throw new RuntimeException(e);
+        }
+
+        return null;
+    }
+
+    @Override
+    public RemoteXmlPullParser getXmlFileParser(String fileName) {
+        try {
+            return RemoteXmlPullParserAdapter.create(mDelegate.getXmlFileParser(fileName));
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteParserFactoryAdapter.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteParserFactoryAdapter.java
new file mode 100644
index 0000000..d1f0411
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteParserFactoryAdapter.java
@@ -0,0 +1,51 @@
+/*
+ * 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.remote.client.adapters;
+
+import com.android.ide.common.rendering.api.ParserFactory;
+import com.android.layout.remote.api.RemoteParserFactory;
+import com.android.layout.remote.api.RemoteXmlPullParser;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+
+public class RemoteParserFactoryAdapter implements RemoteParserFactory {
+
+    private final ParserFactory mDelegate;
+
+    private RemoteParserFactoryAdapter(@NotNull ParserFactory delegate) {
+        mDelegate = delegate;
+    }
+
+    public static RemoteParserFactory create(@NotNull ParserFactory factory)
+            throws RemoteException {
+        return (RemoteParserFactory) UnicastRemoteObject.exportObject(
+                new RemoteParserFactoryAdapter(factory), 0);
+    }
+
+    @Override
+    public RemoteXmlPullParser createParser(String debugName) throws RemoteException {
+        try {
+            return RemoteXmlPullParserAdapter.create(mDelegate.createParser(debugName));
+        } catch (XmlPullParserException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteRenderParamsAdapter.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteRenderParamsAdapter.java
new file mode 100644
index 0000000..a3950d7
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteRenderParamsAdapter.java
@@ -0,0 +1,161 @@
+/*
+ * 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.remote.client.adapters;
+
+import com.android.ide.common.rendering.api.IImageFactory;
+import com.android.ide.common.rendering.api.RenderParams;
+import com.android.ide.common.rendering.api.SessionParams;
+import com.android.ide.common.rendering.api.SessionParams.Key;
+import com.android.layout.remote.api.RemoteAssetRepository;
+import com.android.layout.remote.api.RemoteHardwareConfig;
+import com.android.layout.remote.api.RemoteLayoutLog;
+import com.android.layout.remote.api.RemoteLayoutlibCallback;
+import com.android.layout.remote.api.RemoteRenderParams;
+import com.android.layout.remote.api.RemoteRenderResources;
+import com.android.layout.remote.api.RemoteSessionParams;
+import com.android.tools.layoutlib.annotations.NotNull;
+import com.android.tools.layoutlib.annotations.Nullable;
+
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+
+public class RemoteRenderParamsAdapter implements RemoteRenderParams {
+    private final RenderParams mDelegate;
+
+    protected RemoteRenderParamsAdapter(@NotNull RenderParams params) {
+        mDelegate = params;
+    }
+
+    public static RemoteSessionParams create(@NotNull SessionParams params) throws RemoteException {
+        return (RemoteSessionParams) UnicastRemoteObject.exportObject(
+                new RemoteRenderParamsAdapter(params), 0);
+    }
+
+    @Nullable
+    @Override
+    public String getProjectKey() {
+        Object projectKey = mDelegate.getProjectKey();
+        // We can not transfer a random object so let's send just a string
+        return projectKey != null ? projectKey.toString() : null;
+    }
+
+    @Override
+    public RemoteHardwareConfig getRemoteHardwareConfig() {
+        return new RemoteHardwareConfig(mDelegate.getHardwareConfig());
+    }
+
+    @Override
+    public int getMinSdkVersion() {
+        return mDelegate.getMinSdkVersion();
+    }
+
+    @Override
+    public int getTargetSdkVersion() {
+        return mDelegate.getTargetSdkVersion();
+    }
+
+    @Override
+    public RemoteRenderResources getRemoteResources() {
+        try {
+            return RemoteRenderResourcesAdapter.create(mDelegate.getResources());
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public RemoteAssetRepository getAssets() {
+        try {
+            return RemoteAssetRepositoryAdapter.create(mDelegate.getAssets());
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public RemoteLayoutlibCallback getRemoteLayoutlibCallback() {
+        try {
+            return RemoteLayoutlibCallbackAdapter.create(mDelegate.getLayoutlibCallback());
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public RemoteLayoutLog getLog() {
+        try {
+            return RemoteLayoutLogAdapter.create(mDelegate.getLog());
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public boolean isBgColorOverridden() {
+        return mDelegate.isBgColorOverridden();
+    }
+
+    @Override
+    public int getOverrideBgColor() {
+        return mDelegate.getOverrideBgColor();
+    }
+
+    @Override
+    public long getTimeout() {
+        return mDelegate.getTimeout();
+    }
+
+    @Override
+    public IImageFactory getImageFactory() {
+        return mDelegate.getImageFactory();
+    }
+
+    @Override
+    public String getAppIcon() {
+        return mDelegate.getAppIcon();
+    }
+
+    @Override
+    public String getAppLabel() {
+        return mDelegate.getAppLabel();
+    }
+
+    @Override
+    public String getLocale() {
+        return mDelegate.getLocale();
+    }
+
+    @Override
+    public String getActivityName() {
+        return mDelegate.getActivityName();
+    }
+
+    @Override
+    public boolean isForceNoDecor() {
+        return mDelegate.isForceNoDecor();
+    }
+
+    @Override
+    public boolean isRtlSupported() {
+        return mDelegate.isRtlSupported();
+    }
+
+    @Override
+    public <T> T getFlag(Key<T> key) {
+        return mDelegate.getFlag(key);
+    }
+}
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteRenderResourcesAdapter.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteRenderResourcesAdapter.java
new file mode 100644
index 0000000..4303b77
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteRenderResourcesAdapter.java
@@ -0,0 +1,119 @@
+/*
+ * 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.remote.client.adapters;
+
+import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.layout.remote.api.RemoteRenderResources;
+import com.android.resources.ResourceType;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.List;
+
+public class RemoteRenderResourcesAdapter implements RemoteRenderResources {
+    private final RenderResources mDelegate;
+
+    private RemoteRenderResourcesAdapter(@NotNull RenderResources delegate) {
+        mDelegate = delegate;
+    }
+
+    public static RemoteRenderResources create(@NotNull RenderResources resources)
+            throws RemoteException {
+        return (RemoteRenderResources) UnicastRemoteObject.exportObject(
+                new RemoteRenderResourcesAdapter(resources), 0);
+    }
+
+    @Override
+    public StyleResourceValue getDefaultTheme() {
+        return mDelegate.getDefaultTheme();
+    }
+
+    @Override
+    public void applyStyle(StyleResourceValue theme, boolean useAsPrimary) {
+        mDelegate.applyStyle(theme, useAsPrimary);
+    }
+
+    @Override
+    public void clearStyles() {
+        mDelegate.clearStyles();
+    }
+
+    @Override
+    public List<StyleResourceValue> getAllThemes() {
+        return mDelegate.getAllThemes();
+    }
+
+    @Override
+    public StyleResourceValue getTheme(String name, boolean frameworkTheme) {
+        return mDelegate.getTheme(name, frameworkTheme);
+    }
+
+    @Override
+    public boolean themeIsParentOf(StyleResourceValue parentTheme, StyleResourceValue childTheme) {
+        return mDelegate.themeIsParentOf(parentTheme, childTheme);
+    }
+
+    @Override
+    public ResourceValue getFrameworkResource(ResourceType resourceType, String resourceName) {
+        return mDelegate.getFrameworkResource(resourceType, resourceName);
+    }
+
+    @Override
+    public ResourceValue getProjectResource(ResourceType resourceType, String resourceName) {
+        return mDelegate.getProjectResource(resourceType, resourceName);
+    }
+
+    @Override
+    public ResourceValue findItemInTheme(String attrName, boolean isFrameworkAttr) {
+        return mDelegate.findItemInTheme(attrName, isFrameworkAttr);
+    }
+
+    @Override
+    public ResourceValue findItemInStyle(StyleResourceValue style, String attrName,
+            boolean isFrameworkAttr) {
+        return mDelegate.findItemInStyle(style, attrName, isFrameworkAttr);
+    }
+
+    @Override
+    public ResourceValue findResValue(String reference, boolean forceFrameworkOnly) {
+        return mDelegate.findResValue(reference, forceFrameworkOnly);
+    }
+
+    @Override
+    public ResourceValue resolveValue(ResourceValue value) {
+        return mDelegate.resolveResValue(value);
+    }
+
+    @Override
+    public ResourceValue resolveValue(ResourceType type, String name, String value,
+            boolean isFrameworkValue) throws RemoteException {
+        return mDelegate.resolveValue(type, name, value, isFrameworkValue);
+    }
+
+    @Override
+    public StyleResourceValue getParent(StyleResourceValue style) {
+        return mDelegate.getParent(style);
+    }
+
+    @Override
+    public StyleResourceValue getStyle(String styleName, boolean isFramework) {
+        return mDelegate.getStyle(styleName, isFramework);
+    }
+}
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteRenderSessionAdapter.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteRenderSessionAdapter.java
new file mode 100644
index 0000000..c2c892e
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteRenderSessionAdapter.java
@@ -0,0 +1,111 @@
+/*
+ * 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.remote.client.adapters;
+
+import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.Result;
+import com.android.ide.common.rendering.api.ViewInfo;
+import com.android.layout.remote.api.RemoteRenderSession;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.awt.image.BufferedImage;
+import java.rmi.RemoteException;
+import java.util.Collections;
+import java.util.List;
+
+public class RemoteRenderSessionAdapter extends RenderSession {
+    private final RemoteRenderSession mDelegate;
+
+    public RemoteRenderSessionAdapter(@NotNull RemoteRenderSession delegate) {
+        mDelegate = delegate;
+    }
+
+    @Override
+    public Result getResult() {
+        try {
+            return mDelegate.getResult();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public BufferedImage getImage() {
+        try {
+            return mDelegate.getSerializableImage().getBufferedImage();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void setSystemTimeNanos(long nanos) {
+        try {
+            mDelegate.setSystemTimeNanos(nanos);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void setSystemBootTimeNanos(long nanos) {
+        try {
+            mDelegate.setSystemBootTimeNanos(nanos);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void setElapsedFrameTimeNanos(long nanos) {
+        try {
+            mDelegate.setElapsedFrameTimeNanos(nanos);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public Result render(long timeout, boolean forceMeasure) {
+        try {
+            return mDelegate.render(timeout, forceMeasure);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public List<ViewInfo> getRootViews() {
+        // TODO
+        return Collections.emptyList();
+    }
+
+    @Override
+    public List<ViewInfo> getSystemRootViews() {
+        // TODO
+        return Collections.emptyList();
+    }
+
+    @Override
+    public void dispose() {
+        try {
+            mDelegate.dispose();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteSessionParamsAdapter.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteSessionParamsAdapter.java
new file mode 100644
index 0000000..e6ed871
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteSessionParamsAdapter.java
@@ -0,0 +1,80 @@
+/*
+ * 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.remote.client.adapters;
+
+import com.android.ide.common.rendering.api.AdapterBinding;
+import com.android.ide.common.rendering.api.IImageFactory;
+import com.android.ide.common.rendering.api.ResourceReference;
+import com.android.ide.common.rendering.api.SessionParams;
+import com.android.ide.common.rendering.api.SessionParams.Key;
+import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
+import com.android.layout.remote.api.RemoteAssetRepository;
+import com.android.layout.remote.api.RemoteHardwareConfig;
+import com.android.layout.remote.api.RemoteILayoutPullParser;
+import com.android.layout.remote.api.RemoteLayoutLog;
+import com.android.layout.remote.api.RemoteLayoutlibCallback;
+import com.android.layout.remote.api.RemoteRenderResources;
+import com.android.layout.remote.api.RemoteSessionParams;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.Map;
+
+public class RemoteSessionParamsAdapter extends RemoteRenderParamsAdapter implements RemoteSessionParams {
+    private final SessionParams mDelegate;
+
+    private RemoteSessionParamsAdapter(@NotNull SessionParams params) {
+        super(params);
+        mDelegate = params;
+    }
+
+    public static RemoteSessionParams create(@NotNull SessionParams params) throws RemoteException {
+        return (RemoteSessionParams) UnicastRemoteObject.exportObject(
+                new RemoteSessionParamsAdapter(params), 0);
+    }
+
+    @Override
+    public RenderingMode getRenderingMode() {
+        return mDelegate.getRenderingMode();
+    }
+
+    @Override
+    public boolean isLayoutOnly() {
+        return mDelegate.isLayoutOnly();
+    }
+
+    @Override
+    public Map<ResourceReference, AdapterBinding> getAdapterBindings() {
+        return mDelegate.getAdapterBindings();
+    }
+
+    @Override
+    public boolean getExtendedViewInfoMode() {
+        return mDelegate.getExtendedViewInfoMode();
+    }
+
+    @Override
+    public int getSimulatedPlatformVersion() {
+        return mDelegate.getSimulatedPlatformVersion();
+    }
+
+    @Override
+    public RemoteILayoutPullParser getLayoutDescription() throws RemoteException {
+        return RemoteILayoutPullParserAdapter.create(mDelegate.getLayoutDescription());
+    }
+}
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteXmlPullParserAdapter.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteXmlPullParserAdapter.java
new file mode 100644
index 0000000..e646bbc
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteXmlPullParserAdapter.java
@@ -0,0 +1,234 @@
+/*
+ * 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.remote.client.adapters;
+
+import com.android.layout.remote.api.RemoteXmlPullParser;
+import com.android.layout.remote.util.RemoteInputStream;
+import com.android.layout.remote.util.StreamUtil;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+
+public class RemoteXmlPullParserAdapter implements RemoteXmlPullParser {
+    protected XmlPullParser mDelegate;
+
+    protected RemoteXmlPullParserAdapter(@NotNull XmlPullParser delegate) {
+        mDelegate = delegate;
+    }
+
+    public static RemoteXmlPullParser create(@NotNull XmlPullParser delegate)
+            throws RemoteException {
+        return (RemoteXmlPullParser) UnicastRemoteObject.exportObject(
+                new RemoteXmlPullParserAdapter(delegate), 0);
+    }
+
+    @Override
+    public void setFeature(String name, boolean state)
+            throws XmlPullParserException, RemoteException {
+        mDelegate.setFeature(name, state);
+    }
+
+    @Override
+    public boolean getFeature(String name) throws RemoteException {
+        return mDelegate.getFeature(name);
+    }
+
+    @Override
+    public void setProperty(String name, Object value)
+            throws XmlPullParserException, RemoteException {
+        mDelegate.setProperty(name, value);
+    }
+
+    @Override
+    public Object getProperty(String name) throws RemoteException {
+        return mDelegate.getProperty(name);
+    }
+
+    @Override
+    public void setInput(Reader in) throws XmlPullParserException, RemoteException {
+        mDelegate.setInput(in);
+    }
+
+    @Override
+    public void setInput(RemoteInputStream inputStream, String inputEncoding)
+            throws XmlPullParserException, RemoteException {
+        mDelegate.setInput(StreamUtil.getInputStream(inputStream), inputEncoding);
+    }
+
+    @Override
+    public String getInputEncoding() throws RemoteException {
+        return mDelegate.getInputEncoding();
+    }
+
+    @Override
+    public void defineEntityReplacementText(String entityName, String replacementText)
+            throws XmlPullParserException {
+
+    }
+
+    @Override
+    public int getNamespaceCount(int depth) throws XmlPullParserException, RemoteException {
+        return mDelegate.getNamespaceCount(depth);
+    }
+
+    @Override
+    public String getNamespacePrefix(int pos) throws XmlPullParserException, RemoteException {
+        return mDelegate.getNamespacePrefix(pos);
+    }
+
+    @Override
+    public String getNamespaceUri(int pos) throws XmlPullParserException, RemoteException {
+        return mDelegate.getNamespaceUri(pos);
+    }
+
+    @Override
+    public String getNamespace(String prefix) throws RemoteException {
+        return mDelegate.getNamespace(prefix);
+    }
+
+    @Override
+    public int getDepth() throws RemoteException {
+        return mDelegate.getDepth();
+    }
+
+    @Override
+    public String getPositionDescription() throws RemoteException {
+        return mDelegate.getPositionDescription();
+    }
+
+    @Override
+    public int getLineNumber() throws RemoteException {
+        return mDelegate.getLineNumber();
+    }
+
+    @Override
+    public int getColumnNumber() throws RemoteException {
+        return mDelegate.getColumnNumber();
+    }
+
+    @Override
+    public boolean isWhitespace() throws XmlPullParserException, RemoteException {
+        return mDelegate.isWhitespace();
+    }
+
+    @Override
+    public String getText() throws RemoteException {
+        return mDelegate.getText();
+    }
+
+    @Override
+    public char[] getTextCharacters(int[] holderForStartAndLength) throws RemoteException {
+        return mDelegate.getTextCharacters(holderForStartAndLength);
+    }
+
+    @Override
+    public String getNamespace() throws RemoteException {
+        return mDelegate.getNamespace();
+    }
+
+    @Override
+    public String getName() throws RemoteException {
+        return mDelegate.getName();
+    }
+
+    @Override
+    public String getPrefix() throws RemoteException {
+        return mDelegate.getPrefix();
+    }
+
+    @Override
+    public boolean isEmptyElementTag() throws XmlPullParserException, RemoteException {
+        return mDelegate.isEmptyElementTag();
+    }
+
+    @Override
+    public int getAttributeCount() throws RemoteException {
+        return mDelegate.getAttributeCount();
+    }
+
+    @Override
+    public String getAttributeNamespace(int index) throws RemoteException {
+        return mDelegate.getAttributeNamespace(index);
+    }
+
+    @Override
+    public String getAttributeName(int index) throws RemoteException {
+        return mDelegate.getAttributeName(index);
+    }
+
+    @Override
+    public String getAttributePrefix(int index) throws RemoteException {
+        return mDelegate.getAttributePrefix(index);
+    }
+
+    @Override
+    public String getAttributeType(int index) throws RemoteException {
+        return mDelegate.getAttributeType(index);
+    }
+
+    @Override
+    public boolean isAttributeDefault(int index) throws RemoteException {
+        return mDelegate.isAttributeDefault(index);
+    }
+
+    @Override
+    public String getAttributeValue(int index) throws RemoteException {
+        return mDelegate.getAttributeValue(index);
+    }
+
+    @Override
+    public String getAttributeValue(String namespace, String name) throws RemoteException {
+        return mDelegate.getAttributeValue(namespace, name);
+    }
+
+    @Override
+    public int getEventType() throws XmlPullParserException, RemoteException {
+        return mDelegate.getEventType();
+    }
+
+    @Override
+    public int next() throws XmlPullParserException, IOException, RemoteException {
+        return mDelegate.next();
+    }
+
+    @Override
+    public int nextToken() throws XmlPullParserException, IOException, RemoteException {
+        return mDelegate.nextToken();
+    }
+
+    @Override
+    public void require(int type, String namespace, String name)
+            throws XmlPullParserException, IOException, RemoteException {
+        mDelegate.require(type, namespace, name);
+    }
+
+    @Override
+    public String nextText() throws XmlPullParserException, IOException, RemoteException {
+        return mDelegate.nextText();
+    }
+
+    @Override
+    public int nextTag() throws XmlPullParserException, IOException, RemoteException {
+        return mDelegate.nextTag();
+    }
+}
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/package-info.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/package-info.java
new file mode 100644
index 0000000..6edf00d
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Package containing all the client side adapters. These adapters have the mission of receiving
+ * remote calls and translating them into the local API.
+ */
+package com.android.layoutlib.bridge.remote.client.adapters;
\ No newline at end of file
diff --git a/remote/common/remote common.iml b/remote/common/remote common.iml
new file mode 100644
index 0000000..e56c17c
--- /dev/null
+++ b/remote/common/remote common.iml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/../../../../prebuilts/misc/common/kxml2/kxml2-2.3.0.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="file://$MODULE_DIR$/../../../../libcore/xml/src/main/java" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="library" name="layoutlib_api-prebuilt" level="project" />
+    <orderEntry type="module" module-name="common" />
+  </component>
+</module>
\ No newline at end of file
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteActionBarCallback.java b/remote/common/src/com/android/layout/remote/api/RemoteActionBarCallback.java
new file mode 100644
index 0000000..153a575
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteActionBarCallback.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License") throws RemoteException;
+ * 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.layout.remote.api;
+
+import com.android.ide.common.rendering.api.ActionBarCallback;
+import com.android.ide.common.rendering.api.ActionBarCallback.HomeButtonStyle;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.util.List;
+
+/**
+ * Remote version of the {@link ActionBarCallback} class
+ */
+public interface RemoteActionBarCallback extends Remote {
+
+    List<String> getMenuIdNames() throws RemoteException;
+
+
+    boolean getSplitActionBarWhenNarrow() throws RemoteException;
+
+
+    int getNavigationMode() throws RemoteException;
+
+
+    String getSubTitle() throws RemoteException;
+
+
+    HomeButtonStyle getHomeButtonStyle() throws RemoteException;
+
+
+    boolean isOverflowPopupNeeded() throws RemoteException;
+}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteAssetRepository.java b/remote/common/src/com/android/layout/remote/api/RemoteAssetRepository.java
new file mode 100644
index 0000000..5b4d412
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteAssetRepository.java
@@ -0,0 +1,36 @@
+/*
+ * 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.layout.remote.api;
+
+import com.android.ide.common.rendering.api.AssetRepository;
+import com.android.layout.remote.util.RemoteInputStream;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.io.IOException;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+/**
+ * Remote version of the {@link AssetRepository} class
+ */
+public interface RemoteAssetRepository extends Remote {
+    @NotNull
+    RemoteInputStream openAsset(String path, int mode) throws IOException;
+
+    @NotNull
+    RemoteInputStream openNonAsset(int cookie, String path, int mode) throws IOException;
+}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteBridge.java b/remote/common/src/com/android/layout/remote/api/RemoteBridge.java
new file mode 100644
index 0000000..8188198
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteBridge.java
@@ -0,0 +1,122 @@
+/*
+ * 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.layout.remote.api;
+
+import com.android.ide.common.rendering.api.Bridge;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.Result;
+import com.android.tools.layoutlib.annotations.NotNull;
+import com.android.tools.layoutlib.annotations.Nullable;
+
+import java.io.File;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.util.Map;
+
+/**
+ * Interface that defines the operations available in the remote bridge. This is a remote version of
+ * the local {@link Bridge} API. Most of the methods are mapped 1:1 with the {@link Bridge} API
+ * unless there is a need for a method to be adapted for remote use.
+ */
+public interface RemoteBridge extends Remote {
+    /**
+     * Returns the API level of the layout library.
+     * <p>
+     * While no methods will ever be removed, some may become deprecated, and some new ones will
+     * appear. <p>All Layout libraries based on {@link Bridge} return at minimum an API level of 5.
+     */
+    int getApiLevel() throws RemoteException;
+
+    /**
+     * Returns the revision of the library inside a given (layoutlib) API level. The true revision
+     * number of the library is {@link #getApiLevel()}.{@link #getRevision()}
+     */
+    @SuppressWarnings("JavaDoc")
+    // javadoc pointing to itself.
+    int getRevision() throws RemoteException;
+
+    /**
+     * Returns true if the layout library supports the given feature.
+     *
+     * @see com.android.ide.common.rendering.api.Features
+     */
+    boolean supports(int feature) throws RemoteException;
+
+    /**
+     * Initializes the Bridge object.
+     *
+     * @param platformProperties The build properties for the platform.
+     * @param fontLocation the location of the fonts.
+     * @param enumValueMap map attrName ⇒ { map enumFlagName ⇒ Integer value }. This is typically
+     * read from attrs.xml in the SDK target.
+     * @param log a {@link LayoutLog} object. Can be null.
+     *
+     * @return true if success.
+     */
+    boolean init(@NotNull Map<String, String> platformProperties, File fontLocation,
+            @NotNull Map<String, Map<String, Integer>> enumValueMap, @Nullable RemoteLayoutLog log)
+            throws RemoteException;
+
+    /**
+     * Prepares the layoutlib to be unloaded.
+     */
+    boolean dispose() throws RemoteException;
+
+    /**
+     * Starts a layout session by inflating and rendering it. The method returns a {@link
+     * RenderSession} on which further actions can be taken.
+     *
+     * @return a new {@link RenderSession} object that contains the result of the scene creation and
+     * first rendering.
+     */
+    @NotNull
+    RemoteRenderSession createSession(@NotNull RemoteSessionParams params) throws RemoteException;
+
+    /**
+     * Renders a Drawable. If the rendering is successful, the result image is accessible through
+     * {@link Result#getData()}. It is of type {@link BufferedImage}
+     *
+     * @param params the rendering parameters.
+     *
+     * @return the result of the action.
+     */
+    @NotNull
+    Result renderDrawable(@NotNull RemoteDrawableParams params) throws RemoteException;
+
+    /**
+     * Clears the resource cache for a specific project.
+     * <p>This cache contains bitmaps and nine patches that are loaded from the disk and reused
+     * until this method is called.
+     * <p>The cache is not configuration dependent and should only be cleared when a
+     * resource changes (at this time only bitmaps and 9 patches go into the cache).
+     * <p>
+     * The project key provided must be similar to the one passed in {@link RenderParams}.
+     *
+     * @param projectKey the key for the project.
+     */
+    void clearCaches(String projectKey) throws RemoteException;
+
+    /**
+     * Returns true if the character orientation of the locale is right to left.
+     *
+     * @param locale The locale formatted as language-region
+     *
+     * @return true if the locale is right to left.
+     */
+    boolean isRtl(String locale) throws RemoteException;
+}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteDrawableParams.java b/remote/common/src/com/android/layout/remote/api/RemoteDrawableParams.java
new file mode 100644
index 0000000..9ef1e9e
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteDrawableParams.java
@@ -0,0 +1,31 @@
+/*
+ * 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.layout.remote.api;
+
+import com.android.ide.common.rendering.api.DrawableParams;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.rmi.RemoteException;
+
+/**
+ * Remote version of the {@link DrawableParams} class
+ */
+public interface RemoteDrawableParams extends RemoteRenderParams {
+    @NotNull
+    ResourceValue getDrawable() throws RemoteException;
+}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteHardwareConfig.java b/remote/common/src/com/android/layout/remote/api/RemoteHardwareConfig.java
new file mode 100644
index 0000000..191cca7
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteHardwareConfig.java
@@ -0,0 +1,71 @@
+/*
+ * 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.layout.remote.api;
+
+import com.android.ide.common.rendering.api.HardwareConfig;
+import com.android.resources.Density;
+import com.android.resources.ScreenOrientation;
+import com.android.resources.ScreenRound;
+import com.android.resources.ScreenSize;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.io.Serializable;
+
+/**
+ * Remote version of the {@link HardwareConfig} class
+ */
+// TODO: Just make HardwareConfig serializable
+public class RemoteHardwareConfig implements Serializable {
+    private int mScreenWidth;
+    private int mScreenHeight;
+    private Density mDensity;
+    private float mXdpi;
+    private float mYdpi;
+    private ScreenSize mScreenSize;
+    private ScreenOrientation mOrientation;
+    private ScreenRound mScreenRoundness;
+    private boolean mHasSoftwareButtons;
+
+    public RemoteHardwareConfig() {
+    }
+
+    public RemoteHardwareConfig(@NotNull HardwareConfig config) {
+        this(config.getScreenWidth(), config.getScreenHeight(), config.getDensity(),
+                config.getXdpi(), config.getYdpi(), config.getScreenSize(), config.getOrientation(),
+                config.getScreenRoundness(), config.hasSoftwareButtons());
+    }
+
+    private RemoteHardwareConfig(int screenWidth, int screenHeight, Density density, float xdpi,
+            float ydpi, ScreenSize screenSize, ScreenOrientation orientation,
+            ScreenRound screenRoundness, boolean hasSoftwareButtons) {
+        mScreenWidth = screenWidth;
+        mScreenHeight = screenHeight;
+        mDensity = density;
+        mXdpi = xdpi;
+        mYdpi = ydpi;
+        mScreenSize = screenSize;
+        mOrientation = orientation;
+        mScreenRoundness = screenRoundness;
+        mHasSoftwareButtons = hasSoftwareButtons;
+    }
+
+    @NotNull
+    public HardwareConfig getHardwareConfig() {
+        return new HardwareConfig(mScreenWidth, mScreenHeight, mDensity, mXdpi, mYdpi, mScreenSize,
+                mOrientation, mScreenRoundness, mHasSoftwareButtons);
+    }
+}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteILayoutPullParser.java b/remote/common/src/com/android/layout/remote/api/RemoteILayoutPullParser.java
new file mode 100644
index 0000000..585535b
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteILayoutPullParser.java
@@ -0,0 +1,28 @@
+/*
+ * 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.layout.remote.api;
+
+import com.android.ide.common.rendering.api.ILayoutPullParser;
+
+import java.rmi.RemoteException;
+
+/**
+ * Remote version of the {@link ILayoutPullParser} interface
+ */
+public interface RemoteILayoutPullParser extends RemoteXmlPullParser {
+    Object getViewCookie() throws RemoteException;
+}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteLayoutLog.java b/remote/common/src/com/android/layout/remote/api/RemoteLayoutLog.java
new file mode 100644
index 0000000..6240036
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteLayoutLog.java
@@ -0,0 +1,71 @@
+/*
+ * 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.layout.remote.api;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+
+import java.io.Serializable;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+/**
+ * Remote version of the {@link LayoutLog} class
+ */
+public interface RemoteLayoutLog extends Remote {
+    /**
+     * Logs a warning.
+     *
+     * @param tag a tag describing the type of the warning
+     * @param message the message of the warning
+     * @param data an optional data bundle that the client can use to improve the warning display.
+     */
+    void warning(String tag, String message, Serializable data) throws RemoteException;
+
+    /**
+     * Logs a fidelity warning.
+     * <p>
+     * This type of warning indicates that the render will not be the same as the rendering on a
+     * device due to limitation of the Java rendering API.
+     *
+     * @param tag a tag describing the type of the warning
+     * @param message the message of the warning
+     * @param throwable an optional Throwable that triggered the warning
+     * @param data an optional data bundle that the client can use to improve the warning display.
+     */
+    void fidelityWarning(String tag, String message, Throwable throwable, Serializable data)
+            throws RemoteException;
+
+    /**
+     * Logs an error.
+     *
+     * @param tag a tag describing the type of the error
+     * @param message the message of the error
+     * @param data an optional data bundle that the client can use to improve the error display.
+     */
+    void error(String tag, String message, Serializable data) throws RemoteException;
+
+    /**
+     * Logs an error, and the {@link Throwable} that triggered it.
+     *
+     * @param tag a tag describing the type of the error
+     * @param message the message of the error
+     * @param throwable the Throwable that triggered the error
+     * @param data an optional data bundle that the client can use to improve the error display.
+     */
+    void error(String tag, String message, Throwable throwable, Serializable data)
+            throws RemoteException;
+}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteLayoutlibCallback.java b/remote/common/src/com/android/layout/remote/api/RemoteLayoutlibCallback.java
new file mode 100644
index 0000000..0f315ca
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteLayoutlibCallback.java
@@ -0,0 +1,83 @@
+/*
+ * 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.layout.remote.api;
+
+import com.android.ide.common.rendering.api.AdapterBinding;
+import com.android.ide.common.rendering.api.IProjectCallback.ViewAttribute;
+import com.android.ide.common.rendering.api.LayoutlibCallback;
+import com.android.ide.common.rendering.api.ResourceReference;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.SessionParams.Key;
+import com.android.resources.ResourceType;
+import com.android.util.Pair;
+
+import java.io.Serializable;
+import java.nio.file.Path;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+/**
+ * Remote version of the {@link LayoutlibCallback} class
+ */
+public interface RemoteLayoutlibCallback extends Remote {
+    boolean supports(int ideFeature) throws RemoteException;
+
+    Object loadView(String name, Class[] constructorSignature, Object[] constructorArgs)
+            throws Exception, RemoteException;
+
+    String getNamespace() throws RemoteException;
+
+    RemoteResolveResult resolveResourceId(int id) throws RemoteException;
+
+    String resolveResourceId(int[] id) throws RemoteException;
+
+    Integer getResourceId(ResourceType type, String name) throws RemoteException;
+
+    RemoteILayoutPullParser getParser(ResourceValue layoutResource) throws RemoteException;
+
+    Object getAdapterItemValue(ResourceReference adapterView, Object adapterCookie,
+            ResourceReference itemRef, int fullPosition, int positionPerType,
+            int fullParentPosition, int parentPositionPerType, ResourceReference viewRef,
+            ViewAttribute viewAttribute, Object defaultValue) throws RemoteException;
+
+    AdapterBinding getAdapterBinding(ResourceReference adapterViewRef, Object adapterCookie,
+            Object viewObject) throws RemoteException;
+
+    RemoteActionBarCallback getActionBarCallback() throws RemoteException;
+
+    <T> T getFlag(Key<T> key) throws RemoteException;
+
+    RemoteParserFactory getParserFactory() throws RemoteException;
+
+    Path findClassPath(String name) throws RemoteException;
+
+    RemoteXmlPullParser getXmlFileParser(String fileName) throws RemoteException;
+
+    class RemoteResolveResult implements Serializable {
+        private ResourceType type;
+        private String value;
+
+        public RemoteResolveResult(ResourceType type, String value) {
+            this.type = type;
+            this.value = value;
+        }
+
+        public Pair<ResourceType, String> asPair() {
+            return Pair.of(type, value);
+        }
+    }
+}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteParserFactory.java b/remote/common/src/com/android/layout/remote/api/RemoteParserFactory.java
new file mode 100644
index 0000000..31a35b2
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteParserFactory.java
@@ -0,0 +1,30 @@
+/*
+ * 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.layout.remote.api;
+
+import com.android.ide.common.rendering.api.ParserFactory;
+import com.android.tools.layoutlib.annotations.Nullable;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+/**
+ * Remote version of the {@link ParserFactory} class
+ */
+public interface RemoteParserFactory extends Remote {
+    RemoteXmlPullParser createParser(@Nullable String debugName) throws RemoteException;
+}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteRenderParams.java b/remote/common/src/com/android/layout/remote/api/RemoteRenderParams.java
new file mode 100644
index 0000000..da2bd8e
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteRenderParams.java
@@ -0,0 +1,65 @@
+/*
+ * 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.layout.remote.api;
+
+import com.android.ide.common.rendering.api.IImageFactory;
+import com.android.ide.common.rendering.api.SessionParams.Key;
+import com.android.tools.layoutlib.annotations.Nullable;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+public interface RemoteRenderParams extends Remote {
+    @Nullable
+    String getProjectKey() throws RemoteException;
+
+    RemoteHardwareConfig getRemoteHardwareConfig() throws RemoteException;
+
+    int getMinSdkVersion() throws RemoteException;
+
+    int getTargetSdkVersion() throws RemoteException;
+
+    RemoteRenderResources getRemoteResources() throws RemoteException;
+
+    RemoteAssetRepository getAssets() throws RemoteException;
+
+    RemoteLayoutlibCallback getRemoteLayoutlibCallback() throws RemoteException;
+
+    RemoteLayoutLog getLog() throws RemoteException;
+
+    boolean isBgColorOverridden() throws RemoteException;
+
+    int getOverrideBgColor() throws RemoteException;
+
+    long getTimeout() throws RemoteException;
+
+    IImageFactory getImageFactory() throws RemoteException;
+
+    String getAppIcon() throws RemoteException;
+
+    String getAppLabel() throws RemoteException;
+
+    String getLocale() throws RemoteException;
+
+    String getActivityName() throws RemoteException;
+
+    boolean isForceNoDecor() throws RemoteException;
+
+    boolean isRtlSupported() throws RemoteException;
+
+    <T> T getFlag(Key<T> key) throws RemoteException;
+}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteRenderResources.java b/remote/common/src/com/android/layout/remote/api/RemoteRenderResources.java
new file mode 100644
index 0000000..f7341dc
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteRenderResources.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License") throws RemoteException;
+ * 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.layout.remote.api;
+
+import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.resources.ResourceType;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.util.List;
+
+/**
+ * Remote version of the {@link RenderResources} class
+ */
+public interface RemoteRenderResources extends Remote {
+    StyleResourceValue getDefaultTheme() throws RemoteException;
+
+    void applyStyle(StyleResourceValue theme, boolean useAsPrimary) throws RemoteException;
+
+    void clearStyles() throws RemoteException;
+
+    List<StyleResourceValue> getAllThemes() throws RemoteException;
+
+
+    StyleResourceValue getTheme(String name, boolean frameworkTheme) throws RemoteException;
+
+
+    boolean themeIsParentOf(StyleResourceValue parentTheme, StyleResourceValue childTheme)
+            throws RemoteException;
+
+    ResourceValue getFrameworkResource(ResourceType resourceType, String resourceName)
+            throws RemoteException;
+
+    ResourceValue getProjectResource(ResourceType resourceType, String resourceName)
+            throws RemoteException;
+
+
+    ResourceValue findItemInTheme(String attrName, boolean isFrameworkAttr) throws RemoteException;
+
+
+    ResourceValue findItemInStyle(StyleResourceValue style, String attrName,
+            boolean isFrameworkAttr) throws RemoteException;
+
+
+    ResourceValue findResValue(String reference, boolean forceFrameworkOnly) throws RemoteException;
+
+    ResourceValue resolveValue(ResourceValue value) throws RemoteException;
+
+    ResourceValue resolveValue(ResourceType type, String name, String value,
+            boolean isFrameworkValue) throws RemoteException;
+
+    StyleResourceValue getParent(StyleResourceValue style) throws RemoteException;
+
+    StyleResourceValue getStyle(String styleName, boolean isFramework) throws RemoteException;
+}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteRenderSession.java b/remote/common/src/com/android/layout/remote/api/RemoteRenderSession.java
new file mode 100644
index 0000000..648186f
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteRenderSession.java
@@ -0,0 +1,48 @@
+/*
+ * 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.layout.remote.api;
+
+import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.Result;
+import com.android.layout.remote.util.SerializableImage;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+
+/**
+ * Remote version of the {@link RenderSession} class
+ */
+public interface RemoteRenderSession extends Remote {
+    @NotNull
+    Result getResult() throws RemoteException;
+
+    @NotNull
+    SerializableImage getSerializableImage() throws RemoteException;
+
+    void setSystemTimeNanos(long nanos) throws RemoteException;
+
+    void setSystemBootTimeNanos(long nanos) throws RemoteException;
+
+    void setElapsedFrameTimeNanos(long nanos) throws RemoteException;
+
+    void dispose() throws RemoteException;
+
+    @NotNull
+    Result render(long timeout, boolean forceMeasure) throws RemoteException;
+}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteSessionParams.java b/remote/common/src/com/android/layout/remote/api/RemoteSessionParams.java
new file mode 100644
index 0000000..52a2a09
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteSessionParams.java
@@ -0,0 +1,45 @@
+/*
+ * 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.layout.remote.api;
+
+import com.android.ide.common.rendering.api.AdapterBinding;
+import com.android.ide.common.rendering.api.IImageFactory;
+import com.android.ide.common.rendering.api.ResourceReference;
+import com.android.ide.common.rendering.api.SessionParams;
+import com.android.ide.common.rendering.api.SessionParams.Key;
+import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.util.Map;
+
+/**
+ * Remote version of the {@link SessionParams} class
+ */
+public interface RemoteSessionParams extends RemoteRenderParams {
+    RenderingMode getRenderingMode() throws RemoteException;
+
+    boolean isLayoutOnly() throws RemoteException;
+
+    Map<ResourceReference, AdapterBinding> getAdapterBindings() throws RemoteException;
+
+    boolean getExtendedViewInfoMode() throws RemoteException;
+
+    int getSimulatedPlatformVersion() throws RemoteException;
+
+    RemoteILayoutPullParser getLayoutDescription() throws RemoteException;
+}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteXmlPullParser.java b/remote/common/src/com/android/layout/remote/api/RemoteXmlPullParser.java
new file mode 100644
index 0000000..e3e05d3
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteXmlPullParser.java
@@ -0,0 +1,109 @@
+/*
+ * 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.layout.remote.api;
+
+import com.android.layout.remote.util.RemoteInputStream;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+/**
+ * Remote version of the {@link XmlPullParser} interface
+ */
+public interface RemoteXmlPullParser extends Remote {
+    void setFeature(String name, boolean state) throws XmlPullParserException, RemoteException;
+
+    boolean getFeature(String name) throws RemoteException;
+
+    void setProperty(String name, Object value) throws XmlPullParserException, RemoteException;
+
+    Object getProperty(String name) throws RemoteException;
+
+    void setInput(Reader in) throws XmlPullParserException, RemoteException;
+
+    void setInput(RemoteInputStream inputStream, String inputEncoding)
+            throws XmlPullParserException, RemoteException;
+
+    String getInputEncoding() throws RemoteException;
+
+    void defineEntityReplacementText(String entityName, String replacementText)
+            throws XmlPullParserException, RemoteException;
+
+    int getNamespaceCount(int depth) throws XmlPullParserException, RemoteException;
+
+    String getNamespacePrefix(int pos) throws XmlPullParserException, RemoteException;
+
+    String getNamespaceUri(int pos) throws XmlPullParserException, RemoteException;
+
+    String getNamespace(String prefix) throws RemoteException;
+
+    int getDepth() throws RemoteException;
+
+    String getPositionDescription() throws RemoteException;
+
+    int getLineNumber() throws RemoteException;
+
+    int getColumnNumber() throws RemoteException;
+
+    boolean isWhitespace() throws XmlPullParserException, RemoteException;
+
+    String getText() throws RemoteException;
+
+    char[] getTextCharacters(int[] holderForStartAndLength) throws RemoteException;
+
+    String getNamespace() throws RemoteException;
+
+    String getName() throws RemoteException;
+
+    String getPrefix() throws RemoteException;
+
+    boolean isEmptyElementTag() throws XmlPullParserException, RemoteException;
+
+    int getAttributeCount() throws RemoteException;
+
+    String getAttributeNamespace(int index) throws RemoteException;
+
+    String getAttributeName(int index) throws RemoteException;
+
+    String getAttributePrefix(int index) throws RemoteException;
+
+    String getAttributeType(int index) throws RemoteException;
+
+    boolean isAttributeDefault(int index) throws RemoteException;
+
+    String getAttributeValue(int index) throws RemoteException;
+
+    String getAttributeValue(String namespace, String name) throws RemoteException;
+
+    int getEventType() throws XmlPullParserException, RemoteException;
+
+    int next() throws XmlPullParserException, IOException, RemoteException;
+
+    int nextToken() throws XmlPullParserException, IOException, RemoteException;
+
+    void require(int type, String namespace, String name)
+            throws XmlPullParserException, IOException, RemoteException;
+
+    String nextText() throws XmlPullParserException, IOException, RemoteException;
+
+    int nextTag() throws XmlPullParserException, IOException, RemoteException;
+}
diff --git a/remote/common/src/com/android/layout/remote/api/package-info.java b/remote/common/src/com/android/layout/remote/api/package-info.java
new file mode 100644
index 0000000..c7591c6
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * Package containing all the interfaces that define the remote version of the Layoutlib API. This
+ * interface matches the local layout API closely. In some cases where special capabilities to
+ * support the remote calls are needed, the API will be different.
+ */
+package com.android.layout.remote.api;
\ No newline at end of file
diff --git a/remote/common/src/com/android/layout/remote/util/RemoteInputStream.java b/remote/common/src/com/android/layout/remote/util/RemoteInputStream.java
new file mode 100644
index 0000000..f3b1611
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/util/RemoteInputStream.java
@@ -0,0 +1,42 @@
+/*
+ * 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.layout.remote.util;
+
+import java.io.IOException;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+public interface RemoteInputStream extends Remote {
+    int read() throws IOException;
+
+    byte[] read(int off, int len) throws IOException;
+
+    long skip(long n) throws IOException;
+
+    int available() throws IOException;
+
+    void close() throws IOException;
+
+    void mark(int readlimit) throws RemoteException;
+
+    void reset() throws IOException;
+
+    boolean markSupported() throws RemoteException;
+
+    class EndOfStreamException extends IOException {
+    }
+}
diff --git a/remote/common/src/com/android/layout/remote/util/RemoteInputStreamAdapter.java b/remote/common/src/com/android/layout/remote/util/RemoteInputStreamAdapter.java
new file mode 100644
index 0000000..e983202
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/util/RemoteInputStreamAdapter.java
@@ -0,0 +1,82 @@
+/*
+ * 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.layout.remote.util;
+
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+
+public class RemoteInputStreamAdapter implements RemoteInputStream {
+
+    private InputStream mDelegate;
+
+    private RemoteInputStreamAdapter(@NotNull InputStream delegate) {
+        mDelegate = delegate;
+    }
+
+    public static RemoteInputStream create(@NotNull InputStream is) throws RemoteException {
+        return (RemoteInputStream) UnicastRemoteObject.exportObject(
+                new RemoteInputStreamAdapter(is), 0);
+    }
+
+    @Override
+    public int read() throws IOException {
+        return mDelegate.read();
+    }
+
+    @Override
+    public byte[] read(int off, int len) throws IOException, RemoteException {
+        byte[] buffer = new byte[len];
+        if (mDelegate.read(buffer, off, len) == -1) {
+            throw new EndOfStreamException();
+        }
+        return buffer;
+    }
+
+    @Override
+    public long skip(long n) throws IOException {
+        return mDelegate.skip(n);
+    }
+
+    @Override
+    public int available() throws IOException {
+        return mDelegate.available();
+    }
+
+    @Override
+    public void close() throws IOException {
+        mDelegate.close();
+    }
+
+    @Override
+    public void mark(int readlimit) {
+        mDelegate.mark(readlimit);
+    }
+
+    @Override
+    public void reset() throws IOException {
+        mDelegate.reset();
+    }
+
+    @Override
+    public boolean markSupported() {
+        return mDelegate.markSupported();
+    }
+}
diff --git a/remote/common/src/com/android/layout/remote/util/SerializableImage.java b/remote/common/src/com/android/layout/remote/util/SerializableImage.java
new file mode 100644
index 0000000..999aae4
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/util/SerializableImage.java
@@ -0,0 +1,30 @@
+/*
+ * 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.layout.remote.util;
+
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.awt.image.BufferedImage;
+import java.io.Serializable;
+
+/**
+ * Serializable BufferedImage that can be sent across VMs
+ */
+public interface SerializableImage extends Serializable {
+    @NotNull
+    BufferedImage getBufferedImage();
+}
diff --git a/remote/common/src/com/android/layout/remote/util/SerializableImageImpl.java b/remote/common/src/com/android/layout/remote/util/SerializableImageImpl.java
new file mode 100644
index 0000000..59c8c3f
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/util/SerializableImageImpl.java
@@ -0,0 +1,55 @@
+/*
+ * 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.layout.remote.util;
+
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import javax.imageio.ImageIO;
+
+/**
+ * Naive implementation of {@link SerializableImage} using {@link ImageIO} and PNG format as
+ * transport.
+ */
+public class SerializableImageImpl implements SerializableImage {
+    @NotNull
+    transient private BufferedImage mBufferedImage;
+
+    public SerializableImageImpl(@NotNull BufferedImage delegate) {
+        mBufferedImage = delegate;
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        out.defaultWriteObject();
+        ImageIO.write(mBufferedImage, "png", out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        in.defaultReadObject();
+        mBufferedImage = ImageIO.read(in);
+    }
+
+    @Override
+    @NotNull
+    public BufferedImage getBufferedImage() {
+        return mBufferedImage;
+    }
+}
diff --git a/remote/common/src/com/android/layout/remote/util/StreamUtil.java b/remote/common/src/com/android/layout/remote/util/StreamUtil.java
new file mode 100644
index 0000000..e05b5de
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/util/StreamUtil.java
@@ -0,0 +1,96 @@
+/*
+ * 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.layout.remote.util;
+
+import com.android.layout.remote.util.RemoteInputStream.EndOfStreamException;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.rmi.RemoteException;
+
+public class StreamUtil {
+    /**
+     * Returns a local {@link InputStream} from a {@link RemoteInputStream}
+     */
+    @NotNull
+    public static InputStream getInputStream(@NotNull RemoteInputStream is) {
+        return new InputStream() {
+            @Override
+            public int read() throws IOException {
+                return is.read();
+            }
+
+            @SuppressWarnings("NullableProblems")
+            @Override
+            public int read(byte[] b) throws IOException {
+                return read(b, 0, b.length);
+            }
+
+            @SuppressWarnings("NullableProblems")
+            @Override
+            public int read(byte[] b, int off, int len) throws IOException {
+                try {
+                    byte[] read = is.read(off, len);
+                    int actualLength = Math.min(len, read.length);
+                    System.arraycopy(read, 0, b, off, actualLength);
+                    return actualLength;
+                } catch (EndOfStreamException e) {
+                    return -1;
+                }
+            }
+
+            @Override
+            public long skip(long n) throws IOException {
+                return is.skip(n);
+            }
+
+            @Override
+            public int available() throws IOException {
+                return is.available();
+            }
+
+            @Override
+            public void close() throws IOException {
+                is.close();
+            }
+
+            @Override
+            public synchronized void mark(int readlimit) {
+                try {
+                    is.mark(readlimit);
+                } catch (RemoteException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+
+            @Override
+            public synchronized void reset() throws IOException {
+                is.reset();
+            }
+
+            @Override
+            public boolean markSupported() {
+                try {
+                    return is.markSupported();
+                } catch (RemoteException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        };
+    }
+}
diff --git a/remote/server/remote server.iml b/remote/server/remote server.iml
new file mode 100644
index 0000000..9fc0852
--- /dev/null
+++ b/remote/server/remote server.iml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="module" module-name="bridge" />
+    <orderEntry type="library" name="layoutlib_api-prebuilt" level="project" />
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/../../../../prebuilts/misc/common/kxml2/kxml2-2.3.0.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="file://$MODULE_DIR$/../../../../libcore/xml/src/main/java" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module" module-name="remote common" />
+    <orderEntry type="module" module-name="common" />
+  </component>
+</module>
\ No newline at end of file
diff --git a/remote/server/src/com/android/layoutlib/bridge/remote/server/RemoteBridgeImpl.java b/remote/server/src/com/android/layoutlib/bridge/remote/server/RemoteBridgeImpl.java
new file mode 100644
index 0000000..2593afc
--- /dev/null
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/RemoteBridgeImpl.java
@@ -0,0 +1,163 @@
+/*
+ * 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.remote.server;
+
+import com.android.ide.common.rendering.api.DrawableParams;
+import com.android.ide.common.rendering.api.RenderParams;
+import com.android.ide.common.rendering.api.Result;
+import com.android.ide.common.rendering.api.SessionParams;
+import com.android.layout.remote.api.RemoteBridge;
+import com.android.layout.remote.api.RemoteDrawableParams;
+import com.android.layout.remote.api.RemoteLayoutLog;
+import com.android.layout.remote.api.RemoteRenderParams;
+import com.android.layout.remote.api.RemoteRenderSession;
+import com.android.layout.remote.api.RemoteSessionParams;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.remote.server.adapters.RemoteAssetRepositoryAdapter;
+import com.android.layoutlib.bridge.remote.server.adapters.RemoteILayoutPullParserAdapter;
+import com.android.layoutlib.bridge.remote.server.adapters.RemoteLayoutLogAdapter;
+import com.android.layoutlib.bridge.remote.server.adapters.RemoteLayoutlibCallbackAdapter;
+import com.android.layoutlib.bridge.remote.server.adapters.RemoteRenderResourcesAdapter;
+import com.android.layoutlib.bridge.remote.server.adapters.RemoteRenderSessionAdapter;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.io.File;
+import java.rmi.RemoteException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Remote {@link Bridge} implementation. This class is the remote entry point for the server. Its
+ * responsibility is to receive the remote calls and apply the required transformations to convert
+ * it into a regular call to the {@link Bridge} class.
+ */
+public class RemoteBridgeImpl implements RemoteBridge {
+    private Bridge mBridge = new Bridge();
+
+    /**
+     * The project keys are used as key for some caches. They are usually expected to remain in
+     * memory so WeakReferences are used in the caches.
+     * Because in the remote bridge we do not have a real pointer to the object, we keep the strings
+     * in memory until they are cleared.
+     */
+    private Map<String, String> mCachedProjectKeys = new HashMap<>();
+
+    @Override
+    public int getApiLevel() {
+        return mBridge.getApiLevel();
+    }
+
+    @Override
+    public int getRevision() {
+        return mBridge.getRevision();
+    }
+
+    @Override
+    public boolean supports(int feature) {
+        return mBridge.supports(feature);
+    }
+
+    @Override
+    public boolean init(Map<String, String> platformProperties, File fontLocation,
+            Map<String, Map<String, Integer>> enumValueMap, RemoteLayoutLog log) {
+        return mBridge.init(platformProperties, fontLocation, enumValueMap,
+                log != null ? new RemoteLayoutLogAdapter(log) : null);
+    }
+
+    @Override
+    public boolean dispose() {
+        return mBridge.dispose();
+    }
+
+    private static void setupRenderParams(@NotNull RenderParams params,
+            @NotNull RemoteRenderParams remoteParams) throws RemoteException {
+        params.setAssetRepository(new RemoteAssetRepositoryAdapter(remoteParams.getAssets()));
+        params.setActivityName(remoteParams.getActivityName());
+        params.setAppIcon(remoteParams.getAppIcon());
+        params.setAppLabel(remoteParams.getAppLabel());
+        params.setTimeout(remoteParams.getTimeout());
+        params.setLocale(remoteParams.getLocale());
+        if (remoteParams.isForceNoDecor()) {
+            params.setForceNoDecor();
+        }
+        params.setRtlSupport(remoteParams.isRtlSupported());
+        if (remoteParams.isBgColorOverridden()) {
+            params.setOverrideBgColor(remoteParams.getOverrideBgColor());
+        }
+        params.setImageFactory(remoteParams.getImageFactory());
+        // TODO: Also unpack remote flags and pass them to RenderParams
+    }
+
+    @NotNull
+    @Override
+    public RemoteRenderSession createSession(@NotNull RemoteSessionParams remoteParams) {
+        try {
+            String projectKey = mCachedProjectKeys.putIfAbsent(remoteParams.getProjectKey(),
+                    remoteParams.getProjectKey());
+
+            // Unpack the remote params and convert it into the local SessionParams
+            SessionParams params = new SessionParams(
+                    new RemoteILayoutPullParserAdapter(remoteParams.getLayoutDescription()),
+                    remoteParams.getRenderingMode(), projectKey,
+                    remoteParams.getRemoteHardwareConfig().getHardwareConfig(),
+                    new RemoteRenderResourcesAdapter(remoteParams.getRemoteResources()),
+                    new RemoteLayoutlibCallbackAdapter(remoteParams.getRemoteLayoutlibCallback()),
+                    remoteParams.getMinSdkVersion(), remoteParams.getTargetSdkVersion(),
+                    new RemoteLayoutLogAdapter(remoteParams.getLog()));
+            setupRenderParams(params, remoteParams);
+            return RemoteRenderSessionAdapter.create(mBridge.createSession(params));
+
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @NotNull
+    @Override
+    public Result renderDrawable(@NotNull RemoteDrawableParams remoteParams) {
+        try {
+            String projectKey = mCachedProjectKeys.putIfAbsent(remoteParams.getProjectKey(),
+                    remoteParams.getProjectKey());
+
+            DrawableParams params = new DrawableParams(
+                    remoteParams.getDrawable(),
+                    projectKey,
+                    remoteParams.getRemoteHardwareConfig().getHardwareConfig(),
+                    new RemoteRenderResourcesAdapter(remoteParams.getRemoteResources()),
+                    new RemoteLayoutlibCallbackAdapter(remoteParams.getRemoteLayoutlibCallback()),
+                    remoteParams.getMinSdkVersion(), remoteParams.getTargetSdkVersion(),
+                    new RemoteLayoutLogAdapter(remoteParams.getLog()));
+            setupRenderParams(params, remoteParams);
+            return mBridge.renderDrawable(params);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void clearCaches(String projectKey) {
+        mCachedProjectKeys.remove(projectKey);
+        mBridge.clearCaches(projectKey);
+    }
+
+    @Override
+    public boolean isRtl(String locale) {
+        return mBridge.isRtl(locale);
+    }
+}
diff --git a/remote/server/src/com/android/layoutlib/bridge/remote/server/ServerMain.java b/remote/server/src/com/android/layoutlib/bridge/remote/server/ServerMain.java
new file mode 100644
index 0000000..09c27fd
--- /dev/null
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/ServerMain.java
@@ -0,0 +1,192 @@
+/*
+ * 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.remote.server;
+
+import com.android.layout.remote.api.RemoteBridge;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.management.ManagementFactory;
+import java.lang.management.RuntimeMXBean;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.rmi.NoSuchObjectException;
+import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * Main server class. The main method will start an RMI server for the {@link RemoteBridgeImpl}
+ * class.
+ */
+public class ServerMain {
+    private static final String RUNNING_SERVER_STR = "Server is running on port ";
+    public static int REGISTRY_BASE_PORT = 9000;
+
+    private final int mPort;
+    private final Registry mRegistry;
+
+    private ServerMain(int port, @NotNull Registry registry) {
+        mPort = port;
+        mRegistry = registry;
+    }
+
+    public int getPort() {
+        return mPort;
+    }
+
+    public void stop() {
+        try {
+            UnicastRemoteObject.unexportObject(mRegistry, true);
+        } catch (NoSuchObjectException ignored) {
+        }
+    }
+
+    /**
+     * This will start a new JVM and connect to the new JVM RMI registry.
+     * <p/>
+     * The server will start looking for ports available for the {@link Registry} until a free one
+     * is found. The port number will be returned.
+     * If no ports are available, a {@link RemoteException} will be thrown.
+     * @param basePort port number to start looking for available ports
+     * @param limit number of ports to check. The last port to be checked will be (basePort + limit)
+     */
+    public static ServerMain forkAndStartServer(int basePort, int limit)
+            throws IOException, InterruptedException {
+        // We start a new VM by copying all the parameter that we received in the current VM.
+        // We only remove the agentlib parameter since that could cause a port collision and avoid
+        // the new VM from starting.
+        RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
+        List<String> arguments = runtimeMxBean.getInputArguments().stream()
+                .filter(arg -> !arg.contains("-agentlib")) // Filter agentlib to avoid conflicts
+                .collect(Collectors.toList());
+
+        Path javaPath = Paths.get(System.getProperty("java.home"), "bin", "java");
+        String thisClassName = ServerMain.class.getName()
+                .replace('.','/');
+
+        List<String> cmd = new ArrayList<>();
+        cmd.add(javaPath.toString());
+
+        // Inherited arguments
+        cmd.addAll(arguments);
+
+        // Classpath
+        cmd.add("-cp");
+        cmd.add(System.getProperty("java.class.path"));
+
+        // Class name and path
+        cmd.add(thisClassName);
+
+        // ServerMain parameters [basePort. limit]
+        cmd.add(Integer.toString(basePort));
+        cmd.add(Integer.toString(limit));
+
+        Process process = new ProcessBuilder()
+                .command(cmd)
+                .start();
+
+        BlockingQueue<String> outputQueue = new ArrayBlockingQueue<>(10);
+        Thread outputThread = new Thread(() -> {
+            BufferedReader inputStream = new BufferedReader(
+                    new InputStreamReader(process.getInputStream()));
+            inputStream.lines()
+                    .forEach(outputQueue::offer);
+
+        });
+        outputThread.setName("output thread");
+        outputThread.start();
+
+        Runnable killServer = () -> {
+            process.destroyForcibly();
+            outputThread.interrupt();
+            try {
+                outputThread.join();
+            } catch (InterruptedException ignore) {
+            }
+        };
+
+        // Try to read the "Running on port" line in 10 lines. If it's not there just fail.
+        for (int i = 0; i < 10; i++) {
+            String line = outputQueue.poll(1000, TimeUnit.SECONDS);
+
+            if (line.startsWith(RUNNING_SERVER_STR)) {
+                int runningPort = Integer.parseInt(line.substring(RUNNING_SERVER_STR.length()));
+                System.out.println("Running on port " + runningPort);
+
+                // We already know where the server is running so we just need to get the registry
+                // and return our own instance of ServerMain
+                Registry registry = LocateRegistry.getRegistry(runningPort);
+                return new ServerMain(runningPort, registry) {
+                    @Override
+                    public void stop() {
+                        killServer.run();
+                    }
+                };
+            }
+        }
+
+        killServer.run();
+        throw new IOException("Unable to find start string");
+    }
+
+    /**
+     * The server will start looking for ports available for the {@link Registry} until a free one
+     * is found. The port number will be returned.
+     * If no ports are available, a {@link RemoteException} will be thrown.
+     * @param basePort port number to start looking for available ports
+     * @param limit number of ports to check. The last port to be checked will be (basePort + limit)
+     */
+    private static ServerMain startServer(int basePort, int limit) throws RemoteException {
+        RemoteBridgeImpl remoteBridge = new RemoteBridgeImpl();
+        RemoteBridge stub = (RemoteBridge) UnicastRemoteObject.exportObject(remoteBridge, 0);
+
+        RemoteException lastException = null;
+        for (int port = basePort; port <= basePort + limit; port++) {
+            try {
+                Registry registry = LocateRegistry.createRegistry(port);
+                registry.rebind(RemoteBridge.class.getName(), stub);
+                return new ServerMain(port, registry);
+            } catch (RemoteException e) {
+                lastException = e;
+            }
+        }
+
+        if (lastException == null) {
+            lastException = new RemoteException("Unable to start server");
+        }
+
+        throw lastException;
+    }
+
+    public static void main(String[] args) throws RemoteException {
+        int basePort = args.length > 0 ? Integer.parseInt(args[0]) : REGISTRY_BASE_PORT;
+        int limit = args.length > 1 ? Integer.parseInt(args[1]) : 10;
+
+        ServerMain server = startServer(basePort, limit);
+        System.out.println(RUNNING_SERVER_STR + server.getPort());
+    }
+}
diff --git a/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteActionBarCallbackAdapter.java b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteActionBarCallbackAdapter.java
new file mode 100644
index 0000000..b5c040e
--- /dev/null
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteActionBarCallbackAdapter.java
@@ -0,0 +1,86 @@
+/*
+ * 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.remote.server.adapters;
+
+import com.android.ide.common.rendering.api.ActionBarCallback;
+import com.android.layout.remote.api.RemoteActionBarCallback;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.rmi.RemoteException;
+import java.util.List;
+
+public class RemoteActionBarCallbackAdapter extends ActionBarCallback {
+    private final RemoteActionBarCallback mDelegate;
+
+    public RemoteActionBarCallbackAdapter(@NotNull RemoteActionBarCallback remote) {
+        mDelegate = remote;
+    }
+
+    @Override
+    public List<String> getMenuIdNames() {
+        try {
+            return mDelegate.getMenuIdNames();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public boolean getSplitActionBarWhenNarrow() {
+        try {
+            return mDelegate.getSplitActionBarWhenNarrow();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public int getNavigationMode() {
+        try {
+            return mDelegate.getNavigationMode();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public String getSubTitle() {
+        try {
+            return mDelegate.getSubTitle();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public HomeButtonStyle getHomeButtonStyle() {
+        try {
+            return mDelegate.getHomeButtonStyle();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public boolean isOverflowPopupNeeded() {
+        try {
+            return mDelegate.isOverflowPopupNeeded();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteAssetRepositoryAdapter.java b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteAssetRepositoryAdapter.java
new file mode 100644
index 0000000..490f829
--- /dev/null
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteAssetRepositoryAdapter.java
@@ -0,0 +1,50 @@
+/*
+ * 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.remote.server.adapters;
+
+import com.android.ide.common.rendering.api.AssetRepository;
+import com.android.layout.remote.api.RemoteAssetRepository;
+import com.android.layout.remote.util.StreamUtil;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.rmi.RemoteException;
+
+public class RemoteAssetRepositoryAdapter extends AssetRepository {
+    private final RemoteAssetRepository mDelgate;
+
+    public RemoteAssetRepositoryAdapter(@NotNull RemoteAssetRepository remote) {
+        mDelgate = remote;
+    }
+
+    @Override
+    public boolean isSupported() {
+        return true;
+    }
+
+    @Override
+    public InputStream openAsset(String path, int mode) throws IOException, RemoteException {
+        return StreamUtil.getInputStream(mDelgate.openAsset(path, mode));
+    }
+
+    @Override
+    public InputStream openNonAsset(int cookie, String path, int mode)
+            throws IOException, RemoteException {
+        return StreamUtil.getInputStream(mDelgate.openNonAsset(cookie, path, mode));
+    }
+}
diff --git a/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteILayoutPullParserAdapter.java b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteILayoutPullParserAdapter.java
new file mode 100644
index 0000000..b717feb
--- /dev/null
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteILayoutPullParserAdapter.java
@@ -0,0 +1,46 @@
+/*
+ * 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.remote.server.adapters;
+
+import com.android.ide.common.rendering.api.ILayoutPullParser;
+import com.android.layout.remote.api.RemoteILayoutPullParser;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.rmi.RemoteException;
+
+public class RemoteILayoutPullParserAdapter extends RemoteXmlPullParserAdapter
+        implements ILayoutPullParser {
+    public RemoteILayoutPullParserAdapter(@NotNull RemoteILayoutPullParser delegate) {
+        super(delegate);
+    }
+
+    @Override
+    public Object getViewCookie() {
+        try {
+            return ((RemoteILayoutPullParser) mDelegate).getViewCookie();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public ILayoutPullParser getParser(String layoutName) {
+        throw new UnsupportedOperationException();
+    }
+
+
+}
diff --git a/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteLayoutLogAdapter.java b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteLayoutLogAdapter.java
new file mode 100644
index 0000000..7a3fcfa
--- /dev/null
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteLayoutLogAdapter.java
@@ -0,0 +1,67 @@
+/*
+ * 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.remote.server.adapters;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layout.remote.api.RemoteLayoutLog;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.rmi.RemoteException;
+
+public class RemoteLayoutLogAdapter extends LayoutLog {
+    private final RemoteLayoutLog mLog;
+
+    public RemoteLayoutLogAdapter(@NotNull RemoteLayoutLog log) {
+        mLog = log;
+    }
+
+    @Override
+    public void warning(String tag, String message, Object data) {
+        try {
+            mLog.warning(tag, message, null);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void fidelityWarning(String tag, String message, Throwable throwable, Object data) {
+        try {
+            mLog.fidelityWarning(tag, message, throwable, null);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void error(String tag, String message, Object data) {
+        try {
+            mLog.error(tag, message, null);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void error(String tag, String message, Throwable throwable, Object data) {
+        try {
+            mLog.error(tag, message, throwable, null);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteLayoutlibCallbackAdapter.java b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteLayoutlibCallbackAdapter.java
new file mode 100644
index 0000000..b685098
--- /dev/null
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteLayoutlibCallbackAdapter.java
@@ -0,0 +1,305 @@
+/*
+ * 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.remote.server.adapters;
+
+import com.android.ide.common.rendering.api.ActionBarCallback;
+import com.android.ide.common.rendering.api.AdapterBinding;
+import com.android.ide.common.rendering.api.ILayoutPullParser;
+import com.android.ide.common.rendering.api.LayoutlibCallback;
+import com.android.ide.common.rendering.api.ParserFactory;
+import com.android.ide.common.rendering.api.ResourceReference;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.SessionParams.Key;
+import com.android.layout.remote.api.RemoteLayoutlibCallback;
+import com.android.layout.remote.api.RemoteLayoutlibCallback.RemoteResolveResult;
+import com.android.layoutlib.bridge.MockView;
+import com.android.resources.ResourceType;
+import com.android.tools.layoutlib.annotations.NotNull;
+import com.android.tools.layoutlib.annotations.Nullable;
+import com.android.util.Pair;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.rmi.RemoteException;
+import java.util.HashMap;
+import java.util.function.Function;
+
+public class RemoteLayoutlibCallbackAdapter extends LayoutlibCallback {
+    private final RemoteLayoutlibCallback mDelegate;
+    private final PathClassLoader mPathClassLoader;
+
+    public RemoteLayoutlibCallbackAdapter(@NotNull RemoteLayoutlibCallback remote) {
+        mDelegate = remote;
+
+        // Views requested to this callback need to be brought from the "client" side.
+        // We transform any loadView into two operations:
+        //  - First we ask to where the class is located on disk via findClassPath
+        //  - Second, we instantiate the class in the "server" side
+        HashMap<String, Path> nameToPathCache = new HashMap<>();
+        Function<String, Path> getPathFromName = cacheName -> nameToPathCache.computeIfAbsent(
+                cacheName,
+                name -> {
+                    try {
+                        return mDelegate.findClassPath(name);
+                    } catch (RemoteException e) {
+                        throw new RuntimeException(e);
+                    }
+                });
+
+        mPathClassLoader = new PathClassLoader(getPathFromName);
+    }
+
+    @NotNull
+    private Object createNewInstance(@NotNull Class<?> clazz,
+            @Nullable Class<?>[] constructorSignature, @Nullable Object[] constructorParameters,
+            boolean isView)
+            throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException,
+            IllegalAccessException, InstantiationException {
+        Constructor<?> constructor = null;
+
+        try {
+            constructor = clazz.getConstructor(constructorSignature);
+        } catch (NoSuchMethodException e) {
+            if (!isView) {
+                throw e;
+            }
+
+            // View class has 1-parameter, 2-parameter and 3-parameter constructors
+
+            final int paramsCount = constructorSignature != null ? constructorSignature.length : 0;
+            if (paramsCount == 0) {
+                throw e;
+            }
+            assert constructorParameters != null;
+
+            for (int i = 3; i >= 1; i--) {
+                if (i == paramsCount) {
+                    continue;
+                }
+
+                final int k = paramsCount < i ? paramsCount : i;
+
+                final Class[] sig = new Class[i];
+                System.arraycopy(constructorSignature, 0, sig, 0, k);
+
+                final Object[] params = new Object[i];
+                System.arraycopy(constructorParameters, 0, params, 0, k);
+
+                for (int j = k + 1; j <= i; j++) {
+                    if (j == 2) {
+                        sig[j - 1] = findClass("android.util.AttributeSet");
+                        params[j - 1] = null;
+                    } else if (j == 3) {
+                        // parameter 3: int defstyle
+                        sig[j - 1] = int.class;
+                        params[j - 1] = 0;
+                    }
+                }
+
+                constructorSignature = sig;
+                constructorParameters = params;
+
+                try {
+                    constructor = clazz.getConstructor(constructorSignature);
+                    if (constructor != null) {
+                        if (constructorSignature.length < 2) {
+                            // TODO: Convert this to remote
+//                            LOG.info("wrong_constructor: Custom view " +
+//                                    clazz.getSimpleName() +
+//                                    " is not using the 2- or 3-argument " +
+//                                    "View constructors; XML attributes will not work");
+//                            mDelegate.warning("wrongconstructor", //$NON-NLS-1$
+//                                    String.format(
+//                                            "Custom view %1$s is not using the 2- or 3-argument
+// View constructors; XML attributes will not work",
+//                                            clazz.getSimpleName()), null, null);
+                        }
+                        break;
+                    }
+                } catch (NoSuchMethodException ignored) {
+                }
+            }
+
+            if (constructor == null) {
+                throw e;
+            }
+        }
+
+        constructor.setAccessible(true);
+        return constructor.newInstance(constructorParameters);
+    }
+
+    @Override
+    public Object loadView(String name, Class[] constructorSignature, Object[] constructorArgs)
+            throws Exception {
+        Class<?> viewClass = MockView.class;
+        try {
+            viewClass = findClass(name);
+        } catch (ClassNotFoundException ignore) {
+            // MockView will be used instead
+        }
+        return createNewInstance(viewClass, constructorSignature, constructorArgs, true);
+    }
+
+    @Override
+    public String getNamespace() {
+        try {
+            return mDelegate.getNamespace();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public Pair<ResourceType, String> resolveResourceId(int id) {
+        try {
+            RemoteResolveResult result = mDelegate.resolveResourceId(id);
+            return result != null ? result.asPair() : null;
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public String resolveResourceId(int[] id) {
+        try {
+            return mDelegate.resolveResourceId(id);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public Integer getResourceId(ResourceType type, String name) {
+        try {
+            return mDelegate.getResourceId(type, name);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public ILayoutPullParser getParser(String layoutName) {
+        return null;
+    }
+
+    @Override
+    public ILayoutPullParser getParser(ResourceValue layoutResource) {
+        try {
+            return new RemoteILayoutPullParserAdapter(mDelegate.getParser(layoutResource));
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public Object getAdapterItemValue(ResourceReference adapterView, Object adapterCookie,
+            ResourceReference itemRef, int fullPosition, int positionPerType,
+            int fullParentPosition, int parentPositionPerType, ResourceReference viewRef,
+            ViewAttribute viewAttribute, Object defaultValue) {
+        return null;
+    }
+
+    @Override
+    public AdapterBinding getAdapterBinding(ResourceReference adapterViewRef, Object adapterCookie,
+            Object viewObject) {
+        return null;
+    }
+
+    @Override
+    public ActionBarCallback getActionBarCallback() {
+        try {
+            return new RemoteActionBarCallbackAdapter(mDelegate.getActionBarCallback());
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public Object loadClass(String name, Class[] constructorSignature, Object[] constructorArgs)
+            throws ClassNotFoundException {
+        return super.loadClass(name, constructorSignature, constructorArgs);
+    }
+
+    @Override
+    public boolean supports(int ideFeature) {
+        try {
+            return mDelegate.supports(ideFeature);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public <T> T getFlag(Key<T> key) {
+        return super.getFlag(key);
+    }
+
+    @Override
+    public ParserFactory getParserFactory() {
+        try {
+            return new RemoteParserFactoryAdapter(mDelegate.getParserFactory());
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public Class<?> findClass(String name) throws ClassNotFoundException {
+        return mPathClassLoader.loadClass(name);
+    }
+
+    @Override
+    public XmlPullParser getXmlFileParser(String fileName) {
+        try {
+            return new RemoteXmlPullParserAdapter(mDelegate.getXmlFileParser(fileName));
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Simple class loaders that loads classes from Paths
+     */
+    private static class PathClassLoader extends ClassLoader {
+        private final Function<String, Path> mGetPath;
+
+        private PathClassLoader(@NotNull Function<String, Path> getUrl) {
+            mGetPath = getUrl;
+        }
+
+        @Override
+        protected Class<?> findClass(@NotNull String name) throws ClassNotFoundException {
+            Path path = mGetPath.apply(name);
+
+            if (path != null) {
+                try {
+                    byte[] content = Files.readAllBytes(path);
+                    return defineClass(name, content, 0, content.length);
+                } catch (IOException ignore) {
+                }
+            }
+
+            throw new ClassNotFoundException(name);
+        }
+    }
+}
diff --git a/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteParserFactoryAdapter.java b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteParserFactoryAdapter.java
new file mode 100644
index 0000000..f4ce421
--- /dev/null
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteParserFactoryAdapter.java
@@ -0,0 +1,44 @@
+/*
+ * 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.remote.server.adapters;
+
+
+import com.android.ide.common.rendering.api.ParserFactory;
+import com.android.layout.remote.api.RemoteParserFactory;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.rmi.RemoteException;
+
+class RemoteParserFactoryAdapter extends ParserFactory {
+    private final RemoteParserFactory mDelegate;
+
+    RemoteParserFactoryAdapter(@NotNull RemoteParserFactory remote) {
+        mDelegate = remote;
+    }
+
+    @Override
+    public XmlPullParser createParser(String debugName) throws XmlPullParserException {
+        try {
+            return new RemoteXmlPullParserAdapter(mDelegate.createParser(debugName));
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteRenderResourcesAdapter.java b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteRenderResourcesAdapter.java
new file mode 100644
index 0000000..61ab170
--- /dev/null
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteRenderResourcesAdapter.java
@@ -0,0 +1,202 @@
+/*
+ * 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.remote.server.adapters;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.layout.remote.api.RemoteRenderResources;
+import com.android.resources.ResourceType;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.rmi.RemoteException;
+import java.util.List;
+
+public class RemoteRenderResourcesAdapter extends RenderResources {
+    private final RemoteRenderResources mDelegate;
+
+    public RemoteRenderResourcesAdapter(@NotNull RemoteRenderResources remoteRenderResources) {
+        mDelegate = remoteRenderResources;
+    }
+
+    @Override
+    public void setFrameworkResourceIdProvider(FrameworkResourceIdProvider provider) {
+        // Ignored for remote operations.
+    }
+
+    @Override
+    public void setLogger(LayoutLog logger) {
+        // Ignored for remote operations.
+    }
+
+    @SuppressWarnings("deprecation")
+    @Override
+    public StyleResourceValue getCurrentTheme() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public StyleResourceValue getDefaultTheme() {
+        try {
+            return mDelegate.getDefaultTheme();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void applyStyle(StyleResourceValue theme, boolean useAsPrimary) {
+        try {
+            mDelegate.applyStyle(theme, useAsPrimary);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void clearStyles() {
+        try {
+            mDelegate.clearStyles();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public List<StyleResourceValue> getAllThemes() {
+        try {
+            return mDelegate.getAllThemes();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public StyleResourceValue getTheme(String name, boolean frameworkTheme) {
+        try {
+            return mDelegate.getTheme(name, frameworkTheme);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public boolean themeIsParentOf(StyleResourceValue parentTheme, StyleResourceValue childTheme) {
+        try {
+            return mDelegate.themeIsParentOf(parentTheme, childTheme);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public ResourceValue getFrameworkResource(ResourceType resourceType, String resourceName) {
+        try {
+            return mDelegate.getFrameworkResource(resourceType, resourceName);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public ResourceValue getProjectResource(ResourceType resourceType, String resourceName) {
+        try {
+            return mDelegate.getProjectResource(resourceType, resourceName);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    @Override
+    public ResourceValue findItemInTheme(String itemName) {
+        throw new UnsupportedOperationException("Deprecated");
+    }
+
+    @Override
+    public ResourceValue findItemInTheme(String attrName, boolean isFrameworkAttr) {
+        try {
+            return mDelegate.findItemInTheme(attrName, isFrameworkAttr);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    @Override
+    public ResourceValue findItemInStyle(StyleResourceValue style, String attrName) {
+        throw new UnsupportedOperationException("Deprecated");
+    }
+
+    @Override
+    public ResourceValue findItemInStyle(StyleResourceValue style, String attrName,
+            boolean isFrameworkAttr) {
+        try {
+            return mDelegate.findItemInStyle(style, attrName, isFrameworkAttr);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public ResourceValue findResValue(String reference, boolean forceFrameworkOnly) {
+        try {
+            return mDelegate.findResValue(reference, forceFrameworkOnly);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    @Override
+    public ResourceValue resolveValue(ResourceType type, String name, String value,
+            boolean isFrameworkValue) {
+        try {
+            return mDelegate.resolveValue(type, name, value, isFrameworkValue);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public ResourceValue resolveResValue(ResourceValue value) {
+        try {
+            return mDelegate.resolveValue(value);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public StyleResourceValue getParent(StyleResourceValue style) {
+        try {
+            return mDelegate.getParent(style);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public StyleResourceValue getStyle(String styleName, boolean isFramework) {
+        try {
+            return mDelegate.getStyle(styleName, isFramework);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteRenderSessionAdapter.java b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteRenderSessionAdapter.java
new file mode 100644
index 0000000..9d292e7
--- /dev/null
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteRenderSessionAdapter.java
@@ -0,0 +1,78 @@
+/*
+ * 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.remote.server.adapters;
+
+import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.Result;
+import com.android.layout.remote.api.RemoteRenderSession;
+import com.android.layout.remote.util.SerializableImage;
+import com.android.layout.remote.util.SerializableImageImpl;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+
+public class RemoteRenderSessionAdapter implements RemoteRenderSession {
+    private final RenderSession mDelegate;
+
+    private RemoteRenderSessionAdapter(@NotNull RenderSession delegate) {
+        mDelegate = delegate;
+    }
+
+    public static RemoteRenderSession create(@NotNull RenderSession delegate)
+            throws RemoteException {
+        return (RemoteRenderSession) UnicastRemoteObject.exportObject(
+                new RemoteRenderSessionAdapter(delegate), 0);
+    }
+
+    @NotNull
+    @Override
+    public Result getResult() throws RemoteException {
+        return mDelegate.getResult();
+    }
+
+    @Override
+    public Result render(long timeout, boolean forceMeasure) {
+        return mDelegate.render(timeout, forceMeasure);
+    }
+
+    @NotNull
+    @Override
+    public SerializableImage getSerializableImage() throws RemoteException {
+        return new SerializableImageImpl(mDelegate.getImage());
+    }
+
+    @Override
+    public void setSystemTimeNanos(long nanos) {
+        mDelegate.setSystemTimeNanos(nanos);
+    }
+
+    @Override
+    public void setSystemBootTimeNanos(long nanos) {
+        mDelegate.setSystemBootTimeNanos(nanos);
+    }
+
+    @Override
+    public void setElapsedFrameTimeNanos(long nanos) {
+        mDelegate.setElapsedFrameTimeNanos(nanos);
+    }
+
+    @Override
+    public void dispose() {
+        mDelegate.dispose();
+    }
+}
diff --git a/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteXmlPullParserAdapter.java b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteXmlPullParserAdapter.java
new file mode 100644
index 0000000..6de03bb
--- /dev/null
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteXmlPullParserAdapter.java
@@ -0,0 +1,353 @@
+/*
+ * 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.remote.server.adapters;
+
+import com.android.layout.remote.api.RemoteXmlPullParser;
+import com.android.layout.remote.util.RemoteInputStreamAdapter;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.rmi.RemoteException;
+
+class RemoteXmlPullParserAdapter implements XmlPullParser {
+    protected RemoteXmlPullParser mDelegate;
+
+    RemoteXmlPullParserAdapter(@NotNull RemoteXmlPullParser remote) {
+        mDelegate = remote;
+    }
+
+    @Override
+    public void setFeature(String name, boolean state) throws XmlPullParserException {
+        try {
+            mDelegate.setFeature(name, state);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public boolean getFeature(String name) {
+        try {
+            return mDelegate.getFeature(name);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void setProperty(String name, Object value) throws XmlPullParserException {
+        try {
+            mDelegate.setProperty(name, value);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public Object getProperty(String name) {
+        try {
+            return mDelegate.getProperty(name);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void setInput(Reader in) throws XmlPullParserException {
+        try {
+            mDelegate.setInput(in);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void setInput(InputStream inputStream, String inputEncoding)
+            throws XmlPullParserException {
+        try {
+            mDelegate.setInput(RemoteInputStreamAdapter.create(inputStream), inputEncoding);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public String getInputEncoding() {
+        try {
+            return mDelegate.getInputEncoding();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void defineEntityReplacementText(String entityName, String replacementText)
+            throws XmlPullParserException {
+        try {
+            mDelegate.defineEntityReplacementText(entityName, replacementText);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public int getNamespaceCount(int depth) throws XmlPullParserException {
+        try {
+            return mDelegate.getNamespaceCount(depth);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public String getNamespacePrefix(int pos) throws XmlPullParserException {
+        try {
+            return mDelegate.getNamespacePrefix(pos);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public String getNamespaceUri(int pos) throws XmlPullParserException {
+        try {
+            return mDelegate.getNamespaceUri(pos);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public String getNamespace(String prefix) {
+        try {
+            return mDelegate.getNamespace(prefix);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public int getDepth() {
+        try {
+            return mDelegate.getDepth();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public String getPositionDescription() {
+        try {
+            return mDelegate.getPositionDescription();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public int getLineNumber() {
+        try {
+            return mDelegate.getLineNumber();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public int getColumnNumber() {
+        try {
+            return mDelegate.getColumnNumber();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public boolean isWhitespace() throws XmlPullParserException {
+        try {
+            return mDelegate.isWhitespace();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public String getText() {
+        try {
+            return mDelegate.getText();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public char[] getTextCharacters(int[] holderForStartAndLength) {
+        try {
+            return mDelegate.getTextCharacters(holderForStartAndLength);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public String getNamespace() {
+        try {
+            return mDelegate.getNamespace();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public String getName() {
+        try {
+            return mDelegate.getName();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public String getPrefix() {
+        try {
+            return mDelegate.getPrefix();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public boolean isEmptyElementTag() throws XmlPullParserException {
+        try {
+            return mDelegate.isEmptyElementTag();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public int getAttributeCount() {
+        try {
+            return mDelegate.getAttributeCount();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public String getAttributeNamespace(int index) {
+        try {
+            return mDelegate.getAttributeNamespace(index);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public String getAttributeName(int index) {
+        try {
+            return mDelegate.getAttributeName(index);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public String getAttributePrefix(int index) {
+        try {
+            return mDelegate.getAttributePrefix(index);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public String getAttributeType(int index) {
+        try {
+            return mDelegate.getAttributeType(index);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public boolean isAttributeDefault(int index) {
+        try {
+            return mDelegate.isAttributeDefault(index);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public String getAttributeValue(int index) {
+        try {
+            return mDelegate.getAttributeValue(index);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public String getAttributeValue(String namespace, String name) {
+        try {
+            return mDelegate.getAttributeValue(namespace, name);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public int getEventType() throws XmlPullParserException {
+        try {
+            return mDelegate.getEventType();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public int next() throws XmlPullParserException, IOException {
+        return mDelegate.next();
+    }
+
+    @Override
+    public int nextToken() throws XmlPullParserException, IOException {
+        return mDelegate.nextToken();
+    }
+
+    @Override
+    public void require(int type, String namespace, String name)
+            throws XmlPullParserException, IOException {
+        mDelegate.require(type, namespace, name);
+    }
+
+    @Override
+    public String nextText() throws XmlPullParserException, IOException {
+        return mDelegate.nextText();
+    }
+
+    @Override
+    public int nextTag() throws XmlPullParserException, IOException {
+        return mDelegate.nextTag();
+    }
+}
diff --git a/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/package-info.java b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/package-info.java
new file mode 100644
index 0000000..2491759
--- /dev/null
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Package containing all the server side adapters. These adapters have the mission of receiving
+ * local calls and translating them into the remote API.
+ */
+package com.android.layoutlib.bridge.remote.server.adapters;
\ No newline at end of file
diff --git a/remote/tests/out/failures/activity.png b/remote/tests/out/failures/activity.png
new file mode 100644
index 0000000..2920b7d
--- /dev/null
+++ b/remote/tests/out/failures/activity.png
Binary files differ
diff --git a/remote/tests/out/failures/delta-activity.png b/remote/tests/out/failures/delta-activity.png
new file mode 100644
index 0000000..32d9415
--- /dev/null
+++ b/remote/tests/out/failures/delta-activity.png
Binary files differ
diff --git a/remote/tests/out/failures/delta-remote_component_load.png b/remote/tests/out/failures/delta-remote_component_load.png
new file mode 100644
index 0000000..ddb7f64
--- /dev/null
+++ b/remote/tests/out/failures/delta-remote_component_load.png
Binary files differ
diff --git a/remote/tests/out/failures/remote_component_load.png b/remote/tests/out/failures/remote_component_load.png
new file mode 100644
index 0000000..0ed85d1
--- /dev/null
+++ b/remote/tests/out/failures/remote_component_load.png
Binary files differ
diff --git a/remote/tests/remote tests.iml b/remote/tests/remote tests.iml
new file mode 100644
index 0000000..de0a53a
--- /dev/null
+++ b/remote/tests/remote tests.iml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="true" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="module" module-name="remote client" scope="TEST" />
+    <orderEntry type="module" module-name="bridge" scope="TEST" />
+    <orderEntry type="library" scope="TEST" name="layoutlib_api-prebuilt" level="project" />
+    <orderEntry type="module-library" scope="TEST">
+      <library>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/../../../../prebuilts/misc/common/sdk-common/sdk-common.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/../../../../prebuilts/misc/common/sdk-common/sdk-common-sources.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" scope="TEST">
+      <library>
+        <ANNOTATIONS>
+          <root url="file://$MODULE_DIR$/../.." />
+        </ANNOTATIONS>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/../../../../prebuilts/misc/common/tools-common/tools-common-prebuilt.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/../../../../prebuilts/misc/common/tools-common/tools-common-prebuilt-sources.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" scope="TEST">
+      <library>
+        <ANNOTATIONS>
+          <root url="file://$MODULE_DIR$/../.." />
+        </ANNOTATIONS>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/../../../../prebuilts/misc/common/tools-common/tools-common-prebuilt.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/../../../../prebuilts/misc/common/tools-common/tools-common-prebuilt-sources.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" scope="TEST">
+      <library>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/../../../../prebuilts/misc/common/sdk-common/sdk-common.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/../../../../prebuilts/misc/common/sdk-common/sdk-common-sources.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="library" scope="TEST" name="framework.jar" level="project" />
+    <orderEntry type="module-library" scope="TEST">
+      <library>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/../../../../prebuilts/misc/common/kxml2/kxml2-2.3.0.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="file://$MODULE_DIR$/../../../../libcore/xml/src/main/java" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="library" scope="TEST" name="junit" level="project" />
+    <orderEntry type="module" module-name="remote server" scope="TEST" />
+    <orderEntry type="module" module-name="remote common" scope="TEST" />
+  </component>
+</module>
\ No newline at end of file
diff --git a/remote/tests/src/CustomComponent.java b/remote/tests/src/CustomComponent.java
new file mode 100644
index 0000000..6d6272d
--- /dev/null
+++ b/remote/tests/src/CustomComponent.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.
+ */
+
+import android.content.Context;
+import android.graphics.Color;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+@SuppressWarnings("unused") // Used by test
+public class CustomComponent extends TextView {
+    public CustomComponent(Context context) {
+        super(context);
+
+        init();
+    }
+
+    public CustomComponent(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        init();
+    }
+
+    public CustomComponent(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        init();
+    }
+
+    public CustomComponent(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+
+        init();
+    }
+
+    private void init() {
+        setText("CustomComponent text");
+        setBackgroundColor(Color.RED);
+        setTextColor(Color.WHITE);
+        setTextSize(40f);
+    }
+}
diff --git a/remote/tests/src/RemoteBridgeTest.java b/remote/tests/src/RemoteBridgeTest.java
new file mode 100644
index 0000000..e0b0610
--- /dev/null
+++ b/remote/tests/src/RemoteBridgeTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+
+import com.android.ide.common.rendering.api.Bridge;
+import com.android.ide.common.rendering.api.Result;
+import com.android.ide.common.rendering.api.SessionParams;
+import com.android.layoutlib.bridge.intensive.RenderResult;
+import com.android.layoutlib.bridge.intensive.RenderTestBase;
+import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator;
+import com.android.layoutlib.bridge.intensive.setup.LayoutLibTestCallback;
+import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser;
+import com.android.layoutlib.bridge.intensive.util.ImageUtils;
+import com.android.layoutlib.bridge.remote.client.RemoteBridgeClient;
+import com.android.layoutlib.bridge.remote.server.ServerMain;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.rmi.NotBoundException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+
+public class RemoteBridgeTest extends RenderTestBase {
+    private ServerMain mServerMain;
+    private RemoteBridgeClient mClient;
+
+    /**
+     * Copy of RenderTestBase.renderAndVerify that allows using a different Bridge. TODO: Merge back
+     * into RenderTestBase
+     */
+    protected static RenderResult renderAndVerify(Bridge bridge, SessionParams params,
+            String goldenFileName, long frameTimeNanos) throws ClassNotFoundException {
+        RenderResult result = RenderTestBase.render(bridge, params, frameTimeNanos);
+        try {
+            String goldenImagePath = APP_TEST_DIR + "/golden/" + goldenFileName;
+            assertNotNull(result.getImage());
+            ImageUtils.requireSimilar(goldenImagePath, result.getImage());
+        } catch (IOException e) {
+            getLogger().error(e, e.getMessage());
+        }
+
+        return result;
+    }
+
+    @Before
+    public void setupServer() throws IOException, NotBoundException, InterruptedException {
+        mServerMain = ServerMain.forkAndStartServer(ServerMain.REGISTRY_BASE_PORT, 10);
+        mClient = RemoteBridgeClient.getRemoteBridge(mServerMain.getPort());
+
+        File data_dir = new File(PLATFORM_DIR, "data");
+        File res = new File(data_dir, "res");
+        File fontLocation = new File(data_dir, "fonts");
+        File buildProp = new File(PLATFORM_DIR, "build.prop");
+        File attrs = new File(res, "values" + File.separator + "attrs.xml");
+
+        mClient.init(ConfigGenerator.loadProperties(buildProp), fontLocation,
+                ConfigGenerator.getEnumMap(attrs), getLayoutLog());
+    }
+
+    @After
+    public void stopServer() {
+        mClient.dispose();
+        mServerMain.stop();
+    }
+
+    /**
+     * Same test as RenderTest#testActivity but using the remote bridge
+     */
+    @Test
+    public void testActivity() throws IOException, ClassNotFoundException {
+        SessionParams params = createSessionParams("activity.xml", ConfigGenerator.NEXUS_5);
+        RenderResult result = renderAndVerify(mClient, params, "activity.png", 250);
+        assertEquals(Result.Status.SUCCESS, result.getResult().getStatus());
+        if (result.getResult().getException() != null) {
+            result.getResult().getException().printStackTrace();
+            fail("Unexpected exception");
+        }
+    }
+
+    /**
+     * Same test as RenderTest#testActivity but using the remote bridge
+     */
+    @Test
+    public void testCustomClassLoading() throws ClassNotFoundException {
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+
+        LayoutPullParser parser = LayoutPullParser.createFromString(
+                "<CustomComponent xmlns:android=\"http://schemas" +
+                        ".android.com/apk/res/android\"\n" +
+                        "                android:layout_width=\"match_parent\"\n" +
+                        "                android:layout_height=\"match_parent\"\n>" +
+                        "</CustomComponent>");
+        SessionParams params =
+                getSessionParamsBuilder().setParser(parser).setCallback(layoutLibCallback).setTheme(
+                        "Theme.NoTitleBar", false).build();
+
+        RenderResult result = renderAndVerify(mClient, params, "remote_component_load.png", 250);
+        assertEquals(Result.Status.SUCCESS, result.getResult().getStatus());
+        if (result.getResult().getException() != null) {
+            result.getResult().getException().printStackTrace();
+            fail("Unexpected exception");
+        }
+
+        parser = LayoutPullParser.createFromString(
+                "<MissingCustomComponent xmlns:android=\"http://schemas" +
+                        ".android.com/apk/res/android\"\n" +
+                        "                android:layout_width=\"match_parent\"\n" +
+                        "                android:layout_height=\"match_parent\"\n>" +
+                        "</MissingCustomComponent>");
+        params =
+                getSessionParamsBuilder().setParser(parser).setCallback(layoutLibCallback).setTheme(
+                        "Theme.NoTitleBar", false).build();
+        result = renderAndVerify(mClient, params, "remote_component_load_fail.png", 250);
+        assertEquals(Result.Status.SUCCESS, result.getResult().getStatus());
+        if (result.getResult().getException() != null) {
+            result.getResult().getException().printStackTrace();
+            fail("Unexpected exception");
+        }
+    }
+}
\ No newline at end of file
diff --git a/rename_font/build_font_single.py b/rename_font/build_font_single.py
index 4245cdc..4678a4f 100755
--- a/rename_font/build_font_single.py
+++ b/rename_font/build_font_single.py
@@ -17,8 +17,8 @@
 """
 Rename the PS name of the input font.
 
-OpenType fonts (*.otf) are not currently supported. They are copied to the destination without renaming.
-XML files are also copied in case they are passed there by mistake.
+OpenType fonts (*.otf) and TrueType Collections (*.ttc) are not currently supported. They are copied to the destination
+without renaming. XML files are also copied in case they are passed there by mistake.
 
 Usage: build_font_single.py /path/to/input_font.ttf /path/to/output_font.ttf
 
@@ -63,7 +63,7 @@
 NAMEID_VERSION = 5
 
 # A list of extensions to process.
-EXTENSIONS = ['.ttf', '.otf', '.xml']
+EXTENSIONS = ['.ttf', '.ttc', '.otf', '.xml']
 
 def main(argv):
   if len(argv) < 2: