feat(android): add monochrome themed launcher icon (#4733) (#4736)

Android 13+ recolors the adaptive icon's `<monochrome>` layer with a
wallpaper-derived tint when the user enables themed icons. Support was added
in #2122/#2153 (the `ic_launcher_monochrome.png` assets) but #2353 ("fixed
launcher icon size") rewrote the committed adaptive icon to inset the
foreground 22% and silently dropped the `<monochrome>` layer, so themed icons
stopped working in shipped builds.

Restore it by re-adding a `<monochrome>` layer (same 22% inset as the
foreground) and shipping the monochrome mipmaps. The CI/release flow
regenerates `gen/android` (`tauri android init` + `tauri icon`) then
`git checkout .` to restore tracked customizations; `tauri icon` does not emit
a monochrome layer, so the mipmaps are force-committed under `gen/` like the
other customized resources.

The monochrome artwork is redesigned: Android tints the layer via SRC_IN
(alpha only), which flattened the old desaturated-logo asset into a solid
blob. A narrow vertical center gap now splits the open book into two pages
with a visible spine while keeping the bookmark, so the mark keeps its
character when themed.

Verified on a Pixel 9 Pro emulator (Android 36) with Themed Icons enabled, and
guarded by src/__tests__/android/themed-icon.test.ts.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Huang Xin
2026-06-23 00:44:57 +08:00
committed by GitHub
parent bc9b8b23e6
commit 664b6125a2
12 changed files with 54 additions and 0 deletions
@@ -6,4 +6,9 @@
android:drawable="@mipmap/ic_launcher_foreground"
android:inset="22%"/>
</foreground>
<monochrome>
<inset
android:drawable="@mipmap/ic_launcher_monochrome"
android:inset="22%"/>
</monochrome>
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 17 KiB

@@ -0,0 +1,49 @@
import { describe, it, expect } from 'vitest';
import { existsSync, readFileSync } from 'fs';
import { resolve } from 'path';
/**
* Regression guard for the Android themed ("Material You" / monochrome) launcher
* icon (issue #4733).
*
* Android 13+ recolors the adaptive icon's `<monochrome>` layer with a
* wallpaper-derived tint when the user enables themed icons. Support for it was
* originally added in #2122/#2153 (the `ic_launcher_monochrome.png` assets) and
* wired into the source adaptive icon, but #2353 ("fixed Android launcher icon
* size") rewrote the *committed* `gen/` adaptive icon to inset the foreground
* and silently dropped the `<monochrome>` layer — so themed icons stopped
* working in shipped builds.
*
* The CI/release flow regenerates `gen/android` from scratch
* (`tauri android init` + `tauri icon`) and then `git checkout .` to restore the
* *tracked* customizations. `tauri icon` does not emit a monochrome layer, so
* the themed icon only survives if both the adaptive-icon XML and the
* monochrome mipmaps are tracked under `gen/`.
*
* Invariants:
* 1. The committed adaptive icon declares a `<monochrome>` layer pointing at
* `@mipmap/ic_launcher_monochrome`.
* 2. A tracked `ic_launcher_monochrome.png` exists for every launcher density
* so the resource resolves at build time (these are force-added past the
* `gen/` .gitignore, like the other customized resources).
*/
const appRoot = process.cwd();
const resRoot = resolve(appRoot, 'src-tauri/gen/android/app/src/main/res');
const DENSITIES = ['mdpi', 'hdpi', 'xhdpi', 'xxhdpi', 'xxxhdpi'];
describe('Android themed (monochrome) launcher icon', () => {
it('declares a <monochrome> layer in the adaptive icon', () => {
const xml = readFileSync(resolve(resRoot, 'mipmap-anydpi-v26/ic_launcher.xml'), 'utf8');
expect(xml).toMatch(/<monochrome\b/);
expect(xml).toMatch(/@mipmap\/ic_launcher_monochrome/);
});
it('ships a tracked monochrome mipmap for every density', () => {
const missing = DENSITIES.filter(
(d) => !existsSync(resolve(resRoot, `mipmap-${d}`, 'ic_launcher_monochrome.png')),
);
expect(missing, `missing tracked monochrome mipmaps for: ${missing.join(', ')}`).toEqual([]);
});
});