-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Replacing resources
Xposed makes it easy to replace resources, for example images or strings. Here is how:
@Override
public void initZygote(IXposedHookZygoteInit.StartupParam startupParam) throws Throwable {
XResources.setSystemWideReplacement("android", "bool", "config_unplugTurnsOnScreen", false);
}
@Override
public void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable {
// replacements only for SystemUI
if (!resparam.packageName.equals("com.android.systemui"))
return;
// different ways to specify the resources to be replaced
resparam.res.setReplacement(0x7f080083, "YEAH!"); // WLAN toggle text. You should not do this because the id is not fixed. Only for framework resources, you could use android.R.string.something
resparam.res.setReplacement("com.android.systemui:string/quickpanel_bluetooth_text", "WOO!");
resparam.res.setReplacement("com.android.systemui", "string", "quickpanel_gps_text", "HOO!");
resparam.res.setReplacement("com.android.systemui", "integer", "config_maxLevelOfSignalStrengthIndicator", 6);
}
This is for "simple" replacements where you can give the replacement value directly. This works for: Boolean, Color, Integer, int[], String and String[].
As you can see, there are a few different ways to set a replacement resource. For those resources that are part of the Android framework (available to all apps) and that should be replaced everywhere, you call XResources.setSystemWideReplacement(...)
in the initZygote
method.
For app-specific resources, you need to call res.setReplacement
in hookInitPackageResources
after verifying that you are in the correct app. You should not use setSystemWideReplacement
at this time as it might have side-effects you didn't expect.
Replacing Drawables works similar. However, you can't just use a Drawable as replacement because this might result in the same instance of the Drawable being referenced by different ImageViews. Therefore, you need to use a wrapper:
resparam.res.setReplacement("com.android.systemui", "drawable", "status_bar_background", new XResources.DrawableLoader() {
@Override
public Drawable newDrawable(XResources res, int id) throws Throwable {
return new ColorDrawable(Color.WHITE);
}
});
More complex resources (like animated Drawables) have to be referenced from your module's resources. Let's say you want to replace the battery icon. Here is the code:
package de.robv.android.xposed.mods.coloredcirclebattery;
import android.content.res.XModuleResources;
import de.robv.android.xposed.IXposedHookInitPackageResources;
import de.robv.android.xposed.IXposedHookZygoteInit;
import de.robv.android.xposed.callbacks.XC_InitPackageResources.InitPackageResourcesParam;
public class ColoredCircleBattery implements IXposedHookZygoteInit, IXposedHookInitPackageResources {
private static String MODULE_PATH = null;
@Override
public void initZygote(StartupParam startupParam) throws Throwable {
MODULE_PATH = startupParam.modulePath;
}
@Override
public void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable {
if (!resparam.packageName.equals("com.android.systemui"))
return;
XModuleResources modRes = XModuleResources.createInstance(MODULE_PATH, resparam.res);
resparam.res.setReplacement("com.android.systemui", "drawable", "stat_sys_battery", modRes.fwd(R.drawable.battery_icon));
resparam.res.setReplacement("com.android.systemui", "drawable", "stat_sys_battery_charge", modRes.fwd(R.drawable.battery_icon_charge));
}
}
You can name your replacement resources as you like. I chose
battery_icon
overstat_sys_battery
to make them easier to distinguish in this text.
Then you add the Drawables "battery_icon" and "battery_icon_charge" to your module. In the easiest case, this means you add "res/drawables/battery_icon.png" and "res/drawables/battery_icon_charge.png", but you can use all the ways Android provides to define resources. So for animated icons, you would use XML files with an animation-list
and references to other Drawables, which of course have to be in your module as well.
With these replacements, you ask Xposed to forward all requests for a certain resource to your own module. So whatever you can do with resources in your own app should work as a replacement as well. This also means that you can make use of qualifiers, for example if you need different resources for landscape or lower densities. Translations could be provided the same way. Also you will probably need this if the original resource uses qualifiers. You can't replace only the Spanish version of a text. As mentioned, the request is forwarded, so it's completely handled by your module's resources, which doesn't know that other translations exists.
This technique should work with basically all the resource type, except for special things like themes.
Although you could theoretically replace layouts completely with the technique described above, this has many downsides. You have to copy the complete layout from the original, which reduces compatibility with other ROMs. Themes might be lost. Only one module can replace a layout. If two modules try it, the last one will win. Most important, IDs and references to other resources are pretty hard to define. Therefore, I really don't recommend this.
As a good alternative, you can use post-inflate hooks. This is what you do:
@Override
public void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable {
if (!resparam.packageName.equals("com.android.systemui"))
return;
resparam.res.hookLayout("com.android.systemui", "layout", "status_bar", new XC_LayoutInflated() {
@Override
public void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable {
TextView clock = (TextView) liparam.view.findViewById(
liparam.res.getIdentifier("clock", "id", "com.android.systemui"));
clock.setTextColor(Color.RED);
}
});
}
The callback handleLayoutInflated
is called whenever the layout "status_bar" has been inflated. Inside the LayoutInflatedParam object that you get as a parameter, you can find the just created view and can modify it as needed. You also get resNames
to identify which layout the method was called for (in case you are using the same method for multiple layouts) and variant
, which might for example contain layout-land
if that it is the version of the layout that was loaded. res
helps you to get IDs or additonal resources from the same source as the layout.