Improve error reporting on Exceptions in fw views.

When there's an exception during the inflation of a framework view (for
example invalid attributes), report the exception correctly. The earlier
behaviour assumed the exception to be a ClassNotFoundException and tried
to load it from the user's project. This is not longer the case.

Also, update the MockView class to a FrameLayout with a single TextView.
This means that the MockView is a ViewGroup and will not choke when
someone attempts to add a View to it (although, the view will be
silently dropped).

Change-Id: Ice003817ceb627ebfbbbb245ab6be10f9141e728
This commit is contained in:
Deepanshu Gupta
2015-10-20 17:29:44 -07:00
parent 7a062ed396
commit d30c141a2d
2 changed files with 91 additions and 29 deletions

View File

@@ -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;
@@ -126,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.
@@ -154,23 +158,30 @@ public final class BridgeInflater extends LayoutInflater {
}
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);
} 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);
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;
} finally {
mConstructorArgs[0] = lastContext;
}
}

View File

@@ -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 onDraw(Canvas canvas) {
canvas.drawARGB(0xFF, 0x7F, 0x7F, 0x7F);
public void addView(View child, int index) {
if (child == mView) {
super.addView(child, index);
}
}
super.onDraw(canvas);
@Override
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);
}
public void setGravity(int gravity) {
mView.setGravity(gravity);
}
}