This repository has been archived by the owner on Jun 1, 2023. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 <merge> 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.
- Loading branch information
Showing
1 changed file
with
45 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,19 +6,22 @@ | |
|
||
import java.io.File; | ||
import java.util.HashMap; | ||
import java.util.LinkedList; | ||
import java.util.WeakHashMap; | ||
|
||
import org.xmlpull.v1.XmlPullParser; | ||
|
||
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<HashMap<String, CopyOnWriteSortedSet<XC_LayoutInflated>>>(); | ||
private static final WeakHashMap<XmlResourceParser, XMLInstanceDetails> xmlInstanceDetails | ||
= new WeakHashMap<XmlResourceParser, XMLInstanceDetails>(); | ||
|
||
|
||
private static final String EXTRA_XML_INSTANCE_DETAILS = "xmlInstanceDetails"; | ||
private static final ThreadLocal<LinkedList<MethodHookParam>> sIncludedLayouts = new ThreadLocal<LinkedList<MethodHookParam>>(); | ||
static { | ||
sIncludedLayouts.set(new LinkedList<MethodHookParam>()); | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
rovo89
Author
Owner
|
||
} | ||
|
||
private static final HashMap<String, Long> resDirLastModified = new HashMap<String, Long>(); | ||
private static final HashMap<String, String> resDirPackageNames = new HashMap<String, String>(); | ||
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<String, ResourceNames> 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); | ||
} | ||
} | ||
} | ||
|
Is this initialization working properly?
From reading the code I'd say that "sIncludedLayouts.get()" calls below will return null if ran from a thread other than the one that performed this static initialization block, no?