Adding support for listening to icon config changes

Bug: 183641907
Test: Manual
Change-Id: If1dbf346df34865db23c22c43a36ac9b4403bc18
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
index 7265cb9..cc80de7 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
@@ -84,7 +84,7 @@
         clear();
     }
 
-    protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) {
+    public BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) {
         this(context, fillResIconDpi, iconBitmapSize, false);
     }
 
diff --git a/iconloaderlib/src/com/android/launcher3/icons/IconProvider.java b/iconloaderlib/src/com/android/launcher3/icons/IconProvider.java
index cc463ba..6c96967 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/IconProvider.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/IconProvider.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3.icons;
 
+import static android.content.Intent.ACTION_DATE_CHANGED;
+import static android.content.Intent.ACTION_TIMEZONE_CHANGED;
+import static android.content.Intent.ACTION_TIME_CHANGED;
 import static android.content.res.Resources.ID_NULL;
 
 import android.content.BroadcastReceiver;
@@ -32,6 +35,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.PatternMatcher;
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -45,7 +49,8 @@
 import org.xmlpull.v1.XmlPullParser;
 
 import java.util.Calendar;
-import java.util.function.BiConsumer;
+import java.util.Collections;
+import java.util.Map;
 import java.util.function.Supplier;
 
 /**
@@ -53,6 +58,10 @@
  */
 public class IconProvider {
 
+    private final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
+    private static final int CONFIG_ICON_MASK_RES_ID = Resources.getSystem().getIdentifier(
+            "config_icon_mask", "string", "android");
+
     private static final String TAG_ICON = "icon";
     private static final String ATTR_PACKAGE = "package";
     private static final String ATTR_DRAWABLE = "drawable";
@@ -65,7 +74,9 @@
     private static final String SYSTEM_STATE_SEPARATOR = " ";
     private static final String THEMED_ICON_MAP_FILE = "grayscale_icon_map";
 
-    private ArrayMap<String, ThemeData> mThemedIconMap;
+    private static final Map<String, ThemeData> DISABLED_MAP = Collections.emptyMap();
+
+    private Map<String, ThemeData> mThemedIconMap;
 
     private final Context mContext;
     private final ComponentName mCalendar;
@@ -81,11 +92,18 @@
         mClock = parseComponentOrNull(context, R.string.clock_component_name);
         if (!supportsIconTheme) {
             // Initialize an empty map if theming is not supported
-            mThemedIconMap = new ArrayMap<>();
+            mThemedIconMap = DISABLED_MAP;
         }
     }
 
     /**
+     * Enables or disables icon theme support
+     */
+    public void setIconThemeSupported(boolean isSupported) {
+        mThemedIconMap = isSupported ? null : DISABLED_MAP;
+    }
+
+    /**
      * Adds any modification to the provided systemState for dynamic icons. This system state
      * is used by caches to check for icon invalidation.
      */
@@ -98,15 +116,6 @@
     }
 
     /**
-     * Loads the icon for the provided ActivityInfo such that it can be drawn directly
-     * on the UI
-     * @deprecated Use {@link #getIcon}
-     */
-    public Drawable getIconForUI(ActivityInfo info, UserHandle user) {
-        return getIcon(info);
-    }
-
-    /**
      * Loads the icon for the provided LauncherActivityInfo
      */
     public Drawable getIcon(LauncherActivityInfo info, int iconDpi) {
@@ -165,7 +174,7 @@
         return icon;
     }
 
-    private ArrayMap<String, ThemeData> getThemedIconMap() {
+    private Map<String, ThemeData> getThemedIconMap() {
         if (mThemedIconMap != null) {
             return mThemedIconMap;
         }
@@ -260,60 +269,99 @@
         return Calendar.getInstance().get(Calendar.DAY_OF_MONTH) - 1;
     }
 
-    /**
-     * Registers a callback to listen for calendar icon changes.
-     * The callback receives the packageName for the calendar icon
-     */
-    public static SafeCloseable registerIconChangeListener(Context context,
-            BiConsumer<String, UserHandle> callback, Handler handler) {
-        ComponentName calendar = parseComponentOrNull(context, R.string.calendar_component_name);
-        ComponentName clock = parseComponentOrNull(context, R.string.clock_component_name);
-
-        if (calendar == null && clock == null) {
-            return () -> { };
-        }
-
-        BroadcastReceiver receiver = new DateTimeChangeReceiver(callback);
-        final IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
-        if (calendar != null) {
-            filter.addAction(Intent.ACTION_TIME_CHANGED);
-            filter.addAction(Intent.ACTION_DATE_CHANGED);
-        }
-        context.registerReceiver(receiver, filter, null, handler);
-
-        return () -> context.unregisterReceiver(receiver);
+    private static ComponentName parseComponentOrNull(Context context, int resId) {
+        String cn = context.getString(resId);
+        return TextUtils.isEmpty(cn) ? null : ComponentName.unflattenFromString(cn);
     }
 
-    private static class DateTimeChangeReceiver extends BroadcastReceiver {
+    /**
+     * Returns a string representation of the current system icon state
+     */
+    public String getSystemIconState() {
+        return (CONFIG_ICON_MASK_RES_ID == ID_NULL
+                ? "" : mContext.getResources().getString(CONFIG_ICON_MASK_RES_ID))
+                + (mThemedIconMap == DISABLED_MAP ? ",no-theme" : ",with-theme");
+    }
 
-        private final BiConsumer<String, UserHandle> mCallback;
+    /**
+     * Registers a callback to listen for various system dependent icon changes.
+     */
+    public SafeCloseable registerIconChangeListener(IconChangeListener listener, Handler handler) {
+        return new IconChangeReceiver(listener, handler);
+    }
 
-        DateTimeChangeReceiver(BiConsumer<String, UserHandle> callback) {
+    private class IconChangeReceiver extends BroadcastReceiver implements SafeCloseable {
+
+        private final IconChangeListener mCallback;
+        private String mIconState;
+
+        IconChangeReceiver(IconChangeListener callback, Handler handler) {
             mCallback = callback;
+            mIconState = getSystemIconState();
+
+
+            IntentFilter packageFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
+            packageFilter.addDataScheme("package");
+            packageFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL);
+            mContext.registerReceiver(this, packageFilter, null, handler);
+
+            if (mCalendar != null || mClock != null) {
+                final IntentFilter filter = new IntentFilter(ACTION_TIMEZONE_CHANGED);
+                if (mCalendar != null) {
+                    filter.addAction(Intent.ACTION_TIME_CHANGED);
+                    filter.addAction(ACTION_DATE_CHANGED);
+                }
+                mContext.registerReceiver(this, filter, null, handler);
+            }
         }
 
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
-                ComponentName clock = parseComponentOrNull(context, R.string.clock_component_name);
-                if (clock != null) {
-                    mCallback.accept(clock.getPackageName(), Process.myUserHandle());
-                }
-            }
-
-            ComponentName calendar =
-                    parseComponentOrNull(context, R.string.calendar_component_name);
-            if (calendar != null) {
-                for (UserHandle user
-                        : context.getSystemService(UserManager.class).getUserProfiles()) {
-                    mCallback.accept(calendar.getPackageName(), user);
+            switch (intent.getAction()) {
+                case ACTION_TIMEZONE_CHANGED:
+                    if (mClock != null) {
+                        mCallback.onAppIconChanged(mClock.getPackageName(), Process.myUserHandle());
+                    }
+                    // follow through
+                case ACTION_DATE_CHANGED:
+                case ACTION_TIME_CHANGED:
+                    if (mCalendar != null) {
+                        for (UserHandle user
+                                : context.getSystemService(UserManager.class).getUserProfiles()) {
+                            mCallback.onAppIconChanged(mCalendar.getPackageName(), user);
+                        }
+                    }
+                    break;
+                case ACTION_OVERLAY_CHANGED: {
+                    String newState = getSystemIconState();
+                    if (!mIconState.equals(newState)) {
+                        mIconState = newState;
+                        mCallback.onSystemIconStateChanged(mIconState);
+                    }
+                    break;
                 }
             }
         }
+
+        @Override
+        public void close() {
+            mContext.unregisterReceiver(this);
+        }
     }
 
-    private static ComponentName parseComponentOrNull(Context context, int resId) {
-        String cn = context.getString(resId);
-        return TextUtils.isEmpty(cn) ? null : ComponentName.unflattenFromString(cn);
+    /**
+     * Listener for receiving icon changes
+     */
+    public interface IconChangeListener {
+
+        /**
+         * Called when the icon for a particular app changes
+         */
+        void onAppIconChanged(String packageName, UserHandle user);
+
+        /**
+         * Called when the global icon state changed, which can typically affect all icons
+         */
+        void onSystemIconStateChanged(String iconState);
     }
 }