Skip to content
This repository has been archived by the owner on Jun 1, 2023. It is now read-only.

Commit

Permalink
Trigger layout hooks also if the layout was included
Browse files Browse the repository at this point in the history
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
rovo89 committed Nov 10, 2013
1 parent 60fe6fe commit 5ac7b31
Showing 1 changed file with 45 additions and 3 deletions.
48 changes: 45 additions & 3 deletions src/android/content/res/XResources.java
Expand Up @@ -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;
Expand All @@ -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.

Copy link
@Tungstwenty

Tungstwenty Nov 19, 2013

Contributor

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?

This comment has been minimized.

Copy link
@rovo89

rovo89 Nov 19, 2013

Author Owner

That's a good hint. I will have to double-check it. In my tests, it always worked fine, but that might have happened in a single thread.

This comment has been minimized.

Copy link
@rovo89

rovo89 Nov 19, 2013

Author Owner

Should be fixed in 62ed477. I didn't try to enforce a crash, but overriding the initialValue() should be the correct way without overhead.

}

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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
}
}
}
Expand Down

0 comments on commit 5ac7b31

Please sign in to comment.