From 58849efff2c5bf4488cf597ee3915aab2f682f17 Mon Sep 17 00:00:00 2001
From: James Barr Turn of the use of Reflection to inject the private factory.
+ * This has operational consequences! Please read and understand before disabling.
+ * This is already disabled on pre Honeycomb devices. (API 11) If you disable this you will need to override your {@link android.app.Activity#onCreateView(View, String, android.content.Context, android.util.AttributeSet)}
+ * as this is set as the {@link android.view.LayoutInflater} private factory. Turn of the use of Reflection to inject the private factory.
- * This has operational consequences! Please read and understand before disabling.
- * This is already disabled on pre Honeycomb devices. (API 11)
+ * Use the following code in the Activity if you disable FactoryInjection:
+ *
+ *
+ * @param enabled True if private factory inject is allowed; otherwise, false.
+ */
+ public Builder setPrivateFactoryInjectionEnabled(boolean enabled) {
+ this.reflection = enabled;
+ return this;
+ }
+
+ /**
+ * Due to the poor inflation order where custom views are created and never returned inside an
+ * {@code onCreateView(...)} method. We have to create CustomView's at the latest point in the
+ * overrideable injection flow.
+ *
+ * On HoneyComb+ this is inside the {@link android.app.Activity#onCreateView(View, String, android.content.Context, android.util.AttributeSet)}
+ * Pre HoneyComb this is in the {@link android.view.LayoutInflater.Factory#onCreateView(String, android.util.AttributeSet)}
+ *
+ * We wrap base implementations, so if you LayoutInflater/Factory/Activity creates the
+ * custom view before we get to this point, your view is used. (Such is the case with the
+ * TintEditText etc)
+ *
+ * The problem is, the native methods pass there parents context to the constructor in a really
+ * specific place. We have to mimic this in {@link ViewPumpLayoutInflater#createCustomViewInternal(View, View, String, android.content.Context, android.util.AttributeSet)}
+ * To mimic this we have to use reflection as the Class constructor args are hidden to us.
+ *
+ * We have discussed other means of doing this but this is the only semi-clean way of doing it.
+ * (Without having to do proxy classes etc).
+ *
+ * Calling this will of course speed up inflation by turning off reflection, but not by much,
+ * But if you want ViewPump to inject the correct typeface then you will need to make sure your CustomView's
+ * are created before reaching the LayoutInflater onViewCreated.
+ *
+ * @param enabled True if custom view inflated is allowed; otherwise, false.
+ */
+ public Builder setCustomViewInflationEnabled(boolean enabled) {
+ this.customViewCreation = enabled;
+ return this;
+ }
+
+ public ViewPump build() {
+ return new ViewPump(this);
+ }
+ }
+}
diff --git a/viewpump/src/main/java/io/github/inflationx/viewpump/ViewPumpActivityFactory.java b/viewpump/src/main/java/io/github/inflationx/viewpump/ViewPumpActivityFactory.java
new file mode 100644
index 0000000..e982e13
--- /dev/null
+++ b/viewpump/src/main/java/io/github/inflationx/viewpump/ViewPumpActivityFactory.java
@@ -0,0 +1,32 @@
+package io.github.inflationx.viewpump;
+
+import android.content.Context;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.View;
+
+interface ViewPumpActivityFactory {
+
+ /**
+ * Used to Wrap the Activity onCreateView method.
+ *
+ * You implement this method like so in you base activity.
+ *
+ * {@literal @}Override
+ * {@literal @}TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ * public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
+ * return ViewPumpContextWrapper.onActivityCreateView(this, parent, super.onCreateView(parent, name, context, attrs), name, context, attrs);
+ * }
+ *
+ * {@code
+ * public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
+ * return ViewPumpContextWrapper.get(getBaseContext()).onActivityCreateView(super.onCreateView(parent, name,context, attrs), attrs);
+ * }
+ * }
+ *
+ *
+ * @param parent parent view, can be null.
+ * @param view result of {@code super.onCreateView(parent, name, context, attrs)}, this might be null, which is fine.
+ * @param name Name of View we are trying to inflate
+ * @param context current context (normally the Activity's)
+ * @param attrs see {@link android.view.LayoutInflater.Factory2#onCreateView(View, String, Context, AttributeSet)} @return the result from the activities {@code onCreateView()}
+ * @return The view passed in, or null if nothing was passed in.
+ * @see android.view.LayoutInflater.Factory2
+ */
+ @Nullable
+ View onActivityCreateView(View parent, View view, String name, Context context, AttributeSet attrs);
+}
diff --git a/viewpump/src/main/java/io/github/inflationx/viewpump/ViewPumpContextWrapper.java b/viewpump/src/main/java/io/github/inflationx/viewpump/ViewPumpContextWrapper.java
new file mode 100644
index 0000000..b7d9983
--- /dev/null
+++ b/viewpump/src/main/java/io/github/inflationx/viewpump/ViewPumpContextWrapper.java
@@ -0,0 +1,94 @@
+package io.github.inflationx.viewpump;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+
+public final class ViewPumpContextWrapper extends ContextWrapper {
+
+ private ViewPumpLayoutInflater mInflater;
+
+ /**
+ * Uses the default configuration from {@link ViewPump}
+ *
+ * Remember if you are defining default in the {@link ViewPump} make sure this
+ * is initialised before the activity is created.
+ *
+ * @param base ContextBase to Wrap.
+ * @return ContextWrapper to pass back to the activity.
+ */
+ public static ContextWrapper wrap(@NonNull Context base) {
+ return new ViewPumpContextWrapper(base);
+ }
+
+ /**
+ * You only need to call this IF you disable
+ * {@link ViewPump.Builder#setPrivateFactoryInjectionEnabled(boolean)}
+ * This will need to be called from the
+ * {@link Activity#onCreateView(View, String, Context, AttributeSet)}
+ * method to enable view font injection if the view is created inside the activity onCreateView.
+ *
+ * You would implement this method like so in you base activity.
+ *
+ * {@code
+ * public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
+ * return ViewPumpContextWrapper.onActivityCreateView(this, parent, super.onCreateView(parent, name, context, attrs), name, context, attrs);
+ * }
+ * }
+ *
+ *
+ * @param activity The activity the original that the ContextWrapper was attached too.
+ * @param parent Parent view from onCreateView
+ * @param view The View Created inside onCreateView or from super.onCreateView
+ * @param name The View name from onCreateView
+ * @param context The context from onCreateView
+ * @param attr The AttributeSet from onCreateView
+ * @return The same view passed in, or null if null passed in.
+ */
+ @Nullable
+ public static View onActivityCreateView(Activity activity, View parent, View view, String name, Context context, AttributeSet attr) {
+ return get(activity).onActivityCreateView(parent, view, name, context, attr);
+ }
+
+ /**
+ * Get the ViewPump Activity Fragment Instance to allow callbacks for when views are created.
+ *
+ * @param activity The activity the original that the ContextWrapper was attached too.
+ * @return Interface allowing you to call onActivityViewCreated
+ */
+ static ViewPumpActivityFactory get(@NonNull Activity activity) {
+ if (!(activity.getLayoutInflater() instanceof ViewPumpLayoutInflater)) {
+ throw new RuntimeException("This activity does not wrap the Base Context! See ViewPumpContextWrapper.wrap(Context)");
+ }
+ return (ViewPumpActivityFactory) activity.getLayoutInflater();
+ }
+
+ /**
+ * Uses the default configuration from {@link ViewPump}
+ *
+ * Remember if you are defining default in the
+ * {@link ViewPump} make sure this is initialised before
+ * the activity is created.
+ *
+ * @param base ContextBase to Wrap
+ */
+ private ViewPumpContextWrapper(Context base) {
+ super(base);
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ if (LAYOUT_INFLATER_SERVICE.equals(name)) {
+ if (mInflater == null) {
+ mInflater = new ViewPumpLayoutInflater(LayoutInflater.from(getBaseContext()), this, false);
+ }
+ return mInflater;
+ }
+ return super.getSystemService(name);
+ }
+}
diff --git a/viewpump/src/main/java/io/github/inflationx/viewpump/ViewPumpLayoutInflater.java b/viewpump/src/main/java/io/github/inflationx/viewpump/ViewPumpLayoutInflater.java
new file mode 100644
index 0000000..9e7e6d3
--- /dev/null
+++ b/viewpump/src/main/java/io/github/inflationx/viewpump/ViewPumpLayoutInflater.java
@@ -0,0 +1,436 @@
+package io.github.inflationx.viewpump;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+class ViewPumpLayoutInflater extends LayoutInflater implements ViewPumpActivityFactory {
+
+ private static final String[] sClassPrefixList = {
+ "android.widget.",
+ "android.webkit."
+ };
+
+ private FallbackViewCreator nameAndAttrsViewCreator;
+ private FallbackViewCreator parentAndNameAndAttrsViewCreator;
+
+ // Reflection Hax
+ private boolean mSetPrivateFactory = false;
+ private Field mConstructorArgs = null;
+
+ protected ViewPumpLayoutInflater(Context context) {
+ super(context);
+ nameAndAttrsViewCreator = new NameAndAttrsViewCreator(this);
+ parentAndNameAndAttrsViewCreator = new ParentAndNameAndAttrsViewCreator(this);
+ setUpLayoutFactories(false);
+ }
+
+ protected ViewPumpLayoutInflater(LayoutInflater original, Context newContext, final boolean cloned) {
+ super(original, newContext);
+ nameAndAttrsViewCreator = new NameAndAttrsViewCreator(this);
+ parentAndNameAndAttrsViewCreator = new ParentAndNameAndAttrsViewCreator(this);
+ setUpLayoutFactories(cloned);
+ }
+
+ @Override
+ public LayoutInflater cloneInContext(Context newContext) {
+ return new ViewPumpLayoutInflater(this, newContext, true);
+ }
+
+ // ===
+ // Wrapping goodies
+ // ===
+
+
+ @Override
+ public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
+ setPrivateFactoryInternal();
+ return super.inflate(parser, root, attachToRoot);
+ }
+
+ /**
+ * We don't want to unnecessary create/set our factories if there are none there. We try to be
+ * as lazy as possible.
+ */
+ private void setUpLayoutFactories(boolean cloned) {
+ if (cloned) return;
+ // If we are HC+ we get and set Factory2 otherwise we just wrap Factory1
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ if (getFactory2() != null && !(getFactory2() instanceof WrapperFactory2)) {
+ // Sets both Factory/Factory2
+ setFactory2(getFactory2());
+ }
+ }
+ // We can do this as setFactory2 is used for both methods.
+ if (getFactory() != null && !(getFactory() instanceof WrapperFactory)) {
+ setFactory(getFactory());
+ }
+ }
+
+ @Override
+ public void setFactory(Factory factory) {
+ // Only set our factory and wrap calls to the Factory trying to be set!
+ if (!(factory instanceof WrapperFactory)) {
+ super.setFactory(new WrapperFactory(factory, this));
+ } else {
+ super.setFactory(factory);
+ }
+ }
+
+ @Override
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public void setFactory2(Factory2 factory2) {
+ // Only set our factory and wrap calls to the Factory2 trying to be set!
+ if (!(factory2 instanceof WrapperFactory2)) {
+// LayoutInflaterCompat.setFactory(this, new WrapperFactory2(factory2, mViewPumpFactory));
+ super.setFactory2(new WrapperFactory2(factory2));
+ } else {
+ super.setFactory2(factory2);
+ }
+ }
+
+ private void setPrivateFactoryInternal() {
+ // Already tried to set the factory.
+ if (mSetPrivateFactory) return;
+ // Reflection (Or Old Device) skip.
+ if (!ViewPump.get().isReflection()) return;
+ // Skip if not attached to an activity.
+ if (!(getContext() instanceof Factory2)) {
+ mSetPrivateFactory = true;
+ return;
+ }
+
+ final Method setPrivateFactoryMethod = ReflectionUtils.getMethod(LayoutInflater.class, "setPrivateFactory");
+
+ if (setPrivateFactoryMethod != null) {
+ ReflectionUtils.invokeMethod(this,
+ setPrivateFactoryMethod,
+ new PrivateWrapperFactory2((Factory2) getContext(), this));
+ }
+ mSetPrivateFactory = true;
+ }
+
+ // ===
+ // LayoutInflater ViewCreators
+ // Works in order of inflation
+ // ===
+
+ /**
+ * The Activity onCreateView (PrivateFactory) is the third port of call for LayoutInflation.
+ * We opted to manual injection over aggressive reflection, this should be less fragile.
+ */
+ @Override
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public View onActivityCreateView(View parent, View view, String name, Context context, AttributeSet attrs) {
+ return ViewPump.get().inflate(InflateRequest.builder()
+ .name(name)
+ .context(context)
+ .attrs(attrs)
+ .parent(parent)
+ .fallbackViewCreator(new ActivityViewCreator(this, view))
+ .build()).view();
+ }
+
+ /**
+ * The LayoutInflater onCreateView is the fourth port of call for LayoutInflation.
+ * BUT only for none CustomViews.
+ */
+ @Override
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ protected View onCreateView(View parent, String name, AttributeSet attrs) throws ClassNotFoundException {
+ return ViewPump.get().inflate(InflateRequest.builder()
+ .name(name)
+ .context(getContext())
+ .attrs(attrs)
+ .parent(parent)
+ .fallbackViewCreator(parentAndNameAndAttrsViewCreator)
+ .build()).view();
+ }
+
+ /**
+ * The LayoutInflater onCreateView is the fourth port of call for LayoutInflation.
+ * BUT only for none CustomViews.
+ * Basically if this method doesn't inflate the View nothing probably will.
+ */
+ @Override
+ protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
+ return ViewPump.get().inflate(InflateRequest.builder()
+ .name(name)
+ .context(getContext()) // TODO: is this OK? before was fallbackView.getContext()
+ .attrs(attrs)
+ .fallbackViewCreator(nameAndAttrsViewCreator)
+ .build()).view();
+ }
+
+ /**
+ * Nasty method to inflate custom layouts that haven't been handled else where. If this fails it
+ * will fall back through to the PhoneLayoutInflater method of inflating custom views where
+ * ViewPump will NOT have a hook into.
+ *
+ * @param parent parent view
+ * @param view view if it has been inflated by this point, if this is not null this method
+ * just returns this value.
+ * @param name name of the thing to inflate.
+ * @param viewContext Context to inflate by if parent is null
+ * @param attrs Attr for this view which we can steal fontPath from too.
+ * @return view or the View we inflate in here.
+ */
+ private View createCustomViewInternal(View parent, View view, String name, Context viewContext, AttributeSet attrs) {
+ // I by no means advise anyone to do this normally, but Google have locked down access to
+ // the createView() method, so we never get a callback with attributes at the end of the
+ // createViewFromTag chain (which would solve all this unnecessary rubbish).
+ // We at the very least try to optimise this as much as possible.
+ // We only call for customViews (As they are the ones that never go through onCreateView(...)).
+ // We also maintain the Field reference and make it accessible which will make a pretty
+ // significant difference to performance on Android 4.0+.
+
+ // If CustomViewCreation is off skip this.
+ if (!ViewPump.get().isCustomViewCreation()) return view;
+ if (view == null && name.indexOf('.') > -1) {
+ if (mConstructorArgs == null)
+ mConstructorArgs = ReflectionUtils.getField(LayoutInflater.class, "mConstructorArgs");
+
+ final Object[] mConstructorArgsArr = (Object[]) ReflectionUtils.getValue(mConstructorArgs, this);
+ final Object lastContext = mConstructorArgsArr[0];
+ // The LayoutInflater actually finds out the correct context to use. We just need to set
+ // it on the mConstructor for the internal method.
+ // Set the constructor ars up for the createView, not sure why we can't pass these in.
+ mConstructorArgsArr[0] = viewContext;
+ ReflectionUtils.setValue(mConstructorArgs, this, mConstructorArgsArr);
+ try {
+ view = createView(name, null, attrs);
+ } catch (ClassNotFoundException ignored) {
+ } finally {
+ mConstructorArgsArr[0] = lastContext;
+ ReflectionUtils.setValue(mConstructorArgs, this, mConstructorArgsArr);
+ }
+ }
+ return view;
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ private View superOnCreateView(View parent, String name, AttributeSet attrs) {
+ try {
+ return super.onCreateView(parent, name, attrs);
+ } catch (ClassNotFoundException e) {
+ return null;
+ }
+ }
+
+ private View superOnCreateView(String name, AttributeSet attrs) {
+ try {
+ return super.onCreateView(name, attrs);
+ } catch (ClassNotFoundException e) {
+ return null;
+ }
+ }
+
+ // ===
+ // View creators
+ // ===
+
+ private static class ActivityViewCreator implements FallbackViewCreator {
+ private final ViewPumpLayoutInflater inflater;
+ private final View view;
+
+ public ActivityViewCreator(ViewPumpLayoutInflater inflater, View view) {
+ this.inflater = inflater;
+ this.view = view;
+ }
+
+ @Override
+ public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
+ return inflater.createCustomViewInternal(parent, view, name, context, attrs);
+ }
+ }
+
+ private static class ParentAndNameAndAttrsViewCreator implements FallbackViewCreator {
+ private final ViewPumpLayoutInflater inflater;
+
+ public ParentAndNameAndAttrsViewCreator(ViewPumpLayoutInflater inflater) {
+ this.inflater = inflater;
+ }
+
+ @Override
+ public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
+ return inflater.superOnCreateView(parent, name, attrs);
+ }
+ }
+
+ private static class NameAndAttrsViewCreator implements FallbackViewCreator {
+ private final ViewPumpLayoutInflater inflater;
+
+ public NameAndAttrsViewCreator(ViewPumpLayoutInflater inflater) {
+ this.inflater = inflater;
+ }
+
+ @Override
+ public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
+ // This mimics the {@code PhoneLayoutInflater} in the way it tries to inflate the base
+ // classes, if this fails its pretty certain the app will fail at this point.
+ View view = null;
+ for (String prefix : sClassPrefixList) {
+ try {
+ view = inflater.createView(name, prefix, attrs);
+ } catch (ClassNotFoundException ignored) {
+ }
+ }
+ // In this case we want to let the base class take a crack
+ // at it.
+ if (view == null) view = inflater.superOnCreateView(name, attrs);
+ return view;
+ }
+ }
+
+ // ===
+ // Wrapper Factories for Pre/Post HC
+ // ===
+
+ /**
+ * Factory 1 is the first port of call for LayoutInflation
+ */
+ private static class WrapperFactory implements Factory {
+
+ private final FallbackViewCreator mViewCreator;
+
+ public WrapperFactory(Factory factory, ViewPumpLayoutInflater inflater) {
+ mViewCreator = Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB
+ ? new PreHcWrapperFactoryViewCreator(factory, inflater)
+ : new WrapperFactoryViewCreator(factory);
+ }
+
+ @Override
+ public View onCreateView(String name, Context context, AttributeSet attrs) {
+ return ViewPump.get().inflate(InflateRequest.builder()
+ .name(name)
+ .context(context)
+ .attrs(attrs)
+ .fallbackViewCreator(mViewCreator)
+ .build()).view();
+ }
+ }
+
+ private static class WrapperFactoryViewCreator implements FallbackViewCreator {
+ protected final Factory mFactory;
+
+ public WrapperFactoryViewCreator(Factory factory) {
+ this.mFactory = factory;
+ }
+
+ @Override
+ public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
+ return mFactory.onCreateView(name, context, attrs);
+ }
+ }
+
+ private static class PreHcWrapperFactoryViewCreator extends WrapperFactoryViewCreator implements FallbackViewCreator {
+ protected final ViewPumpLayoutInflater mInflater;
+
+ public PreHcWrapperFactoryViewCreator(Factory factory, ViewPumpLayoutInflater inflater) {
+ super(factory);
+ mInflater = inflater;
+ }
+
+ @Override
+ public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
+ return mInflater.createCustomViewInternal(
+ null, mFactory.onCreateView(name, context, attrs), name, context, attrs);
+ }
+ }
+
+ /**
+ * Factory 2 is the second port of call for LayoutInflation
+ */
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ private static class WrapperFactory2 implements Factory2 {
+ protected final Factory2 mFactory2;
+ private final WrapperFactory2ViewCreator mViewCreator;
+
+ public WrapperFactory2(Factory2 factory2) {
+ mFactory2 = factory2;
+ mViewCreator = new WrapperFactory2ViewCreator(factory2);
+ }
+
+ @Override
+ public View onCreateView(String name, Context context, AttributeSet attrs) {
+ return onCreateView(null, name, context, attrs);
+ }
+
+ @Override
+ public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
+ return ViewPump.get().inflate(InflateRequest.builder()
+ .name(name)
+ .context(context)
+ .attrs(attrs)
+ .parent(parent)
+ .fallbackViewCreator(mViewCreator)
+ .build()).view();
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ private static class WrapperFactory2ViewCreator implements FallbackViewCreator {
+ protected final Factory2 mFactory2;
+
+ public WrapperFactory2ViewCreator(Factory2 factory2) {
+ this.mFactory2 = factory2;
+ }
+
+ @Override
+ public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
+ return mFactory2.onCreateView(parent, name, context, attrs);
+ }
+ }
+
+ /**
+ * Private factory is step three for Activity Inflation, this is what is attached to the
+ * Activity on HC+ devices.
+ */
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ private static class PrivateWrapperFactory2 extends WrapperFactory2 {
+
+ private final PrivateWrapperFactory2ViewCreator mViewCreator;
+
+ public PrivateWrapperFactory2(Factory2 factory2, ViewPumpLayoutInflater inflater) {
+ super(factory2);
+ mViewCreator = new PrivateWrapperFactory2ViewCreator(factory2, inflater);
+ }
+
+ @Override
+ public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
+ return ViewPump.get().inflate(InflateRequest.builder()
+ .name(name)
+ .context(context)
+ .attrs(attrs)
+ .parent(parent)
+ .fallbackViewCreator(mViewCreator)
+ .build()).view();
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ private static class PrivateWrapperFactory2ViewCreator extends WrapperFactory2ViewCreator implements FallbackViewCreator {
+ private final ViewPumpLayoutInflater mInflater;
+
+ public PrivateWrapperFactory2ViewCreator(Factory2 factory2, ViewPumpLayoutInflater mInflater) {
+ super(factory2);
+ this.mInflater = mInflater;
+ }
+
+ @Override
+ public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
+ return mInflater.createCustomViewInternal(parent,
+ mFactory2.onCreateView(parent, name, context, attrs), name, context, attrs);
+ }
+ }
+
+}
diff --git a/viewpump/src/main/res/values/ids.xml b/viewpump/src/main/res/values/ids.xml
new file mode 100644
index 0000000..8866d8d
--- /dev/null
+++ b/viewpump/src/main/res/values/ids.xml
@@ -0,0 +1,4 @@
+
+
If you disable this you will need to override your {@link android.app.Activity#onCreateView(View, String, android.content.Context, android.util.AttributeSet)} * as this is set as the {@link android.view.LayoutInflater} private factory.
@@ -100,7 +98,6 @@ public Builder addInterceptor(Interceptor interceptor) { * Use the following code in the Activity if you disable FactoryInjection: *
* {@literal @}Override
- * {@literal @}TargetApi(Build.VERSION_CODES.HONEYCOMB)
* public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
* return ViewPumpContextWrapper.onActivityCreateView(this, parent, super.onCreateView(parent, name, context, attrs), name, context, attrs);
* }
@@ -119,7 +116,6 @@ public Builder setPrivateFactoryInjectionEnabled(boolean enabled) {
* overrideable injection flow.
*
* On HoneyComb+ this is inside the {@link android.app.Activity#onCreateView(View, String, android.content.Context, android.util.AttributeSet)}
- * Pre HoneyComb this is in the {@link android.view.LayoutInflater.Factory#onCreateView(String, android.util.AttributeSet)}
*
* We wrap base implementations, so if you LayoutInflater/Factory/Activity creates the
* custom view before we get to this point, your view is used. (Such is the case with the
diff --git a/viewpump/src/main/java/io/github/inflationx/viewpump/ViewPumpLayoutInflater.java b/viewpump/src/main/java/io/github/inflationx/viewpump/ViewPumpLayoutInflater.java
index 9e7e6d3..6f9bf38 100644
--- a/viewpump/src/main/java/io/github/inflationx/viewpump/ViewPumpLayoutInflater.java
+++ b/viewpump/src/main/java/io/github/inflationx/viewpump/ViewPumpLayoutInflater.java
@@ -1,8 +1,6 @@
package io.github.inflationx.viewpump;
-import android.annotation.TargetApi;
import android.content.Context;
-import android.os.Build;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -64,11 +62,9 @@ public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)
private void setUpLayoutFactories(boolean cloned) {
if (cloned) return;
// If we are HC+ we get and set Factory2 otherwise we just wrap Factory1
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- if (getFactory2() != null && !(getFactory2() instanceof WrapperFactory2)) {
- // Sets both Factory/Factory2
- setFactory2(getFactory2());
- }
+ if (getFactory2() != null && !(getFactory2() instanceof WrapperFactory2)) {
+ // Sets both Factory/Factory2
+ setFactory2(getFactory2());
}
// We can do this as setFactory2 is used for both methods.
if (getFactory() != null && !(getFactory() instanceof WrapperFactory)) {
@@ -80,14 +76,13 @@ private void setUpLayoutFactories(boolean cloned) {
public void setFactory(Factory factory) {
// Only set our factory and wrap calls to the Factory trying to be set!
if (!(factory instanceof WrapperFactory)) {
- super.setFactory(new WrapperFactory(factory, this));
+ super.setFactory(new WrapperFactory(factory));
} else {
super.setFactory(factory);
}
}
@Override
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void setFactory2(Factory2 factory2) {
// Only set our factory and wrap calls to the Factory2 trying to be set!
if (!(factory2 instanceof WrapperFactory2)) {
@@ -109,6 +104,7 @@ private void setPrivateFactoryInternal() {
return;
}
+ // TODO: we need to get this and wrap it if something has already set this
final Method setPrivateFactoryMethod = ReflectionUtils.getMethod(LayoutInflater.class, "setPrivateFactory");
if (setPrivateFactoryMethod != null) {
@@ -129,7 +125,6 @@ private void setPrivateFactoryInternal() {
* We opted to manual injection over aggressive reflection, this should be less fragile.
*/
@Override
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
public View onActivityCreateView(View parent, View view, String name, Context context, AttributeSet attrs) {
return ViewPump.get().inflate(InflateRequest.builder()
.name(name)
@@ -145,7 +140,6 @@ public View onActivityCreateView(View parent, View view, String name, Context co
* BUT only for none CustomViews.
*/
@Override
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
protected View onCreateView(View parent, String name, AttributeSet attrs) throws ClassNotFoundException {
return ViewPump.get().inflate(InflateRequest.builder()
.name(name)
@@ -165,7 +159,7 @@ protected View onCreateView(View parent, String name, AttributeSet attrs) throws
protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
return ViewPump.get().inflate(InflateRequest.builder()
.name(name)
- .context(getContext()) // TODO: is this OK? before was fallbackView.getContext()
+ .context(getContext())
.attrs(attrs)
.fallbackViewCreator(nameAndAttrsViewCreator)
.build()).view();
@@ -217,7 +211,6 @@ private View createCustomViewInternal(View parent, View view, String name, Conte
return view;
}
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
private View superOnCreateView(View parent, String name, AttributeSet attrs) {
try {
return super.onCreateView(parent, name, attrs);
@@ -281,6 +274,9 @@ public View onCreateView(View parent, String name, Context context, AttributeSet
for (String prefix : sClassPrefixList) {
try {
view = inflater.createView(name, prefix, attrs);
+ if (view != null) {
+ break;
+ }
} catch (ClassNotFoundException ignored) {
}
}
@@ -292,7 +288,7 @@ public View onCreateView(View parent, String name, Context context, AttributeSet
}
// ===
- // Wrapper Factories for Pre/Post HC
+ // Wrapper Factories
// ===
/**
@@ -302,10 +298,8 @@ private static class WrapperFactory implements Factory {
private final FallbackViewCreator mViewCreator;
- public WrapperFactory(Factory factory, ViewPumpLayoutInflater inflater) {
- mViewCreator = Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB
- ? new PreHcWrapperFactoryViewCreator(factory, inflater)
- : new WrapperFactoryViewCreator(factory);
+ public WrapperFactory(Factory factory) {
+ mViewCreator = new WrapperFactoryViewCreator(factory);
}
@Override
@@ -332,25 +326,9 @@ public View onCreateView(View parent, String name, Context context, AttributeSet
}
}
- private static class PreHcWrapperFactoryViewCreator extends WrapperFactoryViewCreator implements FallbackViewCreator {
- protected final ViewPumpLayoutInflater mInflater;
-
- public PreHcWrapperFactoryViewCreator(Factory factory, ViewPumpLayoutInflater inflater) {
- super(factory);
- mInflater = inflater;
- }
-
- @Override
- public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
- return mInflater.createCustomViewInternal(
- null, mFactory.onCreateView(name, context, attrs), name, context, attrs);
- }
- }
-
/**
* Factory 2 is the second port of call for LayoutInflation
*/
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
private static class WrapperFactory2 implements Factory2 {
protected final Factory2 mFactory2;
private final WrapperFactory2ViewCreator mViewCreator;
@@ -377,7 +355,6 @@ public View onCreateView(View parent, String name, Context context, AttributeSet
}
}
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
private static class WrapperFactory2ViewCreator implements FallbackViewCreator {
protected final Factory2 mFactory2;
@@ -392,10 +369,8 @@ public View onCreateView(View parent, String name, Context context, AttributeSet
}
/**
- * Private factory is step three for Activity Inflation, this is what is attached to the
- * Activity on HC+ devices.
+ * Private factory is step three for Activity Inflation, this is what is attached to the Activity
*/
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
private static class PrivateWrapperFactory2 extends WrapperFactory2 {
private final PrivateWrapperFactory2ViewCreator mViewCreator;
@@ -417,7 +392,6 @@ public View onCreateView(View parent, String name, Context context, AttributeSet
}
}
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
private static class PrivateWrapperFactory2ViewCreator extends WrapperFactory2ViewCreator implements FallbackViewCreator {
private final ViewPumpLayoutInflater mInflater;
diff --git a/viewpump/src/main/res/values/ids.xml b/viewpump/src/main/res/values/ids.xml
index 8866d8d..4d0b9c5 100644
--- a/viewpump/src/main/res/values/ids.xml
+++ b/viewpump/src/main/res/values/ids.xml
@@ -1,4 +1,4 @@
-
\ No newline at end of file
+