summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-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/view/BridgeInflater.java24
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java2
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java16
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java4
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java5
-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/RenderSessionImpl.java7
-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
21 files changed, 1029 insertions, 163 deletions
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 4475fa483015..000000000000
--- 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 163fbcb7f900..0e392436d748 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 <T super String>, 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 6a6109047573..31dd3d943769 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 d858953b956c..60514b658649 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 f8b3739b6a89..64cd5031346e 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 857e6d03283e..c7b24bcb352d 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 65b65ec759d0..a545283ea0ca 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 000000000000..dd2978f5c414
--- /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 3c9a062719e2..a2a53fef7f1f 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 000000000000..a3ad2aac7d3d
--- /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 49ee6426acae..2e44a7770aae 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/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
index 1e33e3ab2090..5db1bde5f3f0 100644
--- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
+++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
@@ -36,6 +36,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 +55,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/>
@@ -135,11 +139,23 @@ 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) {
+ // 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();
+ }
+ final Object lastContext = mConstructorArgs[0];
+ mConstructorArgs[0] = context;
// try to load the class from using the custom view loader
try {
view = loadCustomView(name, attrs);
@@ -153,6 +169,8 @@ public final class BridgeInflater extends LayoutInflater {
exception.initCause(e);
}
throw exception;
+ } finally {
+ mConstructorArgs[0] = lastContext;
}
}
@@ -188,7 +206,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/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 689e3597720b..b2dc29a90fb1 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
@@ -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);
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 b76ec1707fcc..567002e564b7 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
@@ -33,6 +33,7 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.Bitmap_Delegate;
@@ -227,16 +228,18 @@ abstract class CustomBar extends LinearLayout {
* Find the background color for this bar from the theme attributes. Only relevant to StatusBar
* and NavigationBar.
* <p/>
- * Returns 0 if not found.
+ * Returns null if not found.
*
* @param colorAttrName the attribute name for the background color
* @param translucentAttrName the attribute name for the translucency property of the bar.
*
* @throws NumberFormatException if color resolved to an invalid string.
*/
- protected int getBarColor(@NonNull String colorAttrName, @NonNull String translucentAttrName) {
+ @Nullable
+ protected Integer getBarColor(@NonNull String colorAttrName,
+ @NonNull String translucentAttrName) {
if (!Config.isGreaterOrEqual(mSimulatedPlatformVersion, LOLLIPOP)) {
- return 0;
+ return null;
}
RenderResources renderResources = getContext().getRenderResources();
// First check if the bar is translucent.
@@ -251,10 +254,11 @@ abstract class CustomBar extends LinearLayout {
if (transparent) {
return getColor(renderResources, colorAttrName);
}
- return 0;
+ return null;
}
- private static int getColor(RenderResources renderResources, String attr) {
+ @Nullable
+ private static Integer getColor(RenderResources renderResources, String attr) {
// From ?attr/foo to @color/bar. This is most likely an ItemResourceValue.
ResourceValue resource = renderResources.findItemInTheme(attr, true);
// Form @color/bar to the #AARRGGBB
@@ -275,7 +279,7 @@ abstract class CustomBar extends LinearLayout {
}
}
}
- return 0;
+ return null;
}
private ResourceValue getResourceValue(String reference) {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java
index 9c89bfe2a28f..d50ce23a0902 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java
@@ -65,8 +65,8 @@ public class NavigationBar extends CustomBar {
super(context, orientation, getShortestWidth(context)>= 600 ? LAYOUT_600DP_XML : LAYOUT_XML,
"navigation_bar.xml", simulatedPlatformVersion);
- int color = getBarColor(ATTR_COLOR, ATTR_TRANSLUCENT);
- setBackgroundColor(color == 0 ? 0xFF000000 : color);
+ Integer color = getBarColor(ATTR_COLOR, ATTR_TRANSLUCENT);
+ setBackgroundColor(color == null ? 0xFF000000 : color);
// Cannot access the inside items through id because no R.id values have been
// created for them.
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java
index 2dc7c65e2085..95a5a58c535f 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java
@@ -71,8 +71,9 @@ public class StatusBar extends CustomBar {
// FIXME: use FILL_H?
setGravity(Gravity.START | Gravity.TOP | Gravity.RIGHT);
- int color = getBarColor(ATTR_COLOR, ATTR_TRANSLUCENT);
- setBackgroundColor(color == 0 ? Config.getStatusBarColor(simulatedPlatformVersion) : color);
+ Integer color = getBarColor(ATTR_COLOR, ATTR_TRANSLUCENT);
+ setBackgroundColor(
+ color == null ? Config.getStatusBarColor(simulatedPlatformVersion) : color);
// Cannot access the inside items through id because no R.id values have been
// created for them.
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 000000000000..71e7fd2c5eea
--- /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 6e67f593aa53..e273b2cd75cc 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/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index ac7c4097e271..2a4f58381aee 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
@@ -421,8 +421,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
gc.setComposite(AlphaComposite.Src);
gc.setColor(new Color(0x00000000, true));
- gc.fillRect(0, 0,
- mMeasuredScreenWidth, mMeasuredScreenHeight);
+ gc.fillRect(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight);
// done
gc.dispose();
@@ -1051,11 +1050,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 000000000000..2c338622301b
--- /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 484240f49b20..c9bc62ec3bcb 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",