Android Pixel Launcher如何动态更新Calendar图标

96
作者 Cbqx5RthO0gf
2017.04.18 17:57* 字数 1049

在一些第三方厂商的系统版本中,系统的日历图标根据日期会更新,但因为它们的系统代码不开源,不适合方便地学习。
Android中动态更新APP的图标并没有一个很直接的方法。为了在所有设备上更新图标,只有一些技巧包括使用Widget,更新Shortcuts或者创建很多<activity-alias>,而且它们不能保证一定成功
随着Google将Android系统版本更新到7.1.2,Pixel Launcher也被强制安装到设备上。虽然图标变得又圆又小,但是Calendar不再一直显示31,而是跟随日期自动更新了图标。所以我利用AOSP的Calendar和逆向工程,研究了动态更新APP图标的方法。


Pixel Launcher中可以显示日期的Calendar

结论是,作为第三方APP,除非用户使用了第三方支持更新图标的Launcher或桌面应用,我们依然不能轻易地动态更新我们的图标。

。。。¬_¬来都来了,看看过程呗。。。

探究过程

我试着反编译了Pixel Launcher,在目录中发现一个仅部分被混淆的类:DynamicIconProvider。

public class DynamicIconProvider
  extends IconProvider
{
  private final BroadcastReceiver cz = new g(this);
  protected final PackageManager mPackageManager;

  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.cz, localIntentFilter, null, new Handler(LauncherModel.getWorkerLooper()));
    this.mPackageManager = paramContext.getPackageManager();
  }

  // Our key function 1
  private int bV()
  {
    return Calendar.getInstance().get(5) - 1;
  }

  // Our key function 2
  private int bW(Bundle paramBundle, Resources paramResources)
  {
    if (paramBundle == null) {
      return 0;
    }
    int i = paramBundle.getInt("com.google.android.calendar.dynamic_icons_nexus_round", 0);
    if (i == 0) {
      return 0;
    }
    try
    {
      i = paramResources.obtainTypedArray(i).getResourceId(bV(), 0);
      return i;
    }
    catch (Resources.NotFoundException paramBundle) {}
    return 0;
  }

  private boolean bX(String paramString)
  {
    return "com.google.android.calendar".equals(paramString);
  }

  // Our key function 3
  public Drawable getIcon(LauncherActivityInfoCompat paramLauncherActivityInfoCompat, int paramInt)
  {
    localObject3 = null;
    Object localObject4 = paramLauncherActivityInfoCompat.getApplicationInfo().packageName;
    Object localObject1 = localObject3;
    if (bX((String)localObject4)) {}
    try
    {
      localObject1 = this.mPackageManager.getActivityInfo(paramLauncherActivityInfoCompat.getComponentName(), 8320).metaData;
      localObject4 = this.mPackageManager.getResourcesForApplication((String)localObject4);
      int i = bW((Bundle)localObject1, (Resources)localObject4);
      localObject1 = localObject3;
      if (i != 0) {
        localObject1 = ((Resources)localObject4).getDrawableForDensity(i, paramInt);
      }
    }
    catch (PackageManager.NameNotFoundException localNameNotFoundException)
    {
      for (;;)
      {
        Object localObject2 = localObject3;
      }
    }
    localObject3 = localObject1;
    if (localObject1 == null) {
      localObject3 = super.getIcon(paramLauncherActivityInfoCompat, paramInt);
    }
    return (Drawable)localObject3;
  }

  public String getIconSystemState(String paramString)
  {
    if (bX(paramString)) {
      return this.mSystemState + " " + bV();
    }
    return this.mSystemState;
  }
}

它继承自IconProvider,这个类是系统源码中Launcher3中的类。这说明Pixel Launcher跟其他替代系统默认桌面的应用的实现方法是类似的。
带注释的三个方法是实现动态更新图标的核心。这个类中的getIcon(Our key function 3)方法根据每个应用的信息,返回其图标的Drawable。这里利用bX判断使用是日历应用,再使用这个应用的metaData及其所有的资源,调用bW(Our key function 2),返回我们要更新的图标的资源ID。

bW方法很简单,首先获取关键字为

com.google.android.calendar.dynamic_icons_nexus_round

(后文还有看到它)的日历提供的所有图标,在根据bV返回的索引选取一个图标。
bV(Our key function 1)则是返回这个月的第几天,5代表一个月的第几天, 减去1是为了求索引值。
这个有点Hard code的实现,是Pixel launcher针对Calendar一款应用实现的图标动态更新,而Calendar应用,只是提供了需要的图标。
例如有的系统中可以让用户随意更换应用图标的功能(好像是锤子?),基本原理也是重载了getIcon,返回用户选择的图标资源。

因此我想去看一下Calendar的实现,找一下这些图标。
然而在找遍了AOSP中Calendar的实现,我并没有找到任何跟日期有关的图标。在焦头烂额之时,我突然想到,Google会不会留了一手,这些代码并没有显示在AOSP中,而是仅用作Google play商店的发布呢?

所以我又反编译了Calendar的apk,在它的Manifest中,我找到了一个并不在开源的Calendar的LaunchInfoActivity类和很多<activity-alias>。

<activity 
    android:excludeFromRecents="true" 
    android:exported="true" 
    android:name="com.android.calendar.event.LaunchInfoActivity" 
    android:taskAffinity="com.android.calendar.event.LaunchInfoActivity" 
    android:theme="@style/LaunchTheme">

查找一番后,我们终于发现了出现在DynamicIconProvider中的metaData的key,与前文提到的key是相同的。

<meta-data 
    android:name="com.google.android.calendar.dynamic_icons_nexus_round" 
    android:resource="@array/calendar_icons_dynamic_nexus_round"/>

接下来按图索骥查找对应的资源。在将对应的资源整理到Android Studio中,我们就得到那些图标。( •̀ ω •́ )y


整理过后的App icon

结论

  1. 没有合适的Launcher的帮助,APP很难动态更新图标。
  2. AOSP中开源项目不一定是我们设备上正在使用的应用。
  3. 现有的一部分第三方Launcher提供了API可以实现动态更改图标,但是因为受众少的问题少有开发者专门为其开发图标包。此次Pixel launcher的发布算是一个“官方正式版”,只不过还没有开放API。我们非常希望Google可以开放Pixel launcher的动态更新图标API。Make our applications amazing again.

喜欢这篇文章点个💖怎么样嘛¬_¬

Andorid