summaryrefslogtreecommitdiffstats
path: root/tools/layoutlib
diff options
context:
space:
mode:
authorSteve Kondik <steve@cyngn.com>2016-03-11 03:47:09 -0800
committerSteve Kondik <steve@cyngn.com>2016-03-11 16:58:39 -0800
commit0e1dbed9194839a90755670d8fdf9046a75b85f7 (patch)
tree010372762ddc617295da2862f7d61813da9e3586 /tools/layoutlib
parent564f10b8f05ddf4d9ea2c0e64f1b113fe6dad4b8 (diff)
parente342181a4a8d8177b3b87ffe141777565fe98f15 (diff)
downloadframeworks_base-0e1dbed9194839a90755670d8fdf9046a75b85f7.zip
frameworks_base-0e1dbed9194839a90755670d8fdf9046a75b85f7.tar.gz
frameworks_base-0e1dbed9194839a90755670d8fdf9046a75b85f7.tar.bz2
Merge tag 'android-6.0.1_r22' of https://android.googlesource.com/platform/frameworks/base into cm-13.0
Android 6.0.1 release 22 Change-Id: I0d31899b234156a91accb61e0a7fb3d8d16d5062
Diffstat (limited to 'tools/layoutlib')
-rw-r--r--tools/layoutlib/.idea/encodings.xml7
-rw-r--r--tools/layoutlib/bridge/src/android/animation/AnimatorInflater_Delegate.java59
-rw-r--r--tools/layoutlib/bridge/src/android/content/res/BridgeResources.java15
-rw-r--r--tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java20
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java1
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java8
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java8
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java72
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java210
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java79
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/drawable/GradientDrawable_Delegate.java73
-rw-r--r--tools/layoutlib/bridge/src/android/preference/Preference_Delegate.java7
-rw-r--r--tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java5
-rw-r--r--tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java5
-rw-r--r--tools/layoutlib/bridge/src/android/view/BridgeInflater.java59
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java77
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java8
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java22
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java4
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutParserWrapper.java377
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java19
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java10
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java4
-rw-r--r--tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/impl/LayoutParserWrapperTest.java183
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java3
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java24
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java8
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java58
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java10
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java30
30 files changed, 1265 insertions, 200 deletions
diff --git a/tools/layoutlib/.idea/encodings.xml b/tools/layoutlib/.idea/encodings.xml
index e206d70..f758959 100644
--- a/tools/layoutlib/.idea/encodings.xml
+++ b/tools/layoutlib/.idea/encodings.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
- <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
-</project>
-
+ <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false">
+ <file url="PROJECT" charset="UTF-8" />
+ </component>
+</project> \ No newline at end of file
diff --git a/tools/layoutlib/bridge/src/android/animation/AnimatorInflater_Delegate.java b/tools/layoutlib/bridge/src/android/animation/AnimatorInflater_Delegate.java
deleted file mode 100644
index 4475fa4..0000000
--- a/tools/layoutlib/bridge/src/android/animation/AnimatorInflater_Delegate.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2014 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.animation;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.Resources.NotFoundException;
-import android.content.res.Resources.Theme;
-import android.util.AttributeSet;
-
-/**
- * Delegate providing alternate implementation to static methods in {@link AnimatorInflater}.
- */
-public class AnimatorInflater_Delegate {
-
- @LayoutlibDelegate
- /*package*/ static Animator loadAnimator(Context context, int id)
- throws NotFoundException {
- return loadAnimator(context.getResources(), context.getTheme(), id);
- }
-
- @LayoutlibDelegate
- /*package*/ static Animator loadAnimator(Resources resources, Theme theme, int id)
- throws NotFoundException {
- return loadAnimator(resources, theme, id, 1);
- }
-
- @LayoutlibDelegate
- /*package*/ static Animator loadAnimator(Resources resources, Theme theme, int id,
- float pathErrorScale) throws NotFoundException {
- // This is a temporary fix to http://b.android.com/77865. This skips loading the
- // animation altogether.
- // TODO: Remove this override when Path.approximate() is supported.
- return new FakeAnimator();
- }
-
- @LayoutlibDelegate
- /*package*/ static ValueAnimator loadAnimator(Resources res, Theme theme,
- AttributeSet attrs, ValueAnimator anim, float pathErrorScale)
- throws NotFoundException {
- return AnimatorInflater.loadAnimator_Original(res, theme, attrs, anim, pathErrorScale);
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java
index 163fbcb..0e39243 100644
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java
@@ -18,6 +18,7 @@ package android.content.res;
import com.android.SdkConstants;
import com.android.ide.common.rendering.api.ArrayResourceValue;
+import com.android.ide.common.rendering.api.DensityBasedResourceValue;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.LayoutlibCallback;
import com.android.ide.common.rendering.api.ResourceValue;
@@ -48,9 +49,6 @@ import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.Iterator;
-/**
- *
- */
public final class BridgeResources extends Resources {
private BridgeContext mContext;
@@ -278,7 +276,7 @@ public final class BridgeResources extends Resources {
* always Strings. The ideal signature for the method should be &lt;T super String&gt;, but java
* generics don't support it.
*/
- private <T extends CharSequence> T[] fillValues(ArrayResourceValue resValue, T[] values) {
+ <T extends CharSequence> T[] fillValues(ArrayResourceValue resValue, T[] values) {
int i = 0;
for (Iterator<String> iterator = resValue.iterator(); iterator.hasNext(); i++) {
@SuppressWarnings("unchecked")
@@ -404,7 +402,7 @@ public final class BridgeResources extends Resources {
if (xml.isFile()) {
// we need to create a pull parser around the layout XML file, and then
// give that to our XmlBlockParser
- parser = ParserFactory.create(xml);
+ parser = ParserFactory.create(xml, true);
}
}
@@ -664,13 +662,18 @@ public final class BridgeResources extends Resources {
Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
if (value != null) {
- String v = value.getSecond().getValue();
+ ResourceValue resVal = value.getSecond();
+ String v = resVal.getValue();
if (v != null) {
if (ResourceHelper.parseFloatAttribute(value.getFirst(), v, outValue,
false /*requireUnit*/)) {
return;
}
+ if (resVal instanceof DensityBasedResourceValue) {
+ outValue.density =
+ ((DensityBasedResourceValue) resVal).getResourceDensity().getDpiValue();
+ }
// else it's a string
outValue.type = TypedValue.TYPE_STRING;
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
index 6a61090..31dd3d9 100644
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
@@ -16,6 +16,7 @@
package android.content.res;
+import com.android.ide.common.rendering.api.ArrayResourceValue;
import com.android.ide.common.rendering.api.AttrResourceValue;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.RenderResources;
@@ -33,6 +34,7 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.annotation.Nullable;
+import android.content.res.Resources.NotFoundException;
import android.content.res.Resources.Theme;
import android.graphics.drawable.Drawable;
import android.util.DisplayMetrics;
@@ -740,12 +742,20 @@ public final class BridgeTypedArray extends TypedArray {
*/
@Override
public CharSequence[] getTextArray(int index) {
- String value = getString(index);
- if (value != null) {
- return new CharSequence[] { value };
+ if (!hasValue(index)) {
+ return null;
}
-
- return null;
+ ResourceValue resVal = mResourceData[index];
+ if (resVal instanceof ArrayResourceValue) {
+ ArrayResourceValue array = (ArrayResourceValue) resVal;
+ int count = array.getElementCount();
+ return count >= 0 ? mBridgeResources.fillValues(array, new CharSequence[count]) : null;
+ }
+ int id = getResourceId(index, 0);
+ String resIdMessage = id > 0 ? " (resource id 0x" + Integer.toHexString(id) + ')' : "";
+ throw new NotFoundException(
+ String.format("%1$s in %2$s%3$s is not a valid array resource.",
+ resVal.getValue(), mNames[index], resIdMessage));
}
@Override
diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
index d858953..60514b6 100644
--- a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
@@ -59,6 +59,7 @@ import java.util.Set;
if (opts.inPremultiplied) {
bitmapCreateFlags.add(BitmapCreateFlags.PREMULTIPLIED);
}
+ opts.inScaled = false;
}
try {
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
index f8b3739..64cd503 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
@@ -35,6 +35,8 @@ import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
@@ -707,6 +709,12 @@ public final class Canvas_Delegate {
@Override
public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
Shape shape = pathDelegate.getJavaShape();
+ Rectangle2D bounds = shape.getBounds2D();
+ if (bounds.isEmpty()) {
+ // Apple JRE 1.6 doesn't like drawing empty shapes.
+ // http://b.android.com/178278
+ return;
+ }
int style = paintDelegate.getStyle();
if (style == Paint.Style.FILL.nativeInt ||
diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
index 857e6d0..c7b24bc 100644
--- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
@@ -178,7 +178,9 @@ public class FontFamily_Delegate {
desiredStyle.mIsItalic = isItalic;
FontInfo bestFont = null;
int bestMatch = Integer.MAX_VALUE;
- for (FontInfo font : mFonts) {
+ //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;
@@ -415,7 +417,9 @@ public class FontFamily_Delegate {
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.
- for (FontInfo font : mFonts) {
+ //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;
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
index 65b65ec..a545283 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
@@ -480,8 +480,10 @@ public class Paint_Delegate {
return;
}
- delegate.mTextSize = textSize;
- delegate.updateFontObject();
+ if (delegate.mTextSize != textSize) {
+ delegate.mTextSize = textSize;
+ delegate.updateFontObject();
+ }
}
@LayoutlibDelegate
@@ -503,8 +505,10 @@ public class Paint_Delegate {
return;
}
- delegate.mTextScaleX = scaleX;
- delegate.updateFontObject();
+ if (delegate.mTextScaleX != scaleX) {
+ delegate.mTextScaleX = scaleX;
+ delegate.updateFontObject();
+ }
}
@LayoutlibDelegate
@@ -526,8 +530,10 @@ public class Paint_Delegate {
return;
}
- delegate.mTextSkewX = skewX;
- delegate.updateFontObject();
+ if (delegate.mTextSkewX != skewX) {
+ delegate.mTextSkewX = skewX;
+ delegate.updateFontObject();
+ }
}
@LayoutlibDelegate
@@ -897,9 +903,12 @@ public class Paint_Delegate {
return 0;
}
- delegate.mTypeface = Typeface_Delegate.getDelegate(typeface);
- delegate.mNativeTypeface = typeface;
- delegate.updateFontObject();
+ 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();
+ }
return typeface;
}
@@ -1214,13 +1223,31 @@ public class Paint_Delegate {
mCap = paint.mCap;
mJoin = paint.mJoin;
mTextAlign = paint.mTextAlign;
- mTypeface = paint.mTypeface;
- mNativeTypeface = paint.mNativeTypeface;
+
+ boolean needsFontUpdate = false;
+ if (mTypeface != paint.mTypeface || mNativeTypeface != paint.mNativeTypeface) {
+ mTypeface = paint.mTypeface;
+ mNativeTypeface = paint.mNativeTypeface;
+ needsFontUpdate = true;
+ }
+
+ if (mTextSize != paint.mTextSize) {
+ mTextSize = paint.mTextSize;
+ needsFontUpdate = true;
+ }
+
+ if (mTextScaleX != paint.mTextScaleX) {
+ mTextScaleX = paint.mTextScaleX;
+ needsFontUpdate = true;
+ }
+
+ if (mTextSkewX != paint.mTextSkewX) {
+ mTextSkewX = paint.mTextSkewX;
+ needsFontUpdate = true;
+ }
+
mStrokeWidth = paint.mStrokeWidth;
mStrokeMiter = paint.mStrokeMiter;
- mTextSize = paint.mTextSize;
- mTextScaleX = paint.mTextScaleX;
- mTextSkewX = paint.mTextSkewX;
mXfermode = paint.mXfermode;
mColorFilter = paint.mColorFilter;
mShader = paint.mShader;
@@ -1228,7 +1255,10 @@ public class Paint_Delegate {
mMaskFilter = paint.mMaskFilter;
mRasterizer = paint.mRasterizer;
mHintingMode = paint.mHintingMode;
- updateFontObject();
+
+ if (needsFontUpdate) {
+ updateFontObject();
+ }
}
private void reset() {
@@ -1264,10 +1294,18 @@ public class Paint_Delegate {
// Get the fonts from the TypeFace object.
List<Font> fonts = mTypeface.getFonts(mFontVariant);
+ if (fonts.isEmpty()) {
+ mFonts = Collections.emptyList();
+ return;
+ }
+
// create new font objects as well as FontMetrics, based on the current text size
// and skew info.
- ArrayList<FontInfo> infoList = new ArrayList<FontInfo>(fonts.size());
- for (Font font : fonts) {
+ 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.
diff --git a/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java
new file mode 100644
index 0000000..dd2978f
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java
@@ -0,0 +1,210 @@
+/*
+ * 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.graphics;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
+
+/**
+ * Delegate implementing the native methods of {@link android.graphics.PathMeasure}
+ * <p/>
+ * Through the layoutlib_create tool, the original native methods of PathMeasure 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 PathMeasure class.
+ *
+ * @see DelegateManager
+ */
+public final class PathMeasure_Delegate {
+ // ---- delegate manager ----
+ private static final DelegateManager<PathMeasure_Delegate> sManager =
+ new DelegateManager<PathMeasure_Delegate>(PathMeasure_Delegate.class);
+
+ // ---- delegate data ----
+ // This governs how accurate the approximation of the Path is.
+ private static final float PRECISION = 0.002f;
+
+ /**
+ * Array containing the path points components. There are three components for each point:
+ * <ul>
+ * <li>Fraction along the length of the path that the point resides</li>
+ * <li>The x coordinate of the point</li>
+ * <li>The y coordinate of the point</li>
+ * </ul>
+ */
+ private float mPathPoints[];
+ private long mNativePath;
+
+ private PathMeasure_Delegate(long native_path, boolean forceClosed) {
+ mNativePath = native_path;
+ if (forceClosed && mNativePath != 0) {
+ // Copy the path and call close
+ mNativePath = Path_Delegate.init2(native_path);
+ Path_Delegate.native_close(mNativePath);
+ }
+
+ mPathPoints =
+ mNativePath != 0 ? Path_Delegate.native_approximate(mNativePath, PRECISION) : null;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long native_create(long native_path, boolean forceClosed) {
+ return sManager.addNewDelegate(new PathMeasure_Delegate(native_path, forceClosed));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_destroy(long native_instance) {
+ sManager.removeJavaReferenceFor(native_instance);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_getPosTan(long native_instance, float distance, float pos[],
+ float tan[]) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "PathMeasure.getPostTan is not supported.", null, null);
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_getMatrix(long native_instance, float distance, long
+ native_matrix, int flags) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "PathMeasure.getMatrix is not supported.", null, null);
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_nextContour(long native_instance) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "PathMeasure.nextContour is not supported.", null, null);
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_setPath(long native_instance, long native_path, boolean
+ forceClosed) {
+ PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
+ assert pathMeasure != null;
+
+ if (forceClosed && native_path != 0) {
+ // Copy the path and call close
+ native_path = Path_Delegate.init2(native_path);
+ Path_Delegate.native_close(native_path);
+ }
+ pathMeasure.mNativePath = native_path;
+ pathMeasure.mPathPoints = Path_Delegate.native_approximate(native_path, PRECISION);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float native_getLength(long native_instance) {
+ PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
+ assert pathMeasure != null;
+
+ if (pathMeasure.mPathPoints == null) {
+ return 0;
+ }
+
+ float length = 0;
+ int nPoints = pathMeasure.mPathPoints.length / 3;
+ for (int i = 1; i < nPoints; i++) {
+ length += Point2D.distance(
+ pathMeasure.mPathPoints[(i - 1) * 3 + 1],
+ pathMeasure.mPathPoints[(i - 1) * 3 + 2],
+ pathMeasure.mPathPoints[i*3 + 1],
+ pathMeasure.mPathPoints[i*3 + 2]);
+ }
+
+ return length;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_isClosed(long native_instance) {
+ PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
+ assert pathMeasure != null;
+
+ Path_Delegate path = Path_Delegate.getDelegate(pathMeasure.mNativePath);
+ if (path == null) {
+ return false;
+ }
+
+ PathIterator pathIterator = path.getJavaShape().getPathIterator(null);
+
+ int type = 0;
+ float segment[] = new float[6];
+ while (!pathIterator.isDone()) {
+ type = pathIterator.currentSegment(segment);
+ pathIterator.next();
+ }
+
+ // A path is a closed path if the last element is SEG_CLOSE
+ return type == PathIterator.SEG_CLOSE;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_getSegment(long native_instance, float startD, float stopD,
+ long native_dst_path, boolean startWithMoveTo) {
+ if (startD < 0) {
+ startD = 0;
+ }
+
+ if (startD >= stopD) {
+ return false;
+ }
+
+ PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
+ assert pathMeasure != null;
+
+ if (pathMeasure.mPathPoints == null) {
+ return false;
+ }
+
+ float accLength = 0;
+ boolean isZeroLength = true; // Whether the output has zero length or not
+ int nPoints = pathMeasure.mPathPoints.length / 3;
+ for (int i = 0; i < nPoints; i++) {
+ float x = pathMeasure.mPathPoints[i * 3 + 1];
+ float y = pathMeasure.mPathPoints[i * 3 + 2];
+ if (accLength >= startD && accLength <= stopD) {
+ if (startWithMoveTo) {
+ startWithMoveTo = false;
+ Path_Delegate.native_moveTo(native_dst_path, x, y);
+ } else {
+ isZeroLength = false;
+ Path_Delegate.native_lineTo(native_dst_path, x, y);
+ }
+ }
+
+ if (i > 0) {
+ accLength += Point2D.distance(
+ pathMeasure.mPathPoints[(i - 1) * 3 + 1],
+ pathMeasure.mPathPoints[(i - 1) * 3 + 2],
+ pathMeasure.mPathPoints[i * 3 + 1],
+ pathMeasure.mPathPoints[i * 3 + 2]);
+ }
+ }
+
+ return !isZeroLength;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
index 3c9a062..a2a53fe 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
@@ -36,6 +36,7 @@ import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
+import java.util.ArrayList;
/**
* Delegate implementing the native methods of android.graphics.Path
@@ -173,11 +174,8 @@ public final class Path_Delegate {
@LayoutlibDelegate
/*package*/ static boolean native_isEmpty(long nPath) {
Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- if (pathDelegate == null) {
- return true;
- }
+ return pathDelegate == null || pathDelegate.isEmpty();
- return pathDelegate.isEmpty();
}
@LayoutlibDelegate
@@ -488,54 +486,44 @@ public final class Path_Delegate {
@LayoutlibDelegate
/*package*/ static float[] native_approximate(long nPath, float error) {
- Bridge.getLog().warning(LayoutLog.TAG_UNSUPPORTED, "Path.approximate() not fully supported",
- null);
Path_Delegate pathDelegate = sManager.getDelegate(nPath);
if (pathDelegate == null) {
return null;
}
- PathIterator pathIterator = pathDelegate.mPath.getPathIterator(null);
- float[] tmp = new float[6];
- float[] coords = new float[6];
- boolean isFirstPoint = true;
- while (!pathIterator.isDone()) {
- int type = pathIterator.currentSegment(tmp);
- switch (type) {
- case PathIterator.SEG_MOVETO:
- case PathIterator.SEG_LINETO:
- store(tmp, coords, 1, isFirstPoint);
- break;
- case PathIterator.SEG_QUADTO:
- store(tmp, coords, 2, isFirstPoint);
- break;
- case PathIterator.SEG_CUBICTO:
- store(tmp, coords, 3, isFirstPoint);
- break;
- case PathIterator.SEG_CLOSE:
- // No points returned.
+ // Get a FlatteningIterator
+ PathIterator iterator = pathDelegate.getJavaShape().getPathIterator(null, error);
+
+ float segment[] = new float[6];
+ float totalLength = 0;
+ ArrayList<Point2D.Float> points = new ArrayList<Point2D.Float>();
+ Point2D.Float previousPoint = null;
+ while (!iterator.isDone()) {
+ int type = iterator.currentSegment(segment);
+ Point2D.Float currentPoint = new Point2D.Float(segment[0], segment[1]);
+ // MoveTo shouldn't affect the length
+ if (previousPoint != null && type != PathIterator.SEG_MOVETO) {
+ totalLength += currentPoint.distance(previousPoint);
}
- isFirstPoint = false;
- pathIterator.next();
+ previousPoint = currentPoint;
+ points.add(currentPoint);
+ iterator.next();
}
- if (isFirstPoint) {
- // No points found
- return new float[0];
- } else {
- return coords;
- }
- }
- private static void store(float[] src, float[] dst, int count, boolean isFirst) {
- if (isFirst) {
- dst[0] = 0; // fraction
- dst[1] = src[0]; // abscissa
- dst[2] = src[1]; // ordinate
- }
- if (count > 1 || !isFirst) {
- dst[3] = 1;
- dst[4] = src[2 * count - 2];
- dst[5] = src[2 * count - 1];
+ int nPoints = points.size();
+ float[] result = new float[nPoints * 3];
+ previousPoint = null;
+ for (int i = 0; i < nPoints; i++) {
+ Point2D.Float point = points.get(i);
+ float distance = previousPoint != null ? (float) previousPoint.distance(point) : .0f;
+ result[i * 3] = distance / totalLength;
+ result[i * 3 + 1] = point.x;
+ result[i * 3 + 2] = point.y;
+
+ totalLength += distance;
+ previousPoint = point;
}
+
+ return result;
}
// ---- Private helper methods ----
@@ -735,6 +723,9 @@ public final class Path_Delegate {
*/
private void cubicTo(float x1, float y1, float x2, float y2,
float x3, float y3) {
+ if (isEmpty()) {
+ mPath.moveTo(0, 0);
+ }
mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3);
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/GradientDrawable_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/GradientDrawable_Delegate.java
new file mode 100644
index 0000000..a3ad2aa
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/drawable/GradientDrawable_Delegate.java
@@ -0,0 +1,73 @@
+/*
+ * 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.graphics.drawable;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.graphics.Path;
+import android.graphics.drawable.GradientDrawable.GradientState;
+
+import java.lang.reflect.Field;
+
+/**
+ * Delegate implementing the native methods of {@link GradientDrawable}
+ *
+ * Through the layoutlib_create tool, the original native methods of GradientDrawable have been
+ * replaced by calls to methods of the same name in this delegate class.
+ */
+public class GradientDrawable_Delegate {
+
+ /**
+ * The ring can be built either by drawing full circles, or by drawing arcs in case the
+ * circle isn't complete. LayoutLib cannot handle drawing full circles (requires path
+ * subtraction). So, if we need to draw full circles, we switch to drawing 99% circle.
+ */
+ @LayoutlibDelegate
+ /*package*/ static Path buildRing(GradientDrawable thisDrawable, GradientState st) {
+ boolean useLevel = st.mUseLevelForShape;
+ int level = thisDrawable.getLevel();
+ // 10000 is the max level. See android.graphics.drawable.Drawable#getLevel()
+ float sweep = useLevel ? (360.0f * level / 10000.0f) : 360f;
+ Field mLevel = null;
+ if (sweep >= 360 || sweep <= -360) {
+ st.mUseLevelForShape = true;
+ // Use reflection to set the value of the field to prevent setting the drawable to
+ // dirty again.
+ try {
+ mLevel = Drawable.class.getDeclaredField("mLevel");
+ mLevel.setAccessible(true);
+ mLevel.setInt(thisDrawable, 9999); // set to one less than max.
+ } catch (NoSuchFieldException e) {
+ // The field has been removed in a recent framework change. Fall back to old
+ // buggy behaviour.
+ } catch (IllegalAccessException e) {
+ // We've already set the field to be accessible.
+ assert false;
+ }
+ }
+ Path path = thisDrawable.buildRing_Original(st);
+ st.mUseLevelForShape = useLevel;
+ if (mLevel != null) {
+ try {
+ mLevel.setInt(thisDrawable, level);
+ } catch (IllegalAccessException e) {
+ assert false;
+ }
+ }
+ return path;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/preference/Preference_Delegate.java b/tools/layoutlib/bridge/src/android/preference/Preference_Delegate.java
index 49ee642..2e44a77 100644
--- a/tools/layoutlib/bridge/src/android/preference/Preference_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/preference/Preference_Delegate.java
@@ -29,9 +29,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
-import java.util.HashMap;
-import java.util.Map;
-
/**
* Delegate that provides implementation for native methods in {@link Preference}
* <p/>
@@ -59,9 +56,9 @@ public class Preference_Delegate {
*/
public static View inflatePreference(Context context, XmlPullParser parser, ViewGroup root) {
PreferenceManager pm = new PreferenceManager(context);
- PreferenceScreen ps = pm.getPreferenceScreen();
PreferenceInflater inflater = new BridgePreferenceInflater(context, pm);
- ps = (PreferenceScreen) inflater.inflate(parser, ps, true);
+ PreferenceScreen ps = (PreferenceScreen) inflater.inflate(parser, null, true);
+ pm.setPreferences(ps);
ListView preferenceView = createContainerView(context, root);
ps.bind(preferenceView);
return preferenceView;
diff --git a/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java b/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java
index 5a59597..44ce731 100644
--- a/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java
@@ -20,9 +20,10 @@ 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.StaticLayout}
+ * Delegate that overrides implementation for certain methods in {@link android.text.Hyphenator}
* <p/>
* Through the layoutlib_create tool, selected methods of StaticLayout have been replaced
* by calls to methods of the same name in this delegate class.
@@ -38,7 +39,7 @@ public class Hyphenator_Delegate {
return null;
}
- /*package*/ static long loadHyphenator(String patternData) {
+ /*package*/ static long loadHyphenator(ByteBuffer buf, int offset) {
return sDelegateManager.addNewDelegate(new Hyphenator_Delegate());
}
}
diff --git a/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java b/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java
index 1b0ba51..65c0a07 100644
--- a/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java
@@ -13,6 +13,7 @@ import android.icu.util.ULocale;
import android.text.Primitive.PrimitiveType;
import android.text.StaticLayout.LineBreaks;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -52,8 +53,8 @@ public class StaticLayout_Delegate {
}
@LayoutlibDelegate
- /*package*/ static long nLoadHyphenator(String patternData) {
- return Hyphenator_Delegate.loadHyphenator(patternData);
+ /*package*/ static long nLoadHyphenator(ByteBuffer buf, int offset) {
+ return Hyphenator_Delegate.loadHyphenator(buf, offset);
}
@LayoutlibDelegate
diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
index 1e33e3a..723e827 100644
--- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
+++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
@@ -23,6 +23,7 @@ import com.android.ide.common.rendering.api.ResourceReference;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.BridgeConstants;
+import com.android.layoutlib.bridge.MockView;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
import com.android.layoutlib.bridge.android.support.DrawerLayoutUtil;
@@ -36,6 +37,7 @@ import org.xmlpull.v1.XmlPullParser;
import android.annotation.NonNull;
import android.content.Context;
+import android.content.res.TypedArray;
import android.util.AttributeSet;
import java.io.File;
@@ -54,6 +56,9 @@ public final class BridgeInflater extends LayoutInflater {
private ResourceReference mResourceReference;
private Map<View, String> mOpenDrawerLayouts;
+ // Keep in sync with the same value in LayoutInflater.
+ private static final int[] ATTRS_THEME = new int[] {com.android.internal.R.attr.theme };
+
/**
* List of class prefixes which are tried first by default.
* <p/>
@@ -122,6 +127,9 @@ public final class BridgeInflater extends LayoutInflater {
if (view == null) {
view = loadCustomView(name, attrs);
}
+ } catch (InflateException e) {
+ // Don't catch the InflateException below as that results in hiding the real cause.
+ throw e;
} catch (Exception e) {
// Wrap the real exception in a ClassNotFoundException, so that the calling method
// can deal with it.
@@ -135,24 +143,45 @@ public final class BridgeInflater extends LayoutInflater {
@Override
public View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
- boolean ignoreThemeAttrs) {
+ boolean ignoreThemeAttr) {
View view;
try {
- view = super.createViewFromTag(parent, name, context, attrs, ignoreThemeAttrs);
+ view = super.createViewFromTag(parent, name, context, attrs, ignoreThemeAttr);
} catch (InflateException e) {
- // try to load the class from using the custom view loader
- try {
- view = loadCustomView(name, attrs);
- } catch (Exception e2) {
- // Wrap the real exception in an InflateException so that the calling
- // method can deal with it.
- InflateException exception = new InflateException();
- if (!e2.getClass().equals(ClassNotFoundException.class)) {
- exception.initCause(e2);
- } else {
- exception.initCause(e);
+ // Creation of ContextThemeWrapper code is same as in the super method.
+ // Apply a theme wrapper, if allowed and one is specified.
+ if (!ignoreThemeAttr) {
+ final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
+ final int themeResId = ta.getResourceId(0, 0);
+ if (themeResId != 0) {
+ context = new ContextThemeWrapper(context, themeResId);
+ }
+ ta.recycle();
+ }
+ if (!(e.getCause() instanceof ClassNotFoundException)) {
+ // There is some unknown inflation exception in inflating a View that was found.
+ view = new MockView(context, attrs);
+ ((MockView) view).setText(name);
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN, e.getMessage(), e, null);
+ } else {
+ final Object lastContext = mConstructorArgs[0];
+ mConstructorArgs[0] = context;
+ // try to load the class from using the custom view loader
+ try {
+ view = loadCustomView(name, attrs);
+ } catch (Exception e2) {
+ // Wrap the real exception in an InflateException so that the calling
+ // method can deal with it.
+ InflateException exception = new InflateException();
+ if (!e2.getClass().equals(ClassNotFoundException.class)) {
+ exception.initCause(e2);
+ } else {
+ exception.initCause(e);
+ }
+ throw exception;
+ } finally {
+ mConstructorArgs[0] = lastContext;
}
- throw exception;
}
}
@@ -188,7 +217,7 @@ public final class BridgeInflater extends LayoutInflater {
File f = new File(value.getValue());
if (f.isFile()) {
try {
- XmlPullParser parser = ParserFactory.create(f);
+ XmlPullParser parser = ParserFactory.create(f, true);
BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(
parser, bridgeContext, value.isFramework());
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
index 44a9aad..d392f21 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
@@ -17,39 +17,90 @@
package com.android.layoutlib.bridge;
import android.content.Context;
-import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
import android.widget.TextView;
/**
* Base class for mocked views.
- *
- * TODO: implement onDraw and draw a rectangle in a random color with the name of the class
- * (or better the id of the view).
+ * <p/>
+ * FrameLayout with a single TextView. Doesn't allow adding any other views to itself.
*/
-public class MockView extends TextView {
+public class MockView extends FrameLayout {
+
+ private final TextView mView;
+
+ public MockView(Context context) {
+ this(context, null);
+ }
public MockView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
- public MockView(Context context, AttributeSet attrs, int defStyle) {
- this(context, attrs, defStyle, 0);
+ public MockView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
}
public MockView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
-
- setText(this.getClass().getSimpleName());
- setTextColor(0xFF000000);
+ mView = new TextView(context, attrs);
+ mView.setTextColor(0xFF000000);
setGravity(Gravity.CENTER);
+ setText(getClass().getSimpleName());
+ addView(mView);
+ setBackgroundColor(0xFF7F7F7F);
+ }
+
+ // Only allow adding one TextView.
+ @Override
+ public void addView(View child) {
+ if (child == mView) {
+ super.addView(child);
+ }
+ }
+
+ @Override
+ public void addView(View child, int index) {
+ if (child == mView) {
+ super.addView(child, index);
+ }
}
@Override
- public void onDraw(Canvas canvas) {
- canvas.drawARGB(0xFF, 0x7F, 0x7F, 0x7F);
+ public void addView(View child, int width, int height) {
+ if (child == mView) {
+ super.addView(child, width, height);
+ }
+ }
+
+ @Override
+ public void addView(View child, ViewGroup.LayoutParams params) {
+ if (child == mView) {
+ super.addView(child, params);
+ }
+ }
+
+ @Override
+ public void addView(View child, int index, ViewGroup.LayoutParams params) {
+ if (child == mView) {
+ super.addView(child, index, params);
+ }
+ }
+
+ // The following methods are called by the IDE via reflection, and should be considered part
+ // of the API.
+ // Historically, MockView used to be a textView and had these methods. Now, we simply delegate
+ // them to the contained textView.
+
+ public void setText(CharSequence text) {
+ mView.setText(text);
+ }
- super.onDraw(canvas);
+ public void setGravity(int gravity) {
+ mView.setGravity(gravity);
}
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 6366424..2b83675 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -409,7 +409,7 @@ public final class BridgeContext extends Context {
pushParser(blockParser);
return Pair.of(
mBridgeInflater.inflate(blockParser, parent, attachToRoot),
- true);
+ Boolean.TRUE);
} finally {
popParser();
}
@@ -436,7 +436,7 @@ public final class BridgeContext extends Context {
// we need to create a pull parser around the layout XML file, and then
// give that to our XmlBlockParser
try {
- XmlPullParser parser = ParserFactory.create(xml);
+ XmlPullParser parser = ParserFactory.create(xml, true);
// set the resource ref to have correct view cookies
mBridgeInflater.setResourceReference(resource);
@@ -447,7 +447,7 @@ public final class BridgeContext extends Context {
pushParser(blockParser);
return Pair.of(
mBridgeInflater.inflate(blockParser, parent, attachToRoot),
- false);
+ Boolean.FALSE);
} finally {
popParser();
}
@@ -470,7 +470,7 @@ public final class BridgeContext extends Context {
resource.getName()), null);
}
- return Pair.of(null, false);
+ return Pair.of(null, Boolean.FALSE);
}
@SuppressWarnings("deprecation")
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
index 868c6d3..cdcf0ea 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
@@ -16,10 +16,13 @@
package com.android.layoutlib.bridge.bars;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.LayoutlibCallback;
import com.android.ide.common.rendering.api.RenderResources;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.SessionParams;
import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.impl.ResourceHelper;
import com.android.resources.ResourceType;
@@ -45,6 +48,8 @@ public class AppCompatActionBar extends BridgeActionBar {
private Object mWindowDecorActionBar;
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;
/**
@@ -70,14 +75,25 @@ public class AppCompatActionBar extends BridgeActionBar {
try {
Class[] constructorParams = {View.class};
Object[] constructorArgs = {getDecorContent()};
- mWindowDecorActionBar = params.getLayoutlibCallback().loadView(WINDOW_ACTION_BAR_CLASS,
- constructorParams, constructorArgs);
+ LayoutlibCallback callback = params.getLayoutlibCallback();
+
+ // Check if the old action bar class is present.
+ String actionBarClass = WINDOW_ACTION_BAR_CLASS;
+ try {
+ callback.findClass(actionBarClass);
+ } catch (ClassNotFoundException expected) {
+ // Failed to find the old class, use the newer one.
+ actionBarClass = WINDOW_ACTION_BAR_CLASS_NEW;
+ }
+ mWindowDecorActionBar = callback.loadView(actionBarClass,
+ constructorParams, constructorArgs);
mWindowActionBarClass = mWindowDecorActionBar == null ? null :
mWindowDecorActionBar.getClass();
setupActionBar();
} catch (Exception e) {
- e.printStackTrace();
+ Bridge.getLog().warning(LayoutLog.TAG_BROKEN,
+ "Failed to load AppCompat ActionBar with unknown error.", e);
}
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
index b76ec17..a6e5fb8 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
@@ -116,11 +116,11 @@ abstract class CustomBar extends LinearLayout {
density = iconLoader.getDensity();
String path = iconLoader.getPath();
// look for a cached bitmap
- Bitmap bitmap = Bridge.getCachedBitmap(path, true /*isFramework*/);
+ Bitmap bitmap = Bridge.getCachedBitmap(path, Boolean.TRUE /*isFramework*/);
if (bitmap == null) {
try {
bitmap = Bitmap_Delegate.createBitmap(stream, false /*isMutable*/, density);
- Bridge.setCachedBitmap(path, bitmap, true /*isFramework*/);
+ Bridge.setCachedBitmap(path, bitmap, Boolean.TRUE /*isFramework*/);
} catch (IOException e) {
return;
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutParserWrapper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutParserWrapper.java
new file mode 100644
index 0000000..71e7fd2
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutParserWrapper.java
@@ -0,0 +1,377 @@
+/*
+ * 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 com.android.layoutlib.bridge.impl;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.annotation.Nullable;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A wrapper around XmlPullParser that can peek forward to inspect if the file is a data-binding
+ * layout and some parts need to be stripped.
+ */
+public class LayoutParserWrapper implements XmlPullParser {
+
+ // Data binding constants.
+ private static final String TAG_LAYOUT = "layout";
+ private static final String TAG_DATA = "data";
+ private static final String DEFAULT = "default=";
+
+ private final XmlPullParser mDelegate;
+
+ // Storage for peeked values.
+ private boolean mPeeked;
+ private int mEventType;
+ private int mDepth;
+ private int mNext;
+ private List<Attribute> mAttributes;
+ private String mText;
+ private String mName;
+
+ // Used to end the document before the actual parser ends.
+ private int mFinalDepth = -1;
+ private boolean mEndNow;
+
+ public LayoutParserWrapper(XmlPullParser delegate) {
+ mDelegate = delegate;
+ }
+
+ public LayoutParserWrapper peekTillLayoutStart() throws IOException, XmlPullParserException {
+ final int STATE_LAYOUT_NOT_STARTED = 0; // <layout> tag not encountered yet.
+ final int STATE_ROOT_NOT_STARTED = 1; // the main view root not found yet.
+ final int STATE_INSIDE_DATA = 2; // START_TAG for <data> found, but not END_TAG.
+
+ int state = STATE_LAYOUT_NOT_STARTED;
+ int dataDepth = -1; // depth of the <data> tag. Should be two.
+ while (true) {
+ int peekNext = peekNext();
+ switch (peekNext) {
+ case START_TAG:
+ if (state == STATE_LAYOUT_NOT_STARTED) {
+ if (mName.equals(TAG_LAYOUT)) {
+ state = STATE_ROOT_NOT_STARTED;
+ } else {
+ return this; // no layout tag in the file.
+ }
+ } else if (state == STATE_ROOT_NOT_STARTED) {
+ if (mName.equals(TAG_DATA)) {
+ state = STATE_INSIDE_DATA;
+ dataDepth = mDepth;
+ } else {
+ mFinalDepth = mDepth;
+ return this;
+ }
+ }
+ break;
+ case END_TAG:
+ if (state == STATE_INSIDE_DATA) {
+ if (mDepth <= dataDepth) {
+ state = STATE_ROOT_NOT_STARTED;
+ }
+ }
+ break;
+ case END_DOCUMENT:
+ // No layout start found.
+ return this;
+ }
+ // consume the peeked tag.
+ next();
+ }
+ }
+
+ private int peekNext() throws IOException, XmlPullParserException {
+ if (mPeeked) {
+ return mNext;
+ }
+ mEventType = mDelegate.getEventType();
+ mNext = mDelegate.next();
+ if (mEventType == START_TAG) {
+ int count = mDelegate.getAttributeCount();
+ mAttributes = count > 0 ? new ArrayList<Attribute>(count) :
+ Collections.<Attribute>emptyList();
+ for (int i = 0; i < count; i++) {
+ mAttributes.add(new Attribute(mDelegate.getAttributeNamespace(i),
+ mDelegate.getAttributeName(i), mDelegate.getAttributeValue(i)));
+ }
+ }
+ mDepth = mDelegate.getDepth();
+ mText = mDelegate.getText();
+ mName = mDelegate.getName();
+ mPeeked = true;
+ return mNext;
+ }
+
+ private void reset() {
+ mAttributes = null;
+ mText = null;
+ mName = null;
+ mPeeked = false;
+ }
+
+ @Override
+ public int next() throws XmlPullParserException, IOException {
+ int returnValue;
+ int depth;
+ if (mPeeked) {
+ returnValue = mNext;
+ depth = mDepth;
+ reset();
+ } else if (mEndNow) {
+ return END_DOCUMENT;
+ } else {
+ returnValue = mDelegate.next();
+ depth = getDepth();
+ }
+ if (returnValue == END_TAG && depth <= mFinalDepth) {
+ mEndNow = true;
+ }
+ return returnValue;
+ }
+
+ @Override
+ public int getEventType() throws XmlPullParserException {
+ return mPeeked ? mEventType : mDelegate.getEventType();
+ }
+
+ @Override
+ public int getDepth() {
+ return mPeeked ? mDepth : mDelegate.getDepth();
+ }
+
+ @Override
+ public String getName() {
+ return mPeeked ? mName : mDelegate.getName();
+ }
+
+ @Override
+ public String getText() {
+ return mPeeked ? mText : mDelegate.getText();
+ }
+
+ @Override
+ public String getAttributeValue(@Nullable String namespace, String name) {
+ String returnValue = null;
+ if (mPeeked) {
+ if (mAttributes == null) {
+ if (mEventType != START_TAG) {
+ throw new IndexOutOfBoundsException("getAttributeValue() called when not at START_TAG.");
+ } else {
+ return null;
+ }
+ } else {
+ for (Attribute attribute : mAttributes) {
+ //noinspection StringEquality for nullness check.
+ if (attribute.name.equals(name) && (attribute.namespace == namespace ||
+ attribute.namespace != null && attribute.namespace.equals(namespace))) {
+ returnValue = attribute.value;
+ break;
+ }
+ }
+ }
+ } else {
+ returnValue = mDelegate.getAttributeValue(namespace, name);
+ }
+ // Check if the value is bound via data-binding, if yes get the default value.
+ if (returnValue != null && mFinalDepth >= 0 && returnValue.startsWith("@{")) {
+ // TODO: Improve the detection of default keyword.
+ int i = returnValue.lastIndexOf(DEFAULT);
+ return i > 0 ? returnValue.substring(i + DEFAULT.length(), returnValue.length() - 1)
+ : null;
+ }
+ return returnValue;
+ }
+
+ private static class Attribute {
+ @Nullable
+ public final String namespace;
+ public final String name;
+ public final String value;
+
+ public Attribute(@Nullable String namespace, String name, String value) {
+ this.namespace = namespace;
+ this.name = name;
+ this.value = value;
+ }
+ }
+
+ // Not affected by peeking.
+
+ @Override
+ public void setFeature(String s, boolean b) throws XmlPullParserException {
+ mDelegate.setFeature(s, b);
+ }
+
+ @Override
+ public void setProperty(String s, Object o) throws XmlPullParserException {
+ mDelegate.setProperty(s, o);
+ }
+
+ @Override
+ public void setInput(InputStream inputStream, String s) throws XmlPullParserException {
+ mDelegate.setInput(inputStream, s);
+ }
+
+ @Override
+ public void setInput(Reader reader) throws XmlPullParserException {
+ mDelegate.setInput(reader);
+ }
+
+ @Override
+ public String getInputEncoding() {
+ return mDelegate.getInputEncoding();
+ }
+
+ @Override
+ public String getNamespace(String s) {
+ return mDelegate.getNamespace(s);
+ }
+
+ @Override
+ public String getPositionDescription() {
+ return mDelegate.getPositionDescription();
+ }
+
+ @Override
+ public int getLineNumber() {
+ return mDelegate.getLineNumber();
+ }
+
+ @Override
+ public String getNamespace() {
+ return mDelegate.getNamespace();
+ }
+
+ @Override
+ public int getColumnNumber() {
+ return mDelegate.getColumnNumber();
+ }
+
+ // -- We don't care much about the methods that follow.
+
+ @Override
+ public void require(int i, String s, String s1) throws XmlPullParserException, IOException {
+ throw new UnsupportedOperationException("Only few parser methods are supported.");
+ }
+
+ @Override
+ public boolean getFeature(String s) {
+ throw new UnsupportedOperationException("Only few parser methods are supported.");
+ }
+
+ @Override
+ public void defineEntityReplacementText(String s, String s1) throws XmlPullParserException {
+ throw new UnsupportedOperationException("Only few parser methods are supported.");
+ }
+
+ @Override
+ public Object getProperty(String s) {
+ throw new UnsupportedOperationException("Only few parser methods are supported.");
+ }
+
+ @Override
+ public int nextToken() throws XmlPullParserException, IOException {
+ throw new UnsupportedOperationException("Only few parser methods are supported.");
+ }
+
+ @Override
+ public int getNamespaceCount(int i) throws XmlPullParserException {
+ throw new UnsupportedOperationException("Only few parser methods are supported.");
+ }
+
+ @Override
+ public String getNamespacePrefix(int i) throws XmlPullParserException {
+ throw new UnsupportedOperationException("Only few parser methods are supported.");
+ }
+
+ @Override
+ public String getNamespaceUri(int i) throws XmlPullParserException {
+ throw new UnsupportedOperationException("Only few parser methods are supported.");
+ }
+
+ @Override
+ public boolean isWhitespace() throws XmlPullParserException {
+ throw new UnsupportedOperationException("Only few parser methods are supported.");
+ }
+
+ @Override
+ public char[] getTextCharacters(int[] ints) {
+ throw new UnsupportedOperationException("Only few parser methods are supported.");
+ }
+
+ @Override
+ public String getPrefix() {
+ throw new UnsupportedOperationException("Only few parser methods are supported.");
+ }
+
+ @Override
+ public boolean isEmptyElementTag() throws XmlPullParserException {
+ throw new UnsupportedOperationException("Only few parser methods are supported.");
+ }
+
+ @Override
+ public int getAttributeCount() {
+ throw new UnsupportedOperationException("Only few parser methods are supported.");
+ }
+
+ @Override
+ public String getAttributeNamespace(int i) {
+ throw new UnsupportedOperationException("Only few parser methods are supported.");
+ }
+
+ @Override
+ public String getAttributeName(int i) {
+ throw new UnsupportedOperationException("Only few parser methods are supported.");
+ }
+
+ @Override
+ public String getAttributePrefix(int i) {
+ throw new UnsupportedOperationException("Only few parser methods are supported.");
+ }
+
+ @Override
+ public String getAttributeType(int i) {
+ throw new UnsupportedOperationException("Only few parser methods are supported.");
+ }
+
+ @Override
+ public boolean isAttributeDefault(int i) {
+ throw new UnsupportedOperationException("Only few parser methods are supported.");
+ }
+
+ @Override
+ public String getAttributeValue(int i) {
+ throw new UnsupportedOperationException("Only few parser methods are supported.");
+ }
+
+ @Override
+ public String nextText() throws XmlPullParserException, IOException {
+ throw new UnsupportedOperationException("Only few parser methods are supported.");
+ }
+
+ @Override
+ public int nextTag() throws XmlPullParserException, IOException {
+ throw new UnsupportedOperationException("Only few parser methods are supported.");
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java
index 6e67f59..e273b2c 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java
@@ -53,24 +53,35 @@ public class ParserFactory {
@NonNull
public static XmlPullParser create(@NonNull File f)
throws XmlPullParserException, FileNotFoundException {
- InputStream stream = new FileInputStream(f);
- return create(stream, f.getName(), f.length());
+ return create(f, false);
}
+ public static XmlPullParser create(@NonNull File f, boolean isLayout)
+ throws XmlPullParserException, FileNotFoundException {
+ InputStream stream = new FileInputStream(f);
+ return create(stream, f.getName(), f.length(), isLayout);
+ }
@NonNull
public static XmlPullParser create(@NonNull InputStream stream, @Nullable String name)
throws XmlPullParserException {
- return create(stream, name, -1);
+ return create(stream, name, -1, false);
}
@NonNull
private static XmlPullParser create(@NonNull InputStream stream, @Nullable String name,
- long size) throws XmlPullParserException {
+ long size, boolean isLayout) throws XmlPullParserException {
XmlPullParser parser = instantiateParser(name);
stream = readAndClose(stream, name, size);
parser.setInput(stream, ENCODING);
+ if (isLayout) {
+ try {
+ return new LayoutParserWrapper(parser).peekTillLayoutStart();
+ } catch (IOException e) {
+ throw new XmlPullParserException(null, parser, e);
+ }
+ }
return parser;
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java
index 9588035..80d7c68 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java
@@ -21,6 +21,7 @@ import com.android.layoutlib.bridge.Bridge;
import android.graphics.BlendComposite;
import android.graphics.BlendComposite.BlendingMode;
+import android.graphics.PorterDuff;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffColorFilter_Delegate;
import android.graphics.PorterDuffXfermode_Delegate;
@@ -34,6 +35,8 @@ import java.awt.Composite;
*/
public final class PorterDuffUtility {
+ private static final int MODES_COUNT = Mode.values().length;
+
// Make the class non-instantiable.
private PorterDuffUtility() {
}
@@ -43,12 +46,11 @@ public final class PorterDuffUtility {
* {@link Mode#SRC_OVER} for invalid modes.
*/
public static Mode getPorterDuffMode(int porterDuffMode) {
- Mode[] values = Mode.values();
- if (porterDuffMode >= 0 && porterDuffMode < values.length) {
- return values[porterDuffMode];
+ if (porterDuffMode >= 0 && porterDuffMode < MODES_COUNT) {
+ return PorterDuff.intToMode(porterDuffMode);
}
Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- String.format("Unknown PorterDuff.Mode: %1$d", porterDuffMode), null /*data*/);
+ String.format("Unknown PorterDuff.Mode: %1$d", porterDuffMode), null);
assert false;
return Mode.SRC_OVER;
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index ac7c409..0ffa357 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -1051,11 +1051,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
}
if (scrollPos != 0) {
view.scrollBy(0, scrollPos);
- } else {
- view.scrollBy(0, scrollPos);
}
- } else {
- view.scrollBy(0, scrollPos);
}
if (!(view instanceof ViewGroup)) {
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/impl/LayoutParserWrapperTest.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/impl/LayoutParserWrapperTest.java
new file mode 100644
index 0000000..2c33862
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/impl/LayoutParserWrapperTest.java
@@ -0,0 +1,183 @@
+/*
+ * 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 com.android.layoutlib.bridge.impl;
+
+import org.junit.Test;
+import org.kxml2.io.KXmlParser;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.StringReader;
+
+import static com.android.SdkConstants.NS_RESOURCES;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.END_TAG;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
+
+public class LayoutParserWrapperTest {
+ @Test
+ @SuppressWarnings("StatementWithEmptyBody") // some for loops need to be empty statements.
+ public void testDataBindingLayout() throws Exception {
+ LayoutParserWrapper parser = getParserFromString(sDataBindingLayout);
+ parser.peekTillLayoutStart();
+ assertEquals("Expected START_TAG", START_TAG, parser.next());
+ assertEquals("RelativeLayout", parser.getName());
+ for (int next = parser.next(); next != START_TAG && next != END_DOCUMENT;
+ next = parser.next());
+ assertEquals("Expected START_TAG", START_TAG, parser.getEventType());
+ assertEquals("TextView", parser.getName());
+ assertEquals("layout_width incorrect for first text view.", "wrap_content",
+ parser.getAttributeValue(NS_RESOURCES, "layout_width"));
+ // Ensure that data-binding part is stripped.
+ assertEquals("Bound attribute android:text incorrect", "World",
+ parser.getAttributeValue(NS_RESOURCES, "text"));
+ assertEquals("resource attribute 'id' for first text view incorrect.", "@+id/first",
+ parser.getAttributeValue(NS_RESOURCES, "id"));
+ for (int next = parser.next();
+ (next != END_TAG || !"RelativeLayout".equals(parser.getName())) && next != END_DOCUMENT;
+ next = parser.next());
+ assertNotSame("Unexpected end of document", END_DOCUMENT, parser.getEventType());
+ assertEquals("Document didn't end when expected.", END_DOCUMENT, parser.next());
+ }
+
+ @Test
+ @SuppressWarnings("StatementWithEmptyBody")
+ public void testNonDataBindingLayout() throws Exception {
+ LayoutParserWrapper parser = getParserFromString(sNonDataBindingLayout);
+ parser.peekTillLayoutStart();
+ assertEquals("Expected START_TAG", START_TAG, parser.next());
+ assertEquals("RelativeLayout", parser.getName());
+ for (int next = parser.next(); next != START_TAG && next != END_DOCUMENT;
+ next = parser.next());
+ assertEquals("Expected START_TAG", START_TAG, parser.getEventType());
+ assertEquals("TextView", parser.getName());
+ assertEquals("layout_width incorrect for first text view.", "wrap_content",
+ parser.getAttributeValue(NS_RESOURCES, "layout_width"));
+ // Ensure that value isn't modified.
+ assertEquals("Bound attribute android:text incorrect", "@{user.firstName,default=World}",
+ parser.getAttributeValue(NS_RESOURCES, "text"));
+ assertEquals("resource attribute 'id' for first text view incorrect.", "@+id/first",
+ parser.getAttributeValue(NS_RESOURCES, "id"));
+ for (int next = parser.next();
+ (next != END_TAG || !"RelativeLayout".equals(parser.getName())) && next != END_DOCUMENT;
+ next = parser.next());
+ assertNotSame("Unexpected end of document", END_DOCUMENT, parser.getEventType());
+ assertEquals("Document didn't end when expected.", END_DOCUMENT, parser.next());
+ }
+
+ private static LayoutParserWrapper getParserFromString(String layoutContent) throws
+ XmlPullParserException {
+ XmlPullParser parser = new KXmlParser();
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+ parser.setInput(new StringReader(layoutContent));
+ return new LayoutParserWrapper(parser);
+ }
+
+ private static final String sDataBindingLayout =
+ //language=XML
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n" +
+ " xmlns:tools=\"http://schemas.android.com/tools\"\n" +
+ " tools:context=\".MainActivity\"\n" +
+ " tools:showIn=\"@layout/activity_main\">\n" +
+ "\n" +
+ " <data>\n" +
+ "\n" +
+ " <variable\n" +
+ " name=\"user\"\n" +
+ " type=\"com.example.User\" />\n" +
+ " <variable\n" +
+ " name=\"activity\"\n" +
+ " type=\"com.example.MainActivity\" />\n" +
+ " </data>\n" +
+ "\n" +
+ " <RelativeLayout\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " android:paddingBottom=\"@dimen/activity_vertical_margin\"\n" +
+ " android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n" +
+ " android:paddingRight=\"@dimen/activity_horizontal_margin\"\n" +
+ " android:paddingTop=\"@dimen/activity_vertical_margin\"\n" +
+ " app:layout_behavior=\"@string/appbar_scrolling_view_behavior\"\n" +
+ " >\n" +
+ "\n" +
+ " <TextView\n" +
+ " android:id=\"@+id/first\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_alignParentStart=\"true\"\n" +
+ " android:layout_alignParentLeft=\"true\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:text=\"@{user.firstName,default=World}\" />\n" +
+ "\n" +
+ " <TextView\n" +
+ " android:id=\"@+id/last\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_toEndOf=\"@id/first\"\n" +
+ " android:layout_toRightOf=\"@id/first\"\n" +
+ " android:text=\"@{user.lastName,default=Hello}\" />\n" +
+ "\n" +
+ " <Button\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_below=\"@id/last\"\n" +
+ " android:text=\"Submit\"\n" +
+ " android:onClick=\"@{activity.onClick}\"/>\n" +
+ " </RelativeLayout>\n" +
+ "</layout>";
+
+ private static final String sNonDataBindingLayout =
+ //language=XML
+ "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " android:paddingBottom=\"@dimen/activity_vertical_margin\"\n" +
+ " android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n" +
+ " android:paddingRight=\"@dimen/activity_horizontal_margin\"\n" +
+ " android:paddingTop=\"@dimen/activity_vertical_margin\"\n" +
+ " app:layout_behavior=\"@string/appbar_scrolling_view_behavior\"\n" +
+ ">\n" +
+ "\n" +
+ " <TextView\n" +
+ " android:id=\"@+id/first\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_alignParentStart=\"true\"\n" +
+ " android:layout_alignParentLeft=\"true\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:text=\"@{user.firstName,default=World}\" />\n" +
+ "\n" +
+ " <TextView\n" +
+ " android:id=\"@+id/last\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_toEndOf=\"@id/first\"\n" +
+ " android:layout_toRightOf=\"@id/first\"\n" +
+ " android:text=\"@{user.lastName,default=Hello}\" />\n" +
+ "\n" +
+ " <Button\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_below=\"@id/last\"\n" +
+ " android:text=\"Submit\"\n" +
+ " android:onClick=\"@{activity.onClick}\"/>\n" +
+ "</RelativeLayout>";
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 484240f..c9bc62e 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -157,7 +157,6 @@ public final class CreateInfo implements ICreateInfo {
* The list of methods to rewrite as delegates.
*/
public final static String[] DELEGATE_METHODS = new String[] {
- "android.animation.AnimatorInflater#loadAnimator", // TODO: remove when Path.approximate() is supported.
"android.app.Fragment#instantiate", //(Landroid/content/Context;Ljava/lang/String;Landroid/os/Bundle;)Landroid/app/Fragment;",
"android.content.res.Resources$Theme#obtainStyledAttributes",
"android.content.res.Resources$Theme#resolveAttribute",
@@ -167,6 +166,7 @@ public final class CreateInfo implements ICreateInfo {
"android.content.res.TypedArray#getValueAt",
"android.content.res.TypedArray#obtain",
"android.graphics.BitmapFactory#finishDecode",
+ "android.graphics.drawable.GradientDrawable#buildRing",
"android.graphics.Typeface#getSystemFontConfigLocation",
"android.os.Handler#sendMessageAtTime",
"android.os.HandlerThread#run",
@@ -235,6 +235,7 @@ public final class CreateInfo implements ICreateInfo {
"android.graphics.Path",
"android.graphics.PathDashPathEffect",
"android.graphics.PathEffect",
+ "android.graphics.PathMeasure",
"android.graphics.PixelXorXfermode",
"android.graphics.PorterDuffColorFilter",
"android.graphics.PorterDuffXfermode",
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
index ae4a57d..7ef7566 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
@@ -17,6 +17,7 @@
package com.android.tools.layoutlib.create;
import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -40,6 +41,7 @@ public class DelegateClassAdapter extends ClassVisitor {
private final String mClassName;
private final Set<String> mDelegateMethods;
private final Log mLog;
+ private boolean mIsStaticInnerClass;
/**
* Creates a new {@link DelegateClassAdapter} that can transform some methods
@@ -62,16 +64,30 @@ public class DelegateClassAdapter extends ClassVisitor {
mLog = log;
mClassName = className;
mDelegateMethods = delegateMethods;
+ // If this is an inner class, by default, we assume it's static. If it's not we will detect
+ // by looking at the fields (see visitField)
+ mIsStaticInnerClass = className.contains("$");
}
//----------------------------------
// Methods from the ClassAdapter
@Override
+ public FieldVisitor visitField(int access, String name, String desc, String signature,
+ Object value) {
+ if (mIsStaticInnerClass && "this$0".equals(name)) {
+ // Having a "this$0" field, proves that this class is not a static inner class.
+ mIsStaticInnerClass = false;
+ }
+
+ return super.visitField(access, name, desc, signature, value);
+ }
+
+ @Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
- boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
+ boolean isStaticMethod = (access & Opcodes.ACC_STATIC) != 0;
boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
boolean useDelegate = (isNative && mDelegateMethods.contains(ALL_NATIVES)) ||
@@ -96,7 +112,8 @@ public class DelegateClassAdapter extends ClassVisitor {
MethodVisitor mwDelegate = super.visitMethod(access, name, desc, signature, exceptions);
DelegateMethodAdapter a = new DelegateMethodAdapter(
- mLog, null, mwDelegate, mClassName, name, desc, isStatic);
+ mLog, null, mwDelegate, mClassName, name, desc, isStaticMethod,
+ mIsStaticInnerClass);
// A native has no code to visit, so we need to generate it directly.
a.generateDelegateCode();
@@ -120,6 +137,7 @@ public class DelegateClassAdapter extends ClassVisitor {
desc, signature, exceptions);
return new DelegateMethodAdapter(
- mLog, mwOriginal, mwDelegate, mClassName, name, desc, isStatic);
+ mLog, mwOriginal, mwDelegate, mClassName, name, desc, isStaticMethod,
+ mIsStaticInnerClass);
}
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
index 12690db..cca9e57 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
@@ -85,6 +85,8 @@ class DelegateMethodAdapter extends MethodVisitor {
private String mDesc;
/** True if the original method is static. */
private final boolean mIsStatic;
+ /** True if the method is contained in a static inner class */
+ private final boolean mIsStaticInnerClass;
/** The internal class name (e.g. <code>com/android/SomeClass$InnerClass</code>.) */
private final String mClassName;
/** The method name. */
@@ -120,7 +122,8 @@ class DelegateMethodAdapter extends MethodVisitor {
String className,
String methodName,
String desc,
- boolean isStatic) {
+ boolean isStatic,
+ boolean isStaticClass) {
super(Opcodes.ASM4);
mLog = log;
mOrgWriter = mvOriginal;
@@ -129,6 +132,7 @@ class DelegateMethodAdapter extends MethodVisitor {
mMethodName = methodName;
mDesc = desc;
mIsStatic = isStatic;
+ mIsStaticInnerClass = isStaticClass;
}
/**
@@ -206,7 +210,7 @@ class DelegateMethodAdapter extends MethodVisitor {
// by the 'this' of any outer class, if any.
if (!mIsStatic) {
- if (outerType != null) {
+ if (outerType != null && !mIsStaticInnerClass) {
// The first-level inner class has a package-protected member called 'this$0'
// that points to the outer class.
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
index 648cea4..e37a09b 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
@@ -27,6 +27,7 @@ import static org.junit.Assert.fail;
import com.android.tools.layoutlib.create.dataclass.ClassWithNative;
import com.android.tools.layoutlib.create.dataclass.OuterClass;
import com.android.tools.layoutlib.create.dataclass.OuterClass.InnerClass;
+import com.android.tools.layoutlib.create.dataclass.OuterClass.StaticInnerClass;
import org.junit.Before;
import org.junit.Test;
@@ -56,6 +57,8 @@ public class DelegateClassAdapterTest {
private static final String OUTER_CLASS_NAME = OuterClass.class.getCanonicalName();
private static final String INNER_CLASS_NAME = OuterClass.class.getCanonicalName() + "$" +
InnerClass.class.getSimpleName();
+ private static final String STATIC_INNER_CLASS_NAME =
+ OuterClass.class.getCanonicalName() + "$" + StaticInnerClass.class.getSimpleName();
@Before
public void setUp() throws Exception {
@@ -294,6 +297,61 @@ public class DelegateClassAdapterTest {
}
}
+ @Test
+ public void testDelegateStaticInner() throws Throwable {
+ // We'll delegate the "get" method of both the inner and outer class.
+ HashSet<String> delegateMethods = new HashSet<String>();
+ delegateMethods.add("get");
+
+ // Generate the delegate for the outer class.
+ ClassWriter cwOuter = new ClassWriter(0 /*flags*/);
+ String outerClassName = OUTER_CLASS_NAME.replace('.', '/');
+ DelegateClassAdapter cvOuter = new DelegateClassAdapter(
+ mLog, cwOuter, outerClassName, delegateMethods);
+ ClassReader cr = new ClassReader(OUTER_CLASS_NAME);
+ cr.accept(cvOuter, 0 /* flags */);
+
+ // Generate the delegate for the static inner class.
+ ClassWriter cwInner = new ClassWriter(0 /*flags*/);
+ String innerClassName = STATIC_INNER_CLASS_NAME.replace('.', '/');
+ DelegateClassAdapter cvInner = new DelegateClassAdapter(
+ mLog, cwInner, innerClassName, delegateMethods);
+ cr = new ClassReader(STATIC_INNER_CLASS_NAME);
+ cr.accept(cvInner, 0 /* flags */);
+
+ // Load the generated classes in a different class loader and try them
+ ClassLoader2 cl2 = null;
+ try {
+ cl2 = new ClassLoader2() {
+ @Override
+ public void testModifiedInstance() throws Exception {
+
+ // Check the outer class
+ Class<?> outerClazz2 = loadClass(OUTER_CLASS_NAME);
+ Object o2 = outerClazz2.newInstance();
+ assertNotNull(o2);
+
+ // Check the inner class. Since it's not a static inner class, we need
+ // to use the hidden constructor that takes the outer class as first parameter.
+ Class<?> innerClazz2 = loadClass(STATIC_INNER_CLASS_NAME);
+ Constructor<?> innerCons = innerClazz2.getConstructor();
+ Object i2 = innerCons.newInstance();
+ assertNotNull(i2);
+
+ // The original StaticInner.get returns 100+10+20,
+ // but the delegate makes it return 6+10+20
+ assertEquals(6+10+20, callGet(i2, 10, 20));
+ assertEquals(100+10+20, callGet_Original(i2, 10, 20));
+ }
+ };
+ cl2.add(OUTER_CLASS_NAME, cwOuter.toByteArray());
+ cl2.add(STATIC_INNER_CLASS_NAME, cwInner.toByteArray());
+ cl2.testModifiedInstance();
+ } catch (Throwable t) {
+ throw dumpGeneratedClass(t, cl2);
+ }
+ }
+
//-------
/**
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java
index f083e76..6dfb816 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java
@@ -45,6 +45,16 @@ public class OuterClass {
}
}
+ public static class StaticInnerClass {
+ public StaticInnerClass() {
+ }
+
+ // StaticInnerClass.get returns 100 + a + b
+ public int get(int a, long b) {
+ return 100 + a + (int) b;
+ }
+ }
+
@SuppressWarnings("unused")
private String privateMethod() {
return "outerPrivateMethod";
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java
new file mode 100644
index 0000000..a29439e
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java
@@ -0,0 +1,30 @@
+/*
+ * 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 com.android.tools.layoutlib.create.dataclass;
+
+import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
+import com.android.tools.layoutlib.create.dataclass.OuterClass.StaticInnerClass;
+
+/**
+ * Used by {@link DelegateClassAdapterTest}.
+ */
+public class OuterClass_StaticInnerClass_Delegate {
+ // The delegate override of Inner.get return 6 + a + b
+ public static int get(StaticInnerClass inner, int a, long b) {
+ return 6 + a + (int) b;
+ }
+}