Adventures in Launcherland: Modding the Pixel Launcher without actually changing the APK (or using Xposed)

Kieron Quinn
11 min readMar 21, 2022

--

Just under 4 years ago I released Pixel Launcher Mods (PLM), a root app that was able to change app icons and labels in the Pixel Launcher, amongst other features like resizing widgets beyond their usual bounds and hiding the clock from the status bar when on the home screen.

In the years since releasing this app, the Pixel Launcher has changed a fair bit, but it still does not support any of these features. The advent of themed icons changed the icon format (twice!), and the codebase for PLM was old and no longer fit for purpose. I’ve completely rebuilt the app — targeting these new formats, adding new features while maintaining the old ones too. And yet, even with these new changes, only root & Magisk is required. No Xposed is needed for this app — but how, you may ask?

tl;dr: Lots of hidden code in Launcher3, you can download Pixel Launcher Mods at my GitHub, and see the XDA thread here. You can also follow me on Twitter @Quinny898 if you wish.

Launcher3 & Android Launchers

Launcher3 is the AOSP launcher app. As the name suggests, it’s actually the third major iteration of the AOSP launcher app, the original Launcher (com.android.launcher) was used until Android 2.1, then Launcher2 (com.android.launcher2) until 4.3, and Launcher3 (com.android.launcher3) since then. Most third party and OEM launchers are based on these AOSP launcher apps, nowadays most are built upon Launcher3, including the Pixel Launcher.

This is useful for modding the launcher, as it means the source code is — at least partially — available to view in AOSP. Obviously, the parts of the Pixel Launcher that are custom are not open source (and the Pixel Launcher APK is obfuscated, making code reading and modifications more difficult), but enough of the base is to be able to be useful.

Icon & Label Modification

This hasn’t changed much since the original PLM app. Launcher3 needs to be fast, so to achieve this, it caches app icons and labels. Icon loading in Android can be quite slow — especially for large numbers of apps — and even calls to get app info require calling the system server via Interprocess Communication (IPC), which can sometimes take a short period of time, which would cause lag or loading times.

Instead of doing this every time the apps are shown, Launcher3, as well as a few other apps in Android, use a library called iconloaderlib. This library is responsible for loading icons, converting them to adaptive icons (if required), and caching all this data in a local database, in memory and/or on disk. In the case of the Pixel Launcher, this database is stored on disk and can be found at:

/data/data/com.google.android.apps.nexuslauncher/databases/app_icons.db

Yes, the Pixel Launcher’s package name is nexuslauncher . Google can never escape the Nexus brand.

Within this database, in the icons table, we can find a number of columns:

app_icons.db’s icons table on Android 12, showing the entry for BBC iPlayer

Of interest to us are the componentName , which is the component of the launcher activity which will be shown to the user; the icon , which has a “blob” of data containing the launcher icon; and the label, which is the label of the activity that is shown to the user.

Editing the database, and then restarting the launcher, is enough to change the label or icons (including adding a themed icon). Restarting the launcher is necessary as the cache is also stored in-memory, and there is seemingly no way to clear the in-memory cache without also clearing the database cache.

However, it’s not quite that simple. Notice how the database also keeps track of when the app was last updated. This value is taken from PackageInfo’s lastUpdateTime, and is used in combination with a LauncherApps Callback to know when an app has changed (eg. it has updated), and will regenerate the icon. PLM has always had to play a cat-and-mouse game of updating the icon after the launcher has reset it back to the default, and restart the launcher, but with the introduction of Quick Step (where the launcher also handles the recent apps UI), this can be quite jarring to the user as it renders navigation gestures briefly useless, and can kill the recents mid-use.

In PLM’s rebuild, this was worked around by simply not restarting the launcher until it’s not visible to the user. By using a ProcessObserver, the app knows when the Pixel Launcher (or recents) is visible, and will delay restarting until it’s not — and even has the option to not restart unless the screen is off for the best chance of not interrupting. Easy.

Icon Formats & Themed Icons

In Android 13, Google are introducing Themed App Icons, where developers can specify an icon that will be tinted to a single colour, and automatically displayed on a “plate” of the system colour (both chosen by Material You’s Monet). A common response to this was that it was “already done in the Pixel Launcher in Android 12”, but in reality it never really was, as such.

In Android 12’s Pixel Launcher, there are themed icons. For some Google apps. It is not possible for developers to implement their own themed icons, without making that their only icon (or using icon changing hacks — which I won’t go into). The themed icons implementation in 12 was literally a static, XML list of icon references of icons within the Pixel Launcher.

A complete list of all the themed icons the Pixel Launcher in 12L can ever show

Strangely, this data is actually also stored in the icon database. Perhaps Google decided that it would be too costly in performance to have to re-read this XML file every time the launcher reloaded, or maybe they originally had other plans for allowing custom icons on the launcher-level, but either way the icon format in the database is different to a normal PNG on 12, whereas before that all icons were stored as bytes of a PNG in the database. The format in 12 is as follows:

[icon type][normal icon PNG][optional UTF-8 string resource name]
  • The icon type is either 1 for a normal icon, or 2 for a themed icon, and tells the parser how to handle the rest of the icon.
  • The normal icon PNG is always present and contains the regular icon, same as before.
  • The optional resource name is present only for themed icons with the type set to 2 . It contains a fully formed resource identifier, such as com.google.android.apps.nexuslauncher:drawable/com.google.android.apps.tasks for Google Tasks.

By changing the icon to reference a different resource, we can change (or add) a themed icon. However, it is not possible to reference an icon outside of the Pixel Launcher’s APK, so we can’t use icon packs here — only for the normal icons. PLM allows you to set any app’s themed icon to any of the included themed icon list on Android 12. You can also get Magisk modules that add more icons to this list using Overlays, such as this one.

In Android 13, however, the icon column is split into two — icon and mono_icon . The icon column behaves the same as in pre-Android 12, storing a compressed PNG of the normal icon, but the mono icon column stores a raw image, in ALPHA_8 format, with no colour. We can change this to our own image, in the same format, which will be used as the themed icon. The same automatic re-setting game applies as before.

App Shortcuts

App Shortcuts are treated by the Pixel Launcher very similarly to apps. This makes sense, as they have much of the same information — info on what to do when pressed, a label, an icon (plus a mono icon on 13) and some other basic data. What is slightly different however, is that the label is updated every time the launcher restarts, before the apps are shown. This means that labels for app shortcuts cannot be edited, only their icons. There’s also currently an issue (or maybe a feature) in the Pixel Launcher in Android 13 where app shortcuts will not show their themed icon, except when being dragged. This cannot be fixed by PLM.

Widget Resizing

Widget Resizing (outside of their original bounds), allows you to — as the name suggests — resize a widget to the smallest size (1x1) or the biggest size (up to 5x5 depending on your grid) available on your screen, regardless of what the widget’s developer has specified. This has mixed results — it breaks some widgets, but works fine with others.

The implementation of this is very simple. Home screen configuration is stored in a database in the same directory as the app icons’ one, with the name of either launcher.db (for 5x5) or launcher_x_by_y.db for other grid sizes. Within that database there is a favorites table, containing apps & widgets, with widgets specifying a value for spanX and spanY . Changing these values and restarting the launcher is sufficient to change their size, with no re-applying after updates required.

Hiding Apps

Hiding apps is a brand new feature in PLM, and is something that was previously thought to be impossible — without using Xposed or modding the APK anyway. For it to work, PLM uses a Runtime Resource Overlay (RRO), the same system used by Substratum and similar to theme apps. It generates them dynamically, using a prebuilt version of the build tool aapt2 (originally intended for use in Termux).

To achieve this, first a Magisk module has to be installed — overlays must be installed to the system to work (even when installed by root), so this is a requirement. PLM will offer to save the module for you when you try to use a feature requiring it. The module simply adds an empty ‘stub’ overlay to the system, which can be installed on top of by PLM.

Next, after selecting apps to hide, the app will generate an overlay containing an array resource called filtered_components , for example:

An auto-generated filtered components resource for hiding BBC iPlayer from the app list

This overlay is then installed (without needing to reinstall the Magisk module — it can install as normal on top) and the launcher restarted, the app will then be hidden.

How does this work? Well, there’s a little-known feature in Launcher3 which “filters” apps based on this hardcoded list of components — AppFilter. Normally, it’s not surfaced at all to the user, in fact it’s only used by the Pixel Launcher to hide some components from the Android emulator (which also includes the Pixel Launcher), and nothing else. By using a dynamic overlay, the list can be changed, and apps hidden without the need for Xposed.

Replacing At a Glance / Search Box

At a Glance is a feature that is fairly unique to the Pixel Launcher. It’s intended to surface useful information, such as calendar events, weather, current activities etc.. Previously it was built in to the Pixel Launcher and data was gathered via the Google app (this is confusingly still available in a widget of the same name), but in Android 12 this changed with the addition of “Smartspace”, a system API that handles this task instead, with the actual AI for deciding which information should be shown residing in the system app Android System Intelligence (com.google.android.as), and the Pixel Launcher using an entirely custom component rather than a widget to display it — as widgets have limited support for which views can be used.

Similarly, the search box in the Pixel Launcher is also a custom view. It may look like a simple widget, but to handle features like animated Google Doodles, and better implement system themes, it requires something more custom.

However, this is one of the custom additions to the Pixel Launcher. The Quick Search Box widget (Google Search) can be traced back to early versions of Android, when it looked very different:

The Quick Search Box on Android 1.6, in the original Launcher (Image via Wikipedia, Apache 2.0 license)

Launcher3’s implementation of the search box uses this widget, via an AppWidgetHostView, in a custom view called QsbContainerView. It’s no longer used in the Pixel Launcher — but the code remains. Normally, that would be useless to us, except for it having an odd quirk.

Because Launcher3 is designed to be a base used by OEMs, Google added an option to change the search widget shown in this QsbContainerView. Oddly, this change is not made to be hardcoded, rather set by a system setting (Settings.Global.SEARCH_PROVIDER_PACKAGE_NAME), meaning a root app can change which package to load the widget from. As far as I can tell, this has never been used by any OEM, but by creating an Overlay that replaces the At a Glance or custom Search Box layouts to point to the original Launcher3 widget-based search box implementation, we can use it in PLM to place a custom widget in place of either at a glance or the search box (it only supports one widget at a time, so unless you want two of the same widget, it’s one or the other).

There is, however, one final hurdle. The given search widget must have the widget category of WIDGET_CATEGORY_SEARCHBOX, or it will be rejected. Widget categories were previously used to specify which widgets should be shown on the lock screen, but it is very very rare to find one that has the search box category, so that should be the nail in the coffin for this idea. Unless…

Proxying a Widget

Widgets in Android are an odd business. They always have been, but hopefully not always will be (Jetpack Compose is looking to make them easier to work with). Traditionally, to create a widget, you must use RemoteViews, a specific subset of Views that can be “described” to a Launcher, which will then render them. You’re limited to some basic views: text, images, lists, nothing custom. All changes to your Views must be done via RemoteViews, using an AppWidgetProvider (which is basically a BroadcastReceiver and isn’t always running).

Now, because RemoteViews are just passing on a description of how to draw a set of views, would it be possible to create a widget whose sole purpose is to host and pass another widget’s RemoteViews along?

The Pixel Launcher on Android 13 showing a BBC Weather widget rather than At a Glance

Yes, yes it is.

This is done, as mentioned above, with a “Proxy Widget” — a widget that fits the requirements set out by Launcher3 (ie. it’s got the category of searchbox, as well as a couple of other requirements), and that in turn hosts another widget and passes its views along. This is incredibly hacky and it’s amazing it even works — most of the time anyway. Some widgets are too modern and simply refuse to work, but some, including the far more feature packed At a Glance replacement Another Widget, work perfectly. To satisfy the requirement for widget hosts to not always be “listening” for changes (to save battery), PLM will also use the same system that is used to know when not to restart the launcher to stop listening when the launcher isn’t visible.

Hiding the Clock

And finally, a little nice-to-have feature that was in the original PLM and survived the rebuild. Inspired by an option in Nova Launcher, PLM includes an option to hide the status bar clock when the Pixel Launcher is showing. This can be used for minimalism, especially if you have a clock widget front-and-centre and don’t like duplication. It works by changing the system setting for hiding “icons” from the status bar: (Settings.Secure.icon_blacklist ), either adding or removing “clock” from the list to hide or show the clock. Once again, the same system as the restart check is used to know whether the launcher is showing, with the side effect that the clock will also be hidden when the app drawer is open or the recents showing — use wisely.

-

That’s all for now. Pixel Launcher Mods is available at my GitHub, and you can read the XDA thread here. You can also follow me on Twitter @Quinny898 if you wish.

Hopefully the worst fast-food based game shitpost edit you’ll see today. If you really want this CD-ROM, it’s available on the Internet Archive

--

--