Snap for 12354992 from de190fc22c4bc8107ea9f289a3a7b248945103b6 to 24Q4-release

Change-Id: Idc680b0ac5edb06f4a92e5f64e55ae03c109961a
diff --git a/msdllib/src/com/google/android/msdl/data/repository/MSDLRepository.kt b/msdllib/src/com/google/android/msdl/data/repository/MSDLRepository.kt
index 5aed913..b891661 100644
--- a/msdllib/src/com/google/android/msdl/data/repository/MSDLRepository.kt
+++ b/msdllib/src/com/google/android/msdl/data/repository/MSDLRepository.kt
@@ -15,23 +15,24 @@
  */
 package com.google.android.msdl.data.repository
 
+import androidx.annotation.VisibleForTesting
 import com.google.android.msdl.data.model.HapticToken
 import com.google.android.msdl.data.model.SoundToken
 
 /**
- * A repository of data for [ReferenceToken].
+ * A repository of data for [HapticToken] and [SoundToken].
  *
  * The principle behind this repository is to hold the data for all tokens as a cache in memory.
  * This is only suitable if the number of tokens and the data stored is manageable. The purpose of
  * this design choice is to provide fast and easy access to the data when required to be played by
  * UI interactions.
  */
-interface MSDLRepository {
+sealed interface MSDLRepository {
 
     /**
-     * Get the [MSDLData] that corresponds to the given haptic reference token. This function needs
-     * to be fast since it will be called repeatedly to deliver feedback. If necessary, a caching
-     * strategy should be applied.
+     * Get the [MSDLHapticData] that corresponds to the given haptic reference token. This function
+     * needs to be fast since it will be called repeatedly to deliver feedback. If necessary, a
+     * caching strategy should be applied.
      *
      * @param[hapticToken] The [HapticToken] that points to the data.
      * @return the data that corresponds to the token at the time this function is called.
@@ -39,14 +40,19 @@
     fun getHapticData(hapticToken: HapticToken): MSDLHapticData?
 
     /**
-     * Get the [MSDLData] that corresponds to the given sound reference token. This function needs
-     * to be fast since it will be called repeatedly to deliver feedback. If necessary, a caching
-     * strategy should be applied.
+     * Get the [MSDLSoundData] that corresponds to the given sound reference token. This function
+     * needs to be fast since it will be called repeatedly to deliver feedback. If necessary, a
+     * caching strategy should be applied.
      *
      * @param[soundToken] The [SoundToken] that points to the data.
      * @return the data that corresponds to the token at the time this function is called.
      */
     fun getAudioData(soundToken: SoundToken): MSDLSoundData?
+
+    companion object {
+
+        @VisibleForTesting fun createRepository(): MSDLRepository = MSDLRepositoryImpl()
+    }
 }
 
 /** Representation of data contained in a [MSDLRepository] */
diff --git a/msdllib/src/com/google/android/msdl/data/repository/MSDLRepositoryImpl.kt b/msdllib/src/com/google/android/msdl/data/repository/MSDLRepositoryImpl.kt
index e409e02..5074a99 100644
--- a/msdllib/src/com/google/android/msdl/data/repository/MSDLRepositoryImpl.kt
+++ b/msdllib/src/com/google/android/msdl/data/repository/MSDLRepositoryImpl.kt
@@ -23,7 +23,7 @@
 import com.google.android.msdl.data.model.SoundToken
 
 /** A [MSDLRepository] that holds haptic compositions as haptic data. */
-class MSDLRepositoryImpl : MSDLRepository {
+internal class MSDLRepositoryImpl : MSDLRepository {
 
     override fun getAudioData(soundToken: SoundToken): MSDLSoundData? {
         // TODO(b/345248875) Implement a caching strategy in accordance to the audio file strategy
@@ -63,13 +63,13 @@
                                     VibrationEffect.Composition.PRIMITIVE_SPIN,
                                     scale = 1f,
                                     delayMillis = SPIN_DELAY.toInt(),
-                                )
+                                ),
                             ),
                             VibrationEffect.createWaveform(
                                 SPIN_WAVEFORM_TIMINGS,
                                 SPIN_WAVEFORM_AMPLITUDES,
                                 -1,
-                            )
+                            ),
                         )
                     },
                 HapticToken.NEGATIVE_CONFIRMATION_MEDIUM_EMPHASIS to
@@ -79,24 +79,24 @@
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
                                     scale = 1f,
-                                    delayMillis = 0
+                                    delayMillis = 0,
                                 ),
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
                                     scale = 1f,
-                                    delayMillis = 114
+                                    delayMillis = 114,
                                 ),
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
                                     scale = 1f,
-                                    delayMillis = 114
-                                )
+                                    delayMillis = 114,
+                                ),
                             ),
                             VibrationEffect.createWaveform(
                                 longArrayOf(10, 10, 10, 114, 10, 10, 10, 114, 10, 10, 10),
                                 intArrayOf(10, 255, 20, 0, 10, 255, 20, 0, 10, 255, 20),
-                                -1
-                            )
+                                -1,
+                            ),
                         )
                     },
                 HapticToken.POSITIVE_CONFIRMATION_HIGH_EMPHASIS to
@@ -106,19 +106,19 @@
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
                                     scale = 1f,
-                                    delayMillis = 0
+                                    delayMillis = 0,
                                 ),
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
                                     scale = 1f,
-                                    delayMillis = 114
-                                )
+                                    delayMillis = 114,
+                                ),
                             ),
                             VibrationEffect.createWaveform(
                                 longArrayOf(10, 10, 10, 114, 10, 10, 10),
                                 intArrayOf(10, 255, 20, 0, 10, 255, 20),
-                                -1
-                            )
+                                -1,
+                            ),
                         )
                     },
                 HapticToken.POSITIVE_CONFIRMATION_MEDIUM_EMPHASIS to
@@ -128,19 +128,19 @@
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
                                     scale = 1f,
-                                    delayMillis = 0
+                                    delayMillis = 0,
                                 ),
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
                                     scale = 1f,
-                                    delayMillis = 52
-                                )
+                                    delayMillis = 52,
+                                ),
                             ),
                             VibrationEffect.createWaveform(
                                 longArrayOf(10, 10, 10, 52, 10, 10, 10),
                                 intArrayOf(10, 255, 20, 0, 10, 255, 20),
-                                -1
-                            )
+                                -1,
+                            ),
                         )
                     },
                 HapticToken.POSITIVE_CONFIRMATION_LOW_EMPHASIS to
@@ -150,19 +150,19 @@
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_TICK,
                                     scale = 1f,
-                                    delayMillis = 0
+                                    delayMillis = 0,
                                 ),
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
                                     scale = 1f,
-                                    delayMillis = 52
-                                )
+                                    delayMillis = 52,
+                                ),
                             ),
                             VibrationEffect.createWaveform(
                                 longArrayOf(5, 52, 10, 10, 10),
                                 intArrayOf(100, 0, 10, 255, 20),
-                                -1
-                            )
+                                -1,
+                            ),
                         )
                     },
                 HapticToken.NEUTRAL_CONFIRMATION_HIGH_EMPHASIS to
@@ -172,14 +172,14 @@
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_THUD,
                                     scale = 1f,
-                                    delayMillis = 0
+                                    delayMillis = 0,
                                 )
                             ),
                             VibrationEffect.createWaveform(
                                 longArrayOf(50, 100, 100, 50),
                                 intArrayOf(5, 50, 20, 10),
-                                -1
-                            )
+                                -1,
+                            ),
                         )
                     },
                 HapticToken.NEUTRAL_CONFIRMATION_MEDIUM_EMPHASIS to
@@ -192,7 +192,7 @@
                                     delayMillis = 0,
                                 )
                             ),
-                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
+                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK),
                         )
                     },
                 HapticToken.LONG_PRESS to
@@ -202,10 +202,10 @@
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
                                     scale = 1f,
-                                    delayMillis = 0
+                                    delayMillis = 0,
                                 )
                             ),
-                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
+                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK),
                         )
                     },
                 HapticToken.SWIPE_THRESHOLD_INDICATOR to
@@ -215,10 +215,10 @@
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
                                     scale = 0.7f,
-                                    delayMillis = 0
+                                    delayMillis = 0,
                                 )
                             ),
-                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
+                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK),
                         )
                     },
                 HapticToken.TAP_HIGH_EMPHASIS to
@@ -228,10 +228,10 @@
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
                                     scale = 0.7f,
-                                    delayMillis = 0
+                                    delayMillis = 0,
                                 )
                             ),
-                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
+                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK),
                         )
                     },
                 HapticToken.TAP_MEDIUM_EMPHASIS to
@@ -241,10 +241,10 @@
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
                                     scale = 0.5f,
-                                    delayMillis = 0
-                                ),
+                                    delayMillis = 0,
+                                )
                             ),
-                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
+                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK),
                         )
                     },
                 HapticToken.DRAG_THRESHOLD_INDICATOR to
@@ -254,10 +254,10 @@
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_TICK,
                                     scale = 1f,
-                                    delayMillis = 0
+                                    delayMillis = 0,
                                 )
                             ),
-                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK)
+                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK),
                         )
                     },
                 HapticToken.DRAG_INDICATOR to
@@ -267,10 +267,10 @@
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_TICK,
                                     scale = 0.5f,
-                                    delayMillis = 0
+                                    delayMillis = 0,
                                 )
                             ),
-                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK)
+                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK),
                         )
                     },
                 HapticToken.TAP_LOW_EMPHASIS to
@@ -280,10 +280,10 @@
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
                                     scale = 0.3f,
-                                    delayMillis = 0
+                                    delayMillis = 0,
                                 )
                             ),
-                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
+                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK),
                         )
                     },
                 HapticToken.KEYPRESS_STANDARD to
@@ -293,10 +293,10 @@
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_TICK,
                                     scale = 0.7f,
-                                    delayMillis = 0
-                                ),
+                                    delayMillis = 0,
+                                )
                             ),
-                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK)
+                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK),
                         )
                     },
                 HapticToken.KEYPRESS_SPACEBAR to
@@ -306,10 +306,10 @@
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
                                     scale = 0.7f,
-                                    delayMillis = 0
+                                    delayMillis = 0,
                                 )
                             ),
-                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
+                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK),
                         )
                     },
                 HapticToken.KEYPRESS_RETURN to
@@ -319,10 +319,10 @@
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
                                     scale = 0.7f,
-                                    delayMillis = 0
+                                    delayMillis = 0,
                                 )
                             ),
-                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
+                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK),
                         )
                     },
                 HapticToken.KEYPRESS_DELETE to
@@ -332,12 +332,12 @@
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
                                     scale = 1f,
-                                    delayMillis = 0
+                                    delayMillis = 0,
                                 )
                             ),
-                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
+                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK),
                         )
-                    }
+                    },
             )
     }
 }
diff --git a/msdllib/src/com/google/android/msdl/domain/MSDLPlayer.kt b/msdllib/src/com/google/android/msdl/domain/MSDLPlayer.kt
index 6fcd75c..c869037 100644
--- a/msdllib/src/com/google/android/msdl/domain/MSDLPlayer.kt
+++ b/msdllib/src/com/google/android/msdl/domain/MSDLPlayer.kt
@@ -16,11 +16,11 @@
 
 package com.google.android.msdl.domain
 
-import android.content.Context
-import android.os.VibratorManager
+import android.os.Vibrator
 import com.google.android.msdl.data.model.FeedbackLevel
 import com.google.android.msdl.data.model.HapticComposition
 import com.google.android.msdl.data.model.MSDLToken
+import com.google.android.msdl.data.repository.MSDLRepository
 import com.google.android.msdl.data.repository.MSDLRepositoryImpl
 import com.google.android.msdl.domain.MSDLPlayerImpl.Companion.REQUIRED_PRIMITIVES
 import java.util.concurrent.Executor
@@ -59,34 +59,43 @@
         /**
          * Create a new [MSDLPlayer].
          *
-         * @param[context] The [Context] this player will get its services from.
+         * @param[vibrator] The [Vibrator] this player will use for haptic playback.
          * @param[executor] An [Executor] to schedule haptic playback.
+         * @param[useHapticFeedbackForToken] A map that determines if a haptic fallback effect
+         *   should be used to play haptics for a given [MSDLToken]. If null, the map will be
+         *   created using the support information from the given vibrator.
          */
         fun createPlayer(
-            context: Context,
+            vibrator: Vibrator,
             executor: Executor = Executors.newSingleThreadExecutor(),
+            useHapticFeedbackForToken: Map<MSDLToken, Boolean>? = null,
         ): MSDLPlayer {
-            // Gather vibration dependencies
-            val vibratorManager =
-                context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
-            val vibrator = vibratorManager.defaultVibrator
 
             // Create repository
             val repository = MSDLRepositoryImpl()
 
-            // Determine the support for haptic primitives to know if fallbacks will be used
+            // Determine the support for haptic primitives to know if fallbacks will be used.
+            // This can be provided by the client. If omitted, it will be determined from the
+            // supported primitives of the given vibrator.
+            val shouldUseFallbackForToken =
+                useHapticFeedbackForToken ?: createHapticFallbackDecisionMap(vibrator, repository)
+
+            return MSDLPlayerImpl(repository, vibrator, executor, shouldUseFallbackForToken)
+        }
+
+        private fun createHapticFallbackDecisionMap(
+            vibrator: Vibrator,
+            repository: MSDLRepository,
+        ): Map<MSDLToken, Boolean> {
             val supportedPrimitives =
                 REQUIRED_PRIMITIVES.associateWith { vibrator.arePrimitivesSupported(it).first() }
-            val useHapticFallbackForToken =
-                MSDLToken.entries.associateWith { token ->
-                    // For each token, determine if the haptic data from the repository should use
-                    // the fallback effect
-                    val hapticComposition =
-                        repository.getHapticData(token.hapticToken)?.get() as? HapticComposition
-                    hapticComposition?.shouldPlayFallback(supportedPrimitives)
-                }
-
-            return MSDLPlayerImpl(repository, vibrator, executor, useHapticFallbackForToken)
+            return MSDLToken.entries.associateWith { token ->
+                // For each token, determine if the haptic data from the repository
+                // should use the fallback effect.
+                val hapticComposition =
+                    repository.getHapticData(token.hapticToken)?.get() as? HapticComposition
+                hapticComposition?.shouldPlayFallback(supportedPrimitives) ?: false
+            }
         }
     }
 }
diff --git a/msdllib/src/com/google/android/msdl/domain/MSDLPlayerImpl.kt b/msdllib/src/com/google/android/msdl/domain/MSDLPlayerImpl.kt
index 9e78cea..97ca638 100644
--- a/msdllib/src/com/google/android/msdl/domain/MSDLPlayerImpl.kt
+++ b/msdllib/src/com/google/android/msdl/domain/MSDLPlayerImpl.kt
@@ -36,7 +36,7 @@
  * @param[useHapticFallbackForToken] A map that determines if the haptic fallback effect should be
  *   used for a given token.
  */
-class MSDLPlayerImpl(
+internal class MSDLPlayerImpl(
     private val repository: MSDLRepository,
     private val vibrator: Vibrator,
     private val executor: Executor,
@@ -55,10 +55,7 @@
         playData(token, properties)
     }
 
-    private fun playData(
-        token: MSDLToken,
-        properties: InteractionProperties?,
-    ) {
+    private fun playData(token: MSDLToken, properties: InteractionProperties?) {
         // Gather the data from the repositories
         val hapticData = repository.getHapticData(token.hapticToken)
         val soundData = repository.getAudioData(token.soundToken)
diff --git a/msdllib/tests/src/com/google/android/msdl/data/repository/MSDLRepositoryImplTest.kt b/msdllib/tests/src/com/google/android/msdl/data/repository/MSDLRepositoryImplTest.kt
index 698a67a..576e749 100644
--- a/msdllib/tests/src/com/google/android/msdl/data/repository/MSDLRepositoryImplTest.kt
+++ b/msdllib/tests/src/com/google/android/msdl/data/repository/MSDLRepositoryImplTest.kt
@@ -19,7 +19,6 @@
 import com.google.android.msdl.data.model.HapticComposition
 import com.google.android.msdl.data.model.HapticToken
 import com.google.common.truth.Truth.assertThat
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -27,12 +26,7 @@
 @RunWith(JUnit4::class)
 class MSDLRepositoryImplTest {
 
-    private lateinit var repository: MSDLRepositoryImpl
-
-    @Before
-    fun setup() {
-        repository = MSDLRepositoryImpl()
-    }
+    private val repository = MSDLRepository.createRepository()
 
     @Test
     fun getHapticData_forAllHapticTokens_returnsCompositions() {
diff --git a/msdllib/tests/src/com/google/android/msdl/domain/MSDLPlayerImplTest.kt b/msdllib/tests/src/com/google/android/msdl/domain/MSDLPlayerImplTest.kt
index 1b10a90..1b929ab 100644
--- a/msdllib/tests/src/com/google/android/msdl/domain/MSDLPlayerImplTest.kt
+++ b/msdllib/tests/src/com/google/android/msdl/domain/MSDLPlayerImplTest.kt
@@ -21,7 +21,7 @@
 import com.google.android.msdl.data.model.FeedbackLevel
 import com.google.android.msdl.data.model.HapticComposition
 import com.google.android.msdl.data.model.MSDLToken
-import com.google.android.msdl.data.repository.MSDLRepositoryImpl
+import com.google.android.msdl.data.repository.MSDLRepository
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.Executor
 import org.junit.Before
@@ -34,13 +34,12 @@
 
     @Parameterized.Parameter lateinit var token: MSDLToken
 
-    private val repository = MSDLRepositoryImpl()
+    private val repository = MSDLRepository.createRepository()
     private val vibrator = FakeVibrator()
     private val executor = Executor { it.run() }
     private val useHapticFallbackForToken = MSDLToken.entries.associateWith { false }.toMutableMap()
 
-    private var msdlPlayer =
-        MSDLPlayerImpl(repository, vibrator, executor, useHapticFallbackForToken)
+    private var msdlPlayer = MSDLPlayer.createPlayer(vibrator, executor, useHapticFallbackForToken)
 
     @Before
     fun setup() {
diff --git a/tracinglib/core/src/FlowTracing.kt b/tracinglib/core/src/FlowTracing.kt
index 69b5965..ce2b645 100644
--- a/tracinglib/core/src/FlowTracing.kt
+++ b/tracinglib/core/src/FlowTracing.kt
@@ -40,7 +40,7 @@
         flowName: String,
         logcat: Boolean = false,
         traceEmissionCount: Boolean = false,
-        crossinline valueToString: (T) -> String = { it.toString() }
+        crossinline valueToString: (T) -> String = { it.toString() },
     ): Flow<T> {
         val stateLogger = TraceStateLogger(flowName, logcat = logcat)
         val baseFlow = if (traceEmissionCount) traceEmissionCount(flowName) else this
@@ -51,7 +51,7 @@
     fun <T : Number> Flow<T>.traceAsCounter(
         counterName: String,
         traceEmissionCount: Boolean = false,
-        valueToInt: (T) -> Int = { it.toInt() }
+        valueToInt: (T) -> Int = { it.toInt() },
     ): Flow<T> {
         val baseFlow = if (traceEmissionCount) traceEmissionCount(counterName) else this
         return baseFlow.onEach {
@@ -80,7 +80,7 @@
      */
     fun <T> Flow<T>.traceEmissionCount(
         flowName: () -> String,
-        uniqueSuffix: Boolean = false
+        uniqueSuffix: Boolean = false,
     ): Flow<T> {
         val trackName by lazy {
             "${flowName()}#emissionCount" + if (uniqueSuffix) "\$${counter.addAndGet(1)}" else ""
@@ -121,7 +121,7 @@
      */
     fun <T> tracedConflatedCallbackFlow(
         name: String,
-        @BuilderInference block: suspend ProducerScope<T>.() -> Unit
+        @BuilderInference block: suspend ProducerScope<T>.() -> Unit,
     ): Flow<T> {
         return callbackFlow {
                 traceAsync(DEFAULT_ASYNC_TRACK_NAME, { "$name#CallbackFlowBlock" }) {
diff --git a/tracinglib/core/src/TraceUtils.kt b/tracinglib/core/src/TraceUtils.kt
index 14b29eb..8ae5482 100644
--- a/tracinglib/core/src/TraceUtils.kt
+++ b/tracinglib/core/src/TraceUtils.kt
@@ -132,7 +132,7 @@
     @JvmStatic
     inline fun traceRunnable(
         crossinline tag: () -> String,
-        crossinline block: () -> Unit
+        crossinline block: () -> Unit,
     ): Runnable {
         return Runnable { traceSection(tag) { block() } }
     }
diff --git a/tracinglib/core/src/coroutines/CoroutineTracing.kt b/tracinglib/core/src/coroutines/CoroutineTracing.kt
index 1cb4687..2138fad 100644
--- a/tracinglib/core/src/coroutines/CoroutineTracing.kt
+++ b/tracinglib/core/src/coroutines/CoroutineTracing.kt
@@ -40,7 +40,7 @@
 @OptIn(ExperimentalContracts::class)
 suspend inline fun <R> coroutineScope(
     traceName: String,
-    crossinline block: suspend CoroutineScope.() -> R
+    crossinline block: suspend CoroutineScope.() -> R,
 ): R {
     contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
     return traceCoroutine(traceName) {
@@ -59,7 +59,7 @@
     crossinline spanName: () -> String,
     context: CoroutineContext = EmptyCoroutineContext,
     // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
-    crossinline block: suspend CoroutineScope.() -> Unit
+    crossinline block: suspend CoroutineScope.() -> Unit,
 ): Job = launch(context) { traceCoroutine(spanName) { block() } }
 
 /**
@@ -71,7 +71,7 @@
     spanName: String,
     context: CoroutineContext = EmptyCoroutineContext,
     // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
-    crossinline block: suspend CoroutineScope.() -> Unit
+    crossinline block: suspend CoroutineScope.() -> Unit,
 ): Job = launch(context) { traceCoroutine(spanName) { block() } }
 
 /**
@@ -83,7 +83,7 @@
     crossinline spanName: () -> String,
     context: CoroutineContext = EmptyCoroutineContext,
     // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
-    crossinline block: suspend CoroutineScope.() -> T
+    crossinline block: suspend CoroutineScope.() -> T,
 ): Deferred<T> = async(context) { traceCoroutine(spanName) { block() } }
 
 /**
@@ -95,7 +95,7 @@
     spanName: String,
     context: CoroutineContext = EmptyCoroutineContext,
     // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
-    crossinline block: suspend CoroutineScope.() -> T
+    crossinline block: suspend CoroutineScope.() -> T,
 ): Deferred<T> = async(context) { traceCoroutine(spanName) { block() } }
 
 /**
@@ -106,7 +106,7 @@
 inline fun <T> runBlocking(
     crossinline spanName: () -> String,
     context: CoroutineContext,
-    crossinline block: suspend () -> T
+    crossinline block: suspend () -> T,
 ): T = runBlocking(context) { traceCoroutine(spanName) { block() } }
 
 /**
@@ -117,7 +117,7 @@
 inline fun <T> runBlocking(
     spanName: String,
     context: CoroutineContext,
-    crossinline block: suspend CoroutineScope.() -> T
+    crossinline block: suspend CoroutineScope.() -> T,
 ): T = runBlocking(context) { traceCoroutine(spanName) { block() } }
 
 /**
@@ -128,7 +128,7 @@
 suspend inline fun <T> withContext(
     spanName: String,
     context: CoroutineContext,
-    crossinline block: suspend CoroutineScope.() -> T
+    crossinline block: suspend CoroutineScope.() -> T,
 ): T = withContext(context) { traceCoroutine(spanName) { block() } }
 
 /**
@@ -139,7 +139,7 @@
 suspend inline fun <T> withContext(
     crossinline spanName: () -> String,
     context: CoroutineContext,
-    crossinline block: suspend CoroutineScope.() -> T
+    crossinline block: suspend CoroutineScope.() -> T,
 ): T = withContext(context) { traceCoroutine(spanName) { block() } }
 
 /**
diff --git a/tracinglib/core/src/coroutines/TraceData.kt b/tracinglib/core/src/coroutines/TraceData.kt
index 97687fe..675a24e 100644
--- a/tracinglib/core/src/coroutines/TraceData.kt
+++ b/tracinglib/core/src/coroutines/TraceData.kt
@@ -42,9 +42,7 @@
  * @see traceCoroutine
  */
 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
-class TraceData(
-    val slices: ArrayDeque<TraceSection> = ArrayDeque(),
-) : Cloneable {
+class TraceData(val slices: ArrayDeque<TraceSection> = ArrayDeque()) : Cloneable {
 
     /**
      * ThreadLocal counter for how many open trace sections there are. This is needed because it is
diff --git a/tracinglib/core/src/coroutines/flow/FlowExt.kt b/tracinglib/core/src/coroutines/flow/FlowExt.kt
index ec3a55a..6ace77e 100644
--- a/tracinglib/core/src/coroutines/flow/FlowExt.kt
+++ b/tracinglib/core/src/coroutines/flow/FlowExt.kt
@@ -54,9 +54,7 @@
 }
 
 @OptIn(ExperimentalTypeInference::class)
-suspend inline fun <T> Flow<T>.collectTraced(
-    @BuilderInference block: FlowCollector<T>,
-) {
+suspend inline fun <T> Flow<T>.collectTraced(@BuilderInference block: FlowCollector<T>) {
     collect(walkStackForClassName(), block)
 }
 
diff --git a/tracinglib/demo/src/com/android/app/tracing/demo/MainActivity.kt b/tracinglib/demo/src/com/android/app/tracing/demo/MainActivity.kt
index 45689e6..d4a3080 100644
--- a/tracinglib/demo/src/com/android/app/tracing/demo/MainActivity.kt
+++ b/tracinglib/demo/src/com/android/app/tracing/demo/MainActivity.kt
@@ -54,7 +54,7 @@
                 context.getString(
                     R.string.run_experiment_button_text,
                     className,
-                    demo.getDescription()
+                    demo.getDescription(),
                 )
             setOnClickListener {
                 val experimentName = "$className #${launchCounter++}"
@@ -67,7 +67,7 @@
                             Trace.TRACE_TAG_APP,
                             TRACK_NAME,
                             "Running $experimentName",
-                            it.hashCode()
+                            it.hashCode(),
                         )
                     }
                 }
diff --git a/tracinglib/demo/src/com/android/app/tracing/demo/experiments/NestedLaunchesWithParentSpan.kt b/tracinglib/demo/src/com/android/app/tracing/demo/experiments/NestedLaunchesWithParentSpan.kt
index 5c0632f..bfaed55 100644
--- a/tracinglib/demo/src/com/android/app/tracing/demo/experiments/NestedLaunchesWithParentSpan.kt
+++ b/tracinglib/demo/src/com/android/app/tracing/demo/experiments/NestedLaunchesWithParentSpan.kt
@@ -30,7 +30,7 @@
 @Inject
 constructor(
     @FixedThread1 private var fixedThreadContext1: CoroutineContext,
-    @FixedThread2 private var fixedThreadContext2: CoroutineContext
+    @FixedThread2 private var fixedThreadContext2: CoroutineContext,
 ) : Experiment {
     override fun getDescription(): String =
         "Nested launches in which only the parent uses a trace name"
diff --git a/tracinglib/demo/src/com/android/app/tracing/demo/experiments/NestedLaunchesWithoutName.kt b/tracinglib/demo/src/com/android/app/tracing/demo/experiments/NestedLaunchesWithoutName.kt
index 0fa7333..bcd4fa5 100644
--- a/tracinglib/demo/src/com/android/app/tracing/demo/experiments/NestedLaunchesWithoutName.kt
+++ b/tracinglib/demo/src/com/android/app/tracing/demo/experiments/NestedLaunchesWithoutName.kt
@@ -29,7 +29,7 @@
 @Inject
 constructor(
     @FixedThread1 private var fixedThreadContext1: CoroutineContext,
-    @FixedThread2 private var fixedThreadContext2: CoroutineContext
+    @FixedThread2 private var fixedThreadContext2: CoroutineContext,
 ) : Experiment {
     override fun getDescription(): String =
         "Nested launches in which only the leaf uses a trace name"
diff --git a/tracinglib/robolectric/src/CoroutineTracingFlagsTest.kt b/tracinglib/robolectric/src/CoroutineTracingFlagsTest.kt
index 7e3298a..1b1cc81 100644
--- a/tracinglib/robolectric/src/CoroutineTracingFlagsTest.kt
+++ b/tracinglib/robolectric/src/CoroutineTracingFlagsTest.kt
@@ -80,7 +80,7 @@
                 assertTrue(
                     "Lazy string should have been called when Compile.IS_DEBUG=true, " +
                         "even when Trace.isEnabled()=false",
-                    lazyStringCalled
+                    lazyStringCalled,
                 )
                 val traceData = traceThreadLocal.get()
                 assertNotNull(traceData)
diff --git a/tracinglib/robolectric/src/CoroutineTracingTest.kt b/tracinglib/robolectric/src/CoroutineTracingTest.kt
index 3fa968e..f337e52 100644
--- a/tracinglib/robolectric/src/CoroutineTracingTest.kt
+++ b/tracinglib/robolectric/src/CoroutineTracingTest.kt
@@ -117,26 +117,26 @@
                     expect(
                         "span-for-coroutineScope-1",
                         "span-for-coroutineScope-2",
-                        "span-for-launch-3"
+                        "span-for-launch-3",
                     )
                     delay(1)
                     expect(
                         "span-for-coroutineScope-1",
                         "span-for-coroutineScope-2",
-                        "span-for-launch-3"
+                        "span-for-launch-3",
                     )
                 }
                 launch("span-for-launch-4") {
                     expect(
                         "span-for-coroutineScope-1",
                         "span-for-coroutineScope-2",
-                        "span-for-launch-4"
+                        "span-for-launch-4",
                     )
                     delay(1)
                     expect(
                         "span-for-coroutineScope-1",
                         "span-for-coroutineScope-2",
-                        "span-for-launch-4"
+                        "span-for-launch-4",
                     )
                 }
             }
@@ -227,13 +227,7 @@
             "stuff"
         }
 
-        val threadContexts =
-            listOf(
-                thread1,
-                thread2,
-                thread3,
-                thread4,
-            )
+        val threadContexts = listOf(thread1, thread2, thread3, thread4)
 
         val finishedLaunches = Channel<Int>()
 
@@ -264,7 +258,7 @@
 
     private fun CoroutineScope.testTraceSectionsMultiThreaded(
         thread1Context: CoroutineContext,
-        thread2Context: CoroutineContext
+        thread2Context: CoroutineContext,
     ) {
         val fetchData1: suspend () -> String = {
             expect("span-for-launch-1")
@@ -277,18 +271,12 @@
         }
 
         val fetchData2: suspend () -> String = {
-            expect(
-                "span-for-launch-1",
-                "span-for-launch-2",
-            )
+            expect("span-for-launch-1", "span-for-launch-2")
             delay(1L)
             traceCoroutine("span-for-fetchData-2") {
                 expect("span-for-launch-1", "span-for-launch-2", "span-for-fetchData-2")
             }
-            expect(
-                "span-for-launch-1",
-                "span-for-launch-2",
-            )
+            expect("span-for-launch-1", "span-for-launch-2")
             "stuff-2"
         }
 
@@ -316,7 +304,7 @@
         // Thread-#1 and Thread-#2 inherit TraceContextElement from the test's CoroutineContext.
         testTraceSectionsMultiThreaded(
             thread1Context = EmptyCoroutineContext,
-            thread2Context = EmptyCoroutineContext
+            thread2Context = EmptyCoroutineContext,
         )
     }
 
@@ -326,7 +314,7 @@
         // does not need a TraceContextElement because it does not do any tracing.
         testTraceSectionsMultiThreaded(
             thread1Context = TraceContextElement(),
-            thread2Context = EmptyCoroutineContext
+            thread2Context = EmptyCoroutineContext,
         )
     }
 
@@ -337,7 +325,7 @@
         // trace context because it does not do any tracing.
         testTraceSectionsMultiThreaded(
             thread1Context = TraceContextElement(),
-            thread2Context = TraceContextElement()
+            thread2Context = TraceContextElement(),
         )
     }
 
@@ -347,7 +335,7 @@
         // trace results.
         testTraceSectionsMultiThreaded(
             thread1Context = TraceContextElement(),
-            thread2Context = TraceContextElement()
+            thread2Context = TraceContextElement(),
         )
     }
 
@@ -365,16 +353,12 @@
         }
 
         val fetchData2: suspend () -> String = {
-            expect(
-                "span-for-launch-2",
-            )
+            expect("span-for-launch-2")
             channel.receive()
             traceCoroutine("span-for-fetchData-2") {
                 expect("span-for-launch-2", "span-for-fetchData-2")
             }
-            expect(
-                "span-for-launch-2",
-            )
+            expect("span-for-launch-2")
             "stuff-2"
         }
 
diff --git a/tracinglib/robolectric/src/FlowTracingTest.kt b/tracinglib/robolectric/src/FlowTracingTest.kt
index 9099451..3492c27 100644
--- a/tracinglib/robolectric/src/FlowTracingTest.kt
+++ b/tracinglib/robolectric/src/FlowTracingTest.kt
@@ -73,7 +73,7 @@
                         expect(
                             "launch-for-collect",
                             "com.android.app.tracing.coroutines.FlowTracingTest\$stateFlowCollection$1\$collectJob$1$2:collect",
-                            "com.android.app.tracing.coroutines.FlowTracingTest\$stateFlowCollection$1\$collectJob$1$2:emit"
+                            "com.android.app.tracing.coroutines.FlowTracingTest\$stateFlowCollection$1\$collectJob$1$2:emit",
                         )
                         incrementCounter()
                     }
@@ -114,7 +114,7 @@
                             "state-flow:collect",
                             "flowOn(the-name):collect",
                             "flowOn(the-name):emit",
-                            "state-flow:emit"
+                            "state-flow:emit",
                         )
                     }
                 }
@@ -140,7 +140,7 @@
                         "my-flow:emit",
                         "multiply-by-3:emit",
                         "mod-2:emit",
-                        "my-collect-call:emit"
+                        "my-collect-call:emit",
                     )
                 }
             }
diff --git a/tracinglib/robolectric/src/TestBase.kt b/tracinglib/robolectric/src/TestBase.kt
index ecb1c43..39657e9 100644
--- a/tracinglib/robolectric/src/TestBase.kt
+++ b/tracinglib/robolectric/src/TestBase.kt
@@ -46,7 +46,7 @@
     fun setup() {
         assumeTrue(
             "Coroutine tracing tests are only applicable on debuggable builds",
-            Compile.IS_DEBUG
+            Compile.IS_DEBUG,
         )
         TraceData.strictModeForTesting = true
         FakeTraceState.isTracingEnabled = true
@@ -109,7 +109,7 @@
 
     private fun assertTraceSectionsEquals(
         expectedOpenTraceSections: Array<out String>,
-        actualOpenSections: Array<String>
+        actualOpenSections: Array<String>,
     ) {
         assertArrayEquals(
             """
@@ -118,7 +118,7 @@
         """
                 .trimIndent(),
             expectedOpenTraceSections,
-            actualOpenSections
+            actualOpenSections,
         )
     }
 
diff --git a/tracinglib/robolectric/src/util/ExampleClass.kt b/tracinglib/robolectric/src/util/ExampleClass.kt
index 9f12b93..e753ae2 100644
--- a/tracinglib/robolectric/src/util/ExampleClass.kt
+++ b/tracinglib/robolectric/src/util/ExampleClass.kt
@@ -27,7 +27,7 @@
         testBase.expect(
             "launch-for-collect",
             "com.android.app.tracing.coroutines.FlowTracingTest\$stateFlowCollection$1\$collectJob$1$3:collect",
-            "com.android.app.tracing.coroutines.FlowTracingTest\$stateFlowCollection$1\$collectJob$1$3:emit"
+            "com.android.app.tracing.coroutines.FlowTracingTest\$stateFlowCollection$1\$collectJob$1$3:emit",
         )
         incrementCounter()
     }
diff --git a/tracinglib/robolectric/src/util/FakeTraceState.kt b/tracinglib/robolectric/src/util/FakeTraceState.kt
index 850e606..476ee8d 100644
--- a/tracinglib/robolectric/src/util/FakeTraceState.kt
+++ b/tracinglib/robolectric/src/util/FakeTraceState.kt
@@ -41,7 +41,7 @@
             assertFalse(
                 "Attempting to close trace section on thread=$threadId, " +
                     "but there are no open sections",
-                allThreadStates[threadId].isNullOrEmpty()
+                allThreadStates[threadId].isNullOrEmpty(),
             )
             // TODO: Replace with .removeLast() once available
             allThreadStates[threadId]!!.removeAt(allThreadStates[threadId]!!.lastIndex)
diff --git a/tracinglib/robolectric/src/util/ShadowTrace.kt b/tracinglib/robolectric/src/util/ShadowTrace.kt
index 6137252..11f8ecd 100644
--- a/tracinglib/robolectric/src/util/ShadowTrace.kt
+++ b/tracinglib/robolectric/src/util/ShadowTrace.kt
@@ -63,7 +63,7 @@
         traceTag: Long,
         trackName: String,
         methodName: String,
-        cookie: Int
+        cookie: Int,
     ) {
         debugLog(
             "asyncTraceForTrackBegin: track=$trackName name=$methodName cookie=${cookie.toHexString()}"
diff --git a/viewcapturelib/AndroidManifest.xml b/viewcapturelib/AndroidManifest.xml
index 1da8129..d86f1c5 100644
--- a/viewcapturelib/AndroidManifest.xml
+++ b/viewcapturelib/AndroidManifest.xml
@@ -15,9 +15,4 @@
      limitations under the License.
 -->
 
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    package="com.android.app.viewcapture">
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"
-        tools:ignore="ProtectedPermissions" />
-</manifest>
+<manifest package="com.android.app.viewcapture"/>
\ No newline at end of file
diff --git a/viewcapturelib/tests/AndroidManifest.xml b/viewcapturelib/tests/AndroidManifest.xml
index 8d31c0e..f32f93c 100644
--- a/viewcapturelib/tests/AndroidManifest.xml
+++ b/viewcapturelib/tests/AndroidManifest.xml
@@ -15,7 +15,12 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     package="com.android.app.viewcapture.test">
+
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"
+        tools:ignore="ProtectedPermissions" />
+
     <application
         android:debuggable="true"
         android:theme="@android:style/Theme.NoTitleBar">
diff --git a/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/WeatherEffect.kt b/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/WeatherEffect.kt
index 0517fa3..0761d3c 100644
--- a/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/WeatherEffect.kt
+++ b/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/WeatherEffect.kt
@@ -16,6 +16,7 @@
 
 package com.google.android.wallpaper.weathereffects.graphics
 
+import android.graphics.Bitmap
 import android.graphics.Canvas
 import android.util.SizeF
 import androidx.annotation.FloatRange
@@ -59,4 +60,16 @@
      * @param intensity [0, 1] the intensity of the weather effect.
      */
     fun setIntensity(@FloatRange(from = 0.0, to = 1.0) intensity: Float)
+
+    /**
+     * Reuse current shader but change background, foreground
+     *
+     * @param foreground A bitmap containing the foreground of the image
+     * @param background A bitmap containing the background of the image
+     */
+    fun setBitmaps(foreground: Bitmap, background: Bitmap)
+
+    companion object {
+        val DEFAULT_INTENSITY = 1f
+    }
 }
diff --git a/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/fog/FogEffect.kt b/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/fog/FogEffect.kt
index 0f7f5d1..73d870c 100644
--- a/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/fog/FogEffect.kt
+++ b/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/fog/FogEffect.kt
@@ -16,24 +16,28 @@
 
 package com.google.android.wallpaper.weathereffects.graphics.fog
 
+import android.graphics.Bitmap
 import android.graphics.BitmapShader
 import android.graphics.Canvas
 import android.graphics.Paint
 import android.graphics.Shader
 import android.util.SizeF
 import com.google.android.wallpaper.weathereffects.graphics.WeatherEffect
+import com.google.android.wallpaper.weathereffects.graphics.WeatherEffect.Companion.DEFAULT_INTENSITY
 import com.google.android.wallpaper.weathereffects.graphics.utils.GraphicsUtils
 import com.google.android.wallpaper.weathereffects.graphics.utils.ImageCrop
 import com.google.android.wallpaper.weathereffects.graphics.utils.TimeUtils
-import java.util.concurrent.TimeUnit
 import kotlin.math.sin
 import kotlin.random.Random
 
 /** Defines and generates the fog weather effect animation. */
 class FogEffect(
     private val fogConfig: FogEffectConfig,
+    private var foreground: Bitmap,
+    private var background: Bitmap,
+    private var intensity: Float = DEFAULT_INTENSITY,
     /** The initial size of the surface where the effect will be shown. */
-    surfaceSize: SizeF
+    private var surfaceSize: SizeF
 ) : WeatherEffect {
 
     private val fogPaint = Paint().also { it.shader = fogConfig.colorGradingShader }
@@ -44,12 +48,13 @@
         adjustCropping(surfaceSize)
         prepareColorGrading()
         updateFogGridSize(surfaceSize)
-        setIntensity(fogConfig.intensity)
+        setIntensity(intensity)
     }
 
     override fun resize(newSurfaceSize: SizeF) {
         adjustCropping(newSurfaceSize)
         updateFogGridSize(newSurfaceSize)
+        surfaceSize = newSurfaceSize
     }
 
     override fun update(deltaMillis: Long, frameTimeNanos: Long) {
@@ -98,13 +103,27 @@
         )
     }
 
+    override fun setBitmaps(foreground: Bitmap, background: Bitmap) {
+        this.foreground = foreground
+        this.background = background
+        fogConfig.shader.setInputBuffer(
+            "background",
+            BitmapShader(background, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
+        )
+        fogConfig.shader.setInputBuffer(
+            "foreground",
+            BitmapShader(foreground, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
+        )
+        adjustCropping(surfaceSize)
+    }
+
     private fun adjustCropping(surfaceSize: SizeF) {
         val imageCropFgd =
             ImageCrop.centerCoverCrop(
                 surfaceSize.width,
                 surfaceSize.height,
-                fogConfig.foreground.width.toFloat(),
-                fogConfig.foreground.height.toFloat()
+                foreground.width.toFloat(),
+                foreground.height.toFloat()
             )
         fogConfig.shader.setFloatUniform(
             "uvOffsetFgd",
@@ -120,8 +139,8 @@
             ImageCrop.centerCoverCrop(
                 surfaceSize.width,
                 surfaceSize.height,
-                fogConfig.background.width.toFloat(),
-                fogConfig.background.height.toFloat()
+                background.width.toFloat(),
+                background.height.toFloat()
             )
         fogConfig.shader.setFloatUniform(
             "uvOffsetBgd",
@@ -143,12 +162,12 @@
     private fun updateTextureUniforms() {
         fogConfig.shader.setInputBuffer(
             "foreground",
-            BitmapShader(fogConfig.foreground, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
+            BitmapShader(foreground, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
         )
 
         fogConfig.shader.setInputBuffer(
             "background",
-            BitmapShader(fogConfig.background, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
+            BitmapShader(background, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
         )
 
         fogConfig.shader.setInputBuffer(
diff --git a/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/fog/FogEffectConfig.kt b/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/fog/FogEffectConfig.kt
index 35f3073..4ac9b06 100644
--- a/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/fog/FogEffectConfig.kt
+++ b/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/fog/FogEffectConfig.kt
@@ -40,14 +40,8 @@
      * tileable, and at least 16-bit per channel for render quality.
      */
     val fogTexture: Bitmap,
-    /** A bitmap containing the foreground of the image. */
-    val foreground: Bitmap,
-    /** A bitmap containing the background of the image. */
-    val background: Bitmap,
     /** Pixel density of the display. Used for dithering. */
     val pixelDensity: Float,
-    /** The amount of the fog. This contributes to the color grading as well. */
-    @FloatRange(from = 0.0, to = 1.0) val intensity: Float,
     /** The intensity of the color grading. 0: no color grading, 1: color grading in full effect. */
     @FloatRange(from = 0.0, to = 1.0) val colorGradingIntensity: Float,
 ) {
@@ -55,30 +49,22 @@
      * Constructor for [FogEffectConfig].
      *
      * @param assets the application [AssetManager].
-     * @param foreground a bitmap containing the foreground of the image.
-     * @param background a bitmap containing the background of the image.
      * @param pixelDensity pixel density of the display.
-     * @param intensity initial intensity that affects the amount of fog and color grading. Expected
-     *   range is [0, 1]. You can always change the intensity dynamically. Defaults to 1.
      */
     constructor(
         assets: AssetManager,
-        foreground: Bitmap,
-        background: Bitmap,
         pixelDensity: Float,
-        intensity: Float = DEFAULT_INTENSITY,
     ) : this(
         shader = GraphicsUtils.loadShader(assets, SHADER_PATH),
         colorGradingShader = GraphicsUtils.loadShader(assets, COLOR_GRADING_SHADER_PATH),
         lut = GraphicsUtils.loadTexture(assets, LOOKUP_TABLE_TEXTURE_PATH),
-        cloudsTexture = GraphicsUtils.loadTexture(assets, CLOUDS_TEXTURE_PATH)
+        cloudsTexture =
+            GraphicsUtils.loadTexture(assets, CLOUDS_TEXTURE_PATH)
                 ?: throw RuntimeException("Clouds texture is missing."),
-        fogTexture = GraphicsUtils.loadTexture(assets, FOG_TEXTURE_PATH)
+        fogTexture =
+            GraphicsUtils.loadTexture(assets, FOG_TEXTURE_PATH)
                 ?: throw RuntimeException("Fog texture is missing."),
-        foreground,
-        background,
         pixelDensity,
-        intensity,
         COLOR_GRADING_INTENSITY
     )
 
@@ -88,7 +74,6 @@
         private const val LOOKUP_TABLE_TEXTURE_PATH = "textures/lut_rain_and_fog.png"
         private const val CLOUDS_TEXTURE_PATH = "textures/clouds.png"
         private const val FOG_TEXTURE_PATH = "textures/fog.png"
-        private const val DEFAULT_INTENSITY = 1f
         private const val COLOR_GRADING_INTENSITY = 0.7f
     }
 }
diff --git a/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/none/NoEffect.kt b/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/none/NoEffect.kt
index 592b627..d1aedcd 100644
--- a/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/none/NoEffect.kt
+++ b/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/none/NoEffect.kt
@@ -23,7 +23,7 @@
 import com.google.android.wallpaper.weathereffects.graphics.utils.MatrixUtils
 
 /** Simply draws foreground and background images with no weather effect. */
-class NoEffect(val foreground: Bitmap, val background: Bitmap, private var surfaceSize: SizeF) :
+class NoEffect(var foreground: Bitmap, var background: Bitmap, private var surfaceSize: SizeF) :
     WeatherEffect {
     override fun resize(newSurfaceSize: SizeF) {
         surfaceSize = newSurfaceSize
@@ -40,7 +40,6 @@
             ),
             null
         )
-
         canvas.drawBitmap(
             foreground,
             MatrixUtils.centerCropMatrix(
@@ -56,4 +55,9 @@
     override fun release() {}
 
     override fun setIntensity(intensity: Float) {}
+
+    override fun setBitmaps(foreground: Bitmap, background: Bitmap) {
+        this.foreground = foreground
+        this.background = background
+    }
 }
diff --git a/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/rain/RainEffect.kt b/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/rain/RainEffect.kt
index f66e0b6..c554b5e 100644
--- a/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/rain/RainEffect.kt
+++ b/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/rain/RainEffect.kt
@@ -16,6 +16,7 @@
 
 package com.google.android.wallpaper.weathereffects.graphics.rain
 
+import android.graphics.Bitmap
 import android.graphics.BitmapShader
 import android.graphics.Canvas
 import android.graphics.Color
@@ -25,20 +26,23 @@
 import android.util.SizeF
 import com.google.android.wallpaper.weathereffects.graphics.FrameBuffer
 import com.google.android.wallpaper.weathereffects.graphics.WeatherEffect
+import com.google.android.wallpaper.weathereffects.graphics.WeatherEffect.Companion.DEFAULT_INTENSITY
 import com.google.android.wallpaper.weathereffects.graphics.utils.GraphicsUtils
 import com.google.android.wallpaper.weathereffects.graphics.utils.ImageCrop
 import com.google.android.wallpaper.weathereffects.graphics.utils.SolidColorShader
 import com.google.android.wallpaper.weathereffects.graphics.utils.TimeUtils
 import java.util.concurrent.Executor
-import java.util.concurrent.TimeUnit
 import kotlin.random.Random
 
 /** Defines and generates the rain weather effect animation. */
 class RainEffect(
     /** The config of the rain effect. */
     private val rainConfig: RainEffectConfig,
+    private var foreground: Bitmap,
+    private var background: Bitmap,
+    private var intensity: Float = DEFAULT_INTENSITY,
     /** The initial size of the surface where the effect will be shown. */
-    surfaceSize: SizeF,
+    private var surfaceSize: SizeF,
     private val mainExecutor: Executor
 ) : WeatherEffect {
 
@@ -46,8 +50,8 @@
 
     // Set blur effect to reduce the outline noise. No need to set blur effect every time we
     // re-generate the outline buffer.
-    private val outlineBuffer =
-        FrameBuffer(rainConfig.background.width, rainConfig.background.height).apply {
+    private var outlineBuffer =
+        FrameBuffer(background.width, background.height).apply {
             setRenderEffect(RenderEffect.createBlurEffect(2f, 2f, Shader.TileMode.CLAMP))
         }
     private val outlineBufferPaint = Paint().also { it.shader = rainConfig.outlineShader }
@@ -59,12 +63,13 @@
         adjustCropping(surfaceSize)
         prepareColorGrading()
         updateRainGridSize(surfaceSize)
-        setIntensity(rainConfig.intensity)
+        setIntensity(intensity)
     }
 
     override fun resize(newSurfaceSize: SizeF) {
         adjustCropping(newSurfaceSize)
         updateRainGridSize(newSurfaceSize)
+        surfaceSize = newSurfaceSize
     }
 
     override fun update(deltaMillis: Long, frameTimeNanos: Long) {
@@ -104,13 +109,27 @@
         createOutlineBuffer()
     }
 
+    override fun setBitmaps(foreground: Bitmap, background: Bitmap) {
+        this.foreground = foreground
+        this.background = background
+        outlineBuffer =
+            FrameBuffer(background.width, background.height).apply {
+                setRenderEffect(RenderEffect.createBlurEffect(2f, 2f, Shader.TileMode.CLAMP))
+            }
+        adjustCropping(surfaceSize)
+        updateTextureUniforms()
+
+        // Need to recreate the outline buffer as the outlineBuffer has changed due to background
+        createOutlineBuffer()
+    }
+
     private fun adjustCropping(surfaceSize: SizeF) {
         val imageCropFgd =
             ImageCrop.centerCoverCrop(
                 surfaceSize.width,
                 surfaceSize.height,
-                rainConfig.foreground.width.toFloat(),
-                rainConfig.foreground.height.toFloat()
+                foreground.width.toFloat(),
+                foreground.height.toFloat()
             )
         rainConfig.rainShowerShader.setFloatUniform(
             "uvOffsetFgd",
@@ -127,8 +146,8 @@
             ImageCrop.centerCoverCrop(
                 surfaceSize.width,
                 surfaceSize.height,
-                rainConfig.background.width.toFloat(),
-                rainConfig.background.height.toFloat()
+                background.width.toFloat(),
+                background.height.toFloat()
             )
         rainConfig.rainShowerShader.setFloatUniform(
             "uvOffsetBgd",
@@ -159,13 +178,13 @@
 
     private fun updateTextureUniforms() {
         val foregroundBuffer =
-            BitmapShader(rainConfig.foreground, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
+            BitmapShader(foreground, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
         rainConfig.rainShowerShader.setInputBuffer("foreground", foregroundBuffer)
         rainConfig.outlineShader.setInputBuffer("texture", foregroundBuffer)
 
         rainConfig.rainShowerShader.setInputBuffer(
             "background",
-            BitmapShader(rainConfig.background, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
+            BitmapShader(background, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
         )
     }
 
@@ -198,7 +217,8 @@
     }
 
     private fun updateRainGridSize(surfaceSize: SizeF) {
-        val widthScreenScale = GraphicsUtils.computeDefaultGridSize(surfaceSize, rainConfig.pixelDensity)
+        val widthScreenScale =
+            GraphicsUtils.computeDefaultGridSize(surfaceSize, rainConfig.pixelDensity)
         rainConfig.rainShowerShader.setFloatUniform("gridScale", widthScreenScale)
         rainConfig.glassRainShader.setFloatUniform("gridScale", widthScreenScale)
     }
diff --git a/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/rain/RainEffectConfig.kt b/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/rain/RainEffectConfig.kt
index 77c93de..1567db3 100644
--- a/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/rain/RainEffectConfig.kt
+++ b/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/rain/RainEffectConfig.kt
@@ -34,43 +34,27 @@
     val outlineShader: RuntimeShader,
     /** The main lut (color grading) for the effect. */
     val lut: Bitmap?,
-    /** A bitmap containing the foreground of the image. */
-    val foreground: Bitmap,
-    /** A bitmap containing the background of the image. */
-    val background: Bitmap,
     /** Pixel density of the display. Used for dithering. */
     val pixelDensity: Float,
-    /** The amount of the rain. This contributes to the color grading as well. */
-    @FloatRange(from = 0.0, to = 1.0) val intensity: Float,
     /** The intensity of the color grading. 0: no color grading, 1: color grading in full effect. */
     @FloatRange(from = 0.0, to = 1.0) val colorGradingIntensity: Float,
 ) {
     /**
      * Constructor for [RainEffectConfig].
      *
-     * @param assets asset manager.
-     * @param foreground a bitmap containing the foreground of the image.
-     * @param background a bitmap containing the background of the image.
+     * @param assets asset manager,
      * @param pixelDensity pixel density of the display.
-     * @param intensity initial intensity that affects the amount of rain and color grading.
-     *   Expected range is [0, 1]. You can always change the intensity dynamically. Defaults to 1.
      */
     constructor(
         assets: AssetManager,
-        foreground: Bitmap,
-        background: Bitmap,
         pixelDensity: Float,
-        intensity: Float = DEFAULT_INTENSITY,
     ) : this(
         rainShowerShader = GraphicsUtils.loadShader(assets, RAIN_SHOWER_LAYER_SHADER_PATH),
         glassRainShader = GraphicsUtils.loadShader(assets, GLASS_RAIN_LAYER_SHADER_PATH),
         colorGradingShader = GraphicsUtils.loadShader(assets, COLOR_GRADING_SHADER_PATH),
         outlineShader = GraphicsUtils.loadShader(assets, OUTLINE_SHADER_PATH),
         lut = GraphicsUtils.loadTexture(assets, LOOKUP_TABLE_TEXTURE_PATH),
-        foreground,
-        background,
         pixelDensity,
-        intensity,
         COLOR_GRADING_INTENSITY
     )
 
@@ -80,7 +64,6 @@
         private const val COLOR_GRADING_SHADER_PATH = "shaders/color_grading_lut.agsl"
         private const val OUTLINE_SHADER_PATH = "shaders/outline.agsl"
         private const val LOOKUP_TABLE_TEXTURE_PATH = "textures/lut_rain_and_fog.png"
-        private const val DEFAULT_INTENSITY = 1f
         private const val COLOR_GRADING_INTENSITY = 0.7f
     }
 }
diff --git a/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/snow/SnowEffect.kt b/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/snow/SnowEffect.kt
index ad353e4..cec6cc2 100644
--- a/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/snow/SnowEffect.kt
+++ b/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/snow/SnowEffect.kt
@@ -16,6 +16,7 @@
 
 package com.google.android.wallpaper.weathereffects.graphics.snow
 
+import android.graphics.Bitmap
 import android.graphics.BitmapShader
 import android.graphics.Canvas
 import android.graphics.Paint
@@ -24,6 +25,7 @@
 import android.util.SizeF
 import com.google.android.wallpaper.weathereffects.graphics.FrameBuffer
 import com.google.android.wallpaper.weathereffects.graphics.WeatherEffect
+import com.google.android.wallpaper.weathereffects.graphics.WeatherEffect.Companion.DEFAULT_INTENSITY
 import com.google.android.wallpaper.weathereffects.graphics.utils.GraphicsUtils
 import com.google.android.wallpaper.weathereffects.graphics.utils.ImageCrop
 import com.google.android.wallpaper.weathereffects.graphics.utils.MathUtils
@@ -35,8 +37,11 @@
 class SnowEffect(
     /** The config of the snow effect. */
     private val snowConfig: SnowEffectConfig,
+    private var foreground: Bitmap,
+    private var background: Bitmap,
+    private var intensity: Float = DEFAULT_INTENSITY,
     /** The initial size of the surface where the effect will be shown. */
-    surfaceSize: SizeF,
+    private var surfaceSize: SizeF,
     /** App main executor. */
     private val mainExecutor: Executor
 ) : WeatherEffect {
@@ -45,7 +50,7 @@
     private val snowPaint = Paint().also { it.shader = snowConfig.colorGradingShader }
     private var elapsedTime: Float = 0f
 
-    private val frameBuffer = FrameBuffer(snowConfig.background.width, snowConfig.background.height)
+    private var frameBuffer = FrameBuffer(background.width, background.height)
     private val frameBufferPaint = Paint().also { it.shader = snowConfig.accumulatedSnowShader }
 
     init {
@@ -54,7 +59,7 @@
         adjustCropping(surfaceSize)
         prepareColorGrading()
         updateSnowGridSize(surfaceSize)
-        setIntensity(snowConfig.intensity)
+        setIntensity(intensity)
 
         // Generate accumulated snow at the end after we updated all the uniforms.
         generateAccumulatedSnow()
@@ -63,6 +68,7 @@
     override fun resize(newSurfaceSize: SizeF) {
         adjustCropping(newSurfaceSize)
         updateSnowGridSize(newSurfaceSize)
+        surfaceSize = newSurfaceSize
     }
 
     override fun update(deltaMillis: Long, frameTimeNanos: Long) {
@@ -105,13 +111,31 @@
         generateAccumulatedSnow()
     }
 
+    override fun setBitmaps(foreground: Bitmap, background: Bitmap) {
+        this.foreground = foreground
+        this.background = background
+        frameBuffer = FrameBuffer(background.width, background.height)
+        snowConfig.shader.setInputBuffer(
+            "background",
+            BitmapShader(background, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
+        )
+        snowConfig.shader.setInputBuffer(
+            "foreground",
+            BitmapShader(foreground, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
+        )
+        adjustCropping(surfaceSize)
+        // generateAccumulatedSnow needs foreground for accumulatedSnowShader, and needs frameBuffer
+        // which is also changed with background
+        generateAccumulatedSnow()
+    }
+
     private fun adjustCropping(surfaceSize: SizeF) {
         val imageCropFgd =
             ImageCrop.centerCoverCrop(
                 surfaceSize.width,
                 surfaceSize.height,
-                snowConfig.foreground.width.toFloat(),
-                snowConfig.foreground.height.toFloat()
+                foreground.width.toFloat(),
+                foreground.height.toFloat()
             )
         snowConfig.shader.setFloatUniform(
             "uvOffsetFgd",
@@ -127,8 +151,8 @@
             ImageCrop.centerCoverCrop(
                 surfaceSize.width,
                 surfaceSize.height,
-                snowConfig.background.width.toFloat(),
-                snowConfig.background.height.toFloat()
+                background.width.toFloat(),
+                background.height.toFloat()
             )
         snowConfig.shader.setFloatUniform(
             "uvOffsetBgd",
@@ -150,12 +174,12 @@
     private fun updateTextureUniforms() {
         snowConfig.shader.setInputBuffer(
             "foreground",
-            BitmapShader(snowConfig.foreground, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
+            BitmapShader(foreground, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
         )
 
         snowConfig.shader.setInputBuffer(
             "background",
-            BitmapShader(snowConfig.background, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
+            BitmapShader(background, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
         )
 
         snowConfig.shader.setInputBuffer(
@@ -182,7 +206,7 @@
         )
         snowConfig.accumulatedSnowShader.setInputBuffer(
             "foreground",
-            BitmapShader(snowConfig.foreground, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
+            BitmapShader(foreground, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
         )
         renderingCanvas.drawPaint(frameBufferPaint)
         frameBuffer.endDrawing()
diff --git a/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/snow/SnowEffectConfig.kt b/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/snow/SnowEffectConfig.kt
index 0d28c68..76b4892 100644
--- a/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/snow/SnowEffectConfig.kt
+++ b/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/snow/SnowEffectConfig.kt
@@ -37,14 +37,8 @@
     val noiseTexture: Bitmap,
     /** The main lut (color grading) for the effect. */
     val lut: Bitmap?,
-    /** A bitmap containing the foreground of the image. */
-    val foreground: Bitmap,
-    /** A bitmap containing the background of the image. */
-    val background: Bitmap,
     /** Pixel density of the display. Used for dithering. */
     val pixelDensity: Float,
-    /** The amount of the snow flakes. This contributes to the color grading as well. */
-    @FloatRange(from = 0.0, to = 1.0) val intensity: Float,
     /** The intensity of the color grading. 0: no color grading, 1: color grading in full effect. */
     @FloatRange(from = 0.0, to = 1.0) val colorGradingIntensity: Float,
     /** Max thickness for the accumulated snow. */
@@ -53,30 +47,22 @@
     /**
      * Constructor for [SnowEffectConfig].
      *
-     * @param context the application context.
-     * @param foreground a bitmap containing the foreground of the image.
-     * @param background a bitmap containing the background of the image.
-     * @param pixelDensity pixel density of the display.
-     * @param intensity initial intensity that affects the amount of snow flakes and color grading.
-     *   Expected range is [0, 1]. You can always change the intensity dynamically. Defaults to 1.
+     * @param assets asset manager,
+     * @param pixelDensity pixel density of the display. Expected range is [0, 1]. You can always
+     *   change the intensity dynamically. Defaults to 1.
      */
     constructor(
         assets: AssetManager,
-        foreground: Bitmap,
-        background: Bitmap,
         pixelDensity: Float,
-        intensity: Float = DEFAULT_INTENSITY,
     ) : this(
         shader = GraphicsUtils.loadShader(assets, SHADER_PATH),
         accumulatedSnowShader = GraphicsUtils.loadShader(assets, ACCUMULATED_SNOW_SHADER_PATH),
         colorGradingShader = GraphicsUtils.loadShader(assets, COLOR_GRADING_SHADER_PATH),
-        noiseTexture = GraphicsUtils.loadTexture(assets, NOISE_TEXTURE_PATH)
-            ?: throw RuntimeException("Noise texture is missing."),
+        noiseTexture =
+            GraphicsUtils.loadTexture(assets, NOISE_TEXTURE_PATH)
+                ?: throw RuntimeException("Noise texture is missing."),
         lut = GraphicsUtils.loadTexture(assets, LOOKUP_TABLE_TEXTURE_PATH),
-        foreground,
-        background,
         pixelDensity,
-        intensity,
         COLOR_GRADING_INTENSITY,
         MAX_SNOW_THICKNESS
     )
@@ -87,7 +73,6 @@
         private const val COLOR_GRADING_SHADER_PATH = "shaders/color_grading_lut.agsl"
         private const val NOISE_TEXTURE_PATH = "textures/clouds.png"
         private const val LOOKUP_TABLE_TEXTURE_PATH = "textures/lut_rain_and_fog.png"
-        private const val DEFAULT_INTENSITY = 1f
         private const val COLOR_GRADING_INTENSITY = 0.7f
         private const val MAX_SNOW_THICKNESS = 10f
     }
diff --git a/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/sun/SunEffect.kt b/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/sun/SunEffect.kt
index f403724..29ffe5d 100644
--- a/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/sun/SunEffect.kt
+++ b/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/sun/SunEffect.kt
@@ -16,6 +16,7 @@
 
 package com.google.android.wallpaper.weathereffects.graphics.sun
 
+import android.graphics.Bitmap
 import android.graphics.BitmapShader
 import android.graphics.Canvas
 import android.graphics.Paint
@@ -31,8 +32,11 @@
 class SunEffect(
     /** The config of the sunny effect. */
     private val sunConfig: SunEffectConfig,
+    private var foreground: Bitmap,
+    private var background: Bitmap,
+    private var intensity: Float = WeatherEffect.DEFAULT_INTENSITY,
     /** The initial size of the surface where the effect will be shown. */
-    surfaceSize: SizeF
+    var surfaceSize: SizeF
 ) : WeatherEffect {
 
     private val sunnyPaint = Paint().also { it.shader = sunConfig.colorGradingShader }
@@ -42,10 +46,13 @@
         updateTextureUniforms()
         adjustCropping(surfaceSize)
         prepareColorGrading()
-        setIntensity(sunConfig.intensity)
+        setIntensity(intensity)
     }
 
-    override fun resize(newSurfaceSize: SizeF) = adjustCropping(newSurfaceSize)
+    override fun resize(newSurfaceSize: SizeF) {
+        adjustCropping(newSurfaceSize)
+        surfaceSize = newSurfaceSize
+    }
 
     override fun update(deltaMillis: Long, frameTimeNanos: Long) {
         elapsedTime += TimeUnit.MILLISECONDS.toSeconds(deltaMillis)
@@ -73,13 +80,28 @@
         )
     }
 
-    private fun adjustCropping(surfaceSize: SizeF) {
-        val imageCropFgd = ImageCrop.centerCoverCrop(
-            surfaceSize.width,
-            surfaceSize.height,
-            sunConfig.foreground.width.toFloat(),
-            sunConfig.foreground.height.toFloat()
+    override fun setBitmaps(foreground: Bitmap, background: Bitmap) {
+        this.foreground = foreground
+        this.background = background
+        sunConfig.shader.setInputBuffer(
+            "background",
+            BitmapShader(background, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
         )
+        sunConfig.shader.setInputBuffer(
+            "foreground",
+            BitmapShader(foreground, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
+        )
+        adjustCropping(surfaceSize)
+    }
+
+    private fun adjustCropping(surfaceSize: SizeF) {
+        val imageCropFgd =
+            ImageCrop.centerCoverCrop(
+                surfaceSize.width,
+                surfaceSize.height,
+                foreground.width.toFloat(),
+                foreground.height.toFloat()
+            )
         sunConfig.shader.setFloatUniform(
             "uvOffsetFgd",
             imageCropFgd.leftOffset,
@@ -90,12 +112,13 @@
             imageCropFgd.horizontalScale,
             imageCropFgd.verticalScale
         )
-        val imageCropBgd = ImageCrop.centerCoverCrop(
-            surfaceSize.width,
-            surfaceSize.height,
-            sunConfig.background.width.toFloat(),
-            sunConfig.background.height.toFloat()
-        )
+        val imageCropBgd =
+            ImageCrop.centerCoverCrop(
+                surfaceSize.width,
+                surfaceSize.height,
+                background.width.toFloat(),
+                background.height.toFloat()
+            )
         sunConfig.shader.setFloatUniform(
             "uvOffsetBgd",
             imageCropBgd.leftOffset,
@@ -116,12 +139,12 @@
     private fun updateTextureUniforms() {
         sunConfig.shader.setInputBuffer(
             "foreground",
-            BitmapShader(sunConfig.foreground, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
+            BitmapShader(foreground, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
         )
 
         sunConfig.shader.setInputBuffer(
             "background",
-            BitmapShader(sunConfig.background, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
+            BitmapShader(background, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
         )
     }
 
@@ -133,9 +156,6 @@
                 BitmapShader(it, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
             )
         }
-        sunConfig.colorGradingShader.setFloatUniform(
-            "intensity",
-            sunConfig.colorGradingIntensity
-        )
+        sunConfig.colorGradingShader.setFloatUniform("intensity", sunConfig.colorGradingIntensity)
     }
 }
diff --git a/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/sun/SunEffectConfig.kt b/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/sun/SunEffectConfig.kt
index 1307d7f..05f6b80 100644
--- a/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/sun/SunEffectConfig.kt
+++ b/weathereffects/graphics/src/main/java/com/google/android/wallpaper/weathereffects/graphics/sun/SunEffectConfig.kt
@@ -16,10 +16,10 @@
 
 package com.google.android.wallpaper.weathereffects.graphics.sun
 
-import androidx.annotation.FloatRange
 import android.content.res.AssetManager
 import android.graphics.Bitmap
 import android.graphics.RuntimeShader
+import androidx.annotation.FloatRange
 import com.google.android.wallpaper.weathereffects.graphics.fog.FogEffectConfig
 import com.google.android.wallpaper.weathereffects.graphics.utils.GraphicsUtils
 
@@ -31,14 +31,8 @@
     val colorGradingShader: RuntimeShader,
     /** The main lut (color grading) for the effect. */
     val lut: Bitmap?,
-    /** A bitmap containing the foreground of the image. */
-    val foreground: Bitmap,
-    /** A bitmap containing the background of the image. */
-    val background: Bitmap,
     /** Pixel density of the display. Used for dithering. */
     val pixelDensity: Float,
-    /** The amount of the fog. This contributes to the color grading as well. */
-    @FloatRange(from = 0.0, to = 1.0) val intensity: Float,
     /** The intensity of the color grading. 0: no color grading, 1: color grading in full effect. */
     @FloatRange(from = 0.0, to = 1.0) val colorGradingIntensity: Float,
 ) {
@@ -46,33 +40,23 @@
      * Constructor for [FogEffectConfig].
      *
      * @param assets the application [AssetManager].
-     * @param foreground a bitmap containing the foreground of the image.
-     * @param background a bitmap containing the background of the image.
      * @param pixelDensity pixel density of the display.
-     * @param intensity initial intensity that affects the amount of fog and color grading. Expected
-     *   range is [0, 1]. You can always change the intensity dynamically. Defaults to 1.
      */
     constructor(
         assets: AssetManager,
-        foreground: Bitmap,
-        background: Bitmap,
         pixelDensity: Float,
-        intensity: Float = DEFAULT_INTENSITY,
     ) : this(
         shader = GraphicsUtils.loadShader(assets, SHADER_PATH),
         colorGradingShader = GraphicsUtils.loadShader(assets, COLOR_GRADING_SHADER_PATH),
         lut = GraphicsUtils.loadTexture(assets, LOOKUP_TABLE_TEXTURE_PATH),
-        foreground,
-        background,
         pixelDensity,
-        intensity,
         COLOR_GRADING_INTENSITY
     )
+
     companion object {
         private const val SHADER_PATH = "shaders/sun_effect.agsl"
         private const val COLOR_GRADING_SHADER_PATH = "shaders/color_grading_lut.agsl"
         private const val LOOKUP_TABLE_TEXTURE_PATH = "textures/lut_rain_and_fog.png"
-        private const val DEFAULT_INTENSITY = 1f
         private const val COLOR_GRADING_INTENSITY = 0.7f
     }
 }
diff --git a/weathereffects/src/com/google/android/wallpaper/weathereffects/WeatherEngine.kt b/weathereffects/src/com/google/android/wallpaper/weathereffects/WeatherEngine.kt
index 1686509..ce6ccf7 100644
--- a/weathereffects/src/com/google/android/wallpaper/weathereffects/WeatherEngine.kt
+++ b/weathereffects/src/com/google/android/wallpaper/weathereffects/WeatherEngine.kt
@@ -44,11 +44,11 @@
 import com.google.android.wallpaper.weathereffects.provider.WallpaperInfoContract
 import com.google.android.wallpaper.weathereffects.sensor.UserPresenceController
 import com.google.android.wallpaper.weathereffects.shared.model.WallpaperImageModel
+import kotlin.math.max
+import kotlin.math.roundToInt
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.launch
-import kotlin.math.max
-import kotlin.math.roundToInt
 
 class WeatherEngine(
     defaultHolder: SurfaceHolder,
@@ -56,8 +56,10 @@
     private val interactor: WeatherEffectsInteractor,
     private val context: Context,
     private val isDebugActivity: Boolean = false,
-    hardwareAccelerated: Boolean = true
-) : CanvasWallpaperEngine(defaultHolder, hardwareAccelerated), LiveWallpaperKeyguardEventListener,
+    hardwareAccelerated: Boolean = true,
+) :
+    CanvasWallpaperEngine(defaultHolder, hardwareAccelerated),
+    LiveWallpaperKeyguardEventListener,
     LiveWallpaperEventListener {
 
     private var lockStartTime: Long = 0
@@ -87,9 +89,7 @@
     init {
         /* Load assets. */
         if (interactor.wallpaperImageModel.value == null) {
-            applicationScope.launch {
-                interactor.loadWallpaper()
-            }
+            applicationScope.launch { interactor.loadWallpaper() }
         }
     }
 
@@ -115,14 +115,15 @@
     }
 
     override fun onResume() {
-        collectWallpaperImageJob = applicationScope.launch {
-            interactor.wallpaperImageModel.collect { asset ->
-                if (asset == null || asset == currentAssets) return@collect
-                currentAssets = asset
-                createWeatherEffect(asset.foreground, asset.background, asset.weatherEffect)
-                updateWallpaperColors(asset.background)
+        collectWallpaperImageJob =
+            applicationScope.launch {
+                interactor.wallpaperImageModel.collect { asset ->
+                    if (asset == null || asset == currentAssets) return@collect
+                    currentAssets = asset
+                    createWeatherEffect(asset.foreground, asset.background, asset.weatherEffect)
+                    updateWallpaperColors(asset.background)
+                }
             }
-        }
         if (activeEffect != null) {
             if (shouldTriggerUpdate()) startUpdateLoop()
         }
@@ -187,40 +188,63 @@
     private fun createWeatherEffect(
         foreground: Bitmap,
         background: Bitmap,
-        weatherEffect: WallpaperInfoContract.WeatherEffect? = null
+        weatherEffect: WallpaperInfoContract.WeatherEffect? = null,
     ) {
         activeEffect?.release()
         activeEffect = null
 
         when (weatherEffect) {
             WallpaperInfoContract.WeatherEffect.RAIN -> {
-                val rainConfig = RainEffectConfig(
-                    context.assets, foreground, background, context.resources.displayMetrics.density
-                )
-                activeEffect = RainEffect(rainConfig, screenSize.toSizeF(), context.mainExecutor)
+                val rainConfig =
+                    RainEffectConfig(context.assets, context.resources.displayMetrics.density)
+                activeEffect =
+                    RainEffect(
+                        rainConfig,
+                        foreground,
+                        background,
+                        effectIntensity,
+                        screenSize.toSizeF(),
+                        context.mainExecutor,
+                    )
             }
-
             WallpaperInfoContract.WeatherEffect.FOG -> {
-                val fogConfig = FogEffectConfig(
-                    context.assets, foreground, background, context.resources.displayMetrics.density
-                )
-                activeEffect = FogEffect(fogConfig, screenSize.toSizeF())
-            }
+                val fogConfig =
+                    FogEffectConfig(context.assets, context.resources.displayMetrics.density)
 
+                activeEffect =
+                    FogEffect(
+                        fogConfig,
+                        foreground,
+                        background,
+                        effectIntensity,
+                        screenSize.toSizeF()
+                    )
+            }
             WallpaperInfoContract.WeatherEffect.SNOW -> {
-                val snowConfig = SnowEffectConfig(
-                    context.assets, foreground, background, context.resources.displayMetrics.density
-                )
-                activeEffect = SnowEffect(snowConfig, screenSize.toSizeF(), context.mainExecutor)
+                val snowConfig =
+                    SnowEffectConfig(context.assets, context.resources.displayMetrics.density)
+                activeEffect =
+                    SnowEffect(
+                        snowConfig,
+                        foreground,
+                        background,
+                        effectIntensity,
+                        screenSize.toSizeF(),
+                        context.mainExecutor,
+                    )
             }
-
             WallpaperInfoContract.WeatherEffect.SUN -> {
-                val snowConfig = SunEffectConfig(
-                    context.assets, foreground, background, context.resources.displayMetrics.density
-                )
-                activeEffect = SunEffect(snowConfig, screenSize.toSizeF())
+                val snowConfig =
+                    SunEffectConfig(context.assets, context.resources.displayMetrics.density)
+                activeEffect =
+                    SunEffect(
+                        snowConfig,
+                        foreground,
+                        background,
+                        effectIntensity,
+                        screenSize.toSizeF()
+                    )
             }
-
             else -> {
                 activeEffect = NoEffect(foreground, background, screenSize.toSizeF())
             }
@@ -239,11 +263,9 @@
 
     private fun onUserPresenceChange(
         newUserPresence: UserPresenceController.UserPresence,
-        oldUserPresence: UserPresenceController.UserPresence
+        oldUserPresence: UserPresenceController.UserPresence,
     ) {
-        playIntensityFadeOutAnimation(
-            getAnimationType(newUserPresence, oldUserPresence)
-        )
+        playIntensityFadeOutAnimation(getAnimationType(newUserPresence, oldUserPresence))
     }
 
     private fun updateCurrentIntensity(intensity: Float = effectIntensity) {
@@ -261,7 +283,6 @@
                 lockStartTime = SystemClock.elapsedRealtime()
                 animateWeatherIntensityOut(AUTO_FADE_DELAY_FROM_AWAY_MILLIS)
             }
-
             AnimationType.UNLOCK -> {
                 // If already running, don't stop it.
                 if (unlockAnimator?.isRunning == true) {
@@ -289,7 +310,6 @@
                 updateCurrentIntensity()
                 animateWeatherIntensityOut(delayTime, AUTO_FADE_SHORT_DURATION_MILLIS)
             }
-
             AnimationType.NONE -> {
                 // No-op.
             }
@@ -300,22 +320,23 @@
 
     private fun animateWeatherIntensityOut(
         delayMillis: Long,
-        durationMillis: Long = AUTO_FADE_DURATION_MILLIS
+        durationMillis: Long = AUTO_FADE_DURATION_MILLIS,
     ) {
-        unlockAnimator = ValueAnimator.ofFloat(effectIntensity, 0f).apply {
-            duration = durationMillis
-            startDelay = delayMillis
-            addUpdateListener { updatedAnimation ->
-                effectIntensity = updatedAnimation.animatedValue as Float
-                updateCurrentIntensity()
+        unlockAnimator =
+            ValueAnimator.ofFloat(effectIntensity, 0f).apply {
+                duration = durationMillis
+                startDelay = delayMillis
+                addUpdateListener { updatedAnimation ->
+                    effectIntensity = updatedAnimation.animatedValue as Float
+                    updateCurrentIntensity()
+                }
+                start()
             }
-            start()
-        }
     }
 
     private fun getAnimationType(
         newPresence: UserPresenceController.UserPresence,
-        oldPresence: UserPresenceController.UserPresence
+        oldPresence: UserPresenceController.UserPresence,
     ): AnimationType {
         if (shouldSkipIntensityOutAnimation()) {
             return AnimationType.NONE
@@ -324,18 +345,16 @@
             UserPresenceController.UserPresence.AWAY -> {
                 if (
                     newPresence == UserPresenceController.UserPresence.LOCKED ||
-                    newPresence == UserPresenceController.UserPresence.ACTIVE
+                        newPresence == UserPresenceController.UserPresence.ACTIVE
                 ) {
                     return AnimationType.WAKE
                 }
             }
-
             UserPresenceController.UserPresence.LOCKED -> {
                 if (newPresence == UserPresenceController.UserPresence.ACTIVE) {
                     return AnimationType.UNLOCK
                 }
             }
-
             else -> {
                 // No-op.
             }
@@ -345,14 +364,15 @@
     }
 
     private fun updateWallpaperColors(background: Bitmap) {
-        backgroundColor = WallpaperColors.fromBitmap(
-            Bitmap.createScaledBitmap(
-                background,
-                256,
-                (background.width / background.height.toFloat() * 256).roundToInt(),
-                /* filter = */ true
+        backgroundColor =
+            WallpaperColors.fromBitmap(
+                Bitmap.createScaledBitmap(
+                    background,
+                    256,
+                    (background.width / background.height.toFloat() * 256).roundToInt(),
+                    /* filter = */ true
+                )
             )
-        )
     }
 
     /**