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

Replacing resources

rovo89 edited this page Feb 10, 2013 · 5 revisions

Xposed makes it easy to replace resources, for example images or strings. Here is how:

Simple resources

@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);
	}
});

Complex resources

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 over stat_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.

Modifying layouts

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.

Clone this wiki locally