Skip to content

Icon Packs

Kvaesitso has built-in support for the ADW launcher icon pack format (the format that is nowadays used by virtually all icon packs and supported by all major custom launchers).

Get started

AndroidManifest.xml

In order to be recognized as an icon pack, your app must declare an activity with at least one of the following intent filters in your AndroidManifest.xml:

xml

<activity android:name="[...]" android:exported="true">
    <intent-filter>
        <action android:name="org.adw.ActivityStarter.THEMES" />
    </intent-filter>
    <intent-filter>
        <action android:name="com.novalauncher.THEME" />
    </intent-filter>
    <intent-filter>
        <!-- Themed icons; only add this if your icon pack supports themed icons -->
        <action android:name="app.lawnchair.icons.THEMED_ICON" />
    </intent-filter>
</activity>

appfilter.xml

Icons are mapped to apps using a file called appfilter.xml. This file must be located in one of these locations:

  • res/xml/appfilter.xml (recommended)
  • res/raw/appfilter.xml
  • assets/appfilter.xml

The file has the following structure:

xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- icons are added here -->
</resources>

drawable.xml

drawable.xml is a file that lists all icons that are included in the icon pack. This file is required for manual icon picking. The file lives in res/xml/drawable.xml, and looks like this:

xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <item drawable="messages" />
    <category title="System" />
    <item drawable="settings" />
    <item drawable="settings_alt_1" />
</resources>

Every icon that should be available for manual icon picking must be listed in this file. You can optionally group icons by adding <category> elements, but these categories aren't used by Kvaesitso.

Static icons

Icons are added to the appfilter.xml file using the <item> tag:

xml

<item component="ComponentInfo{com.android.deskclock/com.android.deskclock.DeskClockTabActivity}"
    drawable="clock" />
  • component is the component name of the target activity. The short form is also supported if the class' package name starts with the app's package name: ComponentInfo{com.android.deskclock/.DeskClockTabActivity}
  • drawable is the name of the icon drawable which must be located in the res/drawable/ folder (or any of its variants, e.g. res/drawable-hdpi/)

Autogenerated icons

The launcher can generate fallback icons for apps that don't have an icon in the icon pack. Fallback icons consist of four parts:

xml

<resources>
    <scale factor="1.0" />
    <iconback img="iconback" />
    <iconupon img="iconupon" />
    <iconmask img="iconmask" />
</resources>
  • scale: the original icon is scaled by this factor
  • iconback: this is drawn behind the original icon
  • iconupon: this is drawn on top of the original icon
  • iconmask: this is used to mask the original icon

You can provide multiple variants of each part by adding a number to the img attribute:

xml

<iconback img1="iconback" img2="iconback2" />

The launcher will then randomly pick one of the variants for each icon.

Dynamic icons

Calendar icons

Kvaesitso supports Nova launcher's dynamic calendar icon standard:

To your appfilter.xml file, add the following:

xml

<calendar component="ComponentInfo{com.google.android.calendar/com.android.calendar.LaunchActivity}"
    prefix="calendar_" />

prefix is the prefix of the icon drawables. You need to provide one drawable for each day of the month (calendar_1, calendar_2, etc. up to calendar_31).

NOTE

Single digit days must not be zero-padded (e.g. calendar_1 is correct but calendar_01 is not). Make sure that all 31 drawables are present, or the launcher will reject the icon.

Clock icons

Dynamic clock icons are supported as well but they work a bit differently than calendar icons. A dynamic clock icon needs at least two entries in your appfilter.xml file:

xml

<resources>
    <item
        component="ComponentInfo{com.google.android.deskclock/com.android.deskclock.DeskClockTabActivity}"
        drawable="clock" />
    <dynamic-clock defaultHour="10" defaultMinute="10" defaultSecond="30" drawable="clock"
        hourLayerIndex="0" minuteLayerIndex="1" secondLayerIndex="2" />
</resources>
  • The first entry is a normal icon entry.
  • The second entry tells the launcher that that specific drawable is a dynamic clock icon. That way you can reuse the same clock icon config for multiple apps.

The icon itself must be either a LayerDrawable, or an AdaptiveIconDrawable with a LayerDrawable as its foreground layer.

NOTE

Some launchers only support AdaptiveIconDrawables, so you should prefer that if possible.

The entry in the appfilter.xml file tells the launcher which layer corresponds to which clock hand. If your icon does not have all three clock hands, you can omit the corresponding layer index attribute or set it to -1.

Here is an example of a clock icon:

xml

<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
    <background android:drawable="@drawable/clock_background" />
    <foreground>
        <layer-list>
            <item>
                <rotate android:drawable="@drawable/clock_hour" android:fromDegrees="300.0"
                    android:pivotX="50.0%" android:pivotY="50.0%" android:toDegrees="5300.0" />
            </item>
            <item>
                <rotate android:drawable="@drawable/clock_minute" android:fromDegrees="60.0"
                    android:pivotX="50.0%" android:pivotY="50.0%" android:toDegrees="60060.0" />
            </item>
            <item>
                <rotate android:drawable="@drawable/clock_second" android:fromDegrees="180.0"
                    android:pivotX="50.0%" android:pivotY="50.0%" android:toDegrees="6180.0" />
            </item>
            <item android:drawable="@drawable/clock_top" />
        </layer-list>
    </foreground>
</adaptive-icon>

Pay attention to how each of the three clock hand layers is wrapped in a RotateDrawable and how there are very specific values set for android:fromDegrees and android:toDegrees. This is required for launchers to be able to animate the clock hands. The values for android:fromDegrees and android:toDegrees must follow these rules:

  • For the clock hour hand, the difference between android:fromDegrees and android:toDegrees must be 5000.
  • For the clock minute hand, the difference between android:fromDegrees and android:toDegrees must be 60000.
  • For the clock second hand, the difference between android:fromDegrees and android:toDegrees must be 6000.
  • Each hand can have an offset from 0° to set the clock to a specific time (this is useful so that launchers that don't support dynamic clock icons don't display the icon as noon). In the example above, the hour hand is offset by 300°, the minute hand by 60° and the second hand by 180°. This means that the clock shows 10:10:30 in its default state.
  • To let the launcher know which time the clock shows in its default state, you can use the defaultHour, defaultMinute and defaultSecond attributes in the appfilter.xml entry.

NOTE

defaultHour, defaultMinute and defaultSecond are independent from each other. If you set defaultHour to 10, then it is expected that the hour hand drawable is rotated by exactly 300°, regardless of the positions of the minute and second hands even if that means that the clock shows an impossible time.

INFO

Why these numbers?

Launchers use the android:level attribute to animate the clock hands. A drawable's level is a number between 0 and 10000 that influences how the drawable is drawn. For RotateDrawables, the level attribute is used to set the rotation angle. Each level corresponds to 1/10000 of the angle between android:fromDegrees and android:toDegrees.

For the second hand, it is expected that 10 levels are equal to 1 second. which means that 600 levels correspond to a full rotation. But since a drawable has 10000 levels, the total angle must be 360/600 * 10000 = 6000 degrees.

For the minute layer, one level is equal to one minute so 60 levels are equal to a full rotation. This means that the total angle must be 360/60 * 10000 = 60000 degrees.

For the hour layer, one level is also equal to one minute, so there are 12 * 60 = 720 levels in a full rotation. 360/720 * 10000 = 5000 degrees.

Technically, you could also use other kinds of drawables that support the android:level attribute (such as a LevelListDrawable), as long as you follow the rules above.

Themed icons

Themed icons are monochrome icons that are tinted to match the theme color of the launcher.

To declare that your icon pack supports themed icons, add the following intent filter to your AndroidManifest.xml:

xml

<intent-filter>
    <action android:name="app.lawnchair.icons.THEMED_ICON" />
</intent-filter>

The launcher will then show a toggle in the icon pack picker which allows the user to enable themed icons.

Themed icons can be provided in any of the following ways:

  • If the icon is an adaptive icon, and the drawable has a <monochrome> layer, this icon will be used as the themed icon.
  • If the icon is an adaptive icon, and it does not have a <monochrome> layer, the foreground layer will be used.
  • If the icon is not an adaptive icon, the entire icon will be used.

In any case, the icon will be tinted using the theme color and the background will be set to a solid color.