Don't allow background apps to change theme

A background app that changes the wallpaper, was able to change the
device theme.

This change will apply the theme if the app is on the foreground, like
WallpaperPicker would be, but would defer the event until the next power
button cycles, like we do with Live Wallpapers, to avoid the same type
of DoS.

Test: manual
Test: atest ThemeOverlayControllerTest
Fixes: 205140487
Change-Id: I2086b29ef9bf3bb6fb7d9ebba6e7b8db9392e459
Merged-In: I2086b29ef9bf3bb6fb7d9ebba6e7b8db9392e459
(cherry picked from commit efaa814a99bc86992c0823f07b015b9a6f80ad75)
Merged-In:I2086b29ef9bf3bb6fb7d9ebba6e7b8db9392e459
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 8d332ab..c81d1fb 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -226,6 +226,14 @@
      */
     public static final String EXTRA_NEW_WALLPAPER_ID = "android.service.wallpaper.extra.ID";
 
+    /**
+     * Extra passed on {@link Intent.ACTION_WALLPAPER_CHANGED} indicating if wallpaper was set from
+     * a foreground app.
+     * @hide
+     */
+    public static final String EXTRA_FROM_FOREGROUND_APP =
+            "android.service.wallpaper.extra.FROM_FOREGROUND_APP";
+
     // flags for which kind of wallpaper to act on
 
     /** @hide */
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 81999b5..f5bedf1 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -253,8 +253,13 @@
                 if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added.");
                 reevaluateSystemTheme(true /* forceReload */);
             } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(intent.getAction())) {
-                mAcceptColorEvents = true;
-                Log.i(TAG, "Allowing color events again");
+                if (intent.getBooleanExtra(WallpaperManager.EXTRA_FROM_FOREGROUND_APP, false)) {
+                    mAcceptColorEvents = true;
+                    Log.i(TAG, "Wallpaper changed, allowing color events again");
+                } else {
+                    Log.i(TAG, "Wallpaper changed from background app, "
+                            + "keep deferring color events. Accepting: " + mAcceptColorEvents);
+                }
             }
         }
     };
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 07d3fc2..5b55c41 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -152,7 +152,7 @@
     }
 
     @Test
-    public void onWallpaperColorsChanged_setsTheme() {
+    public void onWallpaperColorsChanged_setsTheme_whenForeground() {
         // Should ask for a new theme when wallpaper colors change
         WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
                 Color.valueOf(Color.BLUE), null);
@@ -180,13 +180,43 @@
 
         // But should change theme after changing wallpapers
         clearInvocations(mThemeOverlayApplier);
-        mBroadcastReceiver.getValue().onReceive(null, new Intent(Intent.ACTION_WALLPAPER_CHANGED));
+        Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED);
+        intent.putExtra(WallpaperManager.EXTRA_FROM_FOREGROUND_APP, true);
+        mBroadcastReceiver.getValue().onReceive(null, intent);
         mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK),
                 null, null), WallpaperManager.FLAG_SYSTEM);
         verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
     }
 
     @Test
+    public void onWallpaperColorsChanged_setsTheme_skipWhenBackground() {
+        // Should ask for a new theme when wallpaper colors change
+        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+                Color.valueOf(Color.BLUE), null);
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+        ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays =
+                ArgumentCaptor.forClass(Map.class);
+
+        verify(mThemeOverlayApplier)
+                .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any());
+
+        // Assert that we received the colors that we were expecting
+        assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
+                .isEqualTo(new OverlayIdentifier("ffff0000"));
+        assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR))
+                .isEqualTo(new OverlayIdentifier("ffff0000"));
+
+        // Should not change theme after changing wallpapers, if intent doesn't have
+        // WallpaperManager.EXTRA_FROM_FOREGROUND_APP set to true.
+        clearInvocations(mThemeOverlayApplier);
+        mBroadcastReceiver.getValue().onReceive(null, new Intent(Intent.ACTION_WALLPAPER_CHANGED));
+        mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK),
+                null, null), WallpaperManager.FLAG_SYSTEM);
+        verify(mThemeOverlayApplier, never())
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+    }
+
+    @Test
     public void onWallpaperColorsChanged_preservesWallpaperPickerTheme() {
         // Should ask for a new theme when wallpaper colors change
         WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
@@ -455,7 +485,9 @@
         // Regression test: null events should not reset the internal state and allow colors to be
         // applied again.
         clearInvocations(mThemeOverlayApplier);
-        mBroadcastReceiver.getValue().onReceive(null, new Intent(Intent.ACTION_WALLPAPER_CHANGED));
+        Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED);
+        intent.putExtra(WallpaperManager.EXTRA_FROM_FOREGROUND_APP, true);
+        mBroadcastReceiver.getValue().onReceive(null, intent);
         mColorsListener.getValue().onColorsChanged(null, WallpaperManager.FLAG_SYSTEM);
         verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(),
                 any());
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 7713320..66351d67 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wallpaper;
 
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
 import static android.app.WallpaperManager.COMMAND_REAPPLY;
 import static android.app.WallpaperManager.FLAG_LOCK;
 import static android.app.WallpaperManager.FLAG_SYSTEM;
@@ -775,6 +776,7 @@
     private final Context mContext;
     private final WindowManagerInternal mWindowManagerInternal;
     private final IPackageManager mIPackageManager;
+    private final ActivityManager mActivityManager;
     private final MyPackageMonitor mMonitor;
     private final AppOpsManager mAppOpsManager;
 
@@ -923,6 +925,11 @@
          */
         WallpaperColors primaryColors;
 
+        /**
+         * If the wallpaper was set from a foreground app (instead of from a background service).
+         */
+        public boolean fromForegroundApp;
+
         WallpaperConnection connection;
         long lastDiedTime;
         boolean wallpaperUpdating;
@@ -1672,6 +1679,7 @@
         mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
         mDisplayManager = mContext.getSystemService(DisplayManager.class);
         mDisplayManager.registerDisplayListener(mDisplayListener, null /* handler */);
+        mActivityManager = mContext.getSystemService(ActivityManager.class);
         mMonitor = new MyPackageMonitor();
         mColorsChangedListeners = new SparseArray<>();
 
@@ -2613,6 +2621,9 @@
             }
         }
 
+        final boolean fromForegroundApp = Binder.withCleanCallingIdentity(() ->
+                mActivityManager.getPackageImportance(callingPackage) == IMPORTANCE_FOREGROUND);
+
         synchronized (mLock) {
             if (DEBUG) Slog.v(TAG, "setWallpaper which=0x" + Integer.toHexString(which));
             WallpaperData wallpaper;
@@ -2635,6 +2646,7 @@
                     wallpaper.imageWallpaperPending = true;
                     wallpaper.whichPending = which;
                     wallpaper.setComplete = completion;
+                    wallpaper.fromForegroundApp = fromForegroundApp;
                     wallpaper.cropHint.set(cropHint);
                     wallpaper.allowBackup = allowBackup;
                 }
@@ -3017,6 +3029,7 @@
         wallpaper.callbacks.finishBroadcast();
 
         final Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED);
+        intent.putExtra(WallpaperManager.EXTRA_FROM_FOREGROUND_APP, wallpaper.fromForegroundApp);
         mContext.sendBroadcastAsUser(intent, new UserHandle(mCurrentUserId));
     }