Make layoutlib more lenient with recoverable errors

In many cases, throwing an exception will stop the inflation or
rendering of a layout. Sometimes, we could recover from some of the
failures and render a layout that is "usable".
I've done a first pass trying to follow the following rules:
 - Try simple recoveries by choosing sensible defaults. Not trying
 complex recoveries.
 - Only recover if the exception is not part of the class behaviour.
 Avoided removing declared exceptions or exceptions that are expected by
 the framework.
 - I've kept assertions in place so, in most cases, the behaviour will
 be almost identical to the previous one with assertions enabled.

Test: Checked with existing tests
Change-Id: I0001fdd3c808cf405c4eb8d734b9bbe63493e05c
This commit is contained in:
Diego Perez
2017-03-29 17:20:01 +01:00
parent 1af2928767
commit d88c717b4e
17 changed files with 119 additions and 78 deletions

View File

@@ -740,9 +740,11 @@ public final class BridgeTypedArray extends TypedArray {
}
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));
assert false :
String.format("%1$s in %2$s%3$s is not a valid array resource.", resVal.getValue(),
mNames[index], resIdMessage);
return new CharSequence[0];
}
@Override

View File

@@ -88,8 +88,9 @@ public final class BlendComposite implements Composite {
private void setAlpha(float alpha) {
if (alpha < 0.0f || alpha > 1.0f) {
throw new IllegalArgumentException(
"alpha must be comprised between 0.0f and 1.0f");
assert false : "alpha must be comprised between 0.0f and 1.0f";
alpha = Math.min(alpha, 1.0f);
alpha = Math.max(alpha, 0.0f);
}
this.alpha = alpha;
@@ -266,9 +267,21 @@ public final class BlendComposite implements Composite {
return result;
}
};
default:
assert false : "Blender not implement for " + composite.getMode().name();
// Ignore the blend
return new Blender() {
@Override
public int[] blend(int[] src, int[] dst, int[] result) {
result[0] = dst[0];
result[1] = dst[1];
result[2] = dst[2];
result[3] = dst[3];
return result;
}
};
}
throw new IllegalArgumentException("Blender not implement for " +
composite.getMode().name());
}
}
}

View File

@@ -252,7 +252,8 @@ public class FontFamily_Delegate {
/*package*/ static boolean addFont(FontFamily thisFontFamily, String path, int ttcIndex,
FontConfig.Axis[] axes, int weight, int italic) {
if (thisFontFamily.mBuilderPtr == 0) {
throw new IllegalStateException("Unable to call addFont after freezing.");
assert false : "Unable to call addFont after freezing.";
return false;
}
final FontFamily_Delegate delegate = getDelegate(thisFontFamily.mBuilderPtr);
return delegate != null && delegate.addFont(path, ttcIndex, weight, italic);

View File

@@ -49,21 +49,19 @@ public abstract class Gradient_Delegate extends Shader_Delegate {
*/
protected Gradient_Delegate(long nativeMatrix, int colors[], float positions[]) {
super(nativeMatrix);
if (colors.length < 2) {
throw new IllegalArgumentException("needs >= 2 number of colors");
}
if (positions != null && colors.length != positions.length) {
throw new IllegalArgumentException("color and position arrays must be of equal length");
}
assert colors.length >= 2 : "needs >= 2 number of colors";
if (positions == null) {
float spacing = 1.f / (colors.length - 1);
positions = new float[colors.length];
positions[0] = 0.f;
positions[colors.length-1] = 1.f;
for (int i = 1; i < colors.length - 1 ; i++) {
positions[colors.length - 1] = 1.f;
for (int i = 1; i < colors.length - 1; i++) {
positions[i] = spacing * i;
}
} else {
assert colors.length == positions.length :
"color and position " + "arrays must be of equal length";
}
mColors = colors;

View File

@@ -548,10 +548,11 @@ public final class Path_Delegate {
case EVEN_ODD:
case INVERSE_EVEN_ODD:
return GeneralPath.WIND_EVEN_ODD;
}
assert false;
throw new IllegalArgumentException();
default:
assert false;
return GeneralPath.WIND_NON_ZERO;
}
}
@NonNull

View File

@@ -61,10 +61,8 @@ public class RoundRectangle extends RectangularShape {
* ellipse that corner is a quarter of.
*/
public RoundRectangle(float x, float y, float width, float height, float[] cornerDimensions) {
if (cornerDimensions.length != 8) {
throw new IllegalArgumentException("The array of corner dimensions must have eight " +
"elements");
}
assert cornerDimensions.length == 8 : "The array of corner dimensions must have eight " +
"elements";
this.x = x;
this.y = y;

View File

@@ -585,8 +585,8 @@ public class VectorDrawable_Delegate {
return this::setTrimPathOffset;
}
throw new IllegalArgumentException("Invalid VFullPath_Delegate property index "
+ propertyIdx);
assert false : ("Invalid VFullPath_Delegate property index " + propertyIdx);
return t -> {};
}
@NonNull
@@ -598,8 +598,8 @@ public class VectorDrawable_Delegate {
return this::setFillColor;
}
throw new IllegalArgumentException("Invalid VFullPath_Delegate property index "
+ propertyIdx);
assert false : ("Invalid VFullPath_Delegate property index " + propertyIdx);
return t -> {};
}
/////////////////////////////////////////////////////
@@ -827,8 +827,8 @@ public class VectorDrawable_Delegate {
return this::setTranslateY;
}
throw new IllegalArgumentException("Invalid VGroup_Delegate property index "
+ propertyIdx);
assert false : ("Invalid VGroup_Delegate property index " + propertyIdx);
return t -> {};
}
/////////////////////////////////////////////////////

View File

@@ -164,7 +164,9 @@ public class StaticLayout_Delegate {
builder.mTabStopCalculator);
break;
default:
throw new AssertionError("Unknown break strategy: " + builder.mBreakStrategy);
assert false : "Unknown break strategy: " + builder.mBreakStrategy;
builder.mLineBreaker = new GreedyLineBreaker(primitives, builder.mLineWidth,
builder.mTabStopCalculator);
}
builder.mLineBreaker.computeBreaks(recycle);
return recycle.breaks.length;

View File

@@ -361,7 +361,8 @@ public class PathParser_Delegate {
}
return Arrays.copyOf(results, count);
} catch (NumberFormatException e) {
throw new RuntimeException("error in parsing \"" + s + "\"", e);
assert false : "error in parsing \"" + s + "\"" + e;
return new float[0];
}
}

View File

@@ -16,6 +16,8 @@
package android.view;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.layoutlib.bridge.Bridge;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import org.xmlpull.v1.XmlPullParser;
@@ -104,8 +106,10 @@ public class LayoutInflater_Delegate {
if (layout == 0) {
final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
if (value == null || value.length() <= 0) {
throw new InflateException("You must specify a layout in the"
+ " include tag: <include layout=\"@layout/layoutID\" />");
Bridge.getLog().error(LayoutLog.TAG_BROKEN, "You must specify a layout in the"
+ " include tag: <include layout=\"@layout/layoutID\" />", null);
LayoutInflater.consumeChildElements(parser);
return;
}
// Attempt to resolve the "?attr/name" string to an identifier.
@@ -125,11 +129,11 @@ public class LayoutInflater_Delegate {
if (layout == 0) {
final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
if (value == null) {
throw new InflateException("You must specifiy a layout in the"
+ " include tag: <include layout=\"@layout/layoutID\" />");
Bridge.getLog().error(LayoutLog.TAG_BROKEN, "You must specify a layout in the"
+ " include tag: <include layout=\"@layout/layoutID\" />", null);
} else {
throw new InflateException("You must specifiy a valid layout "
+ "reference. The layout ID " + value + " is not valid.");
Bridge.getLog().error(LayoutLog.TAG_BROKEN, "You must specify a valid layout "
+ "reference. The layout ID " + value + " is not valid.", null);
}
} else {
final XmlResourceParser childParser =
@@ -144,8 +148,11 @@ public class LayoutInflater_Delegate {
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(childParser.getPositionDescription() +
": No start tag found!");
Bridge.getLog().error(LayoutLog.TAG_BROKEN,
childParser.getPositionDescription() + ": No start tag found!",
null);
LayoutInflater.consumeChildElements(parser);
return;
}
final String childName = childParser.getName();
@@ -219,7 +226,9 @@ public class LayoutInflater_Delegate {
}
}
} else {
throw new InflateException("<include /> can only be used inside of a ViewGroup");
Bridge.getLog().error(LayoutLog.TAG_BROKEN,
"<include /> can only be used inside of a ViewGroup",
null);
}
LayoutInflater.consumeChildElements(parser);

View File

@@ -37,7 +37,8 @@ public class RectShadowPainter {
@NonNull Canvas canvas) {
Rect outline = new Rect();
if (!viewOutline.getRect(outline)) {
throw new IllegalArgumentException("Outline is not a rect shadow");
assert false : "Outline is not a rect shadow";
return;
}
Rect originCanvasRect = canvas.getClipBounds();

View File

@@ -652,7 +652,8 @@ public class BridgeContext extends Context {
return null;
}
throw new UnsupportedOperationException("Unsupported Service: " + service);
assert false : "Unsupported Service: " + service;
return null;
}
@Override
@@ -682,7 +683,9 @@ public class BridgeContext extends Context {
}
if (style == null) {
throw new Resources.NotFoundException();
Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE,
"Failed to find style with " + resId, null);
return null;
}
}

View File

@@ -49,6 +49,7 @@ import android.content.pm.VersionedPackage;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Handler;
@@ -307,7 +308,8 @@ public class BridgePackageManager extends PackageManager {
@Override
public Drawable getInstantAppIcon(String packageName) {
throw new UnsupportedOperationException();
assert false : "Unsupported operation";
return new ColorDrawable();
}
@Override

View File

@@ -21,6 +21,7 @@ import com.android.ide.common.rendering.api.ActionBarCallback.HomeButtonStyle;
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.layoutlib.bridge.MockView;
import com.android.layoutlib.bridge.android.BridgeContext;
import android.annotation.NonNull;
@@ -53,35 +54,41 @@ public abstract class BridgeActionBar {
mParams = params;
mCallback = params.getLayoutlibCallback().getActionBarCallback();
ResourceValue layoutName = getLayoutResource(context);
if (layoutName == null) {
throw new RuntimeException("Unable to find the layout for Action Bar.");
}
int layoutId;
if (layoutName.isFramework()) {
layoutId = context.getFrameworkResourceValue(layoutName.getResourceType(),
layoutName.getName(), 0);
} else {
layoutId = context.getProjectResourceValue(layoutName.getResourceType(),
layoutName.getName(), 0);
int layoutId = 0;
if (layoutName == null) {
assert false : "Unable to find the layout for Action Bar.";
}
else {
if (layoutName.isFramework()) {
layoutId = context.getFrameworkResourceValue(layoutName.getResourceType(),
layoutName.getName(), 0);
} else {
layoutId = context.getProjectResourceValue(layoutName.getResourceType(),
layoutName.getName(), 0);
}
}
if (layoutId == 0) {
throw new RuntimeException(
String.format("Unable to resolve attribute \"%1$s\" of type \"%2$s\"",
layoutName.getName(), layoutName.getResourceType()));
}
if (mCallback.isOverflowPopupNeeded()) {
// Create a RelativeLayout around the action bar, to which the overflow popup may be
// added.
mEnclosingLayout = new RelativeLayout(mBridgeContext);
setMatchParent(mEnclosingLayout);
} else {
assert false : String.format("Unable to resolve attribute \"%1$s\" of type \"%2$s\"",
layoutName.getName(), layoutName.getResourceType());
mDecorContent = new MockView(context);
mEnclosingLayout = null;
}
else {
if (mCallback.isOverflowPopupNeeded()) {
// Create a RelativeLayout around the action bar, to which the overflow popup may be
// added.
mEnclosingLayout = new RelativeLayout(mBridgeContext);
setMatchParent(mEnclosingLayout);
} else {
mEnclosingLayout = null;
}
// Inflate action bar layout.
mDecorContent =
getInflater(context).inflate(layoutId, mEnclosingLayout, mEnclosingLayout != null);
// Inflate action bar layout.
mDecorContent = getInflater(context).inflate(layoutId, mEnclosingLayout,
mEnclosingLayout != null);
}
}
/**

View File

@@ -83,18 +83,18 @@ abstract class CustomBar extends LinearLayout {
XmlPullParser parser;
try {
parser = ParserFactory.create(getClass().getResourceAsStream(layoutPath), name);
BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(parser, context, false);
try {
inflater.inflate(bridgeParser, this, true);
} finally {
bridgeParser.ensurePopped();
}
} catch (XmlPullParserException e) {
// Should not happen as the resource is bundled with the jar, and ParserFactory should
// have been initialized.
throw new AssertionError(e);
}
BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(parser, context, false);
try {
inflater.inflate(bridgeParser, this, true);
} finally {
bridgeParser.ensurePopped();
assert false;
}
}

View File

@@ -176,7 +176,8 @@ public class FrameworkActionBar extends BridgeActionBar {
ArrayList<MenuItemImpl> menus = mActionBar.getMenuBuilder().getNonActionItems();
ActionMenuPresenter presenter = mActionBar.getActionMenuPresenter();
if (presenter == null) {
throw new RuntimeException("Failed to create a Presenter for Action Bar Menus.");
assert false : "Failed to create a Presenter for Action Bar Menus.";
return false;
}
if (presenter.isOverflowReserved() &&
menus != null) {

View File

@@ -43,6 +43,7 @@ import android.content.res.GradientColor;
import android.content.res.Resources.Theme;
import android.graphics.Bitmap;
import android.graphics.Bitmap_Delegate;
import android.graphics.Color;
import android.graphics.NinePatch_Delegate;
import android.graphics.Rect;
import android.graphics.Typeface;
@@ -184,7 +185,8 @@ public final class ResourceHelper {
}
if (type != XmlPullParser.START_TAG) {
throw new XmlPullParserException("No start tag found");
assert false : "No start tag found";
return null;
}
final String name = blockParser.getName();