From 5ac7b312c821b774c0b95a7d4c141631390bdcd9 Mon Sep 17 00:00:00 2001 From: rovo89 Date: Sun, 10 Nov 2013 18:28:24 +0100 Subject: [PATCH] Trigger layout hooks also if the layout was included Previously, the callback was only triggered for the root layout because only for this the inflate() function is called. The solution is actually a bit tricky because the XML parser for the included layout is a local variable. But as we hook the creation of the parser, we can check if it was created in inside parseInclude() and remember the details for it. This is done with a LinkedList (for recursive calls) inside a ThreadLocal. It's assumed that the included view is the last one. This is almost always the case, except if it's a layout, which doesn't have its own root view but merges into the parent. In this case, it's as easy as calling liparam.view.getParent() to get the container into which all the views have been merged. --- src/android/content/res/XResources.java | 48 +++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/android/content/res/XResources.java b/src/android/content/res/XResources.java index d723f18a..47b95d0f 100644 --- a/src/android/content/res/XResources.java +++ b/src/android/content/res/XResources.java @@ -6,6 +6,7 @@ import java.io.File; import java.util.HashMap; +import java.util.LinkedList; import java.util.WeakHashMap; import org.xmlpull.v1.XmlPullParser; @@ -13,12 +14,14 @@ import android.graphics.Movie; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.util.AttributeSet; import android.util.SparseArray; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import de.robv.android.xposed.XC_MethodHook; +import de.robv.android.xposed.XC_MethodHook.MethodHookParam; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet; import de.robv.android.xposed.callbacks.XC_LayoutInflated; @@ -37,7 +40,13 @@ public class XResources extends MiuiResources { = new SparseArray>>(); private static final WeakHashMap xmlInstanceDetails = new WeakHashMap(); - + + private static final String EXTRA_XML_INSTANCE_DETAILS = "xmlInstanceDetails"; + private static final ThreadLocal> sIncludedLayouts = new ThreadLocal>(); + static { + sIncludedLayouts.set(new LinkedList()); + } + private static final HashMap resDirLastModified = new HashMap(); private static final HashMap resDirPackageNames = new HashMap(); private boolean inited = false; @@ -140,6 +149,33 @@ protected void afterHookedMethod(MethodHookParam param) throws Throwable { } } }); + + findAndHookMethod(LayoutInflater.class, "parseInclude", XmlPullParser.class, View.class, AttributeSet.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + sIncludedLayouts.get().push(param); + } + + @Override + protected void afterHookedMethod(MethodHookParam param) throws Throwable { + sIncludedLayouts.get().pop(); + + if (param.hasThrowable()) + return; + + // filled in by our implementation of loadXmlResourceParser + XMLInstanceDetails details = (XMLInstanceDetails) param.getObjectExtra(EXTRA_XML_INSTANCE_DETAILS); + if (details != null) { + LayoutInflatedParam liparam = new LayoutInflatedParam(details.callbacks); + ViewGroup group = (ViewGroup) param.args[1]; + liparam.view = group.getChildAt(group.getChildCount() - 1); + liparam.resNames = details.resNames; + liparam.variant = details.variant; + liparam.res = details.res; + XCallback.callAll(liparam); + } + } + }); } public static class ResourceNames { @@ -551,13 +587,19 @@ XmlResourceParser loadXmlResourceParser(int id, String type) throws NotFoundExce } else { XposedBridge.log(new NotFoundException("Could not find file name for resource id 0x") + Integer.toHexString(id)); } - + synchronized (xmlInstanceDetails) { synchronized (resourceNames) { HashMap resNamesInner = resourceNames.get(id); if (resNamesInner != null) { synchronized (resNamesInner) { - xmlInstanceDetails.put(result, new XMLInstanceDetails(resNamesInner.get(resDir), variant, callbacks)); + XMLInstanceDetails details = new XMLInstanceDetails(resNamesInner.get(resDir), variant, callbacks); + xmlInstanceDetails.put(result, details); + + // if we were called inside LayoutInflater.parseInclude, store the details for it + MethodHookParam top = sIncludedLayouts.get().peek(); + if (top != null) + top.setObjectExtra(EXTRA_XML_INSTANCE_DETAILS, details); } } }