Support customizing icon color for externally items.

When a setting is injected to homepage, we now allow the setting
provider customize the background color for its icon.

If no custom bg color is provided, we fallback to existing behavior.

This behavior is only needed in homepage, because we don't have any
tinting logic in other pages so external setting providers can just
provide arbitrary icon.

Bug: 77188317
Test: robotests
Change-Id: I7d4b512fafc28537de46192026ce4bbe51b498c4
diff --git a/src/com/android/settings/dashboard/DashboardAdapter.java b/src/com/android/settings/dashboard/DashboardAdapter.java
index 937e6db..52ca127 100644
--- a/src/com/android/settings/dashboard/DashboardAdapter.java
+++ b/src/com/android/settings/dashboard/DashboardAdapter.java
@@ -17,6 +17,7 @@
 
 import android.app.Activity;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.service.settings.suggestions.Suggestion;
@@ -47,6 +48,7 @@
 import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
 import com.android.settingslib.drawer.DashboardCategory;
 import com.android.settingslib.drawer.Tile;
+import com.android.settingslib.drawer.TileUtils;
 import com.android.settingslib.suggestions.SuggestionControllerMixin;
 import com.android.settingslib.utils.IconCache;
 
@@ -316,6 +318,20 @@
         if (!TextUtils.equals(tile.icon.getResPackage(), mContext.getPackageName())
                 && !(icon instanceof RoundedHomepageIcon)) {
             icon = new RoundedHomepageIcon(mContext, icon);
+            try {
+                if (tile.metaData != null) {
+                    final int colorRes = tile.metaData.getInt(
+                            TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_HINT, 0 /* default */);
+                    if (colorRes != 0) {
+                        final int bgColor = mContext.getPackageManager()
+                                .getResourcesForApplication(tile.icon.getResPackage())
+                                .getColor(colorRes, null /* theme */);
+                        ((RoundedHomepageIcon) icon).setBackgroundColor(bgColor);
+                    }
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                Log.e(TAG, "Failed to set background color for " + tile.intent.getPackage());
+            }
             mCache.updateIcon(tile.icon, icon);
         }
         holder.icon.setImageDrawable(icon);
diff --git a/src/com/android/settings/dashboard/RoundedHomepageIcon.java b/src/com/android/settings/dashboard/RoundedHomepageIcon.java
index 19749b9..d63b80a 100644
--- a/src/com/android/settings/dashboard/RoundedHomepageIcon.java
+++ b/src/com/android/settings/dashboard/RoundedHomepageIcon.java
@@ -16,14 +16,21 @@
 
 package com.android.settings.dashboard;
 
+import static android.support.annotation.VisibleForTesting.NONE;
+
 import android.content.Context;
+import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LayerDrawable;
+import android.support.annotation.VisibleForTesting;
 
 import com.android.settings.R;
 
 public class RoundedHomepageIcon extends LayerDrawable {
 
+    @VisibleForTesting(otherwise = NONE)
+    int mBackgroundColor = -1;
+
     public RoundedHomepageIcon(Context context, Drawable foreground) {
         super(new Drawable[] {
                 context.getDrawable(R.drawable.ic_homepage_generic_background),
@@ -33,4 +40,9 @@
                 .getDimensionPixelSize(R.dimen.dashboard_tile_foreground_image_inset);
         setLayerInset(1 /* index */, insetPx, insetPx, insetPx, insetPx);
     }
+
+    public void setBackgroundColor(int color) {
+        mBackgroundColor = color;
+        getDrawable(0).setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java
index 5256ac4..ad0b01f 100644
--- a/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java
@@ -18,6 +18,7 @@
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -30,6 +31,7 @@
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
+import android.os.Bundle;
 import android.service.settings.suggestions.Suggestion;
 import android.support.v7.widget.RecyclerView;
 import android.util.DisplayMetrics;
@@ -46,6 +48,7 @@
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settings.testutils.shadow.SettingsShadowResources;
 import com.android.settingslib.drawer.Tile;
+import com.android.settingslib.drawer.TileUtils;
 import com.android.settingslib.utils.IconCache;
 
 import org.junit.Before;
@@ -193,24 +196,48 @@
 
     @Test
     public void onBindTile_externalTile_shouldUpdateIcon() {
-        final Context context = RuntimeEnvironment.application;
+        final Context context = spy(RuntimeEnvironment.application);
         final View view = LayoutInflater.from(context).inflate(R.layout.dashboard_tile, null);
         final DashboardAdapter.DashboardItemHolder holder =
                 new DashboardAdapter.DashboardItemHolder(view);
         final Tile tile = new Tile();
-        tile.icon = mock(Icon.class);
+        tile.icon = Icon.createWithResource(context, R.drawable.ic_settings);
         when(tile.icon.getResPackage()).thenReturn("another.package");
 
-        final IconCache iconCache = mock(IconCache.class);
-        when(iconCache.getIcon(tile.icon)).thenReturn(context.getDrawable(R.drawable.ic_settings));
+        final IconCache iconCache = new IconCache(context);
 
         mDashboardAdapter = new DashboardAdapter(context, null /* savedInstanceState */,
                 null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */);
         ReflectionHelpers.setField(mDashboardAdapter, "mCache", iconCache);
 
+        doReturn("another.package").when(context).getPackageName();
         mDashboardAdapter.onBindTile(holder, tile);
 
-        verify(iconCache).updateIcon(eq(tile.icon), any(RoundedHomepageIcon.class));
+        assertThat(iconCache.getIcon(tile.icon)).isInstanceOf(RoundedHomepageIcon.class);
+    }
+
+    @Test
+    public void onBindTile_externalTileWithBackgroundColorHint_shouldUpdateIcon() {
+        final Context context = spy(RuntimeEnvironment.application);
+        final View view = LayoutInflater.from(context).inflate(R.layout.dashboard_tile, null);
+        final DashboardAdapter.DashboardItemHolder holder =
+                new DashboardAdapter.DashboardItemHolder(view);
+        final Tile tile = new Tile();
+        tile.metaData = new Bundle();
+        tile.metaData.putInt(TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_HINT,
+                R.color.memory_critical);
+        tile.icon = Icon.createWithResource(context, R.drawable.ic_settings);
+        final IconCache iconCache = new IconCache(context);
+        mDashboardAdapter = new DashboardAdapter(context, null /* savedInstanceState */,
+                null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */);
+        ReflectionHelpers.setField(mDashboardAdapter, "mCache", iconCache);
+
+        doReturn("another.package").when(context).getPackageName();
+        mDashboardAdapter.onBindTile(holder, tile);
+
+        final RoundedHomepageIcon homepageIcon = (RoundedHomepageIcon) iconCache.getIcon(tile.icon);
+        assertThat(homepageIcon.mBackgroundColor)
+                .isEqualTo(RuntimeEnvironment.application.getColor(R.color.memory_critical));
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/dashboard/RoundedHomepageIconTest.java b/tests/robotests/src/com/android/settings/dashboard/RoundedHomepageIconTest.java
index f270395..0c90660 100644
--- a/tests/robotests/src/com/android/settings/dashboard/RoundedHomepageIconTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/RoundedHomepageIconTest.java
@@ -17,10 +17,16 @@
 package com.android.settings.dashboard;
 
 import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.graphics.Color;
+import android.graphics.PorterDuff;
 import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.ShapeDrawable;
 
 import com.android.settings.R;
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
@@ -43,10 +49,22 @@
     @Test
     public void createIcon_shouldSetBackgroundAndInset() {
         final RoundedHomepageIcon icon =
-            new RoundedHomepageIcon(mContext, new ColorDrawable(Color.BLACK));
+                new RoundedHomepageIcon(mContext, new ColorDrawable(Color.BLACK));
 
         assertThat(icon.getNumberOfLayers()).isEqualTo(2);
         assertThat(icon.getDrawable(0))
-            .isEqualTo(mContext.getDrawable(R.drawable.ic_homepage_generic_background));
+                .isEqualTo(mContext.getDrawable(R.drawable.ic_homepage_generic_background));
+    }
+
+    @Test
+    public void setBackgroundColor_shouldUpdateColorFilter() {
+        final RoundedHomepageIcon icon =
+                spy(new RoundedHomepageIcon(mContext, new ColorDrawable(Color.BLACK)));
+        final ShapeDrawable background = mock(ShapeDrawable.class);
+        when(icon.getDrawable(0)).thenReturn(background);
+
+        icon.setBackgroundColor(Color.BLUE);
+
+        verify(background).setColorFilter(Color.BLUE, PorterDuff.Mode.SRC_ATOP);
     }
 }