Merge commit 'e08839c2e6a1b607abbba902ad0d578f8147f6fc' into HEAD
Change-Id: Ia49a7b76ecc99c27b48806f063416451b3df3904
diff --git a/bridge/src/android/animation/PropertyValuesHolder_Delegate.java b/bridge/src/android/animation/PropertyValuesHolder_Delegate.java
index 28a489a..1d7026c 100644
--- a/bridge/src/android/animation/PropertyValuesHolder_Delegate.java
+++ b/bridge/src/android/animation/PropertyValuesHolder_Delegate.java
@@ -106,9 +106,7 @@
try {
method.setAccessible(true);
method.invoke(target, args);
- } catch (IllegalAccessException e) {
- Bridge.getLog().error(null, "Unable to update property during animation", e, null);
- } catch (InvocationTargetException e) {
+ } catch (IllegalAccessException | InvocationTargetException e) {
Bridge.getLog().error(null, "Unable to update property during animation", e, null);
}
}
diff --git a/bridge/src/android/content/res/BridgeTypedArray.java b/bridge/src/android/content/res/BridgeTypedArray.java
index 69242ac..5536c4f 100644
--- a/bridge/src/android/content/res/BridgeTypedArray.java
+++ b/bridge/src/android/content/res/BridgeTypedArray.java
@@ -32,6 +32,7 @@
import android.content.res.Resources.NotFoundException;
import android.content.res.Resources.Theme;
import android.graphics.Typeface;
+import android.graphics.Typeface_Accessor;
import android.graphics.drawable.Drawable;
import android.util.DisplayMetrics;
import android.util.TypedValue;
@@ -297,7 +298,7 @@
}
ColorStateList colorStateList = ResourceHelper.getColorStateList(
- mResourceData[index], mContext);
+ mResourceData[index], mContext, mTheme);
if (colorStateList != null) {
return colorStateList.getDefaultColor();
}
@@ -311,7 +312,7 @@
return null;
}
- return ResourceHelper.getColorStateList(mResourceData[index], mContext);
+ return ResourceHelper.getColorStateList(mResourceData[index], mContext, mTheme);
}
@Override
@@ -320,7 +321,7 @@
return null;
}
- return ResourceHelper.getComplexColor(mResourceData[index], mContext);
+ return ResourceHelper.getComplexColor(mResourceData[index], mContext, mTheme);
}
/**
@@ -607,6 +608,11 @@
return defValue;
}
+ if (Typeface_Accessor.isSystemFont(value)) {
+ // A system font family value, do not try to parse
+ return defValue;
+ }
+
// Handle the @id/<name>, @+id/<name> and @android:id/<name>
// We need to return the exact value that was compiled (from the various R classes),
// as these values can be reused internally with calls to findViewById().
diff --git a/bridge/src/android/content/res/Resources_Delegate.java b/bridge/src/android/content/res/Resources_Delegate.java
index 7ea5061..c1e9cd3 100644
--- a/bridge/src/android/content/res/Resources_Delegate.java
+++ b/bridge/src/android/content/res/Resources_Delegate.java
@@ -45,6 +45,7 @@
import android.annotation.Nullable;
import android.content.res.Resources.NotFoundException;
import android.content.res.Resources.Theme;
+import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.icu.text.PluralRules;
@@ -259,9 +260,9 @@
if (resValue != null) {
ColorStateList stateList = ResourceHelper.getColorStateList(resValue.getSecond(),
- getContext(resources));
+ getContext(resources), theme);
if (stateList != null) {
- return stateList.obtainForTheme(theme);
+ return stateList;
}
}
@@ -382,10 +383,18 @@
for (Iterator<String> iterator = resValue.iterator(); iterator.hasNext(); i++) {
String element = resolveReference(resources, iterator.next(), resValue.isFramework());
try {
- values[i] = getInt(element);
+ if (element.startsWith("#")) {
+ // This integer represents a color (starts with #)
+ values[i] = Color.parseColor(element);
+ } else {
+ values[i] = getInt(element);
+ }
} catch (NumberFormatException e) {
Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
"Integer resource array contains non-integer value: " + element, null);
+ } catch (IllegalArgumentException e2) {
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
+ "Integer resource array contains wrong color format: " + element, null);
}
}
return values;
diff --git a/bridge/src/android/graphics/BaseCanvas_Delegate.java b/bridge/src/android/graphics/BaseCanvas_Delegate.java
index cc71053..f1c63e6 100644
--- a/bridge/src/android/graphics/BaseCanvas_Delegate.java
+++ b/bridge/src/android/graphics/BaseCanvas_Delegate.java
@@ -393,7 +393,6 @@
}
final NinePatchChunk chunkObject = NinePatch_Delegate.getChunk(c);
- assert chunkObject != null;
if (chunkObject == null) {
return;
}
@@ -479,7 +478,7 @@
@LayoutlibDelegate
/*package*/ static void nDrawText(long nativeCanvas, char[] text, int index, int count,
float startX, float startY, int flags, long paint, long typeface) {
- drawText(nativeCanvas, text, index, count, startX, startY, (flags & 1) != 0,
+ drawText(nativeCanvas, text, index, count, startX, startY, flags,
paint, typeface);
}
@@ -502,14 +501,16 @@
char[] buffer = TemporaryBuffer.obtain(count);
TextUtils.getChars(text, start, end, buffer, 0);
- drawText(nativeCanvas, buffer, 0, count, x, y, isRtl, paint, typeface);
+ drawText(nativeCanvas, buffer, 0, count, x, y, isRtl ? Paint.BIDI_RTL : Paint.BIDI_LTR,
+ paint,
+ typeface);
}
@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, typeface);
+ drawText(nativeCanvas, text, start, count, x, y, isRtl ? Paint.BIDI_RTL : Paint.BIDI_LTR, paint, typeface);
}
@LayoutlibDelegate
@@ -574,7 +575,7 @@
}
private static void drawText(long nativeCanvas, final char[] text, final int index,
- final int count, final float startX, final float startY, final boolean isRtl,
+ final int count, final float startX, final float startY, final int bidiFlags,
long paint, final long typeface) {
draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
@@ -591,7 +592,7 @@
int limit = index + count;
if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) {
RectF bounds =
- paintDelegate.measureText(text, index, count, null, 0, isRtl);
+ paintDelegate.measureText(text, index, count, null, 0, bidiFlags);
float m = bounds.right - bounds.left;
if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) {
x -= m / 2;
@@ -601,7 +602,7 @@
}
new BidiRenderer(graphics, paintDelegate, text).setRenderLocation(x,
- startY).renderText(index, limit, isRtl, null, 0, true);
+ startY).renderText(index, limit, bidiFlags, null, 0, true);
});
}
diff --git a/bridge/src/android/graphics/BidiRenderer.java b/bridge/src/android/graphics/BidiRenderer.java
index c6827a3..63691c3 100644
--- a/bridge/src/android/graphics/BidiRenderer.java
+++ b/bridge/src/android/graphics/BidiRenderer.java
@@ -19,8 +19,9 @@
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.layoutlib.bridge.Bridge;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.Paint_Delegate.FontInfo;
-import android.icu.lang.UScript;
import android.icu.lang.UScriptRun;
import android.icu.text.Bidi;
import android.icu.text.BidiRun;
@@ -32,7 +33,6 @@
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
-import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
@@ -45,26 +45,20 @@
private static String JAVA_VENDOR = System.getProperty("java.vendor");
private static class ScriptRun {
- int start;
- int limit;
- boolean isRtl;
- int scriptCode;
- Font font;
+ private final int start;
+ private final int limit;
+ private final Font font;
- public ScriptRun(int start, int limit, boolean isRtl) {
+ private ScriptRun(int start, int limit, @NonNull Font font) {
this.start = start;
this.limit = limit;
- this.isRtl = isRtl;
- this.scriptCode = UScript.INVALID_CODE;
+ this.font = font;
}
}
private final Graphics2D mGraphics;
private final Paint_Delegate mPaint;
private char[] mText;
- // This List can contain nulls. A null font implies that the we weren't able to load the font
- // properly. So, if we encounter a situation where we try to use that font, log a warning.
- private List<Font> mFonts;
// Bounds of the text drawn so far.
private RectF mBounds;
private float mBaseline;
@@ -79,14 +73,6 @@
mGraphics = graphics;
mPaint = paint;
mText = text;
- mFonts = new ArrayList<Font>(paint.getFonts().size());
- for (FontInfo fontInfo : paint.getFonts()) {
- if (fontInfo == null) {
- mFonts.add(null);
- continue;
- }
- mFonts.add(fontInfo.mFont);
- }
mBounds = new RectF();
}
@@ -98,7 +84,7 @@
*
*/
public BidiRenderer setRenderLocation(float x, float y) {
- mBounds = new RectF(x, y, x, y);
+ mBounds.set(x, y, x, y);
mBaseline = y;
return this;
}
@@ -112,6 +98,7 @@
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);
boolean isRtl = visualRun.getDirection() == Bidi.RTL;
@@ -140,7 +127,7 @@
int advancesIndex, boolean draw) {
// We break the text into scripts and then select font based on it and then render each of
// the script runs.
- for (ScriptRun run : getScriptRuns(mText, start, limit, isRtl, mFonts)) {
+ for (ScriptRun run : getScriptRuns(mText, start, limit, mPaint.getFonts())) {
int flag = Font.LAYOUT_NO_LIMIT_CONTEXT | Font.LAYOUT_NO_START_CONTEXT;
flag |= isRtl ? Font.LAYOUT_RIGHT_TO_LEFT : Font.LAYOUT_LEFT_TO_RIGHT;
renderScript(run.start, run.limit, run.font, flag, advances, advancesIndex, draw);
@@ -156,7 +143,7 @@
*/
private void renderScript(int start, int limit, Font preferredFont, int flag,
float[] advances, int advancesIndex, boolean draw) {
- if (mFonts.size() == 0 || preferredFont == null) {
+ if (mPaint.getFonts().size() == 0 || preferredFont == null) {
return;
}
@@ -178,7 +165,10 @@
// The current character cannot be drawn with the preferred font. Cycle through all the
// fonts to check which one can draw it.
int charCount = Character.isHighSurrogate(mText[start]) ? 2 : 1;
- for (Font font : mFonts) {
+ List<FontInfo> fontInfos = mPaint.getFonts();
+ //noinspection ForLoopReplaceableByForEach (avoid iterator allocation)
+ for (int i = 0; i < fontInfos.size(); i++) {
+ Font font = fontInfos.get(i).mFont;
if (font == null) {
logFontWarning();
continue;
@@ -249,60 +239,62 @@
// Update the bounds.
Rectangle2D awtBounds = gv.getLogicalBounds();
- RectF bounds = awtRectToAndroidRect(awtBounds, mBounds.right, mBaseline);
// If the width of the bounds is zero, no text had been drawn earlier. Hence, use the
// coordinates from the bounds as an offset.
if (Math.abs(mBounds.right - mBounds.left) == 0) {
- mBounds = bounds;
+ mBounds = awtRectToAndroidRect(awtBounds, mBounds.right, mBaseline, mBounds);
} else {
- mBounds.union(bounds);
+ mBounds.union(awtRectToAndroidRect(awtBounds, mBounds.right, mBaseline, null));
}
}
// --- Static helper methods ---
- private static RectF awtRectToAndroidRect(Rectangle2D awtRec, float offsetX, float offsetY) {
+ private static RectF awtRectToAndroidRect(Rectangle2D awtRec, float offsetX, float offsetY,
+ @Nullable RectF destination) {
float left = (float) awtRec.getX();
float top = (float) awtRec.getY();
float right = (float) (left + awtRec.getWidth());
float bottom = (float) (top + awtRec.getHeight());
- RectF androidRect = new RectF(left, top, right, bottom);
- androidRect.offset(offsetX, offsetY);
- return androidRect;
+ if (destination != null) {
+ destination.set(left, top, right, bottom);
+ } else {
+ destination = new RectF(left, top, right, bottom);
+ }
+ destination.offset(offsetX, offsetY);
+ return destination;
}
- /* package */ static List<ScriptRun> getScriptRuns(char[] text, int start, int limit,
- boolean isRtl, List<Font> fonts) {
- LinkedList<ScriptRun> scriptRuns = new LinkedList<ScriptRun>();
+ private static List<ScriptRun> getScriptRuns(char[] text, int start, int limit, List<FontInfo> fonts) {
+ LinkedList<ScriptRun> scriptRuns = new LinkedList<>();
int count = limit - start;
UScriptRun uScriptRun = new UScriptRun(text, start, count);
while (uScriptRun.next()) {
int scriptStart = uScriptRun.getScriptStart();
int scriptLimit = uScriptRun.getScriptLimit();
- ScriptRun run = new ScriptRun(scriptStart, scriptLimit, isRtl);
- run.scriptCode = uScriptRun.getScriptCode();
- setScriptFont(text, run, fonts);
+ ScriptRun run = new ScriptRun(
+ scriptStart, scriptLimit,
+ getScriptFont(text, scriptStart, scriptLimit, fonts));
scriptRuns.add(run);
}
-
return scriptRuns;
}
// TODO: Replace this method with one which returns the font based on the scriptCode.
- private static void setScriptFont(char[] text, ScriptRun run,
- List<Font> fonts) {
- for (Font font : fonts) {
- if (font == null) {
+ @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 (font.canDisplayUpTo(text, run.start, run.limit) == -1) {
- run.font = font;
- return;
+ if (fontInfo.mFont.canDisplayUpTo(text, start, limit) == -1) {
+ return fontInfo.mFont;
}
}
- run.font = fonts.get(0);
+
+ return fonts.get(0).mFont;
}
private static int getIcuFlags(int bidiFlag) {
diff --git a/bridge/src/android/graphics/ColorFilter_Delegate.java b/bridge/src/android/graphics/ColorFilter_Delegate.java
index cb013b6..84424bc 100644
--- a/bridge/src/android/graphics/ColorFilter_Delegate.java
+++ b/bridge/src/android/graphics/ColorFilter_Delegate.java
@@ -21,6 +21,8 @@
import java.awt.Graphics2D;
+import libcore.util.NativeAllocationRegistry_Delegate;
+
/**
* Delegate implementing the native methods of android.graphics.ColorFilter
*
@@ -41,6 +43,7 @@
// ---- delegate manager ----
protected static final DelegateManager<ColorFilter_Delegate> sManager =
new DelegateManager<ColorFilter_Delegate>(ColorFilter_Delegate.class);
+ private static long sFinalizer = -1;
// ---- delegate helper data ----
@@ -66,8 +69,14 @@
// ---- native methods ----
@LayoutlibDelegate
- /*package*/ static void nSafeUnref(long native_instance) {
- sManager.removeJavaReferenceFor(native_instance);
+ /*package*/ static long nativeGetFinalizer() {
+ synchronized (ColorFilter_Delegate.class) {
+ if (sFinalizer == -1) {
+ sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
+ sManager::removeJavaReferenceFor);
+ }
+ }
+ return sFinalizer;
}
// ---- Private delegate/helper methods ----
diff --git a/bridge/src/android/graphics/NinePatch_Delegate.java b/bridge/src/android/graphics/NinePatch_Delegate.java
index 1f0eb3b..43e5b0f 100644
--- a/bridge/src/android/graphics/NinePatch_Delegate.java
+++ b/bridge/src/android/graphics/NinePatch_Delegate.java
@@ -19,14 +19,11 @@
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.layoutlib.bridge.impl.GcSnapshot;
import com.android.ninepatch.NinePatchChunk;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import android.graphics.drawable.NinePatchDrawable;
-import java.awt.Graphics2D;
-import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -50,7 +47,7 @@
// ---- delegate manager ----
private static final DelegateManager<NinePatch_Delegate> sManager =
- new DelegateManager<NinePatch_Delegate>(NinePatch_Delegate.class);
+ new DelegateManager<>(NinePatch_Delegate.class);
// ---- delegate helper data ----
/**
@@ -62,8 +59,7 @@
* Using the cache map allows us to not have to deserialize the byte[] back into a
* {@link NinePatchChunk} every time a rendering is done.
*/
- private final static Map<byte[], SoftReference<NinePatchChunk>> sChunkCache =
- new HashMap<byte[], SoftReference<NinePatchChunk>>();
+ private final static Map<byte[], SoftReference<NinePatchChunk>> sChunkCache = new HashMap<>();
// ---- delegate data ----
private byte[] chunk;
@@ -97,7 +93,7 @@
// get the array and add it to the cache
byte[] array = baos.toByteArray();
- sChunkCache.put(array, new SoftReference<NinePatchChunk>(chunk));
+ sChunkCache.put(array, new SoftReference<>(chunk));
return array;
}
@@ -122,7 +118,7 @@
// put back the chunk in the cache
if (chunk != null) {
- sChunkCache.put(array, new SoftReference<NinePatchChunk>(chunk));
+ sChunkCache.put(array, new SoftReference<>(chunk));
}
} catch (IOException e) {
Bridge.getLog().error(LayoutLog.TAG_BROKEN,
@@ -151,7 +147,6 @@
/*package*/ static boolean isNinePatchChunk(byte[] chunk) {
NinePatchChunk chunkObject = getChunk(chunk);
return chunkObject != null;
-
}
@LayoutlibDelegate
diff --git a/bridge/src/android/graphics/Paint_Delegate.java b/bridge/src/android/graphics/Paint_Delegate.java
index 60e5cd9..406157e 100644
--- a/bridge/src/android/graphics/Paint_Delegate.java
+++ b/bridge/src/android/graphics/Paint_Delegate.java
@@ -1158,6 +1158,26 @@
return distanceToI > distanceToIMinus1 ? i : i - 1;
}
+ @LayoutlibDelegate
+ /*package*/ static float nGetUnderlinePosition(long paintPtr, long typefacePtr) {
+ return (1.0f / 9.0f) * nGetTextSize(paintPtr);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float nGetUnderlineThickness(long paintPtr, long typefacePtr) {
+ return (1.0f / 18.0f) * nGetTextSize(paintPtr);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float nGetStrikeThruPosition(long paintPtr, long typefacePtr) {
+ return (-79.0f / 252.0f) * nGetTextSize(paintPtr);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float nGetStrikeThruThickness(long paintPtr, long typefacePtr) {
+ return (1.0f / 18.0f) * nGetTextSize(paintPtr);
+ }
+
// ---- Private delegate/helper methods ----
/*package*/ Paint_Delegate() {
diff --git a/bridge/src/android/graphics/drawable/AdaptiveIconDrawable_Delegate.java b/bridge/src/android/graphics/drawable/AdaptiveIconDrawable_Delegate.java
new file mode 100644
index 0000000..ca6bc85
--- /dev/null
+++ b/bridge/src/android/graphics/drawable/AdaptiveIconDrawable_Delegate.java
@@ -0,0 +1,37 @@
+/*
+ * 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.graphics.drawable;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.content.res.Resources;
+import android.content.res.Resources_Delegate;
+import android.util.PathParser;
+
+import static com.android.layoutlib.bridge.android.RenderParamsFlags.FLAG_KEY_ADAPTIVE_ICON_MASK_PATH;
+
+public class AdaptiveIconDrawable_Delegate {
+
+ @LayoutlibDelegate
+ /*package*/ static void constructor_after(AdaptiveIconDrawable icon) {
+ String pathString = Resources_Delegate.getLayoutlibCallback(Resources.getSystem()).getFlag(
+ FLAG_KEY_ADAPTIVE_ICON_MASK_PATH);
+ if (pathString != null) {
+ AdaptiveIconDrawable.sMask = PathParser.createPathFromPathData(pathString);
+ }
+ }
+}
diff --git a/bridge/src/android/preference/BridgePreferenceInflater.java b/bridge/src/android/preference/BridgePreferenceInflater.java
index aa393a9..cb56116 100644
--- a/bridge/src/android/preference/BridgePreferenceInflater.java
+++ b/bridge/src/android/preference/BridgePreferenceInflater.java
@@ -30,7 +30,7 @@
}
@Override
- protected Preference onCreateItem(String name, AttributeSet attrs)
+ public Preference createItem(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException {
Object viewKey = null;
BridgeContext bc = null;
@@ -39,17 +39,19 @@
if (context instanceof BridgeContext) {
bc = (BridgeContext) context;
}
+
if (attrs instanceof BridgeXmlBlockParser) {
viewKey = ((BridgeXmlBlockParser) attrs).getViewCookie();
}
Preference preference = null;
try {
- preference = super.onCreateItem(name, attrs);
+ preference = super.createItem(name, prefix, attrs);
} catch (ClassNotFoundException | InflateException exception) {
// name is probably not a valid preference type
- if ("SwitchPreferenceCompat".equals(name)) {
- preference = super.onCreateItem("SwitchPreference", attrs);
+ if ("android.support.v7.preference".equals(prefix) &&
+ "SwitchPreferenceCompat".equals(name)) {
+ preference = super.createItem("SwitchPreference", prefix, attrs);
}
}
diff --git a/bridge/src/android/text/StaticLayout_Delegate.java b/bridge/src/android/text/StaticLayout_Delegate.java
index cc03143..54854b5 100644
--- a/bridge/src/android/text/StaticLayout_Delegate.java
+++ b/bridge/src/android/text/StaticLayout_Delegate.java
@@ -59,11 +59,12 @@
}
@LayoutlibDelegate
- /*package*/ static void nSetLocale(long nativeBuilder, String locale, long nativeHyphenator) {
+ /*package*/ static void nSetLocales(long nativeBuilder, String locales,
+ long[] nativeHyphenators) {
Builder builder = sBuilderManager.getDelegate(nativeBuilder);
if (builder != null) {
- builder.mLocale = locale;
- builder.mNativeHyphenator = nativeHyphenator;
+ builder.mLocales = locales;
+ builder.mNativeHyphenators = nativeHyphenators;
}
}
@@ -138,7 +139,7 @@
// compute all possible breakpoints.
int length = builder.mWidths.length;
- BreakIterator it = BreakIterator.getLineInstance(new ULocale(builder.mLocale));
+ BreakIterator it = BreakIterator.getLineInstance(new ULocale(builder.mLocales));
it.setText(new Segment(builder.mText, 0, length));
// average word length in english is 5. So, initialize the possible breaks with a guess.
@@ -226,11 +227,11 @@
* Java representation of the native Builder class.
*/
private static class Builder {
- String mLocale;
+ String mLocales;
char[] mText;
float[] mWidths;
LineBreaker mLineBreaker;
- long mNativeHyphenator;
+ long[] mNativeHyphenators;
int mBreakStrategy;
LineWidth mLineWidth;
TabStops mTabStopCalculator;
diff --git a/bridge/src/android/util/PathParser_Delegate.java b/bridge/src/android/util/PathParser_Delegate.java
index 7b69388..4010b67 100644
--- a/bridge/src/android/util/PathParser_Delegate.java
+++ b/bridge/src/android/util/PathParser_Delegate.java
@@ -286,6 +286,8 @@
switch (currentChar) {
case ' ':
case ',':
+ case '\t':
+ case '\n':
foundSeparator = true;
break;
case '-':
diff --git a/bridge/src/android/view/Choreographer_Delegate.java b/bridge/src/android/view/Choreographer_Delegate.java
index 494ffa1..1dc7778 100644
--- a/bridge/src/android/view/Choreographer_Delegate.java
+++ b/bridge/src/android/view/Choreographer_Delegate.java
@@ -15,12 +15,12 @@
*/
package android.view;
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-import com.android.tools.layoutlib.java.System_Delegate;
-import java.lang.reflect.Field;
+import android.animation.AnimationHandler;
+import android.util.TimeUtils;
+import android.view.animation.AnimationUtils;
+
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -31,7 +31,7 @@
*
*/
public class Choreographer_Delegate {
- static final AtomicReference<Choreographer> mInstance = new AtomicReference<Choreographer>();
+ private static final AtomicReference<Choreographer> mInstance = new AtomicReference<Choreographer>();
@LayoutlibDelegate
public static Choreographer getInstance() {
@@ -55,31 +55,39 @@
public static void doFrame(long frameTimeNanos) {
Choreographer thisChoreographer = Choreographer.getInstance();
- thisChoreographer.mLastFrameTimeNanos = frameTimeNanos - thisChoreographer
- .getFrameIntervalNanos();
- thisChoreographer.mFrameInfo.markInputHandlingStart();
- thisChoreographer.doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
+ AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
- thisChoreographer.mFrameInfo.markAnimationsStart();
- thisChoreographer.doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
+ try {
+ thisChoreographer.mLastFrameTimeNanos = frameTimeNanos - thisChoreographer.getFrameIntervalNanos();
+ thisChoreographer.mFrameInfo.markInputHandlingStart();
+ thisChoreographer.doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
- thisChoreographer.mFrameInfo.markPerformTraversalsStart();
- thisChoreographer.doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
+ thisChoreographer.mFrameInfo.markAnimationsStart();
+ thisChoreographer.doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
- thisChoreographer.doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
+ thisChoreographer.mFrameInfo.markPerformTraversalsStart();
+ thisChoreographer.doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
+
+ thisChoreographer.doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
+ } finally {
+ AnimationUtils.unlockAnimationClock();
+ }
+ }
+
+ public static void clearFrames() {
+ Choreographer thisChoreographer = Choreographer.getInstance();
+
+ thisChoreographer.removeCallbacks(Choreographer.CALLBACK_INPUT, null, null);
+ thisChoreographer.removeCallbacks(Choreographer.CALLBACK_ANIMATION, null, null);
+ thisChoreographer.removeCallbacks(Choreographer.CALLBACK_TRAVERSAL, null, null);
+ thisChoreographer.removeCallbacks(Choreographer.CALLBACK_COMMIT, null, null);
+
+ // Release animation handler instance since it holds references to the callbacks
+ AnimationHandler.sAnimatorHandler.set(null);
}
public static void dispose() {
- try {
- Field threadInstanceField = Choreographer.class.getDeclaredField("sThreadInstance");
- threadInstanceField.setAccessible(true);
- @SuppressWarnings("unchecked") ThreadLocal<Choreographer> threadInstance =
- (ThreadLocal<Choreographer>) threadInstanceField.get(null);
- threadInstance.remove();
- } catch (ReflectiveOperationException e) {
- assert false;
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- "Unable to clear Choreographer memory.", e, null);
- }
+ clearFrames();
+ Choreographer.releaseInstance();
}
}
diff --git a/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
index fdf6d63..dd2668f 100644
--- a/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
+++ b/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
@@ -16,9 +16,6 @@
package com.android.layoutlib.bridge;
-import com.android.ide.common.rendering.api.IAnimationListener;
-import com.android.ide.common.rendering.api.ILayoutPullParser;
-import com.android.ide.common.rendering.api.RenderParams;
import com.android.ide.common.rendering.api.RenderSession;
import com.android.ide.common.rendering.api.Result;
import com.android.ide.common.rendering.api.ViewInfo;
@@ -28,8 +25,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.view.View;
-import android.view.ViewGroup;
import java.awt.image.BufferedImage;
import java.util.Collections;
@@ -122,100 +117,6 @@
}
@Override
- public Result animate(Object targetObject, String animationName,
- boolean isFrameworkAnimation, IAnimationListener listener) {
- if (mSession != null) {
- try {
- Bridge.prepareThread();
- mLastResult = mSession.acquire(RenderParams.DEFAULT_TIMEOUT);
- if (mLastResult.isSuccess()) {
- mLastResult = mSession.animate(targetObject, animationName, isFrameworkAnimation,
- listener);
- }
- } finally {
- mSession.release();
- Bridge.cleanupThread();
- }
- }
-
- return mLastResult;
- }
-
- @Override
- public Result insertChild(Object parentView, ILayoutPullParser childXml, int index,
- IAnimationListener listener) {
- if (!(parentView instanceof ViewGroup)) {
- throw new IllegalArgumentException("parentView is not a ViewGroup");
- }
-
- if (mSession != null) {
- try {
- Bridge.prepareThread();
- mLastResult = mSession.acquire(RenderParams.DEFAULT_TIMEOUT);
- if (mLastResult.isSuccess()) {
- mLastResult =
- mSession.insertChild((ViewGroup) parentView, childXml, index, listener);
- }
- } finally {
- mSession.release();
- Bridge.cleanupThread();
- }
- }
-
- return mLastResult;
- }
-
-
- @Override
- public Result moveChild(Object parentView, Object childView, int index,
- Map<String, String> layoutParams, IAnimationListener listener) {
- if (!(parentView instanceof ViewGroup)) {
- throw new IllegalArgumentException("parentView is not a ViewGroup");
- }
- if (!(childView instanceof View)) {
- throw new IllegalArgumentException("childView is not a View");
- }
-
- if (mSession != null) {
- try {
- Bridge.prepareThread();
- mLastResult = mSession.acquire(RenderParams.DEFAULT_TIMEOUT);
- if (mLastResult.isSuccess()) {
- mLastResult = mSession.moveChild((ViewGroup) parentView, (View) childView, index,
- layoutParams, listener);
- }
- } finally {
- mSession.release();
- Bridge.cleanupThread();
- }
- }
-
- return mLastResult;
- }
-
- @Override
- public Result removeChild(Object childView, IAnimationListener listener) {
- if (!(childView instanceof View)) {
- throw new IllegalArgumentException("childView is not a View");
- }
-
- if (mSession != null) {
- try {
- Bridge.prepareThread();
- mLastResult = mSession.acquire(RenderParams.DEFAULT_TIMEOUT);
- if (mLastResult.isSuccess()) {
- mLastResult = mSession.removeChild((View) childView, listener);
- }
- } finally {
- mSession.release();
- Bridge.cleanupThread();
- }
- }
-
- return mLastResult;
- }
-
- @Override
public void setSystemTimeNanos(long nanos) {
System_Delegate.setNanosTime(nanos);
}
diff --git a/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 0f4cbb7..4c6c9d4 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -2017,7 +2017,7 @@
@Override
public boolean canLoadUnsafeResources() {
- return false;
+ return true;
}
private class AttributeHolder {
diff --git a/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java b/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
index 59a9e6c..98937ef 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
@@ -288,6 +288,11 @@
}
@Override
+ public String[] getNamesForUids(int[] uids) {
+ return null;
+ }
+
+ @Override
public int getUidForSharedUser(String sharedUserName) throws NameNotFoundException {
return 0;
}
diff --git a/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java b/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java
index 051de90..a20652f 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java
@@ -59,6 +59,11 @@
*/
public static final Key<Boolean> FLAG_DO_NOT_RENDER_ON_CREATE =
new Key<Boolean>("doNotRenderOnCreate", Boolean.class);
+ /**
+ * The adaptive icon mask path. Used via {@link LayoutlibCallback#getFlag(Key)}
+ */
+ public static final Key<String> FLAG_KEY_ADAPTIVE_ICON_MASK_PATH =
+ new Key<>("adaptiveIconMaskPath", String.class);
// Disallow instances.
private RenderParamsFlags() {}
diff --git a/bridge/src/com/android/layoutlib/bridge/android/support/FragmentTabHostUtil.java b/bridge/src/com/android/layoutlib/bridge/android/support/FragmentTabHostUtil.java
new file mode 100644
index 0000000..730ac13
--- /dev/null
+++ b/bridge/src/com/android/layoutlib/bridge/android/support/FragmentTabHostUtil.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.android.support;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.util.ReflectionUtils.ReflectionException;
+
+import android.content.Context;
+import android.widget.TabHost;
+
+import static com.android.layoutlib.bridge.util.ReflectionUtils.getCause;
+import static com.android.layoutlib.bridge.util.ReflectionUtils.getMethod;
+import static com.android.layoutlib.bridge.util.ReflectionUtils.invoke;
+
+/**
+ * Utility class for working with android.support.v4.app.FragmentTabHost
+ */
+public class FragmentTabHostUtil {
+
+ public static final String CN_FRAGMENT_TAB_HOST = "android.support.v4.app.FragmentTabHost";
+
+ /**
+ * Calls the setup method for the FragmentTabHost tabHost
+ */
+ public static void setup(TabHost tabHost, Context context) {
+ try {
+ invoke(getMethod(tabHost.getClass(), "setup", Context.class,
+ Class.forName("android.support.v4.app.FragmentManager", true,
+ tabHost.getClass().getClassLoader()), int.class), tabHost, context, null,
+ android.R.id.tabcontent);
+ } catch (ReflectionException | ClassNotFoundException e) {
+ Throwable cause = getCause(e);
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ "Error occurred while trying to setup FragmentTabHost.", cause, null);
+ }
+ }
+}
diff --git a/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java b/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
index 2984fc0..369f1c6 100644
--- a/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
+++ b/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
@@ -209,7 +209,7 @@
textColor = res.resolveResValue(textColor);
if (textColor != null) {
ColorStateList stateList = ResourceHelper.getColorStateList(
- textColor, bridgeContext);
+ textColor, bridgeContext, null);
if (stateList != null) {
textView.setTextColor(stateList);
}
diff --git a/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java b/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
index 4696b56..5b42df1 100644
--- a/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
+++ b/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
@@ -29,6 +29,7 @@
import com.android.resources.ScreenOrientation;
import com.android.resources.ScreenRound;
import com.android.resources.ScreenSize;
+import com.android.tools.layoutlib.annotations.VisibleForTesting;
import android.content.res.Configuration;
import android.os.HandlerThread_Delegate;
@@ -67,7 +68,8 @@
* The current context being rendered. This is set through {@link #acquire(long)} and
* {@link #init(long)}, and unset in {@link #release()}.
*/
- private static BridgeContext sCurrentContext = null;
+ @VisibleForTesting
+ static BridgeContext sCurrentContext = null;
private final T mParams;
diff --git a/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index 70a854f..2e31980 100644
--- a/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -18,8 +18,6 @@
import com.android.ide.common.rendering.api.AdapterBinding;
import com.android.ide.common.rendering.api.HardwareConfig;
-import com.android.ide.common.rendering.api.IAnimationListener;
-import com.android.ide.common.rendering.api.ILayoutPullParser;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.LayoutlibCallback;
import com.android.ide.common.rendering.api.RenderResources;
@@ -27,7 +25,6 @@
import com.android.ide.common.rendering.api.ResourceReference;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.Result;
-import com.android.ide.common.rendering.api.Result.Status;
import com.android.ide.common.rendering.api.SessionParams;
import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
import com.android.ide.common.rendering.api.ViewInfo;
@@ -40,24 +37,20 @@
import com.android.internal.view.menu.MenuView;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes;
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
import com.android.layoutlib.bridge.android.RenderParamsFlags;
import com.android.layoutlib.bridge.android.graphics.NopCanvas;
import com.android.layoutlib.bridge.android.support.DesignLibUtil;
+import com.android.layoutlib.bridge.android.support.FragmentTabHostUtil;
import com.android.layoutlib.bridge.android.support.SupportPreferencesUtil;
import com.android.layoutlib.bridge.impl.binding.FakeAdapter;
import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter;
+import com.android.layoutlib.bridge.util.ReflectionUtils;
import com.android.resources.ResourceType;
import com.android.tools.layoutlib.java.System_Delegate;
import com.android.util.Pair;
import com.android.util.PropertiesMap;
-import android.animation.AnimationThread;
-import android.animation.Animator;
-import android.animation.AnimatorInflater;
-import android.animation.LayoutTransition;
-import android.animation.LayoutTransition.TransitionListener;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Fragment_Delegate;
@@ -96,11 +89,9 @@
import java.util.List;
import java.util.Map;
-import static com.android.ide.common.rendering.api.Result.Status.ERROR_ANIM_NOT_FOUND;
import static com.android.ide.common.rendering.api.Result.Status.ERROR_INFLATION;
import static com.android.ide.common.rendering.api.Result.Status.ERROR_NOT_INFLATED;
import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
-import static com.android.ide.common.rendering.api.Result.Status.ERROR_VIEWGROUP_NO_CHILDREN;
import static com.android.ide.common.rendering.api.Result.Status.SUCCESS;
import static com.android.layoutlib.bridge.util.ReflectionUtils.isInstanceOf;
@@ -357,6 +348,8 @@
visitAllChildren(mViewRoot, 0, 0, params.getExtendedViewInfoMode(),
false);
+ Choreographer_Delegate.clearFrames();
+
return SUCCESS.createResult();
} catch (PostInflateException e) {
return ERROR_INFLATION.createResult(e.getMessage(), e);
@@ -604,407 +597,6 @@
}
/**
- * Animate an object
- * <p>
- * {@link #acquire(long)} must have been called before this.
- *
- * @throws IllegalStateException if the current context is different than the one owned by
- * the scene, or if {@link #acquire(long)} was not called.
- *
- * @see RenderSession#animate(Object, String, boolean, IAnimationListener)
- */
- public Result animate(Object targetObject, String animationName,
- boolean isFrameworkAnimation, IAnimationListener listener) {
- checkLock();
-
- BridgeContext context = getContext();
-
- // find the animation file.
- ResourceValue animationResource;
- int animationId = 0;
- if (isFrameworkAnimation) {
- animationResource = context.getRenderResources().getFrameworkResource(
- ResourceType.ANIMATOR, animationName);
- if (animationResource != null) {
- animationId = Bridge.getResourceId(ResourceType.ANIMATOR, animationName);
- }
- } else {
- animationResource = context.getRenderResources().getProjectResource(
- ResourceType.ANIMATOR, animationName);
- if (animationResource != null) {
- animationId = context.getLayoutlibCallback().getResourceId(
- ResourceType.ANIMATOR, animationName);
- }
- }
-
- if (animationResource != null) {
- try {
- Animator anim = AnimatorInflater.loadAnimator(context, animationId);
- if (anim != null) {
- anim.setTarget(targetObject);
-
- new PlayAnimationThread(anim, this, animationName, listener).start();
-
- return SUCCESS.createResult();
- }
- } catch (Exception e) {
- // get the real cause of the exception.
- Throwable t = e;
- while (t.getCause() != null) {
- t = t.getCause();
- }
-
- return ERROR_UNKNOWN.createResult(t.getMessage(), t);
- }
- }
-
- return ERROR_ANIM_NOT_FOUND.createResult();
- }
-
- /**
- * Insert a new child into an existing parent.
- * <p>
- * {@link #acquire(long)} must have been called before this.
- *
- * @throws IllegalStateException if the current context is different than the one owned by
- * the scene, or if {@link #acquire(long)} was not called.
- *
- * @see RenderSession#insertChild(Object, ILayoutPullParser, int, IAnimationListener)
- */
- public Result insertChild(final ViewGroup parentView, ILayoutPullParser childXml,
- final int index, IAnimationListener listener) {
- checkLock();
-
- BridgeContext context = getContext();
-
- // create a block parser for the XML
- BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
- childXml, context, false /* platformResourceFlag */);
-
- // inflate the child without adding it to the root since we want to control where it'll
- // get added. We do pass the parentView however to ensure that the layoutParams will
- // be created correctly.
- final View child = mInflater.inflate(blockParser, parentView, false /*attachToRoot*/);
- blockParser.ensurePopped();
-
- invalidateRenderingSize();
-
- if (listener != null) {
- new AnimationThread(this, "insertChild", listener) {
-
- @Override
- public Result preAnimation() {
- parentView.setLayoutTransition(new LayoutTransition());
- return addView(parentView, child, index);
- }
-
- @Override
- public void postAnimation() {
- parentView.setLayoutTransition(null);
- }
- }.start();
-
- // always return success since the real status will come through the listener.
- return SUCCESS.createResult(child);
- }
-
- // add it to the parentView in the correct location
- Result result = addView(parentView, child, index);
- if (!result.isSuccess()) {
- return result;
- }
-
- result = render(false /*freshRender*/);
- if (result.isSuccess()) {
- result = result.getCopyWithData(child);
- }
-
- return result;
- }
-
- /**
- * Adds a given view to a given parent at a given index.
- *
- * @param parent the parent to receive the view
- * @param view the view to add to the parent
- * @param index the index where to do the add.
- *
- * @return a Result with {@link Status#SUCCESS} or
- * {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support
- * adding views.
- */
- private Result addView(ViewGroup parent, View view, int index) {
- try {
- parent.addView(view, index);
- return SUCCESS.createResult();
- } catch (UnsupportedOperationException e) {
- // looks like this is a view class that doesn't support children manipulation!
- return ERROR_VIEWGROUP_NO_CHILDREN.createResult();
- }
- }
-
- /**
- * Moves a view to a new parent at a given location
- * <p>
- * {@link #acquire(long)} must have been called before this.
- *
- * @throws IllegalStateException if the current context is different than the one owned by
- * the scene, or if {@link #acquire(long)} was not called.
- *
- * @see RenderSession#moveChild(Object, Object, int, Map, IAnimationListener)
- */
- public Result moveChild(final ViewGroup newParentView, final View childView, final int index,
- Map<String, String> layoutParamsMap, final IAnimationListener listener) {
- checkLock();
-
- invalidateRenderingSize();
-
- LayoutParams layoutParams = null;
- if (layoutParamsMap != null) {
- // need to create a new LayoutParams object for the new parent.
- layoutParams = newParentView.generateLayoutParams(
- new BridgeLayoutParamsMapAttributes(layoutParamsMap));
- }
-
- // get the current parent of the view that needs to be moved.
- final ViewGroup previousParent = (ViewGroup) childView.getParent();
-
- if (listener != null) {
- final LayoutParams params = layoutParams;
-
- // there is no support for animating views across layouts, so in case the new and old
- // parent views are different we fake the animation through a no animation thread.
- if (previousParent != newParentView) {
- new Thread("not animated moveChild") {
- @Override
- public void run() {
- Result result = moveView(previousParent, newParentView, childView, index,
- params);
- if (!result.isSuccess()) {
- listener.done(result);
- }
-
- // ready to do the work, acquire the scene.
- result = acquire(250);
- if (!result.isSuccess()) {
- listener.done(result);
- return;
- }
-
- try {
- result = render(false /*freshRender*/);
- if (result.isSuccess()) {
- listener.onNewFrame(RenderSessionImpl.this.getSession());
- }
- } finally {
- release();
- }
-
- listener.done(result);
- }
- }.start();
- } else {
- new AnimationThread(this, "moveChild", listener) {
-
- @Override
- public Result preAnimation() {
- // set up the transition for the parent.
- LayoutTransition transition = new LayoutTransition();
- previousParent.setLayoutTransition(transition);
-
- // tweak the animation durations and start delays (to match the duration of
- // animation playing just before).
- // Note: Cannot user Animation.setDuration() directly. Have to set it
- // on the LayoutTransition.
- transition.setDuration(LayoutTransition.DISAPPEARING, 100);
- // CHANGE_DISAPPEARING plays after DISAPPEARING
- transition.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 100);
-
- transition.setDuration(LayoutTransition.CHANGE_DISAPPEARING, 100);
-
- transition.setDuration(LayoutTransition.CHANGE_APPEARING, 100);
- // CHANGE_APPEARING plays after CHANGE_APPEARING
- transition.setStartDelay(LayoutTransition.APPEARING, 100);
-
- transition.setDuration(LayoutTransition.APPEARING, 100);
-
- return moveView(previousParent, newParentView, childView, index, params);
- }
-
- @Override
- public void postAnimation() {
- previousParent.setLayoutTransition(null);
- newParentView.setLayoutTransition(null);
- }
- }.start();
- }
-
- // always return success since the real status will come through the listener.
- return SUCCESS.createResult(layoutParams);
- }
-
- Result result = moveView(previousParent, newParentView, childView, index, layoutParams);
- if (!result.isSuccess()) {
- return result;
- }
-
- result = render(false /*freshRender*/);
- if (layoutParams != null && result.isSuccess()) {
- result = result.getCopyWithData(layoutParams);
- }
-
- return result;
- }
-
- /**
- * Moves a View from its current parent to a new given parent at a new given location, with
- * an optional new {@link LayoutParams} instance
- *
- * @param previousParent the previous parent, still owning the child at the time of the call.
- * @param newParent the new parent
- * @param movedView the view to move
- * @param index the new location in the new parent
- * @param params an option (can be null) {@link LayoutParams} instance.
- *
- * @return a Result with {@link Status#SUCCESS} or
- * {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support
- * adding views.
- */
- private Result moveView(ViewGroup previousParent, final ViewGroup newParent,
- final View movedView, final int index, final LayoutParams params) {
- try {
- // check if there is a transition on the previousParent.
- LayoutTransition previousTransition = previousParent.getLayoutTransition();
- if (previousTransition != null) {
- // in this case there is an animation. This means we have to wait for the child's
- // parent reference to be null'ed out so that we can add it to the new parent.
- // It is technically removed right before the DISAPPEARING animation is done (if
- // the animation of this type is not null, otherwise it's after which is impossible
- // to handle).
- // Because there is no move animation, if the new parent is the same as the old
- // parent, we need to wait until the CHANGE_DISAPPEARING animation is done before
- // adding the child or the child will appear in its new location before the
- // other children have made room for it.
-
- // add a listener to the transition to be notified of the actual removal.
- previousTransition.addTransitionListener(new TransitionListener() {
- private int mChangeDisappearingCount = 0;
-
- @Override
- public void startTransition(LayoutTransition transition, ViewGroup container,
- View view, int transitionType) {
- if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) {
- mChangeDisappearingCount++;
- }
- }
-
- @Override
- public void endTransition(LayoutTransition transition, ViewGroup container,
- View view, int transitionType) {
- if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) {
- mChangeDisappearingCount--;
- }
-
- if (transitionType == LayoutTransition.CHANGE_DISAPPEARING &&
- mChangeDisappearingCount == 0) {
- // add it to the parentView in the correct location
- if (params != null) {
- newParent.addView(movedView, index, params);
- } else {
- newParent.addView(movedView, index);
- }
- }
- }
- });
-
- // remove the view from the current parent.
- previousParent.removeView(movedView);
-
- // and return since adding the view to the new parent is done in the listener.
- return SUCCESS.createResult();
- } else {
- // standard code with no animation. pretty simple.
- previousParent.removeView(movedView);
-
- // add it to the parentView in the correct location
- if (params != null) {
- newParent.addView(movedView, index, params);
- } else {
- newParent.addView(movedView, index);
- }
-
- return SUCCESS.createResult();
- }
- } catch (UnsupportedOperationException e) {
- // looks like this is a view class that doesn't support children manipulation!
- return ERROR_VIEWGROUP_NO_CHILDREN.createResult();
- }
- }
-
- /**
- * Removes a child from its current parent.
- * <p>
- * {@link #acquire(long)} must have been called before this.
- *
- * @throws IllegalStateException if the current context is different than the one owned by
- * the scene, or if {@link #acquire(long)} was not called.
- *
- * @see RenderSession#removeChild(Object, IAnimationListener)
- */
- public Result removeChild(final View childView, IAnimationListener listener) {
- checkLock();
-
- invalidateRenderingSize();
-
- final ViewGroup parent = (ViewGroup) childView.getParent();
-
- if (listener != null) {
- new AnimationThread(this, "moveChild", listener) {
-
- @Override
- public Result preAnimation() {
- parent.setLayoutTransition(new LayoutTransition());
- return removeView(parent, childView);
- }
-
- @Override
- public void postAnimation() {
- parent.setLayoutTransition(null);
- }
- }.start();
-
- // always return success since the real status will come through the listener.
- return SUCCESS.createResult();
- }
-
- Result result = removeView(parent, childView);
- if (!result.isSuccess()) {
- return result;
- }
-
- return render(false /*freshRender*/);
- }
-
- /**
- * Removes a given view from its current parent.
- *
- * @param view the view to remove from its parent
- *
- * @return a Result with {@link Status#SUCCESS} or
- * {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support
- * adding views.
- */
- private Result removeView(ViewGroup parent, View view) {
- try {
- parent.removeView(view);
- return SUCCESS.createResult();
- } catch (UnsupportedOperationException e) {
- // looks like this is a view class that doesn't support children manipulation!
- return ERROR_VIEWGROUP_NO_CHILDREN.createResult();
- }
- }
-
- /**
* Post process on a view hierarchy that was just inflated.
* <p/>
* At the moment this only supports TabHost: If {@link TabHost} is detected, look for the
@@ -1240,7 +832,11 @@
// this must be called before addTab() so that the TabHost searches its TabWidget
// and FrameLayout.
- tabHost.setup();
+ if (ReflectionUtils.isInstanceOf(tabHost, FragmentTabHostUtil.CN_FRAGMENT_TAB_HOST)) {
+ FragmentTabHostUtil.setup(tabHost, getContext());
+ } else {
+ tabHost.setup();
+ }
if (count == 0) {
// Create a dummy child to get a single tab
@@ -1539,8 +1135,8 @@
mContentRoot = null;
if (createdLooper) {
- Bridge.cleanupThread();
Choreographer_Delegate.dispose();
+ Bridge.cleanupThread();
}
}
}
diff --git a/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java b/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
index 2826050..c3b9aed 100644
--- a/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
+++ b/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
@@ -40,6 +40,7 @@
import android.content.res.ComplexColor_Accessor;
import android.content.res.FontResourcesParser;
import android.content.res.GradientColor;
+import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.graphics.Bitmap;
import android.graphics.Bitmap_Delegate;
@@ -80,55 +81,57 @@
* @return the color as an int
* @throws NumberFormatException if the conversion failed.
*/
- public static int getColor(String value) {
- if (value != null) {
- value = value.trim();
- if (!value.startsWith("#")) {
- if (value.startsWith(SdkConstants.PREFIX_THEME_REF)) {
- throw new NumberFormatException(String.format(
- "Attribute '%s' not found. Are you using the right theme?", value));
- }
- throw new NumberFormatException(
- String.format("Color value '%s' must start with #", value));
- }
-
- value = value.substring(1);
-
- // make sure it's not longer than 32bit
- if (value.length() > 8) {
- throw new NumberFormatException(String.format(
- "Color value '%s' is too long. Format is either" +
- "#AARRGGBB, #RRGGBB, #RGB, or #ARGB",
- value));
- }
-
- if (value.length() == 3) { // RGB format
- char[] color = new char[8];
- color[0] = color[1] = 'F';
- color[2] = color[3] = value.charAt(0);
- color[4] = color[5] = value.charAt(1);
- color[6] = color[7] = value.charAt(2);
- value = new String(color);
- } else if (value.length() == 4) { // ARGB format
- char[] color = new char[8];
- color[0] = color[1] = value.charAt(0);
- color[2] = color[3] = value.charAt(1);
- color[4] = color[5] = value.charAt(2);
- color[6] = color[7] = value.charAt(3);
- value = new String(color);
- } else if (value.length() == 6) {
- value = "FF" + value;
- }
-
- // this is a RRGGBB or AARRGGBB value
-
- // Integer.parseInt will fail to parse strings like "ff191919", so we use
- // a Long, but cast the result back into an int, since we know that we're only
- // dealing with 32 bit values.
- return (int)Long.parseLong(value, 16);
+ public static int getColor(@Nullable String value) {
+ if (value == null) {
+ throw new NumberFormatException("null value");
}
- throw new NumberFormatException();
+ value = value.trim();
+ int len = value.length();
+
+ // make sure it's not longer than 32bit or smaller than the RGB format
+ if (len < 2 || len > 9) {
+ throw new NumberFormatException(String.format(
+ "Color value '%s' has wrong size. Format is either" +
+ "#AARRGGBB, #RRGGBB, #RGB, or #ARGB",
+ value));
+ }
+
+ if (value.charAt(0) != '#') {
+ if (value.startsWith(SdkConstants.PREFIX_THEME_REF)) {
+ throw new NumberFormatException(String.format(
+ "Attribute '%s' not found. Are you using the right theme?", value));
+ }
+ throw new NumberFormatException(
+ String.format("Color value '%s' must start with #", value));
+ }
+
+ value = value.substring(1);
+
+ if (len == 4) { // RGB format
+ char[] color = new char[8];
+ color[0] = color[1] = 'F';
+ color[2] = color[3] = value.charAt(0);
+ color[4] = color[5] = value.charAt(1);
+ color[6] = color[7] = value.charAt(2);
+ value = new String(color);
+ } else if (len == 5) { // ARGB format
+ char[] color = new char[8];
+ color[0] = color[1] = value.charAt(0);
+ color[2] = color[3] = value.charAt(1);
+ color[4] = color[5] = value.charAt(2);
+ color[6] = color[7] = value.charAt(3);
+ value = new String(color);
+ } else if (len == 7) {
+ value = "FF" + value;
+ }
+
+ // this is a RRGGBB or AARRGGBB value
+
+ // Integer.parseInt will fail to parse strings like "ff191919", so we use
+ // a Long, but cast the result back into an int, since we know that we're only
+ // dealing with 32 bit values.
+ return (int)Long.parseLong(value, 16);
}
/**
@@ -149,6 +152,13 @@
return null;
}
+ // try to load the color state list from an int
+ try {
+ int color = getColor(value);
+ return ColorStateList.valueOf(color);
+ } catch (NumberFormatException ignored) {
+ }
+
XmlPullParser parser = null;
// first check if the value is a file (xml most likely)
Boolean psiParserSupport = context.getLayoutlibCallback().getFlag(
@@ -216,16 +226,6 @@
return null;
}
- } else {
- // try to load the color state list from an int
- try {
- int color = getColor(value);
- return ColorStateList.valueOf(color);
- } catch (NumberFormatException e) {
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
- "Failed to convert " + value + " into a ColorStateList", e,
- null /*data*/);
- }
}
return null;
@@ -240,8 +240,9 @@
*/
@Nullable
public static ColorStateList getColorStateList(@NonNull ResourceValue resValue,
- @NonNull BridgeContext context) {
- return (ColorStateList) getInternalComplexColor(resValue, context, context.getTheme(),
+ @NonNull BridgeContext context, @Nullable Resources.Theme theme) {
+ return (ColorStateList) getInternalComplexColor(resValue, context,
+ theme != null ? theme : context.getTheme(),
false);
}
@@ -254,8 +255,10 @@
*/
@Nullable
public static ComplexColor getComplexColor(@NonNull ResourceValue resValue,
- @NonNull BridgeContext context) {
- return getInternalComplexColor(resValue, context, context.getTheme(), true);
+ @NonNull BridgeContext context, @Nullable Resources.Theme theme) {
+ return getInternalComplexColor(resValue, context,
+ theme != null ? theme : context.getTheme(),
+ true);
}
/**
@@ -316,10 +319,19 @@
}
String lowerCaseValue = stringValue.toLowerCase();
+ // try the simple case first. Attempt to get a color from the value
+ try {
+ int color = getColor(stringValue);
+ return new ColorDrawable(color);
+ } catch (NumberFormatException ignore) {
+ }
Density density = Density.MEDIUM;
if (value instanceof DensityBasedResourceValue) {
density = ((DensityBasedResourceValue) value).getResourceDensity();
+ if (density == Density.NODPI || density == Density.ANYDPI) {
+ density = Density.getEnum(context.getConfiguration().densityDpi);
+ }
}
if (lowerCaseValue.endsWith(NinePatch.EXTENSION_9PATCH)) {
@@ -375,17 +387,6 @@
Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
"Failed lot load " + bmpFile.getAbsolutePath(), e, null /*data*/);
}
- } else {
- // attempt to get a color from the value
- try {
- int color = getColor(stringValue);
- return new ColorDrawable(color);
- } catch (NumberFormatException e) {
- // we'll return null below.
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
- "Failed to convert " + stringValue + " into a drawable", e,
- null /*data*/);
- }
}
}
diff --git a/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_circle.png b/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_circle.png
new file mode 100644
index 0000000..3e837da
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_circle.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_rounded_corners.png b/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_rounded_corners.png
new file mode 100644
index 0000000..0c09075
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_rounded_corners.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_squircle.png b/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_squircle.png
new file mode 100644
index 0000000..f3fb015
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_squircle.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png b/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png
index bb69ca8..bca3347 100644
--- a/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png
+++ b/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/ninepatch_background.png b/bridge/tests/res/testApp/MyApplication/golden/ninepatch_background.png
new file mode 100644
index 0000000..45a63aa
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/ninepatch_background.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
new file mode 100644
index 0000000..5fc6052
--- /dev/null
+++ 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/src/main/res/drawable-nodpi/ninepatch.9.png b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable-nodpi/ninepatch.9.png
new file mode 100644
index 0000000..fb3660e
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable-nodpi/ninepatch.9.png
Binary files differ
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
new file mode 100644
index 0000000..c5b0f01
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_line_of_path_data.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="16dp"
+ android:viewportHeight="16"
+ android:viewportWidth="16"
+ android:width="16dp">
+
+ <group
+ android:translateX="-2.000000"
+ android:translateY="-14.000000">
+ <group
+ android:translateX="2.000000"
+ android:translateY="14.000000">
+ <group
+ android:translateX="0.538462"
+ android:translateY="1.846154">
+ <path
+ android:fillColor="#D8D8D8"
+ android:fillType="evenOdd"
+ android:pathData="M 7.5502538 11.69230767 C 8.90972395258 11.69230767 10.01179226 12.2433418237 10.01179226 12.9230769 C 10.01179226 13.6028119763 8.90972395258 14.15384613 7.5502538 14.15384613 C 6.19078364742 14.15384613 5.08871534 13.6028119763 5.08871534 12.9230769 C 5.08871534 12.2433418237 6.19078364742 11.69230767 7.5502538 11.69230767 Z"
+ android:strokeWidth="1"/>
+ <!-- The ugly format below is intended for testing, please don't modify it-->
+ <path
+ android:fillColor="#FFFF00FF"
+ android:fillType="evenOdd"
+ android:pathData="M9.69758952,10.4615385 L13.6123406,10.4615385 C14.2852173,10.4615385
+
+14.8461538,9.9121408 14.8461538,9.23442444 L14.8461538,1.84249863
+C14.8461538,1.17296776 14.2937568,0.615384615 13.6123406,0.615384615
+L1.31073636,0.615384615 C0.637859638,0.615384615 0.0769230769,1.16478227
+ 0.0769230769,1.84249863 L0.0769230769,9.23442444 C0.0769230769,9.90395531
+
+0.629320101,10.4615385 1.31073636,10.4615385 L5.1043961,10.4615385
+C5.12507441,10.5113515 5.16340706,10.5657803 5.2204207,10.622794
+ L6.96901704,12.3713903 C7.21238046,12.6147537 7.59317013,12.6094967
+7.83127651,12.3713903 L9.57987285,10.622794 C9.63729929,10.5653675
+9.676186,10.5110368 9.69758952,10.4615385 Z"
+ android:strokeWidth="1"
+ />
+ </group>
+ <path
+ android:fillType="evenOdd"
+ android:pathData="M4.92307692,1.229485 C4.92307692,0.550459184 5.46918972,0 6.15360922,0
+L9.84639078,0 C10.525995,0 11.0769231,0.544812716 11.0769231,1.229485
+L11.0769231,3.69230769 L4.92307692,3.69230769 L4.92307692,1.229485 Z"
+ android:strokeColor="#FFFF00FF"
+ android:strokeWidth="2.46153846"/>
+ <path
+ android:fillType="evenOdd"
+ android:pathData="M4.92307692,1.229485 C4.92307692,0.550459184 5.46918972,0 6.15360922,0
+L9.84639078,0 C10.525995,0 11.0769231,0.544812716 11.0769231,1.229485
+L11.0769231,3.69230769 L4.92307692,3.69230769 L4.92307692,1.229485 Z"
+ android:strokeColor="#FFFF00FF"
+ android:strokeWidth="2.46153846"/>
+ <path
+ android:fillColor="#FFFFFF"
+ android:fillType="evenOdd"
+ android:pathData="M 4.307692305 4.92307692 L 11.692307695 4.92307692 Q 12.30769231 4.92307692 12.30769231 5.538461535 L 12.30769231 5.538461535 Q 12.30769231 6.15384615 11.692307695 6.15384615 L 4.307692305 6.15384615 Q 3.69230769 6.15384615 3.69230769 5.538461535 L 3.69230769 5.538461535 Q 3.69230769 4.92307692 4.307692305 4.92307692 Z"
+ android:strokeWidth="1"/>
+ </group>
+ </group>
+</vector>
\ No newline at end of file
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java b/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java
index d8937f4..46e3778 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java
@@ -173,6 +173,11 @@
continue;
}
+ // constructor_after methods are called by the constructor of the original class
+ if ("constructor_after".equals(delegateMethod.getName())) {
+ continue;
+ }
+
if (!checkedDelegateMethods.contains(delegateMethod)) {
mErrors.add(String.format(
"Delegate method %1$s.%2$s is not used anymore and must be removed",
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/impl/RenderActionTestUtil.java b/bridge/tests/src/com/android/layoutlib/bridge/impl/RenderActionTestUtil.java
new file mode 100644
index 0000000..67f1b7e
--- /dev/null
+++ b/bridge/tests/src/com/android/layoutlib/bridge/impl/RenderActionTestUtil.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.layoutlib.bridge.impl;
+
+import com.android.layoutlib.bridge.android.BridgeContext;
+
+import android.annotation.Nullable;
+
+public class RenderActionTestUtil {
+ @Nullable
+ public static BridgeContext setBridgeContext(@Nullable BridgeContext context) {
+ BridgeContext oldContext = RenderAction.sCurrentContext;
+ RenderAction.sCurrentContext = context;
+ return oldContext;
+ }
+}
\ No newline at end of file
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/impl/ResourceHelperTest.java b/bridge/tests/src/com/android/layoutlib/bridge/impl/ResourceHelperTest.java
new file mode 100644
index 0000000..4e18f07
--- /dev/null
+++ b/bridge/tests/src/com/android/layoutlib/bridge/impl/ResourceHelperTest.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.impl;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class ResourceHelperTest {
+ private static void assertNumberFormatException(Runnable runnable) {
+ try {
+ runnable.run();
+ fail("NumberFormatException expected");
+ } catch (NumberFormatException ignored) {
+ }
+ }
+
+ @Test
+ public void testGetColor() {
+ assertNumberFormatException(() -> ResourceHelper.getColor(""));
+ assertNumberFormatException(() -> ResourceHelper.getColor("AFAFAF"));
+ assertNumberFormatException(() -> ResourceHelper.getColor("AAA"));
+ assertNumberFormatException(() -> ResourceHelper.getColor("#JFAFAF"));
+ assertNumberFormatException(() -> ResourceHelper.getColor("#AABBCCDDEE"));
+ assertNumberFormatException(() -> ResourceHelper.getColor("#JAAA"));
+ assertNumberFormatException(() -> ResourceHelper.getColor("#AA BBCC"));
+
+ assertEquals(0xffaaaaaa, ResourceHelper.getColor("#AAA"));
+ assertEquals(0xffaaaaaa, ResourceHelper.getColor(" #AAA"));
+ assertEquals(0xffaaaaaa, ResourceHelper.getColor("#AAA "));
+ assertEquals(0xffaaaaaa, ResourceHelper.getColor(" #AAA "));
+ assertEquals(0xaaaaaa, ResourceHelper.getColor("#0AAA"));
+ assertEquals(0xffaabbcc, ResourceHelper.getColor("#AABBCC"));
+ assertEquals(0x12aabbcc, ResourceHelper.getColor("#12AABBCC"));
+ assertEquals(0x12345, ResourceHelper.getColor("#12345"));
+ }
+}
\ No newline at end of file
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
index eb264d6..e98c789 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
@@ -20,6 +20,7 @@
import com.android.layoutlib.bridge.TestDelegates;
import com.android.layoutlib.bridge.android.BridgeXmlBlockParserTest;
import com.android.layoutlib.bridge.impl.LayoutParserWrapperTest;
+import com.android.layoutlib.bridge.impl.ResourceHelperTest;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@@ -36,7 +37,7 @@
RenderTests.class, LayoutParserWrapperTest.class,
BridgeXmlBlockParserTest.class, BridgeXmlPullAttributesTest.class,
Matrix_DelegateTest.class, TestDelegates.class, PerformanceTests.class,
- BridgeRenderSessionTest.class
+ BridgeRenderSessionTest.class, ResourceHelperTest.class
})
public class Main {
}
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 208c2d7..b09184b 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java
@@ -17,30 +17,45 @@
package com.android.layoutlib.bridge.intensive;
import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.SessionParams;
import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
import com.android.ide.common.rendering.api.ViewInfo;
import com.android.internal.R;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.RenderParamsFlags;
+import com.android.layoutlib.bridge.impl.ParserFactory;
import com.android.layoutlib.bridge.impl.RenderAction;
+import com.android.layoutlib.bridge.impl.RenderActionTestUtil;
+import com.android.layoutlib.bridge.impl.ResourceHelper;
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.resources.Density;
import com.android.resources.Navigation;
import com.android.resources.ResourceType;
+import com.android.resources.ResourceUrl;
import org.junit.Test;
+import org.kxml2.io.KXmlParser;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
import android.content.res.AssetManager;
+import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.Resources_Delegate;
+import android.graphics.Color;
import android.util.DisplayMetrics;
+import android.util.StateSet;
import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
+import java.io.File;
import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.concurrent.TimeUnit;
@@ -348,6 +363,37 @@
renderAndVerify(params, "vector_drawable_91383.png", TimeUnit.SECONDS.toNanos(2));
}
+ /**
+ * Test a vector drawable that uses trimStart and trimEnd. It also tests all the primitives
+ * for vector drawables (lines, moves and cubic and quadratic curves).
+ */
+ @Test
+ public void testVectorDrawableHasMultipleLineInPathData() 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/multi_line_of_path_data\" />\n\n" +
+ "</LinearLayout>");
+ // Create LayoutLibCallback.
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+
+ SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+ layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
+ RenderingMode.V_SCROLL, 22);
+
+ renderAndVerify(params, "vector_drawable_multi_line_of_path_data.png",
+ TimeUnit.SECONDS.toNanos(2));
+ }
+
/** Test activity.xml */
@Test
public void testScrollingAndMeasure() throws ClassNotFoundException, FileNotFoundException {
@@ -481,7 +527,7 @@
@Test
public void testAdaptiveIcon() throws ClassNotFoundException, FileNotFoundException {
// Create the layout pull parser.
- LayoutPullParser parser = LayoutPullParser.createFromString(
+ String layout =
"<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
" android:padding=\"16dp\"\n" +
" android:orientation=\"horizontal\"\n" +
@@ -491,7 +537,8 @@
" android:layout_height=\"wrap_content\"\n" +
" android:layout_width=\"wrap_content\"\n" +
" android:src=\"@drawable/adaptive\" />\n" +
- "</LinearLayout>\n");
+ "</LinearLayout>\n";
+ LayoutPullParser parser = LayoutPullParser.createFromString(layout);
// Create LayoutLibCallback.
LayoutLibTestCallback layoutLibCallback =
new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
@@ -502,6 +549,32 @@
RenderingMode.V_SCROLL, 22);
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);
+ 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);
+ 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);
+ renderAndVerify(params, "adaptive_icon_squircle.png");
}
@Test
@@ -532,6 +605,84 @@
}
@Test
+ public void testColorStateList() throws Exception {
+ final String STATE_LIST = "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n" +
+ " <item android:state_pressed=\"true\"\n" +
+ " android:color=\"?android:attr/colorForeground\"/> \n" +
+ " <item android:state_focused=\"true\"\n" +
+ " android:color=\"?android:attr/colorBackground\"/> \n" +
+ " <item android:color=\"#a000\"/> <!-- default -->\n" + "</selector>";
+
+ File tmpColorList = File.createTempFile("statelist", "xml");
+ try(PrintWriter output = new PrintWriter(new FileOutputStream(tmpColorList))) {
+ output.println(STATE_LIST);
+ }
+
+ // Setup
+ // Create the layout pull parser for our resources (empty.xml can not be part of the test
+ // app as it won't compile).
+ ParserFactory.setParserFactory(new com.android.ide.common.rendering.api.ParserFactory() {
+ @Override
+ public XmlPullParser createParser(String debugName) throws XmlPullParserException {
+ return new KXmlParser();
+ }
+ });
+
+ LayoutPullParser parser = LayoutPullParser.createFromPath("/empty.xml");
+ // Create LayoutLibCallback.
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(RenderTestBase.getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+ SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4,
+ layoutLibCallback, "Theme.Material", false, RenderingMode.NORMAL, 22);
+ DisplayMetrics metrics = new DisplayMetrics();
+ Configuration configuration = RenderAction.getConfiguration(params);
+
+ BridgeContext mContext =
+ new BridgeContext(params.getProjectKey(), metrics, params.getResources(),
+ params.getAssets(), params.getLayoutlibCallback(), configuration,
+ params.getTargetSdkVersion(), params.isRtlSupported());
+ mContext.initResources();
+ BridgeContext oldContext = RenderActionTestUtil.setBridgeContext(mContext);
+
+ try {
+ ColorStateList stateList = ResourceHelper.getColorStateList(
+ new ResourceValue(ResourceUrl.create(null, ResourceType.COLOR, "test_list"),
+ tmpColorList.getAbsolutePath()), mContext, null);
+ assertNotNull(stateList);
+ assertEquals(Color.parseColor("#ffffffff"), stateList.getColorForState(
+ StateSet.get(StateSet.VIEW_STATE_PRESSED),
+ 0
+ ));
+ assertEquals(Color.parseColor("#ff303030"), stateList.getColorForState(
+ StateSet.get(StateSet.VIEW_STATE_FOCUSED),
+ 0
+ ));
+ assertEquals(Color.parseColor("#AA000000"), stateList.getDefaultColor());
+
+ // Now apply theme overlay and check the colors changed
+ Resources.Theme theme = mContext.getResources().newTheme();
+ theme.applyStyle(R.style.ThemeOverlay_Material_Light, true);
+ stateList = ResourceHelper.getColorStateList(
+ new ResourceValue(ResourceUrl.create(null, ResourceType.COLOR, "test_list"),
+ tmpColorList.getAbsolutePath()), mContext, theme);
+ assertNotNull(stateList);
+ assertEquals(Color.parseColor("#ff000000"), stateList.getColorForState(
+ StateSet.get(StateSet.VIEW_STATE_PRESSED),
+ 0
+ ));
+ assertEquals(Color.parseColor("#fffafafa"), stateList.getColorForState(
+ StateSet.get(StateSet.VIEW_STATE_FOCUSED),
+ 0
+ ));
+ assertEquals(Color.parseColor("#AA000000"), stateList.getDefaultColor());
+ } finally {
+ RenderActionTestUtil.setBridgeContext(oldContext);
+ }
+ mContext.disposeResources();
+ }
+
+ @Test
public void testRectangleShadow() throws Exception {
renderAndVerify("shadows_test.xml", "shadows_test.png");
}
@@ -573,4 +724,42 @@
context.disposeResources();
}
+
+ /**
+ * If a 9patch image was in the nodpi or anydpi folder, the density of the image was 0 resulting
+ * in a float division by 0 and thus an infinite padding
+ * when layoutlib tries to scale the padding of the 9patch.
+ *
+ * http://b/37136109
+ */
+ @Test
+ public void test9PatchNoDPIBackground() throws Exception {
+ String layout =
+ "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " android:background=\"@drawable/ninepatch\"\n" +
+ " android:layout_margin=\"20dp\"\n" +
+ " android:orientation=\"vertical\">\n\n" +
+ " <Button\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:text=\"Button\" />\n\n" +
+ " <Button\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:text=\"Button\" />\n"
+ + "</LinearLayout>";
+
+ LayoutPullParser parser = LayoutPullParser.createFromString(layout);
+ // Create LayoutLibCallback.
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+
+ SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5, layoutLibCallback,
+ "Theme.Material.NoActionBar.Fullscreen", false, RenderingMode.V_SCROLL, 22);
+
+ renderAndVerify(params, "ninepatch_background.png");
+ }
}
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java b/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java
index 75145d7..184538e 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java
@@ -43,9 +43,6 @@
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLClassLoader;
import java.util.Map;
import com.google.android.collect.Maps;
@@ -64,6 +61,7 @@
private final ILogger mLog;
private final ActionBarCallback mActionBarCallback = new ActionBarCallback();
private final ClassLoader mModuleClassLoader;
+ private String mAdaptiveIconMaskPath;
public LayoutLibTestCallback(ILogger logger, ClassLoader classLoader) {
mLog = logger;
@@ -197,6 +195,13 @@
if (key.equals(RenderParamsFlags.FLAG_KEY_APPLICATION_PACKAGE)) {
return (T) PACKAGE_NAME;
}
+ if (key.equals(RenderParamsFlags.FLAG_KEY_ADAPTIVE_ICON_MASK_PATH)) {
+ return (T) mAdaptiveIconMaskPath;
+ }
return null;
}
+
+ public void setAdaptiveIconMaskPath(String adaptiveIconMaskPath) {
+ mAdaptiveIconMaskPath = adaptiveIconMaskPath;
+ }
}
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 cdf5633..45facf5 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
@@ -56,7 +56,7 @@
private static final int THUMBNAIL_SIZE = 1000;
- private static final double MAX_PERCENT_DIFFERENCE = 0.3;
+ private static final double MAX_PERCENT_DIFFERENCE = 0.1;
public static void requireSimilar(@NonNull String relativePath, @NonNull BufferedImage image)
throws IOException {
diff --git a/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 5951a67..f595803 100644
--- a/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -167,6 +167,7 @@
"android.graphics.drawable.AnimatedVectorDrawable$VectorDrawableAnimatorRT#useLastSeenTarget",
"android.graphics.drawable.AnimatedVectorDrawable$VectorDrawableAnimatorRT#onDraw",
"android.graphics.drawable.GradientDrawable#buildRing",
+ "android.graphics.drawable.AdaptiveIconDrawable#<init>",
"android.graphics.FontFamily#addFont",
"android.graphics.Typeface#getSystemFontConfigLocation",
"android.graphics.Typeface#makeFamilyFromParsed",
@@ -336,7 +337,8 @@
"android.graphics.drawable.VectorDrawable#mVectorState",
"android.view.Choreographer#mLastFrameTimeNanos",
"android.graphics.FontFamily#mBuilderPtr",
- "android.graphics.Typeface#sDynamicTypefaceCache"
+ "android.graphics.Typeface#sDynamicTypefaceCache",
+ "android.graphics.drawable.AdaptiveIconDrawable#sMask",
};
/**