ICON - TEST - legacy icon + gradle script to preserve legacy icon

This commit is contained in:
Alex Sim committed 2026-09-17 02:12:01 +02:00
1 parent 10ea49884a
commit a3f94f2cf8
7 files changed
+914 -17

No files matched your search

+2
View File
@@ -39,6 +39,8 @@ def frameworkStubs16 = resolveJitpackStub('16-r2')
def frameworkStubs17 = resolveJitpackStub('17-r2')
apply plugin: 'com.google.protobuf'
// Keep the launcher icons' anydpi-v26 versioned so pre-Oreo lookups ("force legacy icons") get the legacy icon
apply from: 'gradle/legacy-icon.gradle'
android {
def (sdkMajor, sdkMinor) = COMPILE_SDK.tokenize('.')
+766
View File
@@ -0,0 +1,766 @@
/*
* Keeps the launcher icons' "-v26" qualifier in the packaged resources, so launchers that
* resolve icons as if the platform were pre-Oreo (e.g. a "force legacy icons" option) get the
* app's legacy icon or vector freeform icon instead of its adaptive icon.
*
* aapt2 (link and optimize) strips every version qualifier <= minSdk, so with minSdk >= 26
* res/mipmap-anydpi-v26/ is packaged as plain "anydpi", which wins at any sdk. There is no flag
* to turn that off, so after every task that writes linked resources this puts sdkVersion 26
* back, only on:
* - the resources referenced by android:icon / android:roundIcon of <application>,
* <activity> and <activity-alias> in the packaged manifest (plus the resources those
* reference, if they are aliases),
* - and only on their "anydpi" values without an sdk version that are <adaptive-icon> files,
* - and only if the resource has another value to fall back to (e.g. a plain default).
* Nothing else in the table changes, and nothing at all happens when the manifest's minSdk is
* below 26 (aapt2 keeps -v26 then). Adaptive icons need API 26 anyway, so real devices are
* unaffected. Both formats are handled: resources.arsc (APKs) and resources.pb (app bundles;
* bundletool converts it without collapsing versions again).
*
* USAGE
*
* 1. Apply it in the app module's build script, after the Android plugin:
*
* apply from: 'gradle/legacy-icon.gradle' // build.gradle
* apply(from = "gradle/legacy-icon.gradle") // build.gradle.kts
*
* The path is relative to the module directory (use rootProject.file(...) if the script
* lives elsewhere). Nothing else to configure: icons are read from the manifest.
*
* 2. Keep the adaptive icon where it normally is and point the manifest at it:
*
* res/mipmap-anydpi-v26/ic_launcher.xml // <adaptive-icon>
* <application android:icon="@mipmap/ic_launcher" ...>
*
* android:roundIcon and <activity>/<activity-alias> icons are handled the same way.
*
* 3. Give the icon a legacy (non-adaptive) value without a version qualifier. Either PNGs
* (res/mipmap-mdpi/ic_launcher.png, ...), or a single vector: put the vector in
* res/drawable/ic_launcher_legacy.xml and alias it from any values file (e.g. res/values/ic_launcher.xml):
*
* <resources xmlns:tools="http://schemas.android.com/tools">
* <item name="ic_launcher" type="mipmap"
* tools:ignore="ReferenceType">@drawable/ic_launcher_legacy</item>
* </resources>
*
* The alias keeps the vector out of res/mipmap/ (where the IDE flags <vector>), and
* tools:ignore silences the ReferenceType lint error (a mipmap aliasing a drawable), which
* otherwise fails release builds. Without a legacy value the icon is left untouched.
*
* If minSdk < 26 it is a no-op as it's not needed.
* Hooks AGP's process*Resources, convertShrunkResourcesToBinary*, optimize*Resources and minify*WithR8 tasks.
*/
class LegacyIconResPatcher {
static final int RES_STRING_POOL_TYPE = 0x0001
static final int RES_TABLE_TYPE = 0x0002
static final int RES_XML_TYPE = 0x0003
static final int RES_XML_START_ELEMENT_TYPE = 0x0102
static final int RES_XML_RESOURCE_MAP_TYPE = 0x0180
static final int RES_TABLE_PACKAGE_TYPE = 0x0200
static final int RES_TABLE_TYPE_TYPE = 0x0201
static final int RES_TABLE_TYPE_SPEC_TYPE = 0x0202
static final int ATTR_ICON = 0x01010002 // android:icon
static final int ATTR_ROUND_ICON = 0x0101052c // android:roundIcon
static final int ATTR_MIN_SDK = 0x0101020c // android:minSdkVersion
static final String ADAPTIVE_ICON = 'adaptive-icon'
static final Set<String> ICON_ELEMENTS = ['application', 'activity', 'activity-alias'] as Set
static final int TYPE_REFERENCE = 0x01
static final int TYPE_STRING = 0x03
static final int TYPE_DYNAMIC_REFERENCE = 0x07
static final int TYPE_INT_DEC = 0x10
static final int TYPE_INT_HEX = 0x11
static final int PREVIEW_SDK = 10000 // minSdkVersion given as a codename
static final int DENSITY_ANY = 0xfffe
static final int CONFIG_VERSION = 0x0400
static final int NO_ENTRY = -1
static final int ADAPTIVE_ICON_SDK = 26
static final int MAX_ALIAS_DEPTH = 8
/** Patches the resource table inside an .ap_ file in place. Returns true if it changed. */
static boolean patchApFile(File ap) {
String name = null
byte[] patched = null
new java.util.zip.ZipFile(ap).withCloseable { zip ->
def manifestEntry = zip.getEntry('AndroidManifest.xml')
if (manifestEntry == null) return
byte[] manifest = zip.getInputStream(manifestEntry).bytes
Closure<byte[]> readFile = { path ->
def e = zip.getEntry(path as String)
e == null ? null : zip.getInputStream(e).bytes
}
if (zip.getEntry('resources.arsc') != null) {
name = 'resources.arsc'
Map info = binaryManifestInfo(manifest)
if ((info.minSdk as int) < ADAPTIVE_ICON_SDK) return
patched = patchArsc(readFile.call(name), info.icons as Set<Integer>, readFile)
} else if (zip.getEntry('resources.pb') != null) {
name = 'resources.pb'
Map info = protoManifestInfo(manifest)
if ((info.minSdk as int) < ADAPTIVE_ICON_SDK) return
patched = patchProto(readFile.call(name), info.icons as Set<Integer>, readFile)
}
}
if (patched == null) return false
replaceZipEntry(ap, name, patched)
return true
}
// ---- Zip: replace one entry, copy every other one byte for byte ----
// Recompressing the other entries would make their bytes depend on the JDK's zlib, and the
// packager copies compressed entries from the .ap_ into the APK as they are.
static void replaceZipEntry(File file, String name, byte[] data) {
byte[] zip = file.bytes
def buf = le(zip)
int eocd = -1
for (int p = zip.length - 22; p >= Math.max(0, zip.length - 22 - 0xffff); p--) {
if (buf.getInt(p) == 0x06054b50) { eocd = p; break }
}
if (eocd < 0) throw new IllegalStateException("${file.name}: no zip end record")
int count = u16(buf, eocd + 10)
long cdOffset = buf.getInt(eocd + 16) & 0xffffffffL
if (count == 0xffff || cdOffset == 0xffffffffL) throw new IllegalStateException("${file.name}: zip64 is not supported")
// Central directory records: {record offset, record length, local header offset}
List<int[]> records = []
int target = -1
for (int i = 0, p = (int) cdOffset; i < count; i++) {
if (buf.getInt(p) != 0x02014b50) throw new IllegalStateException("${file.name}: bad central directory")
int nameLen = u16(buf, p + 28)
int length = 46 + nameLen + u16(buf, p + 30) + u16(buf, p + 32)
if (new String(zip, p + 46, nameLen, 'UTF-8') == name) target = i
records.add([p, length, buf.getInt(p + 42)] as int[])
p += length
}
if (target < 0) throw new IllegalStateException("${file.name}: no $name")
int method = u16(buf, records[target][0] + 10)
byte[] stored = data
if (method == java.util.zip.ZipEntry.DEFLATED) {
def deflater = new java.util.zip.Deflater(java.util.zip.Deflater.DEFAULT_COMPRESSION, true)
try {
deflater.setInput(data)
deflater.finish()
def out = new ByteArrayOutputStream()
byte[] chunk = new byte[65536]
while (!deflater.finished()) out.write(chunk, 0, deflater.deflate(chunk))
stored = out.toByteArray()
} finally {
deflater.end()
}
} else if (method != java.util.zip.ZipEntry.STORED) {
throw new IllegalStateException("${file.name}: unsupported compression for $name")
}
def crc = new java.util.zip.CRC32()
crc.update(data)
// Local entries in file order; each spans up to the next one (or the central directory).
List<int[]> locals = records.collect { it } .sort { it[2] }
def out = new ByteArrayOutputStream()
Map<Integer, Integer> moved = [:]
for (int i = 0; i < locals.size(); i++) {
int start = locals[i][2]
int end = i + 1 < locals.size() ? locals[i + 1][2] : (int) cdOffset
moved.put(start, out.size())
if (locals[i].is(records[target])) {
int headerLen = 30 + u16(buf, start + 26) + u16(buf, start + 28)
byte[] header = Arrays.copyOfRange(zip, start, start + headerLen)
def h = le(header)
h.putShort(6, (short) (u16(h, 6) & ~0x08)) // sizes are in the header now
h.putInt(14, (int) crc.value)
h.putInt(18, stored.length)
h.putInt(22, data.length)
out.write(header)
out.write(stored)
} else {
out.write(zip, start, end - start)
}
}
int newCdOffset = out.size()
for (int i = 0; i < records.size(); i++) {
int[] r = records[i]
byte[] record = Arrays.copyOfRange(zip, r[0], r[0] + r[1])
def c = le(record)
c.putInt(42, moved.get(r[2]))
if (i == target) {
c.putShort(8, (short) (u16(c, 8) & ~0x08))
c.putInt(16, (int) crc.value)
c.putInt(20, stored.length)
c.putInt(24, data.length)
}
out.write(record)
}
byte[] end = Arrays.copyOfRange(zip, eocd, zip.length)
le(end).putInt(12, out.size() - newCdOffset)
le(end).putInt(16, newCdOffset)
out.write(end)
File tmp = new File(file.absoluteFile.parentFile, file.name + '.tmp')
tmp.bytes = out.toByteArray()
java.nio.file.Files.move(tmp.toPath(), file.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING)
}
// ---- Binary format: AndroidManifest.xml (AXML) and resources.arsc ----
// frameworks/base/libs/androidfw/include/androidfw/ResourceTypes.h
/**
* [icons: resource ids of the icons declared in a binary manifest, minSdk: its minSdkVersion
* (1 if missing, PREVIEW_SDK for a codename)].
*/
static Map binaryManifestInfo(byte[] xml) {
Set<Integer> icons = new HashSet<>()
int minSdk = 1
def buf = le(xml)
if (xml.length < 8 || u16(buf, 0) != RES_XML_TYPE) return [icons: icons, minSdk: minSdk]
int pool = -1
int[] resMap = new int[0]
int pos = u16(buf, 2)
while (pos + 8 <= xml.length) {
int type = u16(buf, pos)
int size = buf.getInt(pos + 4)
if (size <= 0) break
if (type == RES_STRING_POOL_TYPE) {
pool = pos
} else if (type == RES_XML_RESOURCE_MAP_TYPE) {
int start = pos + u16(buf, pos + 2)
resMap = new int[(pos + size - start).intdiv(4)]
for (int i = 0; i < resMap.length; i++) resMap[i] = buf.getInt(start + 4 * i)
} else if (type == RES_XML_START_ELEMENT_TYPE && pool >= 0) {
// ResXMLTree_attrExt: ns, name, attributeStart, attributeSize, attributeCount
int ext = pos + u16(buf, pos + 2)
String element = stringAt(buf, pool, buf.getInt(ext + 4))
boolean iconElement = ICON_ELEMENTS.contains(element)
int attrs = ext + u16(buf, ext + 8)
int attrSize = u16(buf, ext + 10)
int count = u16(buf, ext + 12)
for (int i = 0; i < count; i++) {
// ResXMLTree_attribute: ns, name, rawValue, Res_value(size, res0, dataType, data)
int a = attrs + i * attrSize
int nameIdx = buf.getInt(a + 4)
int attrId = nameIdx >= 0 && nameIdx < resMap.length ? resMap[nameIdx] : 0
int dataType = buf.get(a + 15) & 0xff
int data = buf.getInt(a + 16)
if (iconElement && (attrId == ATTR_ICON || attrId == ATTR_ROUND_ICON)
&& (dataType == TYPE_REFERENCE || dataType == TYPE_DYNAMIC_REFERENCE)) {
icons.add(data)
} else if (element == 'uses-sdk' && attrId == ATTR_MIN_SDK) {
minSdk = dataType == TYPE_INT_DEC || dataType == TYPE_INT_HEX ? data : PREVIEW_SDK
}
}
}
pos += size
}
return [icons: icons, minSdk: minSdk]
}
/** Name of the root element of a binary (AXML) or proto XML file, or null. */
static String rootElement(byte[] file) {
if (file == null || file.length < 8) return null
def buf = le(file)
if (u16(buf, 0) == RES_XML_TYPE) {
int pool = -1
for (int pos = u16(buf, 2); pos + 8 <= file.length; ) {
int size = buf.getInt(pos + 4)
if (size <= 0) break
int type = u16(buf, pos)
if (type == RES_STRING_POOL_TYPE) pool = pos
if (type == RES_XML_START_ELEMENT_TYPE && pool >= 0) {
return stringAt(buf, pool, buf.getInt(pos + u16(buf, pos + 2) + 4))
}
pos += size
}
return null
}
try {
byte[] element = messageAt(file, 1)
return element == null ? null : stringField(element, 3)
} catch (RuntimeException ignored) {
return null // not proto XML (e.g. a PNG)
}
}
/** Returns the patched resources.arsc, or null if nothing changed. */
static byte[] patchArsc(byte[] data, Set<Integer> icons, Closure<byte[]> readFile) {
if (icons.isEmpty()) return null
def buf = le(data)
if (data.length < 12 || u16(buf, 0) != RES_TABLE_TYPE) return null
int headerSize = u16(buf, 2)
// File values are indexes into the table's global string pool, its first chunk.
int pool = u16(buf, headerSize) == RES_STRING_POOL_TYPE ? headerSize : -1
Closure<Boolean> isAdaptiveIcon = { index ->
String path = pool < 0 ? null : stringAt(buf, pool, index as int)
path != null && rootElement(readFile.call(path)) == ADAPTIVE_ICON
}
def out = new ByteArrayOutputStream()
out.write(data, 0, headerSize)
boolean changed = false
int pos = headerSize
while (pos + 8 <= data.length) {
int size = buf.getInt(pos + 4)
if (size <= 0) break
byte[] chunk = Arrays.copyOfRange(data, pos, pos + size)
if (u16(buf, pos) == RES_TABLE_PACKAGE_TYPE) {
byte[] patched = patchPackage(chunk, icons, isAdaptiveIcon)
if (patched != null) { chunk = patched; changed = true }
}
out.write(chunk)
pos += size
}
if (!changed) return null
if (pos < data.length) out.write(data, pos, data.length - pos)
byte[] result = out.toByteArray()
le(result).putInt(4, result.length)
return result
}
private static byte[] patchPackage(byte[] pkg, Set<Integer> icons, Closure<Boolean> isAdaptiveIcon) {
def buf = le(pkg)
int pkgId = buf.getInt(8)
int headerSize = u16(buf, 2)
List<int[]> chunks = [] // {offset, size}
for (int pos = headerSize; pos + 8 <= pkg.length; ) {
int size = buf.getInt(pos + 4)
if (size <= 0) break
chunks.add([pos, size] as int[])
pos += size
}
// The icons, plus whatever they alias to.
Set<Integer> targets = new HashSet<>(icons)
for (int depth = 0; depth < MAX_ALIAS_DEPTH; depth++) {
Set<Integer> found = new HashSet<>()
for (int[] c : chunks) {
if (u16(buf, c[0]) != RES_TABLE_TYPE_TYPE) continue
for (int[] e : entries(buf, c[0])) {
if (!targets.contains(resId(pkgId, buf, c[0], e[0]))) continue
int ref = referenceOf(buf, e[1])
if (ref != 0) found.add(ref)
}
}
if (!targets.addAll(found)) break
}
// Values that are not candidates: a target needs one of these to fall back to.
Set<Integer> hasFallback = new HashSet<>()
for (int[] c : chunks) {
if (u16(buf, c[0]) != RES_TABLE_TYPE_TYPE || isUnversionedAnyDpi(buf, c[0])) continue
for (int[] e : entries(buf, c[0])) hasFallback.add(resId(pkgId, buf, c[0], e[0]))
}
def out = new ByteArrayOutputStream()
out.write(pkg, 0, headerSize)
Map<Integer, List<Integer>> versioned = [:] // type id -> entry indices
Map<Integer, Integer> addedChunks = [:] // type id -> extra type chunks
for (int[] c : chunks) {
int pos = c[0]
if (u16(buf, pos) != RES_TABLE_TYPE_TYPE || !isUnversionedAnyDpi(buf, pos)) {
out.write(pkg, pos, c[1])
continue
}
List<int[]> hit = []
List<int[]> rest = []
for (int[] e : entries(buf, pos)) {
int id = resId(pkgId, buf, pos, e[0])
boolean icon = targets.contains(id) && hasFallback.contains(id)
int[] value = valueOf(buf, e[1])
if (icon && value != null && value[0] == TYPE_STRING && isAdaptiveIcon.call(value[1])) hit.add(e)
else rest.add(e)
}
if (hit.isEmpty()) {
out.write(pkg, pos, c[1])
continue
}
int typeId = pkg[pos + 8] & 0xff
if (!versioned.containsKey(typeId)) versioned.put(typeId, [])
for (int[] e : hit) versioned.get(typeId).add(e[0])
if (rest.isEmpty()) {
// Everything in this chunk is an icon: just version it in place.
byte[] chunk = Arrays.copyOfRange(pkg, pos, pos + c[1])
le(chunk).putShort(20 + 24, (short) ADAPTIVE_ICON_SDK)
out.write(chunk)
} else {
// Split: the icons move to their own anydpi-v26 chunk, the rest stays as is.
out.write(buildTypeChunk(buf, pos, rest, 0))
out.write(buildTypeChunk(buf, pos, hit, ADAPTIVE_ICON_SDK))
addedChunks.put(typeId, (addedChunks.get(typeId) ?: 0) + 1)
}
}
if (versioned.isEmpty()) return null
byte[] result = out.toByteArray()
def rb = le(result)
rb.putInt(4, result.length)
for (int pos = headerSize; pos + 8 <= result.length; ) {
int size = rb.getInt(pos + 4)
if (size <= 0) break
int typeId = result[pos + 8] & 0xff
if (u16(rb, pos) == RES_TABLE_TYPE_SPEC_TYPE && versioned.containsKey(typeId)) {
// These entries now vary by sdk version too.
int count = rb.getInt(pos + 12)
int flags = pos + u16(rb, pos + 2)
for (int idx : versioned.get(typeId)) {
if (idx < count) rb.putInt(flags + 4 * idx, rb.getInt(flags + 4 * idx) | CONFIG_VERSION)
}
// typesCount is only a size hint, and 0 means unset.
int typesCount = u16(rb, pos + 10)
int added = addedChunks.get(typeId) ?: 0
if (typesCount != 0 && added != 0) rb.putShort(pos + 10, (short) (typesCount + added))
}
pos += size
}
return result
}
private static boolean isUnversionedAnyDpi(java.nio.ByteBuffer buf, int chunk) {
int cfg = chunk + 20
return buf.getInt(cfg) >= 28 && u16(buf, cfg + 14) == DENSITY_ANY && u16(buf, cfg + 24) == 0
}
private static int resId(int pkgId, java.nio.ByteBuffer buf, int chunk, int idx) {
return (pkgId << 24) | ((buf.get(chunk + 8) & 0xff) << 16) | idx
}
/** {entry index, absolute entry offset} for every entry present in a type chunk. */
private static List<int[]> entries(java.nio.ByteBuffer buf, int chunk) {
int flags = buf.get(chunk + 9) & 0xff
int count = buf.getInt(chunk + 12)
int start = chunk + buf.getInt(chunk + 16)
int offsets = chunk + u16(buf, chunk + 2)
List<int[]> result = []
for (int i = 0; i < count; i++) {
if ((flags & 0x01) != 0) { // FLAG_SPARSE: {idx, offset / 4}
result.add([u16(buf, offsets + 4 * i), start + 4 * u16(buf, offsets + 4 * i + 2)] as int[])
} else if ((flags & 0x02) != 0) { // FLAG_OFFSET16: offset / 4, 0xffff = none
int off = u16(buf, offsets + 2 * i)
if (off != 0xffff) result.add([i, start + 4 * off] as int[])
} else {
int off = buf.getInt(offsets + 4 * i)
if (off != NO_ENTRY) result.add([i, start + off] as int[])
}
}
return result
}
/** Bytes taken by the entry at [e], including its value(s). */
private static int entryLength(java.nio.ByteBuffer buf, int e) {
int size = u16(buf, e)
int flags = u16(buf, e + 2)
if ((flags & 0x08) != 0) return 8 // FLAG_COMPACT
if ((flags & 0x01) != 0) return size + 12 * buf.getInt(e + 12) // FLAG_COMPLEX: map entries
return size + u16(buf, e + size) // entry + Res_value
}
/** {dataType, data} of a simple entry, or null for a complex (map) entry. */
private static int[] valueOf(java.nio.ByteBuffer buf, int e) {
int size = u16(buf, e)
int flags = u16(buf, e + 2)
if ((flags & 0x01) != 0) return null
if ((flags & 0x08) != 0) return [flags >>> 8, buf.getInt(e + 4)] as int[] // FLAG_COMPACT
return [buf.get(e + size + 3) & 0xff, buf.getInt(e + size + 4)] as int[]
}
/** The resource id a simple entry points to, or 0. */
private static int referenceOf(java.nio.ByteBuffer buf, int e) {
int[] value = valueOf(buf, e)
return value != null && (value[0] == TYPE_REFERENCE || value[0] == TYPE_DYNAMIC_REFERENCE) ? value[1] : 0
}
/** A dense copy of the type chunk at [chunk] holding only [entries], with the given sdk. */
private static byte[] buildTypeChunk(java.nio.ByteBuffer buf, int chunk, List<int[]> entries, int sdk) {
byte[] src = buf.array()
int headerSize = u16(buf, chunk + 2)
int count = 0
for (int[] e : entries) count = Math.max(count, e[0] + 1)
int[] offsets = new int[count]
Arrays.fill(offsets, NO_ENTRY)
def data = new ByteArrayOutputStream()
for (int[] e : entries) {
offsets[e[0]] = data.size()
data.write(src, e[1], entryLength(buf, e[1]))
while (data.size() % 4 != 0) data.write(0)
}
int entriesStart = headerSize + 4 * count
byte[] out = new byte[entriesStart + data.size()]
System.arraycopy(src, chunk, out, 0, headerSize) // header, including the config
def b = le(out)
b.putInt(4, out.length)
out[9] = 0 // dense, 32-bit offsets
b.putInt(12, count)
b.putInt(16, entriesStart)
b.putShort(20 + 24, (short) sdk)
for (int i = 0; i < count; i++) b.putInt(headerSize + 4 * i, offsets[i])
System.arraycopy(data.toByteArray(), 0, out, entriesStart, data.size())
return out
}
private static String stringAt(java.nio.ByteBuffer buf, int pool, int index) {
if (u16(buf, pool) != RES_STRING_POOL_TYPE || index < 0 || index >= buf.getInt(pool + 8)) return null
int flags = buf.getInt(pool + 16)
int p = pool + buf.getInt(pool + 20) + buf.getInt(pool + u16(buf, pool + 2) + 4 * index)
if ((flags & 0x100) != 0) { // UTF-8
p += ((buf.get(p) & 0x80) != 0) ? 2 : 1 // utf16 length
int len = buf.get(p) & 0xff
if ((len & 0x80) != 0) { len = ((len & 0x7f) << 8) | (buf.get(p + 1) & 0xff); p += 2 } else { p += 1 }
return new String(buf.array(), p, len, 'UTF-8')
}
int len = u16(buf, p)
if ((len & 0x8000) != 0) { len = ((len & 0x7fff) << 16) | u16(buf, p + 2); p += 4 } else { p += 2 }
return new String(buf.array(), p, len * 2, 'UTF-16LE')
}
private static java.nio.ByteBuffer le(byte[] b) {
return java.nio.ByteBuffer.wrap(b).order(java.nio.ByteOrder.LITTLE_ENDIAN)
}
private static int u16(java.nio.ByteBuffer buf, int pos) {
return buf.getShort(pos) & 0xffff
}
// ---- Proto format: AndroidManifest.xml (aapt.pb.XmlNode) and resources.pb ----
// frameworks/base/tools/aapt2/Resources.proto, Configuration.proto
// XmlNode.element(1) > XmlElement.name(3), attribute(4), child(5)
// XmlAttribute.resource_id(5), compiled_item(6) > Item.ref(1) > Reference.type(1), id(2)
// ResourceTable.package(2) > Package.package_id(1), type(3) > Type.type_id(1), entry(3)
// > Entry.entry_id(1), config_value(6) > ConfigValue.config(1), value(2) > Value.item(4)
// Configuration.density(18), sdk_version(24)
/** Same as [binaryManifestInfo], for a proto manifest. */
static Map protoManifestInfo(byte[] xml) {
Set<Integer> icons = new HashSet<>()
int[] minSdk = [1]
collectProtoManifest(xml, icons, minSdk)
return [icons: icons, minSdk: minSdk[0]]
}
private static void collectProtoManifest(byte[] node, Set<Integer> icons, int[] minSdk) {
byte[] element = messageAt(node, 1)
if (element == null) return
String name = stringField(element, 3)
for (long[] f : fields(element)) {
if (f[1] != 2) continue
if (f[0] == 4) {
byte[] attr = payload(element, f)
long attrId = varintField(attr, 5)
if (ICON_ELEMENTS.contains(name) && (attrId == ATTR_ICON || attrId == ATTR_ROUND_ICON)) {
byte[] ref = messageAt(attr, 6, 1)
if (ref != null && varintField(ref, 1) == 0 && varintField(ref, 2) != 0) {
icons.add((int) varintField(ref, 2))
}
} else if (name == 'uses-sdk' && attrId == ATTR_MIN_SDK) {
byte[] prim = messageAt(attr, 6, 7)
if (prim != null && hasVarint(prim, 6)) minSdk[0] = (int) varintField(prim, 6)
else if (prim != null && hasVarint(prim, 7)) minSdk[0] = (int) varintField(prim, 7)
else minSdk[0] = (stringField(attr, 3) ?: '').isInteger() ? stringField(attr, 3).toInteger() : PREVIEW_SDK
}
} else if (f[0] == 5) {
collectProtoManifest(payload(element, f), icons, minSdk)
}
}
}
/** Returns the patched resources.pb, or null if nothing changed. */
static byte[] patchProto(byte[] table, Set<Integer> icons, Closure<byte[]> readFile) {
if (icons.isEmpty()) return null
Set<Integer> targets = new HashSet<>(icons)
for (int depth = 0; depth < MAX_ALIAS_DEPTH; depth++) {
Set<Integer> found = new HashSet<>()
for (byte[] entry : protoEntries(table, targets)) {
for (long[] f : fields(entry)) {
if (f[0] != 6 || f[1] != 2) continue
byte[] ref = messageAt(payload(entry, f), 2, 4, 1)
if (ref != null && varintField(ref, 1) == 0 && varintField(ref, 2) != 0) {
found.add((int) varintField(ref, 2))
}
}
}
if (!targets.addAll(found)) break
}
return rewrite(table, 2) { pkg ->
int pkgId = idOf(pkg)
rewrite(pkg, 3) { type ->
int typeId = idOf(type)
rewrite(type, 3) { entry ->
targets.contains((pkgId << 24) | (typeId << 16) | idOf(entry)) ? patchProtoEntry(entry, readFile) : null
}
}
}
}
/** Entries of [table] whose resource id is in [ids]. */
private static List<byte[]> protoEntries(byte[] table, Set<Integer> ids) {
List<byte[]> result = []
for (long[] p : fields(table)) {
if (p[0] != 2 || p[1] != 2) continue
byte[] pkg = payload(table, p)
int pkgId = idOf(pkg)
for (long[] t : fields(pkg)) {
if (t[0] != 3 || t[1] != 2) continue
byte[] type = payload(pkg, t)
int typeId = idOf(type)
for (long[] e : fields(type)) {
if (e[0] != 3 || e[1] != 2) continue
byte[] entry = payload(type, e)
if (ids.contains((pkgId << 24) | (typeId << 16) | idOf(entry))) result.add(entry)
}
}
}
return result
}
private static byte[] patchProtoEntry(byte[] entry, Closure<byte[]> readFile) {
boolean fallback = false
for (long[] f : fields(entry)) {
if (f[0] == 6 && f[1] == 2 && !isUnversionedAnyDpi(messageAt(payload(entry, f), 1))) fallback = true
}
if (!fallback) return null
return rewrite(entry, 6) { cv ->
byte[] file = messageAt(cv as byte[], 2, 4, 5) // Value.item.file
String path = file == null ? null : stringField(file, 1)
boolean adaptive = path != null && isUnversionedAnyDpi(messageAt(cv as byte[], 1)) &&
rootElement(readFile.call(path)) == ADAPTIVE_ICON
adaptive ? rewrite(cv as byte[], 1) { cfg -> patchConfig(cfg as byte[]) } : null
}
}
private static boolean isUnversionedAnyDpi(byte[] cfg) {
return cfg != null && varintField(cfg, 18) == DENSITY_ANY && varintField(cfg, 24) == 0
}
private static byte[] patchConfig(byte[] cfg) {
if (!isUnversionedAnyDpi(cfg)) return null
def out = new ByteArrayOutputStream()
for (long[] f : fields(cfg)) {
if (f[0] != 24) out.write(cfg, (int) f[2], (int) (f[3] - f[2]))
}
writeVarint(out, 24 << 3)
writeVarint(out, ADAPTIVE_ICON_SDK)
return out.toByteArray()
}
/** The id(1) of a PackageId/TypeId/EntryId message in field 1, 0 if absent. */
private static int idOf(byte[] msg) {
byte[] id = messageAt(msg, 1)
return id == null ? 0 : (int) varintField(id, 1)
}
/**
* Copies [msg], passing the payload of every length-delimited [field] through [patch].
* Returns null if [patch] changed nothing.
*/
private static byte[] rewrite(byte[] msg, int field, Closure<byte[]> patch) {
boolean changed = false
def out = new ByteArrayOutputStream()
for (long[] f : fields(msg)) {
byte[] patched = (f[0] == field && f[1] == 2) ? patch.call(payload(msg, f)) : null
if (patched == null) {
out.write(msg, (int) f[2], (int) (f[3] - f[2]))
} else {
writeVarint(out, (field << 3) | 2)
writeVarint(out, patched.length)
out.write(patched)
changed = true
}
}
return changed ? out.toByteArray() : null
}
/** The nested message at the given field path (first occurrence at each level), or null. */
private static byte[] messageAt(byte[] msg, int... path) {
byte[] current = msg
for (int field : path) {
byte[] next = null
for (long[] f : fields(current)) {
if (f[0] == field && f[1] == 2) { next = payload(current, f); break }
}
if (next == null) return null
current = next
}
return current
}
private static long varintField(byte[] msg, int field) {
long value = 0
for (long[] f : fields(msg)) {
if (f[0] == field && f[1] == 0) value = f[4]
}
return value
}
private static boolean hasVarint(byte[] msg, int field) {
for (long[] f : fields(msg)) {
if (f[0] == field && f[1] == 0) return true
}
return false
}
private static String stringField(byte[] msg, int field) {
byte[] s = messageAt(msg, field)
return s == null ? null : new String(s, 'UTF-8')
}
private static byte[] payload(byte[] msg, long[] f) {
return Arrays.copyOfRange(msg, (int) f[5], (int) f[3])
}
/**
* Top-level fields of [msg], each as {field, wireType, start, end, varintValue, payloadStart}.
* ponytail: plain arrays and loops, Gradle's Groovy miscompiles closures with long params.
*/
private static List<long[]> fields(byte[] msg) {
List<long[]> result = []
int[] pos = [0]
while (pos[0] < msg.length) {
int start = pos[0]
long key = readVarint(msg, pos)
int wire = (int) (key & 7)
long value = 0
int payloadStart = 0
switch (wire) {
case 0: value = readVarint(msg, pos); break
case 1: pos[0] += 8; break
case 2:
int len = (int) readVarint(msg, pos)
payloadStart = pos[0]
pos[0] += len
break
case 5: pos[0] += 4; break
default: throw new IllegalStateException("Unsupported wire type $wire in proto resources")
}
result << ([key >>> 3, wire, start, pos[0], value, payloadStart] as long[])
}
return result
}
private static long readVarint(byte[] b, int[] pos) {
long result = 0
for (int shift = 0; ; shift += 7) {
int x = b[pos[0]++] & 0xff
result |= (long) (x & 0x7f) << shift
if ((x & 0x80) == 0) return result
}
}
private static void writeVarint(ByteArrayOutputStream out, long v) {
while ((v & ~0x7fL) != 0) { out.write((int) ((v & 0x7f) | 0x80)); v >>>= 7 }
out.write((int) v)
}
}
tasks.configureEach { task ->
// minify*WithR8 writes the shrunk resources.pb that app bundles are built from
if (task.name ==~ /(process|optimize)\w+Resources|convertShrunkResourcesToBinary\w+|minify\w+WithR8/) {
task.doLast {
task.outputs.files.each { root ->
if (!root.exists()) return
def files = root.isDirectory()
? root.listFiles().findAll { it.name.endsWith('.ap_') }
: (root.name.endsWith('.ap_') ? [root] : [])
files.each { ap ->
if (LegacyIconResPatcher.patchApFile(ap)) {
logger.info("Restored anydpi-v26 on the launcher icons in ${ap.name}")
}
}
}
}
}
}
+1 -1
View File
@@ -2,7 +2,7 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<gradient android:startColor="#960EFC" android:endColor="#FF804A" />
<gradient android:startColor="@color/ic_launcher_gradient_start" android:endColor="@color/ic_launcher_gradient_end" />
</shape>
</item>
</selector>
+17 -7
View File
@@ -7,59 +7,69 @@
android:scaleY="0.57"
android:translateX="220.16"
android:translateY="220.16">
<!-- nose -->
<path
android:pathData="m449.7,748.1l125.5,0l-14.3,71.7l-95.6,0z"
android:fillColor="#000000"/>
<!-- eye, left -->
<path
android:pathData="m230.1,535 l54.9,-57.1 227.2,188.5 -197,2.7z"
android:fillColor="#000000"/>
<!-- eye, right -->
<path
android:pathData="m794.4,535.2 l-54.9,-57.1 -227.2,188.5 197,2.7z"
android:fillColor="#000000"/>
<!-- eye highlight, right -->
<path
android:pathData="m727.2,558.1c-11.1,6.4 -22.1,13 -33.1,19.5 -3.3,1.9 -7.2,4.4 -6.5,8.9 0.4,13.4 -2.5,26.9 -9.6,38.3 -9.9,17.8 -41,31.4 -61,33.8l32,4.6 12.9,-6.5c8.6,-3.8 17.1,-7.9 24.6,-13.7 8.3,-6.4 15.4,-14.4 21.1,-23.1 11.8,-18.5 16.9,-40.2 19.5,-61.7z"
android:fillColor="#c89ab1"/>
<!-- eye highlight, left -->
<path
android:pathData="m296.5,558.3c11.1,6.4 22.1,13 33.1,19.5 3.3,1.9 7.2,4.4 6.5,8.9 -0.4,13.4 2.5,26.9 9.6,38.3 9.9,17.8 41,31.4 61,33.8L374.7,663.3 361.7,656.8c-8.6,-3.8 -17.1,-7.9 -24.6,-13.7 -8.3,-6.4 -15.4,-14.4 -21.1,-23.1 -11.8,-18.5 -16.9,-40.2 -19.5,-61.7z"
android:fillColor="#c89ab1"/>
<!-- inner ear, left -->
<path
android:pathData="m277,434.4c-11.5,-40.3 -48,-91 -71,-119.9l0.7,77.2 73.8,62.1z"
android:fillColor="#000000"/>
<!-- inner ear, right -->
<path
android:pathData="m747.6,434c11.5,-40.3 48,-91 71,-119.9l-0.7,77.2 -73.8,62.1z"
android:fillColor="#000000"/>
<!-- ear, left -->
<path
android:pathData="M102.2,233.8 L186,285.6 237.2,504.7 87.5,451.1Z"
android:fillColor="#5a1e64"/>
<!-- ear, right -->
<path
android:pathData="M921.3,234.8 L837.5,286.6 786.3,505.7 936,452.1Z"
android:fillColor="#5a1e64"/>
<!-- ear highlight, right -->
<path
android:pathData="M816.5,314.4 L853.4,280.5c4.7,13.4 8.6,29.7 10.2,43.9 3.4,26.9 0.5,54.4 -6.8,80.5 -2.4,9 -6.8,17.8 -9.9,26.6l-50.7,18.6 -1,-67.8c5,-20.1 13.1,-48.7 21.3,-67.9z"
android:fillColor="#b24090"/>
<!-- ear highlight, left -->
<path
android:pathData="m160.9,370.2c-2,-30.5 1.2,-60.3 13.4,-86.4l34.2,32.7c9.6,21 16.8,43.7 22.6,64.8l-2.2,67.6 -52.1,-16.8C167.6,412.9 162.9,391.3 160.9,370.2Z"
android:fillColor="#b24090"/>
<!-- dark whisker, left -->
<path
android:pathData="m175.7,712.1 l141.7,-24.8 83.2,80.9 -169.4,5.5 13,-31.5z"
android:fillColor="#5a1e64"/>
<!-- dark whisker, right -->
<path
android:pathData="m853.4,709.7 l-148.2,-17.5 -80.5,77.5 169.4,5.5 -13,-31.5z"
android:fillColor="#5a1e64"/>
<!-- whisker, left -->
<path
android:pathData="m147.6,649.3 l81.2,-31.9 184,110.2c-15.6,2.9 -54,0.4 -69.7,2.8l-12.8,-10.9z"
android:fillColor="#c89ab1"/>
<!-- whisker, right -->
<path
android:pathData="m875.9,649.3 l-81.2,-31.9 -184,110.2c15.6,2.9 54.8,0.3 70.5,2.7l12,-10.8z"
android:fillColor="#c89ab1"/>
<!-- head -->
<path
android:pathData="m108.8,159.8c25,-7.7 52,-7 77,0 37.3,10.1 69.8,32.8 97.9,58.6 12.7,12.5 25.6,24.7 36.6,38.7 17.2,20.5 31.8,43 44.9,66.3l12.1,20.1 -4.2,-19.8c10.6,18.8 17.2,39.4 25.9,59.1 6.8,16.5 14.9,32.5 22.1,48.9 -13.9,-9.2 -26.9,-19.7 -40.8,-28.9 -9.3,-6 -18.1,-12.8 -27.8,-18.1 10.3,21.3 23.7,40.8 37.1,60.2 12.6,17.5 25.7,34.5 39.6,51L512.6,594.1 611.7,473.9c14,-18 27.4,-36.5 39.8,-55.7 6.9,-10.9 14.1,-21.7 19.2,-33.6 -16.6,9.7 -31.9,21.5 -47.8,32.5 -7.1,4.8 -13.6,10.6 -21.3,14.6 12.4,-24 23.6,-48.7 33.9,-73.7 4.9,-11.1 9.7,-24.4 16,-35.1L648,340.8l8.1,-13.8c20.9,-38.7 47.8,-74 79,-105 28.9,-27.4 62.4,-51.8 101.3,-62.4 27,-7.6 56.2,-7.1 82.8,1.9 36.3,12.3 67.3,39.8 82.8,75 17.5,38.4 19.2,82.5 10.9,123.5 -8.5,40.3 -28.2,78.4 -56.8,108.1 -28.7,30.1 -67.1,52.4 -108.9,57.2 -12.7,1.5 -25.5,-0 -38.1,0.8 11.9,4.3 24.5,6.6 36.4,10.8 -5.9,9.8 -13.7,18.2 -20.9,27 22.9,0.4 45.7,0 68.6,0.3 -10.4,16.2 -23,31 -37.6,43.6 -4.3,3.5 -8.9,6.8 -13.2,10.3l80.8,25.2c-19.6,29 -43.8,55.6 -73.6,74.4 -6.1,3.7 -12.8,6.3 -19.1,9.6 14.6,1.5 29.2,1.5 43.8,1.9 -6.2,6.3 -13.6,11.1 -20.7,16.3 -31.2,22.5 -66.6,38.3 -102.6,51.5 -9.9,4.2 -20.9,6.7 -29.5,13.5 -15.3,11.8 -31.7,22.1 -48.6,31.6 -10.9,5.9 -21.7,12.2 -33.4,16 4.5,-4.9 9.8,-9 14.3,-13.7 10.8,-11.5 21.6,-23.1 31.8,-35.1 4.6,-5.7 10.1,-10.8 13.3,-17.4 -6.2,3.5 -11.5,8.1 -17.3,12.2 -19,13.8 -37.7,28.4 -58.5,39.4 -19.1,9.9 -39.7,17.1 -61.1,20.2 -3.3,0.3 -6.4,-1 -9.4,-2.1 -9.5,-3.8 -18.8,-8.1 -27.9,-12.8 -4,-2 -7.8,-4.5 -12,-6 -2.4,-0.9 -4.7,0.9 -6.8,1.8 -10.5,5.7 -21.2,11 -32.1,15.8 -4.8,2 -9.9,4.2 -15.2,3.2 -17.1,-2.7 -33.8,-7.6 -49.3,-15.1 -31.3,-14.5 -57.4,-37.5 -85.3,-57.4 4,7 9.6,13 14.8,19.2 7.5,8.4 15,16.8 22.5,25.2 7.1,7.9 15.2,14.7 22,22.8 -29.3,-12.6 -57.3,-28.7 -82.1,-48.7 -8,-6.1 -18,-8.5 -27.1,-12.2 -23.5,-8.7 -46.5,-18.8 -68.7,-30.5 -19.3,-10.8 -37.8,-23.1 -54.5,-37.6 14.6,-0.4 29.2,-1 43.8,-1.8 -7.3,-4 -15.3,-6.7 -22.2,-11.3 -28.7,-18.9 -52,-45 -71.8,-72.9l80.8,-25.1C159.7,604.7 144.2,584.3 129.4,564.3c13.9,0.5 27.9,-0.2 41.8,-0.2 9.3,-0.1 18.7,0.2 28,-0.3 -6.8,-9.1 -14.5,-17.5 -20.9,-26.9 12.5,-3.6 25.2,-6.5 37.4,-11 -14.1,-0.6 -28.3,1.1 -42.4,-0.9 -31.1,-4.6 -60.7,-18.2 -84.7,-38.4 -22.1,-18 -40.1,-40.6 -54.2,-65.3C13.5,383.2 3.7,339 6.9,295.7c2.9,-36.5 16.5,-73.2 42.7,-99.5 16.1,-17 36.7,-29.8 59.3,-36.4m-24.2,47.6c6.9,3 14.4,4.4 21.4,7.2 7.3,2.8 14.4,6.2 21.4,9.6 26.3,13.2 49.1,33.3 65,58 7.1,11.6 12.5,24.1 17.2,36.9 -12.1,-10.9 -23,-23.1 -36,-33 -12.3,-10.9 -26,-20.1 -40.3,-28.1 -17.1,19.3 -25.7,45 -26.7,70.5 -1.3,36.1 10.2,71.8 29.3,102.1 13.8,21.9 32.7,40.8 55.4,53.4 7.9,4.3 16.4,8.1 25.5,8.7 -4.6,-5.6 -10,-10.4 -14.6,-16 -11.4,-13.3 -19.9,-28.8 -25.9,-45.2 12.7,0.3 25,3.9 37,7.8 -4.8,-7.7 -9.7,-15.3 -13.7,-23.5 -10.5,-21.8 -15.6,-46.2 -15.5,-70.4 15,8.8 28.8,19.4 41.5,31.3 1.3,1.2 11.6,10.9 16.5,17 4.6,5.7 9.6,11.1 14,17 4.6,6.6 9,13.3 14,19.6 1.9,-45.7 -8.6,-92.1 -30.4,-132.3 -18,-32.7 -44.9,-61.1 -78.3,-78.2 -23.6,-11.9 -51,-17.5 -77.1,-12.4m778.2,11.1c-17.5,8 -32.7,20.2 -46.1,33.8 -29.2,29 -47.8,67.3 -56.5,107.3 -4.9,23.2 -8.1,47 -6.8,70.8 10.3,-14.6 20.6,-29.4 33,-42.3 4.8,-5.2 13.5,-14.2 23.4,-22 9.2,-7.7 19.4,-14.1 29.4,-20.7 0.5,20.1 -3,40.3 -10,59.2 -4.6,12.6 -11.8,24 -19,35.3 12,-4 24.3,-7.5 37,-8.8 -4.3,14.8 -12.2,28.3 -21.3,40.7 -5.8,8 -13.6,14.3 -19.9,21.8 16.6,-2.4 31.4,-11.1 44.7,-21 27.1,-21.2 46.3,-51.5 57.1,-84 8.8,-25.9 11.9,-54.1 6.4,-81 -3.9,-18 -11.4,-35.5 -23.4,-49.6 -13.4,6.9 -25.9,15.6 -37.6,25.1 -14.1,10.6 -26,23.7 -38.5,36.1 9.3,-28.2 29,-57.5 53.8,-77.5 16.6,-13.4 36,-23.1 56.3,-29.6 5.1,-1.5 10.3,-2.8 15.4,-4.7 -26,-4.9 -53.4,-0.3 -77.1,11.1M258.7,517.3c5.8,32.7 16.5,65 34.7,92.9 9.8,15.3 23.3,28.6 39.5,37.1 26.8,14.2 60,13.8 88.7,5.7 3.5,-1 7.3,-3 11,-1.6 8.4,2.7 17,5.3 25.9,5.9 -4.9,-5 -10.9,-8.6 -16.5,-12.7 -24.7,-17.9 -49.5,-35.8 -74.6,-53.2 -36.1,-24.9 -72.4,-49.5 -108.7,-74.1m497,6.3c-18.7,13.2 -37.9,25.8 -56.8,38.7 -37,25.5 -74.5,50.3 -110.5,77.2 -8,6.1 -16.5,11.5 -24.2,17.9 9.1,-0.2 17.7,-3.5 26.4,-6.2 3.9,-1.4 7.9,0.5 11.7,1.6 13.6,4.5 48.8,9.3 69.9,1.4 15.4,-4.9 31,-13.1 42.7,-24.2 12.7,-12.9 22.9,-27.8 30.5,-44.2 10.1,-21.8 16.4,-44.6 20,-68.3 -3.3,2 -6.5,4 -9.6,6.2M178,643.6c6.4,4.4 13.2,8.1 19.9,11.8 37.3,19.6 76.4,35.6 114.1,54.6 6.1,3.1 12.6,5.4 18.2,9.5 -21.9,-4.1 -43.6,-9.1 -65.8,-11 -25,-2.2 -51.1,-2.1 -74.8,7.3 4.3,1.3 8.6,2.4 12.9,3.3 43.9,8.8 87.9,17.7 131.6,27.7 -15.2,2 -30.6,3.1 -45.7,6.1 -18.2,3.8 -36.5,8.6 -52.8,17.7 49.4,-2.4 98.6,-7 147.9,-10.3 -13.5,-9.9 -27.6,-19.1 -40.4,-29.9 15.7,-2.4 31.3,-5 46.9,-7.9 -25.4,-18.2 -53.1,-33 -81.5,-45.8 -41.2,-18.1 -85.1,-32.1 -130.4,-33m536.8,32.6c-18.6,8.4 -37,17.3 -54.2,28.3 -9.4,6.1 -19.4,11.4 -28.4,18.2 16.3,2.5 32.6,5.6 49.1,7.5 -13.7,10.4 -28.1,19.9 -41.7,30.5 7.6,0.2 15.2,0.5 22.9,1.3 29,2.2 58.1,4.4 87.1,6.2 12.1,0.3 24.1,2 36.3,1.3 -15.7,-7.9 -32.9,-12.4 -50,-16.1 -15.2,-3.4 -30.8,-4.3 -46.1,-7 29.4,-7 59,-13 88.7,-18.6 18.8,-4.1 37.9,-6.9 56.5,-11.7 -4.4,-2.4 -9.2,-3.6 -14.1,-4.5 -22.1,-5 -45.1,-5.1 -67.5,-2.6 -20.2,2 -40,6.8 -59.9,10.6 9.5,-6.1 20Line truncated
android:fillColor="#fcecdc"/>
<path
android:pathData="m371.7,271.4c12.1,2.7 24,6.3 36,9.6 12.2,3.5 24.4,6.9 36.2,11.5 4.2,1.5 8.2,4.9 12.9,3.9 31.9,-5.8 64.6,-5.4 96.7,-2.1 6.6,0.5 13.6,3.7 20.1,0.5 12.3,-5.6 25.4,-9.3 38.3,-13.1 13.2,-3.6 26.3,-7.9 39.8,-10.3L512.6,501.3Z"
android:fillColor="#fcecdc"/>
<path
android:pathData="m509,859.9c2.3,-1.3 4.7,0.1 6.8,1.1 11,5.8 22.1,11.5 33.3,17 2.8,1.1 5.7,3 8.8,2.2l52.2,-15.5c-18.8,24.1 -40.6,46.2 -65.9,63.6l1.4,-15 -34.3,25.3 -34.2,-25.8 2.2,15.7c-18.9,-13.7 -36.6,-29.2 -52.6,-46.4 -5.1,-5.5 -10.2,-11 -14.5,-17.1l36.5,10.8c5.9,1.4 11.4,4.4 17.5,4.9 5,-1.2 9.4,-4.2 14.1,-6.3 9.7,-4.6 19,-10 28.8,-14.3z"
android:pathData="@string/ic_launcher_path_head"
android:fillColor="#fcecdc"/>
</group>
</vector>
+100
View File
@@ -0,0 +1,100 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<!-- Fits the stroked head edge to edge: its bounds, stroke included, are
x -30.19..1054.21, y 117.78..975.1 (half of strokeWidth 73 around the path).
scale = 1024 / width (rounded down), pivot = bounds center; recompute if either changes.
The gradient spans the same bounds, so it still covers the canvas like the adaptive one -->
<!-- the icon is slighly moved to the bottom (looks nicer); to center, set android:translateY to -34.44 -->
<group android:pivotX="512.01" android:pivotY="546.44"
android:translateX="-0.01" android:translateY="-16"
android:scaleX="0.9442" android:scaleY="0.9442">
<!-- Border: the head stroked with the adaptive background gradient;
half of strokeWidth shows outside the head -->
<path android:pathData="@string/ic_launcher_path_head"
android:strokeWidth="73"
android:strokeLineJoin="round">
<aapt:attr name="android:fillColor">
<gradient android:type="linear"
android:startX="-30.19" android:startY="546.44"
android:endX="1054.21" android:endY="546.44"
android:startColor="@color/ic_launcher_gradient_start"
android:endColor="@color/ic_launcher_gradient_end"/>
</aapt:attr>
<aapt:attr name="android:strokeColor">
<gradient android:type="linear"
android:startX="-30.19" android:startY="546.44"
android:endX="1054.21" android:endY="546.44"
android:startColor="@color/ic_launcher_gradient_start"
android:endColor="@color/ic_launcher_gradient_end"/>
</aapt:attr>
</path>
<!-- nose -->
<path
android:pathData="m449.7,748.1l125.5,0l-14.3,71.7l-95.6,0z"
android:fillColor="#000000"/>
<!-- eye, left -->
<path
android:pathData="m230.1,535 l54.9,-57.1 227.2,188.5 -197,2.7z"
android:fillColor="#000000"/>
<!-- eye, right -->
<path
android:pathData="m794.4,535.2 l-54.9,-57.1 -227.2,188.5 197,2.7z"
android:fillColor="#000000"/>
<!-- eye highlight, right -->
<path
android:pathData="m727.2,558.1c-11.1,6.4 -22.1,13 -33.1,19.5 -3.3,1.9 -7.2,4.4 -6.5,8.9 0.4,13.4 -2.5,26.9 -9.6,38.3 -9.9,17.8 -41,31.4 -61,33.8l32,4.6 12.9,-6.5c8.6,-3.8 17.1,-7.9 24.6,-13.7 8.3,-6.4 15.4,-14.4 21.1,-23.1 11.8,-18.5 16.9,-40.2 19.5,-61.7z"
android:fillColor="#c89ab1"/>
<!-- eye highlight, left -->
<path
android:pathData="m296.5,558.3c11.1,6.4 22.1,13 33.1,19.5 3.3,1.9 7.2,4.4 6.5,8.9 -0.4,13.4 2.5,26.9 9.6,38.3 9.9,17.8 41,31.4 61,33.8L374.7,663.3 361.7,656.8c-8.6,-3.8 -17.1,-7.9 -24.6,-13.7 -8.3,-6.4 -15.4,-14.4 -21.1,-23.1 -11.8,-18.5 -16.9,-40.2 -19.5,-61.7z"
android:fillColor="#c89ab1"/>
<!-- inner ear, left -->
<path
android:pathData="m277,434.4c-11.5,-40.3 -48,-91 -71,-119.9l0.7,77.2 73.8,62.1z"
android:fillColor="#000000"/>
<!-- inner ear, right -->
<path
android:pathData="m747.6,434c11.5,-40.3 48,-91 71,-119.9l-0.7,77.2 -73.8,62.1z"
android:fillColor="#000000"/>
<!-- ear, left -->
<path
android:pathData="M102.2,233.8 L186,285.6 237.2,504.7 87.5,451.1Z"
android:fillColor="#5a1e64"/>
<!-- ear, right -->
<path
android:pathData="M921.3,234.8 L837.5,286.6 786.3,505.7 936,452.1Z"
android:fillColor="#5a1e64"/>
<!-- ear highlight, right -->
<path
android:pathData="M816.5,314.4 L853.4,280.5c4.7,13.4 8.6,29.7 10.2,43.9 3.4,26.9 0.5,54.4 -6.8,80.5 -2.4,9 -6.8,17.8 -9.9,26.6l-50.7,18.6 -1,-67.8c5,-20.1 13.1,-48.7 21.3,-67.9z"
android:fillColor="#b24090"/>
<!-- ear highlight, left -->
<path
android:pathData="m160.9,370.2c-2,-30.5 1.2,-60.3 13.4,-86.4l34.2,32.7c9.6,21 16.8,43.7 22.6,64.8l-2.2,67.6 -52.1,-16.8C167.6,412.9 162.9,391.3 160.9,370.2Z"
android:fillColor="#b24090"/>
<!-- dark whisker, left -->
<path
android:pathData="m175.7,712.1 l141.7,-24.8 83.2,80.9 -169.4,5.5 13,-31.5z"
android:fillColor="#5a1e64"/>
<!-- dark whisker, right -->
<path
android:pathData="m853.4,709.7 l-148.2,-17.5 -80.5,77.5 169.4,5.5 -13,-31.5z"
android:fillColor="#5a1e64"/>
<!-- whisker, left -->
<path
android:pathData="m147.6,649.3 l81.2,-31.9 184,110.2c-15.6,2.9 -54,0.4 -69.7,2.8l-12.8,-10.9z"
android:fillColor="#c89ab1"/>
<!-- whisker, right -->
<path
android:pathData="m875.9,649.3 l-81.2,-31.9 -184,110.2c15.6,2.9 54.8,0.3 70.5,2.7l12,-10.8z"
android:fillColor="#c89ab1"/>
<!-- head -->
<path
android:pathData="@string/ic_launcher_path_head"
android:fillColor="#fcecdc"/>
</group>
</vector>
+1 -9
View File
@@ -39,15 +39,7 @@
android:fillAlpha="0.43"/>
<!-- head -->
<path
android:pathData="m108.8,159.8c25,-7.7 52,-7 77,0 37.3,10.1 69.8,32.8 97.9,58.6 12.7,12.5 25.6,24.7 36.6,38.7 17.2,20.5 31.8,43 44.9,66.3l12.1,20.1 -4.2,-19.8c10.6,18.8 17.2,39.4 25.9,59.1 6.8,16.5 14.9,32.5 22.1,48.9 -13.9,-9.2 -26.9,-19.7 -40.8,-28.9 -9.3,-6 -18.1,-12.8 -27.8,-18.1 10.3,21.3 23.7,40.8 37.1,60.2 12.6,17.5 25.7,34.5 39.6,51L512.6,594.1 611.7,473.9c14,-18 27.4,-36.5 39.8,-55.7 6.9,-10.9 14.1,-21.7 19.2,-33.6 -16.6,9.7 -31.9,21.5 -47.8,32.5 -7.1,4.8 -13.6,10.6 -21.3,14.6 12.4,-24 23.6,-48.7 33.9,-73.7 4.9,-11.1 9.7,-24.4 16,-35.1L648,340.8l8.1,-13.8c20.9,-38.7 47.8,-74 79,-105 28.9,-27.4 62.4,-51.8 101.3,-62.4 27,-7.6 56.2,-7.1 82.8,1.9 36.3,12.3 67.3,39.8 82.8,75 17.5,38.4 19.2,82.5 10.9,123.5 -8.5,40.3 -28.2,78.4 -56.8,108.1 -28.7,30.1 -67.1,52.4 -108.9,57.2 -12.7,1.5 -25.5,-0 -38.1,0.8 11.9,4.3 24.5,6.6 36.4,10.8 -5.9,9.8 -13.7,18.2 -20.9,27 22.9,0.4 45.7,0 68.6,0.3 -10.4,16.2 -23,31 -37.6,43.6 -4.3,3.5 -8.9,6.8 -13.2,10.3l80.8,25.2c-19.6,29 -43.8,55.6 -73.6,74.4 -6.1,3.7 -12.8,6.3 -19.1,9.6 14.6,1.5 29.2,1.5 43.8,1.9 -6.2,6.3 -13.6,11.1 -20.7,16.3 -31.2,22.5 -66.6,38.3 -102.6,51.5 -9.9,4.2 -20.9,6.7 -29.5,13.5 -15.3,11.8 -31.7,22.1 -48.6,31.6 -10.9,5.9 -21.7,12.2 -33.4,16 4.5,-4.9 9.8,-9 14.3,-13.7 10.8,-11.5 21.6,-23.1 31.8,-35.1 4.6,-5.7 10.1,-10.8 13.3,-17.4 -6.2,3.5 -11.5,8.1 -17.3,12.2 -19,13.8 -37.7,28.4 -58.5,39.4 -19.1,9.9 -39.7,17.1 -61.1,20.2 -3.3,0.3 -6.4,-1 -9.4,-2.1 -9.5,-3.8 -18.8,-8.1 -27.9,-12.8 -4,-2 -7.8,-4.5 -12,-6 -2.4,-0.9 -4.7,0.9 -6.8,1.8 -10.5,5.7 -21.2,11 -32.1,15.8 -4.8,2 -9.9,4.2 -15.2,3.2 -17.1,-2.7 -33.8,-7.6 -49.3,-15.1 -31.3,-14.5 -57.4,-37.5 -85.3,-57.4 4,7 9.6,13 14.8,19.2 7.5,8.4 15,16.8 22.5,25.2 7.1,7.9 15.2,14.7 22,22.8 -29.3,-12.6 -57.3,-28.7 -82.1,-48.7 -8,-6.1 -18,-8.5 -27.1,-12.2 -23.5,-8.7 -46.5,-18.8 -68.7,-30.5 -19.3,-10.8 -37.8,-23.1 -54.5,-37.6 14.6,-0.4 29.2,-1 43.8,-1.8 -7.3,-4 -15.3,-6.7 -22.2,-11.3 -28.7,-18.9 -52,-45 -71.8,-72.9l80.8,-25.1C159.7,604.7 144.2,584.3 129.4,564.3c13.9,0.5 27.9,-0.2 41.8,-0.2 9.3,-0.1 18.7,0.2 28,-0.3 -6.8,-9.1 -14.5,-17.5 -20.9,-26.9 12.5,-3.6 25.2,-6.5 37.4,-11 -14.1,-0.6 -28.3,1.1 -42.4,-0.9 -31.1,-4.6 -60.7,-18.2 -84.7,-38.4 -22.1,-18 -40.1,-40.6 -54.2,-65.3C13.5,383.2 3.7,339 6.9,295.7c2.9,-36.5 16.5,-73.2 42.7,-99.5 16.1,-17 36.7,-29.8 59.3,-36.4m-24.2,47.6c6.9,3 14.4,4.4 21.4,7.2 7.3,2.8 14.4,6.2 21.4,9.6 26.3,13.2 49.1,33.3 65,58 7.1,11.6 12.5,24.1 17.2,36.9 -12.1,-10.9 -23,-23.1 -36,-33 -12.3,-10.9 -26,-20.1 -40.3,-28.1 -17.1,19.3 -25.7,45 -26.7,70.5 -1.3,36.1 10.2,71.8 29.3,102.1 13.8,21.9 32.7,40.8 55.4,53.4 7.9,4.3 16.4,8.1 25.5,8.7 -4.6,-5.6 -10,-10.4 -14.6,-16 -11.4,-13.3 -19.9,-28.8 -25.9,-45.2 12.7,0.3 25,3.9 37,7.8 -4.8,-7.7 -9.7,-15.3 -13.7,-23.5 -10.5,-21.8 -15.6,-46.2 -15.5,-70.4 15,8.8 28.8,19.4 41.5,31.3 1.3,1.2 11.6,10.9 16.5,17 4.6,5.7 9.6,11.1 14,17 4.6,6.6 9,13.3 14,19.6 1.9,-45.7 -8.6,-92.1 -30.4,-132.3 -18,-32.7 -44.9,-61.1 -78.3,-78.2 -23.6,-11.9 -51,-17.5 -77.1,-12.4m778.2,11.1c-17.5,8 -32.7,20.2 -46.1,33.8 -29.2,29 -47.8,67.3 -56.5,107.3 -4.9,23.2 -8.1,47 -6.8,70.8 10.3,-14.6 20.6,-29.4 33,-42.3 4.8,-5.2 13.5,-14.2 23.4,-22 9.2,-7.7 19.4,-14.1 29.4,-20.7 0.5,20.1 -3,40.3 -10,59.2 -4.6,12.6 -11.8,24 -19,35.3 12,-4 24.3,-7.5 37,-8.8 -4.3,14.8 -12.2,28.3 -21.3,40.7 -5.8,8 -13.6,14.3 -19.9,21.8 16.6,-2.4 31.4,-11.1 44.7,-21 27.1,-21.2 46.3,-51.5 57.1,-84 8.8,-25.9 11.9,-54.1 6.4,-81 -3.9,-18 -11.4,-35.5 -23.4,-49.6 -13.4,6.9 -25.9,15.6 -37.6,25.1 -14.1,10.6 -26,23.7 -38.5,36.1 9.3,-28.2 29,-57.5 53.8,-77.5 16.6,-13.4 36,-23.1 56.3,-29.6 5.1,-1.5 10.3,-2.8 15.4,-4.7 -26,-4.9 -53.4,-0.3 -77.1,11.1M258.7,517.3c5.8,32.7 16.5,65 34.7,92.9 9.8,15.3 23.3,28.6 39.5,37.1 26.8,14.2 60,13.8 88.7,5.7 3.5,-1 7.3,-3 11,-1.6 8.4,2.7 17,5.3 25.9,5.9 -4.9,-5 -10.9,-8.6 -16.5,-12.7 -24.7,-17.9 -49.5,-35.8 -74.6,-53.2 -36.1,-24.9 -72.4,-49.5 -108.7,-74.1m497,6.3c-18.7,13.2 -37.9,25.8 -56.8,38.7 -37,25.5 -74.5,50.3 -110.5,77.2 -8,6.1 -16.5,11.5 -24.2,17.9 9.1,-0.2 17.7,-3.5 26.4,-6.2 3.9,-1.4 7.9,0.5 11.7,1.6 13.6,4.5 48.8,9.3 69.9,1.4 15.4,-4.9 31,-13.1 42.7,-24.2 12.7,-12.9 22.9,-27.8 30.5,-44.2 10.1,-21.8 16.4,-44.6 20,-68.3 -3.3,2 -6.5,4 -9.6,6.2M178,643.6c6.4,4.4 13.2,8.1 19.9,11.8 37.3,19.6 76.4,35.6 114.1,54.6 6.1,3.1 12.6,5.4 18.2,9.5 -21.9,-4.1 -43.6,-9.1 -65.8,-11 -25,-2.2 -51.1,-2.1 -74.8,7.3 4.3,1.3 8.6,2.4 12.9,3.3 43.9,8.8 87.9,17.7 131.6,27.7 -15.2,2 -30.6,3.1 -45.7,6.1 -18.2,3.8 -36.5,8.6 -52.8,17.7 49.4,-2.4 98.6,-7 147.9,-10.3 -13.5,-9.9 -27.6,-19.1 -40.4,-29.9 15.7,-2.4 31.3,-5 46.9,-7.9 -25.4,-18.2 -53.1,-33 -81.5,-45.8 -41.2,-18.1 -85.1,-32.1 -130.4,-33m536.8,32.6c-18.6,8.4 -37,17.3 -54.2,28.3 -9.4,6.1 -19.4,11.4 -28.4,18.2 16.3,2.5 32.6,5.6 49.1,7.5 -13.7,10.4 -28.1,19.9 -41.7,30.5 7.6,0.2 15.2,0.5 22.9,1.3 29,2.2 58.1,4.4 87.1,6.2 12.1,0.3 24.1,2 36.3,1.3 -15.7,-7.9 -32.9,-12.4 -50,-16.1 -15.2,-3.4 -30.8,-4.3 -46.1,-7 29.4,-7 59,-13 88.7,-18.6 18.8,-4.1 37.9,-6.9 56.5,-11.7 -4.4,-2.4 -9.2,-3.6 -14.1,-4.5 -22.1,-5 -45.1,-5.1 -67.5,-2.6 -20.2,2 -40,6.8 -59.9,10.6 9.5,-6.1 20Line truncated
android:fillColor="#000000"/>
<!-- brow -->
<path
android:pathData="m371.7,271.4c12.1,2.7 24,6.3 36,9.6 12.2,3.5 24.4,6.9 36.2,11.5 4.2,1.5 8.2,4.9 12.9,3.9 31.9,-5.8 64.6,-5.4 96.7,-2.1 6.6,0.5 13.6,3.7 20.1,0.5 12.3,-5.6 25.4,-9.3 38.3,-13.1 13.2,-3.6 26.3,-7.9 39.8,-10.3L512.6,501.3Z"
android:fillColor="#000000"/>
<!-- muzzle -->
<path
android:pathData="m509,859.9c2.3,-1.3 4.7,0.1 6.8,1.1 11,5.8 22.1,11.5 33.3,17 2.8,1.1 5.7,3 8.8,2.2l52.2,-15.5c-18.8,24.1 -40.6,46.2 -65.9,63.6l1.4,-15 -34.3,25.3 -34.2,-25.8 2.2,15.7c-18.9,-13.7 -36.6,-29.2 -52.6,-46.4 -5.1,-5.5 -10.2,-11 -14.5,-17.1l36.5,10.8c5.9,1.4 11.4,4.4 17.5,4.9 5,-1.2 9.4,-4.2 14.1,-6.3 9.7,-4.6 19,-10 28.8,-14.3z"
android:pathData="@string/ic_launcher_path_head"
android:fillColor="#000000"/>
</group>
</vector>
+27
View File
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Background gradient, shared by ic_launcher_background and ic_launcher_legacy -->
<color name="ic_launcher_gradient_start">#960EFC</color>
<color name="ic_launcher_gradient_end">#FF804A</color>
<!-- Head outline plus brow and muzzle, shared by ic_launcher_foreground, ic_launcher_monochrome and ic_launcher_legacy -->
<!-- See below for (commented) decoupled version -->
<string name="ic_launcher_path_head" translatable="false">m108.8,159.8c25,-7.7 52,-7 77,0 37.3,10.1 69.8,32.8 97.9,58.6 12.7,12.5 25.6,24.7 36.6,38.7 17.2,20.5 31.8,43 44.9,66.3l12.1,20.1 -4.2,-19.8c10.6,18.8 17.2,39.4 25.9,59.1 6.8,16.5 14.9,32.5 22.1,48.9 -13.9,-9.2 -26.9,-19.7 -40.8,-28.9 -9.3,-6 -18.1,-12.8 -27.8,-18.1 10.3,21.3 23.7,40.8 37.1,60.2 12.6,17.5 25.7,34.5 39.6,51L512.6,594.1 611.7,473.9c14,-18 27.4,-36.5 39.8,-55.7 6.9,-10.9 14.1,-21.7 19.2,-33.6 -16.6,9.7 -31.9,21.5 -47.8,32.5 -7.1,4.8 -13.6,10.6 -21.3,14.6 12.4,-24 23.6,-48.7 33.9,-73.7 4.9,-11.1 9.7,-24.4 16,-35.1L648,340.8l8.1,-13.8c20.9,-38.7 47.8,-74 79,-105 28.9,-27.4 62.4,-51.8 101.3,-62.4 27,-7.6 56.2,-7.1 82.8,1.9 36.3,12.3 67.3,39.8 82.8,75 17.5,38.4 19.2,82.5 10.9,123.5 -8.5,40.3 -28.2,78.4 -56.8,108.1 -28.7,30.1 -67.1,52.4 -108.9,57.2 -12.7,1.5 -25.5,-0 -38.1,0.8 11.9,4.3 24.5,6.6 36.4,10.8 -5.9,9.8 -13.7,18.2 -20.9,27 22.9,0.4 45.7,0 68.6,0.3 -10.4,16.2 -23,31 -37.6,43.6 -4.3,3.5 -8.9,6.8 -13.2,10.3l80.8,25.2c-19.6,29 -43.8,55.6 -73.6,74.4 -6.1,3.7 -12.8,6.3 -19.1,9.6 14.6,1.5 29.2,1.5 43.8,1.9 -6.2,6.3 -13.6,11.1 -20.7,16.3 -31.2,22.5 -66.6,38.3 -102.6,51.5 -9.9,4.2 -20.9,6.7 -29.5,13.5 -15.3,11.8 -31.7,22.1 -48.6,31.6 -10.9,5.9 -21.7,12.2 -33.4,16 4.5,-4.9 9.8,-9 14.3,-13.7 10.8,-11.5 21.6,-23.1 31.8,-35.1 4.6,-5.7 10.1,-10.8 13.3,-17.4 -6.2,3.5 -11.5,8.1 -17.3,12.2 -19,13.8 -37.7,28.4 -58.5,39.4 -19.1,9.9 -39.7,17.1 -61.1,20.2 -3.3,0.3 -6.4,-1 -9.4,-2.1 -9.5,-3.8 -18.8,-8.1 -27.9,-12.8 -4,-2 -7.8,-4.5 -12,-6 -2.4,-0.9 -4.7,0.9 -6.8,1.8 -10.5,5.7 -21.2,11 -32.1,15.8 -4.8,2 -9.9,4.2 -15.2,3.2 -17.1,-2.7 -33.8,-7.6 -49.3,-15.1 -31.3,-14.5 -57.4,-37.5 -85.3,-57.4 4,7 9.6,13 14.8,19.2 7.5,8.4 15,16.8 22.5,25.2 7.1,7.9 15.2,14.7 22,22.8 -29.3,-12.6 -57.3,-28.7 -82.1,-48.7 -8,-6.1 -18,-8.5 -27.1,-12.2 -23.5,-8.7 -46.5,-18.8 -68.7,-30.5 -19.3,-10.8 -37.8,-23.1 -54.5,-37.6 14.6,-0.4 29.2,-1 43.8,-1.8 -7.3,-4 -15.3,-6.7 -22.2,-11.3 -28.7,-18.9 -52,-45 -71.8,-72.9l80.8,-25.1C159.7,604.7 144.2,584.3 129.4,564.3c13.9,0.5 27.9,-0.2 41.8,-0.2 9.3,-0.1 18.7,0.2 28,-0.3 -6.8,-9.1 -14.5,-17.5 -20.9,-26.9 12.5,-3.6 25.2,-6.5 37.4,-11 -14.1,-0.6 -28.3,1.1 -42.4,-0.9 -31.1,-4.6 -60.7,-18.2 -84.7,-38.4 -22.1,-18 -40.1,-40.6 -54.2,-65.3C13.5,383.2 3.7,339 6.9,295.7c2.9,-36.5 16.5,-73.2 42.7,-99.5 16.1,-17 36.7,-29.8 59.3,-36.4m-24.2,47.6c6.9,3 14.4,4.4 21.4,7.2 7.3,2.8 14.4,6.2 21.4,9.6 26.3,13.2 49.1,33.3 65,58 7.1,11.6 12.5,24.1 17.2,36.9 -12.1,-10.9 -23,-23.1 -36,-33 -12.3,-10.9 -26,-20.1 -40.3,-28.1 -17.1,19.3 -25.7,45 -26.7,70.5 -1.3,36.1 10.2,71.8 29.3,102.1 13.8,21.9 32.7,40.8 55.4,53.4 7.9,4.3 16.4,8.1 25.5,8.7 -4.6,-5.6 -10,-10.4 -14.6,-16 -11.4,-13.3 -19.9,-28.8 -25.9,-45.2 12.7,0.3 25,3.9 37,7.8 -4.8,-7.7 -9.7,-15.3 -13.7,-23.5 -10.5,-21.8 -15.6,-46.2 -15.5,-70.4 15,8.8 28.8,19.4 41.5,31.3 1.3,1.2 11.6,10.9 16.5,17 4.6,5.7 9.6,11.1 14,17 4.6,6.6 9,13.3 14,19.6 1.9,-45.7 -8.6,-92.1 -30.4,-132.3 -18,-32.7 -44.9,-61.1 -78.3,-78.2 -23.6,-11.9 -51,-17.5 -77.1,-12.4m778.2,11.1c-17.5,8 -32.7,20.2 -46.1,33.8 -29.2,29 -47.8,67.3 -56.5,107.3 -4.9,23.2 -8.1,47 -6.8,70.8 10.3,-14.6 20.6,-29.4 33,-42.3 4.8,-5.2 13.5,-14.2 23.4,-22 9.2,-7.7 19.4,-14.1 29.4,-20.7 0.5,20.1 -3,40.3 -10,59.2 -4.6,12.6 -11.8,24 -19,35.3 12,-4 24.3,-7.5 37,-8.8 -4.3,14.8 -12.2,28.3 -21.3,40.7 -5.8,8 -13.6,14.3 -19.9,21.8 16.6,-2.4 31.4,-11.1 44.7,-21 27.1,-21.2 46.3,-51.5 57.1,-84 8.8,-25.9 11.9,-54.1 6.4,-81 -3.9,-18 -11.4,-35.5 -23.4,-49.6 -13.4,6.9 -25.9,15.6 -37.6,25.1 -14.1,10.6 -26,23.7 -38.5,36.1 9.3,-28.2 29,-57.5 53.8,-77.5 16.6,-13.4 36,-23.1 56.3,-29.6 5.1,-1.5 10.3,-2.8 15.4,-4.7 -26,-4.9 -53.4,-0.3 -77.1,11.1M258.7,517.3c5.8,32.7 16.5,65 34.7,92.9 9.8,15.3 23.3,28.6 39.5,37.1 26.8,14.2 60,13.8 88.7,5.7 3.5,-1 7.3,-3 11,-1.6 8.4,2.7 17,5.3 25.9,5.9 -4.9,-5 -10.9,-8.6 -16.5,-12.7 -24.7,-17.9 -49.5,-35.8 -74.6,-53.2 -36.1,-24.9 -72.4,-49.5 -108.7,-74.1m497,6.3c-18.7,13.2 -37.9,25.8 -56.8,38.7 -37,25.5 -74.5,50.3 -110.5,77.2 -8,6.1 -16.5,11.5 -24.2,17.9 9.1,-0.2 17.7,-3.5 26.4,-6.2 3.9,-1.4 7.9,0.5 11.7,1.6 13.6,4.5 48.8,9.3 69.9,1.4 15.4,-4.9 31,-13.1 42.7,-24.2 12.7,-12.9 22.9,-27.8 30.5,-44.2 10.1,-21.8 16.4,-44.6 20,-68.3 -3.3,2 -6.5,4 -9.6,6.2M178,643.6c6.4,4.4 13.2,8.1 19.9,11.8 37.3,19.6 76.4,35.6 114.1,54.6 6.1,3.1 12.6,5.4 18.2,9.5 -21.9,-4.1 -43.6,-9.1 -65.8,-11 -25,-2.2 -51.1,-2.1 -74.8,7.3 4.3,1.3 8.6,2.4 12.9,3.3 43.9,8.8 87.9,17.7 131.6,27.7 -15.2,2 -30.6,3.1 -45.7,6.1 -18.2,3.8 -36.5,8.6 -52.8,17.7 49.4,-2.4 98.6,-7 147.9,-10.3 -13.5,-9.9 -27.6,-19.1 -40.4,-29.9 15.7,-2.4 31.3,-5 46.9,-7.9 -25.4,-18.2 -53.1,-33 -81.5,-45.8 -41.2,-18.1 -85.1,-32.1 -130.4,-33m536.8,32.6c-18.6,8.4 -37,17.3 -54.2,28.3 -9.4,6.1 -19.4,11.4 -28.4,18.2 16.3,2.5 32.6,5.6 49.1,7.5 -13.7,10.4 -28.1,19.9 -41.7,30.5 7.6,0.2 15.2,0.5 22.9,1.3 29,2.2 58.1,4.4 87.1,6.2 12.1,0.3 24.1,2 36.3,1.3 -15.7,-7.9 -32.9,-12.4 -50,-16.1 -15.2,-3.4 -30.8,-4.3 -46.1,-7 29.4,-7 59,-13 88.7,-18.6 18.8,-4.1 37.9,-6.9 56.5,-11.7 -4.4,-2.4 -9.2,-3.6 -14.1,-4.5 -22.1,-5 -45.1,-5.1 -67.5,-2.6 -2Line truncated
<!-- OLD ICON PATHS: decoupled head, brow and muzzle respectvely
<path
android:pathData="m108.8,159.8c25,-7.7 52,-7 77,0 37.3,10.1 69.8,32.8 97.9,58.6 12.7,12.5 25.6,24.7 36.6,38.7 17.2,20.5 31.8,43 44.9,66.3l12.1,20.1 -4.2,-19.8c10.6,18.8 17.2,39.4 25.9,59.1 6.8,16.5 14.9,32.5 22.1,48.9 -13.9,-9.2 -26.9,-19.7 -40.8,-28.9 -9.3,-6 -18.1,-12.8 -27.8,-18.1 10.3,21.3 23.7,40.8 37.1,60.2 12.6,17.5 25.7,34.5 39.6,51L512.6,594.1 611.7,473.9c14,-18 27.4,-36.5 39.8,-55.7 6.9,-10.9 14.1,-21.7 19.2,-33.6 -16.6,9.7 -31.9,21.5 -47.8,32.5 -7.1,4.8 -13.6,10.6 -21.3,14.6 12.4,-24 23.6,-48.7 33.9,-73.7 4.9,-11.1 9.7,-24.4 16,-35.1L648,340.8l8.1,-13.8c20.9,-38.7 47.8,-74 79,-105 28.9,-27.4 62.4,-51.8 101.3,-62.4 27,-7.6 56.2,-7.1 82.8,1.9 36.3,12.3 67.3,39.8 82.8,75 17.5,38.4 19.2,82.5 10.9,123.5 -8.5,40.3 -28.2,78.4 -56.8,108.1 -28.7,30.1 -67.1,52.4 -108.9,57.2 -12.7,1.5 -25.5,-0 -38.1,0.8 11.9,4.3 24.5,6.6 36.4,10.8 -5.9,9.8 -13.7,18.2 -20.9,27 22.9,0.4 45.7,0 68.6,0.3 -10.4,16.2 -23,31 -37.6,43.6 -4.3,3.5 -8.9,6.8 -13.2,10.3l80.8,25.2c-19.6,29 -43.8,55.6 -73.6,74.4 -6.1,3.7 -12.8,6.3 -19.1,9.6 14.6,1.5 29.2,1.5 43.8,1.9 -6.2,6.3 -13.6,11.1 -20.7,16.3 -31.2,22.5 -66.6,38.3 -102.6,51.5 -9.9,4.2 -20.9,6.7 -29.5,13.5 -15.3,11.8 -31.7,22.1 -48.6,31.6 -10.9,5.9 -21.7,12.2 -33.4,16 4.5,-4.9 9.8,-9 14.3,-13.7 10.8,-11.5 21.6,-23.1 31.8,-35.1 4.6,-5.7 10.1,-10.8 13.3,-17.4 -6.2,3.5 -11.5,8.1 -17.3,12.2 -19,13.8 -37.7,28.4 -58.5,39.4 -19.1,9.9 -39.7,17.1 -61.1,20.2 -3.3,0.3 -6.4,-1 -9.4,-2.1 -9.5,-3.8 -18.8,-8.1 -27.9,-12.8 -4,-2 -7.8,-4.5 -12,-6 -2.4,-0.9 -4.7,0.9 -6.8,1.8 -10.5,5.7 -21.2,11 -32.1,15.8 -4.8,2 -9.9,4.2 -15.2,3.2 -17.1,-2.7 -33.8,-7.6 -49.3,-15.1 -31.3,-14.5 -57.4,-37.5 -85.3,-57.4 4,7 9.6,13 14.8,19.2 7.5,8.4 15,16.8 22.5,25.2 7.1,7.9 15.2,14.7 22,22.8 -29.3,-12.6 -57.3,-28.7 -82.1,-48.7 -8,-6.1 -18,-8.5 -27.1,-12.2 -23.5,-8.7 -46.5,-18.8 -68.7,-30.5 -19.3,-10.8 -37.8,-23.1 -54.5,-37.6 14.6,-0.4 29.2,-1 43.8,-1.8 -7.3,-4 -15.3,-6.7 -22.2,-11.3 -28.7,-18.9 -52,-45 -71.8,-72.9l80.8,-25.1C159.7,604.7 144.2,584.3 129.4,564.3c13.9,0.5 27.9,-0.2 41.8,-0.2 9.3,-0.1 18.7,0.2 28,-0.3 -6.8,-9.1 -14.5,-17.5 -20.9,-26.9 12.5,-3.6 25.2,-6.5 37.4,-11 -14.1,-0.6 -28.3,1.1 -42.4,-0.9 -31.1,-4.6 -60.7,-18.2 -84.7,-38.4 -22.1,-18 -40.1,-40.6 -54.2,-65.3C13.5,383.2 3.7,339 6.9,295.7c2.9,-36.5 16.5,-73.2 42.7,-99.5 16.1,-17 36.7,-29.8 59.3,-36.4m-24.2,47.6c6.9,3 14.4,4.4 21.4,7.2 7.3,2.8 14.4,6.2 21.4,9.6 26.3,13.2 49.1,33.3 65,58 7.1,11.6 12.5,24.1 17.2,36.9 -12.1,-10.9 -23,-23.1 -36,-33 -12.3,-10.9 -26,-20.1 -40.3,-28.1 -17.1,19.3 -25.7,45 -26.7,70.5 -1.3,36.1 10.2,71.8 29.3,102.1 13.8,21.9 32.7,40.8 55.4,53.4 7.9,4.3 16.4,8.1 25.5,8.7 -4.6,-5.6 -10,-10.4 -14.6,-16 -11.4,-13.3 -19.9,-28.8 -25.9,-45.2 12.7,0.3 25,3.9 37,7.8 -4.8,-7.7 -9.7,-15.3 -13.7,-23.5 -10.5,-21.8 -15.6,-46.2 -15.5,-70.4 15,8.8 28.8,19.4 41.5,31.3 1.3,1.2 11.6,10.9 16.5,17 4.6,5.7 9.6,11.1 14,17 4.6,6.6 9,13.3 14,19.6 1.9,-45.7 -8.6,-92.1 -30.4,-132.3 -18,-32.7 -44.9,-61.1 -78.3,-78.2 -23.6,-11.9 -51,-17.5 -77.1,-12.4m778.2,11.1c-17.5,8 -32.7,20.2 -46.1,33.8 -29.2,29 -47.8,67.3 -56.5,107.3 -4.9,23.2 -8.1,47 -6.8,70.8 10.3,-14.6 20.6,-29.4 33,-42.3 4.8,-5.2 13.5,-14.2 23.4,-22 9.2,-7.7 19.4,-14.1 29.4,-20.7 0.5,20.1 -3,40.3 -10,59.2 -4.6,12.6 -11.8,24 -19,35.3 12,-4 24.3,-7.5 37,-8.8 -4.3,14.8 -12.2,28.3 -21.3,40.7 -5.8,8 -13.6,14.3 -19.9,21.8 16.6,-2.4 31.4,-11.1 44.7,-21 27.1,-21.2 46.3,-51.5 57.1,-84 8.8,-25.9 11.9,-54.1 6.4,-81 -3.9,-18 -11.4,-35.5 -23.4,-49.6 -13.4,6.9 -25.9,15.6 -37.6,25.1 -14.1,10.6 -26,23.7 -38.5,36.1 9.3,-28.2 29,-57.5 53.8,-77.5 16.6,-13.4 36,-23.1 56.3,-29.6 5.1,-1.5 10.3,-2.8 15.4,-4.7 -26,-4.9 -53.4,-0.3 -77.1,11.1M258.7,517.3c5.8,32.7 16.5,65 34.7,92.9 9.8,15.3 23.3,28.6 39.5,37.1 26.8,14.2 60,13.8 88.7,5.7 3.5,-1 7.3,-3 11,-1.6 8.4,2.7 17,5.3 25.9,5.9 -4.9,-5 -10.9,-8.6 -16.5,-12.7 -24.7,-17.9 -49.5,-35.8 -74.6,-53.2 -36.1,-24.9 -72.4,-49.5 -108.7,-74.1m497,6.3c-18.7,13.2 -37.9,25.8 -56.8,38.7 -37,25.5 -74.5,50.3 -110.5,77.2 -8,6.1 -16.5,11.5 -24.2,17.9 9.1,-0.2 17.7,-3.5 26.4,-6.2 3.9,-1.4 7.9,0.5 11.7,1.6 13.6,4.5 48.8,9.3 69.9,1.4 15.4,-4.9 31,-13.1 42.7,-24.2 12.7,-12.9 22.9,-27.8 30.5,-44.2 10.1,-21.8 16.4,-44.6 20,-68.3 -3.3,2 -6.5,4 -9.6,6.2M178,643.6c6.4,4.4 13.2,8.1 19.9,11.8 37.3,19.6 76.4,35.6 114.1,54.6 6.1,3.1 12.6,5.4 18.2,9.5 -21.9,-4.1 -43.6,-9.1 -65.8,-11 -25,-2.2 -51.1,-2.1 -74.8,7.3 4.3,1.3 8.6,2.4 12.9,3.3 43.9,8.8 87.9,17.7 131.6,27.7 -15.2,2 -30.6,3.1 -45.7,6.1 -18.2,3.8 -36.5,8.6 -52.8,17.7 49.4,-2.4 98.6,-7 147.9,-10.3 -13.5,-9.9 -27.6,-19.1 -40.4,-29.9 15.7,-2.4 31.3,-5 46.9,-7.9 -25.4,-18.2 -53.1,-33 -81.5,-45.8 -41.2,-18.1 -85.1,-32.1 -130.4,-33m536.8,32.6c-18.6,8.4 -37,17.3 -54.2,28.3 -9.4,6.1 -19.4,11.4 -28.4,18.2 16.3,2.5 32.6,5.6 49.1,7.5 -13.7,10.4 -28.1,19.9 -41.7,30.5 7.6,0.2 15.2,0.5 22.9,1.3 29,2.2 58.1,4.4 87.1,6.2 12.1,0.3 24.1,2 36.3,1.3 -15.7,-7.9 -32.9,-12.4 -50,-16.1 -15.2,-3.4 -30.8,-4.3 -46.1,-7 29.4,-7 59,-13 88.7,-18.6 18.8,-4.1 37.9,-6.9 56.5,-11.7 -4.4,-2.4 -9.2,-3.6 -14.1,-4.5 -22.1,-5 -45.1,-5.1 -67.5,-2.6 -20.2,2 -40,6.8 -59.9,10.6 9.5,-6.1 20Line truncated
android:fillColor="#fcecdc"/>
<path
android:pathData="m371.7,271.4c12.1,2.7 24,6.3 36,9.6 12.2,3.5 24.4,6.9 36.2,11.5 4.2,1.5 8.2,4.9 12.9,3.9 31.9,-5.8 64.6,-5.4 96.7,-2.1 6.6,0.5 13.6,3.7 20.1,0.5 12.3,-5.6 25.4,-9.3 38.3,-13.1 13.2,-3.6 26.3,-7.9 39.8,-10.3L512.6,501.3Z"
android:fillColor="#fcecdc"/>
<path
android:pathData="m509,859.9c2.3,-1.3 4.7,0.1 6.8,1.1 11,5.8 22.1,11.5 33.3,17 2.8,1.1 5.7,3 8.8,2.2l52.2,-15.5c-18.8,24.1 -40.6,46.2 -65.9,63.6l1.4,-15 -34.3,25.3 -34.2,-25.8 2.2,15.7c-18.9,-13.7 -36.6,-29.2 -52.6,-46.4 -5.1,-5.5 -10.2,-11 -14.5,-17.1l36.5,10.8c5.9,1.4 11.4,4.4 17.5,4.9 5,-1.2 9.4,-4.2 14.1,-6.3 9.7,-4.6 19,-10 28.8,-14.3z"
android:fillColor="#fcecdc"/>
-->
<!-- Legacy (non-adaptive) icon: unscaled, see drawable/ic_launcher_legacy. The adaptive icon in
mipmap-anydpi-v26 takes precedence on API 26+; see gradle/legacy-icon.gradle -->
<!-- A vector in res/mipmap trips the IDE, so alias the drawable instead -->
<item name="ic_launcher" type="mipmap" tools:ignore="ReferenceType">@drawable/ic_launcher_legacy</item>
</resources>