Click here to Skip to main content
14,580,625 members

Dynamically Changing Android App Icons, For the Rest of Us (Literally)

Rate this:
5.00 (1 vote)
Please Sign up or sign in to vote.
5.00 (1 vote)
1 Jun 2020CPOL
This is the story of the letters in the date of Google Calendar.
In this article, we dig into Google Calendar APK, take a closer look at .smali files, and a class called DynamicIconProvider.

What

If you’re an Android user, you’ve probably noticed how the Google Calendar app icon behaves:

Image 1

There’s that little blue (#A7C5F8 for the skeptical) circle just sitting there, maybe signaling that there’s been something going on in the calendar app since the last time you have checked. This is not the story of that little blueish dot.

It is, however, the story of the pair of digits that you can see in the center of the above image, which happen to mysteriously coincide with today’s date (July 29th).

Why

Google calendar is probably the best calendar app (or service) that you will find out there, because one, it syncs painlessly on all your devices, and two, its icon has those friendly little digits on that nice blue (a number of colors are involved including #3764D0) background which correctly shows the day of the month, whenever you take a look at it, and this fancy, mysterious little feature, is what I’m going to try to figure out. I got sucked into this while trying to implement the exact same feature for Persian Calendar.

How

Alright, so let’s get to work. I found a Google Calendar APK on the internet (I’m not putting it here because of copyright and stuff) and decompiled it. Next, I tried something as simple as:
Please note: Code excerpts are incomplete. I have only copied the relevant parts.

grep -Ri dynamic . | grep icon

Here’s an important part of the output:

./res/values-v26/arrays.xml:    <array name="calendar_icons_dynamic_nexus_round">

Now here’s an important portion of that file:

<array name="calendar_icons_dynamic">
    <item>@drawable/logo_calendar_01_adaptive</item>
    <item>@drawable/logo_calendar_02_adaptive</item>
    <item>@drawable/logo_calendar_03_adaptive</item>
    <item>@drawable/logo_calendar_04_adaptive</item>
    <item>@drawable/logo_calendar_05_adaptive</item>

Now let’s have a look at res/drawable-v26/logo_calendar_03_adaptive.xml:

<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon

  xmlns:android="http://schemas.android.com/apk/res/android">
    <background android:drawable="@drawable/adaptive_background" />
    <foreground>
        <layer-list>
            <item android:drawable="@drawable/adaptive_base" />
            <item android:drawable="@drawable/calendar_date_03_adaptive" />
        </layer-list>
    </foreground>
</adaptive-icon>

Quite self explanatory, right? Each of those resources are pngs containing exactly what you’re thinking: The adaptive_base:

Image 2

And the calendar_date_03_adaptive:

Image 3

After that, I took a dive into .smali files and also browsed the remains of the actual code after converting the apk to jar using dex2jar (I used jd-gui).

After hours of navigating the code, I hadn’t found a single reference to any of these drawables that I mentioned above. This is why I set the calendar app aside and went diving into pixel launcher’s source code. Here’s what you can find there in a class called DyanmicIconProvider:

private boolean fe(String paramString) // Write: fe, Read: IsAppOurOwnCalender
{
  return "com.google.android.calendar".equals(paramString);
}

public Drawable getIcon(LauncherActivityInfo paramLauncherActivityInfo,
                        int paramInt, boolean paramBoolean)
{
  localObject3 = null;
  Object localObject4 = paramLauncherActivityInfo.getApplicationInfo().packageName;
  if (fe((String)localObject4)) {}

And all of a sudden, everything makes sense if you take a glimpse on other parts of this file:

public DynamicIconProvider(Context paramContext)
 {
   IntentFilter localIntentFilter = new IntentFilter("android.intent.action.DATE_CHANGED");
   localIntentFilter.addAction("android.intent.action.TIME_SET");
   localIntentFilter.addAction("android.intent.action.TIMEZONE_CHANGED");
   paramContext.registerReceiver(this.gv, localIntentFilter, null,
                                 new Handler(LauncherModel.getWorkerLooper()));
   this.mContext = paramContext;
   this.mPackageManager = paramContext.getPackageManager();
 }

Note the action class paths. I also found this c.class with barely even readable decompiled function/class names which is probably responsible for updating the face of the clock app (deskclock). Here are some interesting parts:

localPackageManager.getApplicationInfo("com.google.android.deskclock", 8320);
localb.fW = localBundle.getInt
            ("com.google.android.apps.nexuslauncher.HOUR_LAYER_INDEX", -1);
localb.fY = localBundle.getInt
            ("com.google.android.apps.nexuslauncher.MINUTE_LAYER_INDEX", -1);
localb.fZ = localBundle.getInt
            ("com.google.android.apps.nexuslauncher.SECOND_LAYER_INDEX", -1);
localb.fS = localBundle.getInt("com.google.android.apps.nexuslauncher.DEFAULT_HOUR", 0);
localb.fT = localBundle.getInt("com.google.android.apps.nexuslauncher.DEFAULT_MINUTE", 0);
localb.fU = localBundle.getInt("com.google.android.apps.nexuslauncher.DEFAULT_SECOND", 0);

It is also interesting to note the difference between the design decisions in case of the calendar app and the deskclock app adaptive icons. To further understand this, let’s have a look at a part of launcher_clock.xml from deskclock:

<foreground>
    <layer-list>
        <item>
            <rotate android:drawable="@mipmap/launcher_clock_hour" 

             android:fromDegrees="300.0" android:toDegrees="5300.0" 

             android:pivotX="50.0%" android:pivotY="50.0%" />
        </item>
        <item>
            <rotate android:drawable="@mipmap/launcher_clock_minute" 

             android:fromDegrees="60.0" android:toDegrees="60060.0" 

             android:pivotX="50.0%" android:pivotY="50.0%" />
        </item>
        <item>
            <rotate android:drawable="@mipmap/launcher_clock_second" 

             android:fromDegrees="180.0" android:toDegrees="6180.0" 

             android:pivotX="50.0%" android:pivotY="50.0%" android:level="300" />
        </item>
        <item android:drawable="@mipmap/launcher_clock_top" />
        <item android:drawable="@mipmap/launcher_clock_banner" />
    </layer-list>
</foreground>

And apart from this, there is no other reference to launcher_clock_second in the codebase. Interestingly enough, we can see two very different solutions for a fairly similar problem: dynamic launcher icons. Due to the complications of online text rendering, the authors have decided to pre-cook all the required icons in case of the calendar app. In the case of deskclock however, there would just be too many icons if they were to create the drawables beforehand, which has resulted in bringing this seemingly unrelated (as far as a launcher is concerned) logic into the code of the pixel launcher:

  bool1 = bool2;
  if (this.fV.getDrawable(this.fW).setLevel((i + (12 - j)) % 12 * 60 + this.fX.get(12))) {
    bool1 = true;
  }
}

So

This is where my search ends. This is bad news for you if you wanted this fancy little feature in your app too. With the launcher handling all the workload here and not even providing an internal API, we are probably not likely to see this feature be available to the public anytime soon. Let’s go back to widgets.

Update 1: Thank you Ebrahim for finding this piece of code. This is most likely the actual source code of the part that I was investigating, from LIFE-OS, under com.google.android.apps.nexuslauncher;

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

I make things faster.

https://blog.farnasirim.ir

Comments and Discussions

 
QuestionFormat Pin
Nelek30-May-20 1:17
protectorNelek30-May-20 1:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Technical Blog
Posted 1 Jun 2020

Stats

2.5K views