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: