Enable unfold animation progress calculation in arbitrary thread

This cl changes some classes to make unfold animation progresses calculation happen on an arbitrary thread. Now there are 2 consumers of progresses calulated in the background:

1. Launcher process. Launcher receives animation progresses from SystemUI. if SystemUI janks (e.g. due to notification measuring), launcher would have janked as well. After this change, Launcher receives the progresses calculted in the background, so it is unaffected by systemui main thread being blocked.

 2. The dark scrim added after unfold (controlled by "UnfoldLightRevealOverlayAnimation") is now fully independent from the main thread: as it also uses the background progress, and the window is using a separate UI thread, it will not jank when the systemui main thread is stuck.

For now the only missing part is receiving the screenStatus in the bg thread. When we tried it caused regressions in the past. All other signals can now happen either on the unfold progress thread or main.

Most classes have been changed to be created with an arbitrary Handler. In the unfold lib there are 2 now: (1) UnfoldMain and (2) UnfoldBg.

There are now 2 injectable versions of FoldStateProvider. The default one that uses the main thread (and should not have any change in behaviour), and the new one annotated with @UnfoldBgProgress, that receives and executes all (except screen state) the calculations in the background.

PhysicsBasedUnfoldTransitionProgressProvider can now use any of those. Only when the handler provided is different than the main one, another scheduler (that works in the background) is provided to the DynamicAnimation.

+ UnfoldLightRevealOverlayAnimation moved to the new UnfoldBg thread.

Bug: 277879146
Bug: 310572965
Bug: 307511460
Bug: 292472402
Flag: ACONFIG unfold_animation_background_progress DISABLED
Test: DeviceFoldStateProviderTest, PhysicsBasedUnfoldTransitionProgressProviderTest + manual + perfetto trace
Change-Id: I767263b6a5f3c6fd3b6f52c946d724599013a47c
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index a745ab5..5202c49 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -114,6 +114,13 @@
 }
 
 flag {
+    name: "unfold_animation_background_progress"
+    namespace: "systemui"
+    description: "Moves unfold animation progress calculation to a background thread"
+    bug: "277879146"
+}
+
+flag {
     name: "qs_new_pipeline"
     namespace: "systemui"
     description: "Use the new pipeline for Quick Settings. Should have no behavior changes."
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt
index c9e57b4..b33f6fa 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt
@@ -32,8 +32,7 @@
     override val isHomeActivity: Boolean?
         get() = _isHomeActivity
 
-    private var _isHomeActivity: Boolean? = null
-
+    @Volatile private var _isHomeActivity: Boolean? = null
 
     override fun init() {
         _isHomeActivity = activityManager.isOnHomeActivity()
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateManagerFoldProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateManagerFoldProvider.kt
index 3b8d318..baa8889 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateManagerFoldProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateManagerFoldProvider.kt
@@ -18,18 +18,19 @@
 import android.hardware.devicestate.DeviceStateManager
 import com.android.systemui.unfold.updates.FoldProvider
 import com.android.systemui.unfold.updates.FoldProvider.FoldCallback
+import java.util.concurrent.ConcurrentHashMap
 import java.util.concurrent.Executor
 import javax.inject.Inject
 import javax.inject.Singleton
 
 @Singleton
-class DeviceStateManagerFoldProvider @Inject constructor(
-    private val deviceStateManager: DeviceStateManager,
-    private val context: Context
-) : FoldProvider {
+class DeviceStateManagerFoldProvider
+@Inject
+constructor(private val deviceStateManager: DeviceStateManager, private val context: Context) :
+    FoldProvider {
 
-    private val callbacks: MutableMap<FoldCallback,
-            DeviceStateManager.DeviceStateCallback> = hashMapOf()
+    private val callbacks =
+        ConcurrentHashMap<FoldCallback, DeviceStateManager.DeviceStateCallback>()
 
     override fun registerCallback(callback: FoldCallback, executor: Executor) {
         val listener = FoldStateListener(context, callback)
@@ -39,13 +40,9 @@
 
     override fun unregisterCallback(callback: FoldCallback) {
         val listener = callbacks.remove(callback)
-        listener?.let {
-            deviceStateManager.unregisterCallback(it)
-        }
+        listener?.let { deviceStateManager.unregisterCallback(it) }
     }
 
-    private inner class FoldStateListener(
-        context: Context,
-        listener: FoldCallback
-    ) : DeviceStateManager.FoldStateListener(context, { listener.onFoldUpdated(it) })
+    private inner class FoldStateListener(context: Context, listener: FoldCallback) :
+        DeviceStateManager.FoldStateListener(context, { listener.onFoldUpdated(it) })
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
index 7b67e3f..7af9917 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
@@ -15,24 +15,29 @@
 package com.android.systemui.unfold.system
 
 import android.os.Handler
+import android.os.HandlerThread
+import android.os.Looper
+import android.os.Process
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dagger.qualifiers.UiBackground
 import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
-import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
+import com.android.systemui.unfold.dagger.UnfoldBg
 import com.android.systemui.unfold.dagger.UnfoldMain
+import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
 import com.android.systemui.unfold.updates.FoldProvider
 import com.android.systemui.unfold.util.CurrentActivityTypeProvider
 import dagger.Binds
 import dagger.Module
+import dagger.Provides
 import java.util.concurrent.Executor
+import javax.inject.Singleton
 
 /**
- * Dagger module with system-only dependencies for the unfold animation.
- * The code that is used to calculate unfold transition progress
- * depends on some hidden APIs that are not available in normal
- * apps. In order to re-use this code and use alternative implementations
- * of these classes in other apps and hidden APIs here.
+ * Dagger module with system-only dependencies for the unfold animation. The code that is used to
+ * calculate unfold transition progress depends on some hidden APIs that are not available in normal
+ * apps. In order to re-use this code and use alternative implementations of these classes in other
+ * apps and hidden APIs here.
  */
 @Module
 abstract class SystemUnfoldSharedModule {
@@ -61,4 +66,22 @@
     @Binds
     @UnfoldSingleThreadBg
     abstract fun backgroundExecutor(@UiBackground executor: Executor): Executor
+
+    companion object {
+        @Provides
+        @UnfoldBg
+        @Singleton
+        fun unfoldBgProgressHandler(@UnfoldBg looper: Looper): Handler {
+            return Handler(looper)
+        }
+
+        @Provides
+        @UnfoldBg
+        @Singleton
+        fun provideBgLooper(): Looper {
+            return HandlerThread("UnfoldBg", Process.THREAD_PRIORITY_FOREGROUND)
+                .apply { start() }
+                .looper
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index d9e0629..e7b8773 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -19,6 +19,7 @@
 import com.android.systemui.BootCompleteCacheImpl;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.Dependency;
+import com.android.systemui.Flags;
 import com.android.systemui.InitController;
 import com.android.systemui.SystemUIAppComponentFactoryBase;
 import com.android.systemui.dagger.qualifiers.PerUser;
@@ -35,6 +36,7 @@
 import com.android.systemui.unfold.FoldStateLoggingProvider;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
+import com.android.systemui.unfold.dagger.UnfoldBg;
 import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder;
 import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.bubbles.Bubbles;
@@ -144,7 +146,15 @@
         getConnectingDisplayViewModel().init();
         getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init);
         getFoldStateLogger().ifPresent(FoldStateLogger::init);
-        getUnfoldTransitionProgressProvider()
+
+        Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider;
+
+        if (Flags.unfoldAnimationBackgroundProgress()) {
+            unfoldTransitionProgressProvider = getBgUnfoldTransitionProgressProvider();
+        } else {
+            unfoldTransitionProgressProvider = getUnfoldTransitionProgressProvider();
+        }
+        unfoldTransitionProgressProvider
                 .ifPresent(
                         (progressProvider) ->
                                 getUnfoldTransitionProgressForwarder()
@@ -170,7 +180,14 @@
     ContextComponentHelper getContextComponentHelper();
 
     /**
-     * Creates a UnfoldTransitionProgressProvider.
+     * Creates a UnfoldTransitionProgressProvider that calculates progress in the background.
+     */
+    @SysUISingleton
+    @UnfoldBg
+    Optional<UnfoldTransitionProgressProvider> getBgUnfoldTransitionProgressProvider();
+
+    /**
+     * Creates a UnfoldTransitionProgressProvider that calculates progress in the main thread.
      */
     @SysUISingleton
     Optional<UnfoldTransitionProgressProvider> getUnfoldTransitionProgressProvider();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
index f9b89b1..7354cfc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
@@ -18,6 +18,7 @@
 import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
 import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
 import com.android.app.tracing.traceSection
+import java.util.concurrent.CopyOnWriteArrayList
 import javax.inject.Inject
 import javax.inject.Singleton
 
@@ -29,7 +30,7 @@
         screenLifecycle.addObserver(this)
     }
 
-    private val listeners: MutableList<ScreenListener> = mutableListOf()
+    private val listeners: MutableList<ScreenListener> = CopyOnWriteArrayList()
 
     override fun removeCallback(listener: ScreenListener) {
         listeners.remove(listener)
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
index 8ae093a..10fc83c 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.shade.NotificationPanelUnfoldAnimationController
 import com.android.systemui.statusbar.phone.StatusBarMoveFromCenterAnimationController
+import com.android.systemui.unfold.dagger.UnfoldBg
 import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
 import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityManager
@@ -33,10 +34,7 @@
 import javax.inject.Named
 import javax.inject.Scope
 
-@Scope
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class SysUIUnfoldScope
+@Scope @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class SysUIUnfoldScope
 
 /**
  * Creates an injectable [SysUIUnfoldComponent] that provides objects that have been scoped with
@@ -55,20 +53,21 @@
     @Provides
     @SysUISingleton
     fun provideSysUIUnfoldComponent(
-            provider: Optional<UnfoldTransitionProgressProvider>,
-            rotationProvider: Optional<NaturalRotationUnfoldProgressProvider>,
-            @Named(UNFOLD_STATUS_BAR) scopedProvider:
-            Optional<ScopedUnfoldTransitionProgressProvider>,
-            unfoldLatencyTracker: Lazy<UnfoldLatencyTracker>,
-            factory: SysUIUnfoldComponent.Factory
+        provider: Optional<UnfoldTransitionProgressProvider>,
+        rotationProvider: Optional<NaturalRotationUnfoldProgressProvider>,
+        @Named(UNFOLD_STATUS_BAR) scopedProvider: Optional<ScopedUnfoldTransitionProgressProvider>,
+        @UnfoldBg bgProvider: Optional<UnfoldTransitionProgressProvider>,
+        unfoldLatencyTracker: Lazy<UnfoldLatencyTracker>,
+        factory: SysUIUnfoldComponent.Factory
     ): Optional<SysUIUnfoldComponent> {
         val p1 = provider.getOrNull()
         val p2 = rotationProvider.getOrNull()
         val p3 = scopedProvider.getOrNull()
-        return if (p1 == null || p2 == null || p3 == null) {
+        val p4 = bgProvider.getOrNull()
+        return if (p1 == null || p2 == null || p3 == null || p4 == null) {
             Optional.empty()
         } else {
-            Optional.of(factory.create(p1, p2, p3, unfoldLatencyTracker.get()))
+            Optional.of(factory.create(p1, p2, p3, p4, unfoldLatencyTracker.get()))
         }
     }
 }
@@ -76,13 +75,15 @@
 @SysUIUnfoldScope
 @Subcomponent
 interface SysUIUnfoldComponent {
+
     @Subcomponent.Factory
     interface Factory {
         fun create(
-                @BindsInstance p1: UnfoldTransitionProgressProvider,
-                @BindsInstance p2: NaturalRotationUnfoldProgressProvider,
-                @BindsInstance p3: ScopedUnfoldTransitionProgressProvider,
-                @BindsInstance p4: UnfoldLatencyTracker,
+            @BindsInstance p1: UnfoldTransitionProgressProvider,
+            @BindsInstance p2: NaturalRotationUnfoldProgressProvider,
+            @BindsInstance p3: ScopedUnfoldTransitionProgressProvider,
+            @BindsInstance @UnfoldBg p4: UnfoldTransitionProgressProvider,
+            @BindsInstance p5: UnfoldLatencyTracker,
         ): SysUIUnfoldComponent
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index 36a1e8a..b72c6f1 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -35,6 +35,9 @@
 import android.view.SurfaceSession
 import android.view.WindowManager
 import android.view.WindowlessWindowManager
+import com.android.app.tracing.traceSection
+import com.android.keyguard.logging.ScrimLogger
+import com.android.systemui.Flags.unfoldAnimationBackgroundProgress
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
@@ -45,16 +48,16 @@
 import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation.AddOverlayReason.FOLD
 import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation.AddOverlayReason.UNFOLD
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.dagger.UnfoldBg
 import com.android.systemui.unfold.updates.RotationChangeProvider
 import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled
 import com.android.systemui.util.concurrency.ThreadFactory
-import com.android.app.tracing.traceSection
-import com.android.keyguard.logging.ScrimLogger
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper
 import java.util.Optional
 import java.util.concurrent.Executor
 import java.util.function.Consumer
 import javax.inject.Inject
+import javax.inject.Provider
 
 @SysUIUnfoldScope
 class UnfoldLightRevealOverlayAnimation
@@ -65,11 +68,14 @@
     private val deviceStateManager: DeviceStateManager,
     private val contentResolver: ContentResolver,
     private val displayManager: DisplayManager,
-    private val unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
+    @UnfoldBg
+    private val unfoldTransitionBgProgressProvider: Provider<UnfoldTransitionProgressProvider>,
+    private val unfoldTransitionProgressProvider: Provider<UnfoldTransitionProgressProvider>,
     private val displayAreaHelper: Optional<DisplayAreaHelper>,
     @Main private val executor: Executor,
     private val threadFactory: ThreadFactory,
-    private val rotationChangeProvider: RotationChangeProvider,
+    @UnfoldBg private val rotationChangeProvider: RotationChangeProvider,
+    @UnfoldBg private val unfoldProgressHandler: Handler,
     private val displayTracker: DisplayTracker,
     private val scrimLogger: ScrimLogger,
 ) {
@@ -96,11 +102,15 @@
     fun init() {
         // This method will be called only on devices where this animation is enabled,
         // so normally this thread won't be created
-        bgHandler = threadFactory.buildHandlerOnNewThread(TAG)
+        bgHandler = unfoldProgressHandler
         bgExecutor = threadFactory.buildDelayableExecutorOnHandler(bgHandler)
 
         deviceStateManager.registerCallback(bgExecutor, FoldListener())
-        unfoldTransitionProgressProvider.addCallback(transitionListener)
+        if (unfoldAnimationBackgroundProgress()) {
+            unfoldTransitionBgProgressProvider.get().addCallback(transitionListener)
+        } else {
+            unfoldTransitionProgressProvider.get().addCallback(transitionListener)
+        }
         rotationChangeProvider.addCallback(rotationWatcher)
 
         val containerBuilder =
@@ -169,8 +179,13 @@
 
         overlayAddReason = reason
 
-        val newRoot = SurfaceControlViewHost(context, context.display!!, wwm,
-                "UnfoldLightRevealOverlayAnimation")
+        val newRoot =
+            SurfaceControlViewHost(
+                context,
+                context.display,
+                wwm,
+                "UnfoldLightRevealOverlayAnimation"
+            )
         val params = getLayoutParams()
         val newView =
             LightRevealScrim(
@@ -353,12 +368,13 @@
     }
 
     private fun executeInBackground(f: () -> Unit) {
-        check(Looper.myLooper() != bgHandler.looper) {
-            "Trying to execute using background handler while already running" +
-                " in the background handler"
+        // This is needed to allow progresses to be received both from the main thread (that will
+        // schedule a runnable on the bg thread), and from the bg thread directly (no reposting).
+        if (bgHandler.looper.isCurrentThread) {
+            f()
+        } else {
+            bgHandler.post(f)
         }
-        // The UiBackground executor is not used as it doesn't have a prepared looper.
-        bgHandler.post(f)
     }
 
     private fun ensureInBackground() {
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt
index 12b8845..94912bf8 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt
@@ -21,11 +21,14 @@
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.unfold.system.DeviceStateRepository
 import com.android.systemui.unfold.updates.FoldStateRepository
 import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.plus
 
 /**
  * Logs several unfold related details in a trace. Mainly used for debugging and investigate
@@ -37,7 +40,8 @@
 constructor(
     private val context: Context,
     private val foldStateRepository: FoldStateRepository,
-    @Application private val applicationScope: CoroutineScope,
+    @Application applicationScope: CoroutineScope,
+    @Background private val coroutineContext: CoroutineContext,
     private val deviceStateRepository: DeviceStateRepository
 ) : CoreStartable {
     private val isFoldable: Boolean
@@ -46,20 +50,22 @@
                 .getIntArray(com.android.internal.R.array.config_foldedDeviceStates)
                 .isNotEmpty()
 
+    private val bgScope = applicationScope.plus(coroutineContext)
+
     override fun start() {
         if (!isFoldable) return
 
-        applicationScope.launch {
+        bgScope.launch {
             val foldUpdateLogger = TraceStateLogger("FoldUpdate")
             foldStateRepository.foldUpdate.collect { foldUpdateLogger.log(it.name) }
         }
 
-        applicationScope.launch {
+        bgScope.launch {
             foldStateRepository.hingeAngle.collect {
                 Trace.traceCounter(Trace.TRACE_TAG_APP, "hingeAngle", it.toInt())
             }
         }
-        applicationScope.launch {
+        bgScope.launch {
             val foldedStateLogger = TraceStateLogger("FoldedState")
             deviceStateRepository.isFolded.collect { isFolded ->
                 foldedStateLogger.log(
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
index 7b628f8..0531487 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.LifecycleScreenStatusProvider
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.dagger.UnfoldMain
 import com.android.systemui.unfold.data.repository.UnfoldTransitionRepository
 import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl
 import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
@@ -102,7 +103,7 @@
     @Singleton
     fun provideNaturalRotationProgressProvider(
         context: Context,
-        rotationChangeProvider: RotationChangeProvider,
+        @UnfoldMain rotationChangeProvider: RotationChangeProvider,
         unfoldTransitionProgressProvider: Optional<UnfoldTransitionProgressProvider>
     ): Optional<NaturalRotationUnfoldProgressProvider> =
         unfoldTransitionProgressProvider.map { provider ->
@@ -153,7 +154,8 @@
 
         return resultingProvider?.get()?.orElse(null)?.let { unfoldProgressProvider ->
             UnfoldProgressProvider(unfoldProgressProvider, foldProvider)
-        } ?: ShellUnfoldProgressProvider.NO_PROVIDER
+        }
+            ?: ShellUnfoldProgressProvider.NO_PROVIDER
     }
 
     @Provides
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
index 9fe2f56..14fb054 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
@@ -15,9 +15,10 @@
  */
 package com.android.systemui.unfold.progress
 
+import android.os.Handler
+import android.os.HandlerThread
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
-import androidx.test.platform.app.InstrumentationRegistry
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
 import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
@@ -26,6 +27,8 @@
 import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
 import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING
 import com.android.systemui.unfold.util.TestFoldStateProvider
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -37,16 +40,28 @@
     private val foldStateProvider: TestFoldStateProvider = TestFoldStateProvider()
     private val listener = TestUnfoldProgressListener()
     private lateinit var progressProvider: UnfoldTransitionProgressProvider
+    private val schedulerFactory =
+        mock<UnfoldFrameCallbackScheduler.Factory>().apply {
+            whenever(create()).then { UnfoldFrameCallbackScheduler() }
+        }
+    private val mockBgHandler = mock<Handler>()
+    private val fakeHandler = Handler(HandlerThread("UnfoldBg").apply { start() }.looper)
 
     @Before
     fun setUp() {
-        progressProvider = PhysicsBasedUnfoldTransitionProgressProvider(context, foldStateProvider)
+        progressProvider =
+            PhysicsBasedUnfoldTransitionProgressProvider(
+                context,
+                schedulerFactory,
+                foldStateProvider = foldStateProvider,
+                progressHandler = fakeHandler
+            )
         progressProvider.addCallback(listener)
     }
 
     @Test
     fun testUnfold_emitsIncreasingTransitionEvents() {
-        runOnMainThreadWithInterval(
+        runOnProgressThreadWithInterval(
             { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
             { foldStateProvider.sendHingeAngleUpdate(10f) },
             { foldStateProvider.sendUnfoldedScreenAvailable() },
@@ -63,7 +78,7 @@
 
     @Test
     fun testUnfold_emitsFinishingEvent() {
-        runOnMainThreadWithInterval(
+        runOnProgressThreadWithInterval(
             { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
             { foldStateProvider.sendHingeAngleUpdate(10f) },
             { foldStateProvider.sendUnfoldedScreenAvailable() },
@@ -77,7 +92,7 @@
 
     @Test
     fun testUnfold_screenAvailableOnlyAfterFullUnfold_emitsIncreasingTransitionEvents() {
-        runOnMainThreadWithInterval(
+        runOnProgressThreadWithInterval(
             { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
             { foldStateProvider.sendHingeAngleUpdate(10f) },
             { foldStateProvider.sendHingeAngleUpdate(90f) },
@@ -94,7 +109,7 @@
 
     @Test
     fun testFold_emitsDecreasingTransitionEvents() {
-        runOnMainThreadWithInterval(
+        runOnProgressThreadWithInterval(
             { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_CLOSING) },
             { foldStateProvider.sendHingeAngleUpdate(170f) },
             { foldStateProvider.sendHingeAngleUpdate(90f) },
@@ -110,7 +125,7 @@
 
     @Test
     fun testUnfoldAndStopUnfolding_finishesTheUnfoldTransition() {
-        runOnMainThreadWithInterval(
+        runOnProgressThreadWithInterval(
             { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
             { foldStateProvider.sendUnfoldedScreenAvailable() },
             { foldStateProvider.sendHingeAngleUpdate(10f) },
@@ -126,7 +141,7 @@
 
     @Test
     fun testFoldImmediatelyAfterUnfold_runsFoldAnimation() {
-        runOnMainThreadWithInterval(
+        runOnProgressThreadWithInterval(
             { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
             { foldStateProvider.sendUnfoldedScreenAvailable() },
             { foldStateProvider.sendHingeAngleUpdate(10f) },
@@ -144,9 +159,12 @@
         with(listener.ensureTransitionFinished()) { assertHasFoldAnimationAtTheEnd() }
     }
 
-    private fun runOnMainThreadWithInterval(vararg blocks: () -> Unit, intervalMillis: Long = 60) {
+    private fun runOnProgressThreadWithInterval(
+        vararg blocks: () -> Unit,
+        intervalMillis: Long = 60,
+    ) {
         blocks.forEach {
-            InstrumentationRegistry.getInstrumentation().runOnMainSync { it() }
+            fakeHandler.post(it)
             Thread.sleep(intervalMillis)
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
index aa49287..552b60c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
@@ -37,7 +37,6 @@
 import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityProvider
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.fail
 import java.util.concurrent.Executor
@@ -105,16 +104,15 @@
 
         foldStateProvider =
             DeviceFoldStateProvider(
-                config,
-                testHingeAngleProvider,
-                screenOnStatusProvider,
-                foldProvider,
-                activityTypeProvider,
-                unfoldKeyguardVisibilityProvider,
-                rotationChangeProvider,
-                context,
-                context.mainExecutor,
-                handler
+                    config,
+                    context,
+                    screenOnStatusProvider,
+                    activityTypeProvider,
+                    unfoldKeyguardVisibilityProvider,
+                    foldProvider,
+                    testHingeAngleProvider,
+                    rotationChangeProvider,
+                    handler
             )
 
         foldStateProvider.addCallback(
@@ -151,6 +149,12 @@
             null
         }
 
+        whenever(handler.post(any<Runnable>())).then { invocationOnMock ->
+            val runnable = invocationOnMock.getArgument<Runnable>(0)
+            runnable.run()
+            null
+        }
+
         // By default, we're on launcher.
         setupForegroundActivityType(isHomeActivity = true)
         setIsLargeScreen(true)
@@ -171,7 +175,7 @@
     }
 
     @Test
-    fun testOnUnfold_hingeAngleDecreasesBeforeInnerScreenAvailable_emitsOnlyStartAndInnerScreenAvailableEvents() {
+    fun onUnfold_angleDecrBeforeInnerScrAvailable_emitsOnlyStartAndInnerScrAvailableEvents() {
         setFoldState(folded = true)
         foldUpdates.clear()
 
@@ -187,7 +191,7 @@
     }
 
     @Test
-    fun testOnUnfold_hingeAngleDecreasesAfterInnerScreenAvailable_emitsStartInnerScreenAvailableAndStartClosingEvents() {
+    fun onUnfold_angleDecrAfterInnerScrAvailable_emitsStartInnerScrAvailableAndStartClosingEvnts() {
         setFoldState(folded = true)
         foldUpdates.clear()
 
@@ -690,7 +694,7 @@
             callbacks.forEach { it.onFoldUpdated(isFolded) }
         }
 
-        fun getNumberOfCallbacks(): Int{
+        fun getNumberOfCallbacks(): Int {
             return callbacks.size
         }
     }
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt
index a639df5..2bc2db3 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt
@@ -16,9 +16,12 @@
 
 package com.android.systemui.unfold
 
+import android.os.Handler
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.dagger.UnfoldMain
 import com.android.systemui.unfold.dagger.UseReceivingFilter
 import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver
+import com.android.systemui.unfold.updates.RotationChangeProvider
 import com.android.systemui.unfold.util.ATraceLoggerTransitionProgressListener
 import dagger.Module
 import dagger.Provides
@@ -33,16 +36,25 @@
     @Singleton
     fun provideTransitionProvider(
         config: UnfoldTransitionConfig,
-        traceListener: ATraceLoggerTransitionProgressListener,
+        traceListener: ATraceLoggerTransitionProgressListener.Factory,
         remoteReceiverProvider: Provider<RemoteUnfoldTransitionReceiver>,
     ): Optional<RemoteUnfoldTransitionReceiver> {
         if (!config.isEnabled) {
             return Optional.empty()
         }
         val remoteReceiver = remoteReceiverProvider.get()
-        remoteReceiver.addCallback(traceListener)
+        remoteReceiver.addCallback(traceListener.create("remoteReceiver"))
         return Optional.of(remoteReceiver)
     }
 
     @Provides @UseReceivingFilter fun useReceivingFilter(): Boolean = true
+
+    @Provides
+    @UnfoldMain
+    fun provideMainRotationChangeProvider(
+        rotationChangeProviderFactory: RotationChangeProvider.Factory,
+        @UnfoldMain mainHandler: Handler,
+    ): RotationChangeProvider {
+        return rotationChangeProviderFactory.create(mainHandler)
+    }
 }
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
index c3a6cf0..31b7ccc 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
@@ -22,12 +22,12 @@
 import android.hardware.display.DisplayManager
 import android.os.Handler
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.dagger.UnfoldBg
 import com.android.systemui.unfold.dagger.UnfoldMain
 import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
 import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver
 import com.android.systemui.unfold.updates.FoldProvider
 import com.android.systemui.unfold.updates.RotationChangeProvider
-import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
 import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
 import com.android.systemui.unfold.util.CurrentActivityTypeProvider
 import com.android.systemui.unfold.util.UnfoldTransitionATracePrefix
@@ -63,13 +63,12 @@
             @BindsInstance @UnfoldSingleThreadBg singleThreadBgExecutor: Executor,
             @BindsInstance @UnfoldTransitionATracePrefix tracingTagPrefix: String,
             @BindsInstance displayManager: DisplayManager,
-            @BindsInstance contentResolver: ContentResolver = context.contentResolver
+            @BindsInstance @UnfoldBg bgHandler: Handler,
+            @BindsInstance contentResolver: ContentResolver = context.contentResolver,
         ): UnfoldSharedComponent
     }
 
     val unfoldTransitionProvider: Optional<UnfoldTransitionProgressProvider>
-    val hingeAngleProvider: HingeAngleProvider
-    val rotationChangeProvider: RotationChangeProvider
 }
 
 /**
@@ -94,7 +93,8 @@
     }
 
     val remoteTransitionProgress: Optional<RemoteUnfoldTransitionReceiver>
-    val rotationChangeProvider: RotationChangeProvider
+
+    @UnfoldMain fun getRotationChangeProvider(): RotationChangeProvider
 }
 
 /**
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
index 7473ca6..42d31b3 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
@@ -16,7 +16,10 @@
 
 package com.android.systemui.unfold
 
+import android.os.Handler
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.dagger.UnfoldBg
+import com.android.systemui.unfold.dagger.UnfoldMain
 import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider
 import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider
 import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder
@@ -24,6 +27,7 @@
 import com.android.systemui.unfold.updates.FoldStateProvider
 import com.android.systemui.unfold.updates.FoldStateRepository
 import com.android.systemui.unfold.updates.FoldStateRepositoryImpl
+import com.android.systemui.unfold.updates.RotationChangeProvider
 import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider
 import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
 import com.android.systemui.unfold.updates.hinge.HingeSensorAngleProvider
@@ -38,16 +42,18 @@
 import javax.inject.Provider
 import javax.inject.Singleton
 
-@Module(includes = [UnfoldSharedInternalModule::class])
+@Module(
+    includes =
+        [
+            UnfoldSharedInternalModule::class,
+            UnfoldRotationProviderInternalModule::class,
+            HingeAngleProviderInternalModule::class,
+            FoldStateProviderModule::class,
+        ]
+)
 class UnfoldSharedModule {
     @Provides
     @Singleton
-    fun provideFoldStateProvider(
-        deviceFoldStateProvider: DeviceFoldStateProvider
-    ): FoldStateProvider = deviceFoldStateProvider
-
-    @Provides
-    @Singleton
     fun unfoldKeyguardVisibilityProvider(
         impl: UnfoldKeyguardVisibilityManagerImpl
     ): UnfoldKeyguardVisibilityProvider = impl
@@ -60,9 +66,7 @@
 
     @Provides
     @Singleton
-    fun foldStateRepository(
-            impl: FoldStateRepositoryImpl
-    ): FoldStateRepository = impl
+    fun foldStateRepository(impl: FoldStateRepositoryImpl): FoldStateRepository = impl
 }
 
 /**
@@ -77,17 +81,69 @@
     fun unfoldTransitionProgressProvider(
         config: UnfoldTransitionConfig,
         scaleAwareProviderFactory: ScaleAwareTransitionProgressProvider.Factory,
+        tracingListener: ATraceLoggerTransitionProgressListener.Factory,
+        physicsBasedUnfoldTransitionProgressProvider:
+            PhysicsBasedUnfoldTransitionProgressProvider.Factory,
+        fixedTimingTransitionProgressProvider: Provider<FixedTimingTransitionProgressProvider>,
+        foldStateProvider: FoldStateProvider,
+        @UnfoldMain mainHandler: Handler,
+    ): Optional<UnfoldTransitionProgressProvider> {
+        return createOptionalUnfoldTransitionProgressProvider(
+            config = config,
+            scaleAwareProviderFactory = scaleAwareProviderFactory,
+            tracingListener = tracingListener.create("MainThread"),
+            physicsBasedUnfoldTransitionProgressProvider =
+                physicsBasedUnfoldTransitionProgressProvider,
+            fixedTimingTransitionProgressProvider = fixedTimingTransitionProgressProvider,
+            foldStateProvider = foldStateProvider,
+            progressHandler = mainHandler,
+        )
+    }
+
+    @Provides
+    @Singleton
+    @UnfoldBg
+    fun unfoldBgTransitionProgressProvider(
+        config: UnfoldTransitionConfig,
+        scaleAwareProviderFactory: ScaleAwareTransitionProgressProvider.Factory,
+        tracingListener: ATraceLoggerTransitionProgressListener.Factory,
+        physicsBasedUnfoldTransitionProgressProvider:
+            PhysicsBasedUnfoldTransitionProgressProvider.Factory,
+        fixedTimingTransitionProgressProvider: Provider<FixedTimingTransitionProgressProvider>,
+        @UnfoldBg bgFoldStateProvider: FoldStateProvider,
+        @UnfoldBg bgHandler: Handler,
+    ): Optional<UnfoldTransitionProgressProvider> {
+        return createOptionalUnfoldTransitionProgressProvider(
+            config = config,
+            scaleAwareProviderFactory = scaleAwareProviderFactory,
+            tracingListener = tracingListener.create("BgThread"),
+            physicsBasedUnfoldTransitionProgressProvider =
+                physicsBasedUnfoldTransitionProgressProvider,
+            fixedTimingTransitionProgressProvider = fixedTimingTransitionProgressProvider,
+            foldStateProvider = bgFoldStateProvider,
+            progressHandler = bgHandler,
+        )
+    }
+
+    private fun createOptionalUnfoldTransitionProgressProvider(
+        config: UnfoldTransitionConfig,
+        scaleAwareProviderFactory: ScaleAwareTransitionProgressProvider.Factory,
         tracingListener: ATraceLoggerTransitionProgressListener,
         physicsBasedUnfoldTransitionProgressProvider:
-            Provider<PhysicsBasedUnfoldTransitionProgressProvider>,
+            PhysicsBasedUnfoldTransitionProgressProvider.Factory,
         fixedTimingTransitionProgressProvider: Provider<FixedTimingTransitionProgressProvider>,
+        foldStateProvider: FoldStateProvider,
+        progressHandler: Handler,
     ): Optional<UnfoldTransitionProgressProvider> {
         if (!config.isEnabled) {
             return Optional.empty()
         }
         val baseProgressProvider =
             if (config.isHingeAngleEnabled) {
-                physicsBasedUnfoldTransitionProgressProvider.get()
+                physicsBasedUnfoldTransitionProgressProvider.create(
+                    foldStateProvider,
+                    progressHandler
+                )
             } else {
                 fixedTimingTransitionProgressProvider.get()
             }
@@ -101,26 +157,105 @@
     }
 
     @Provides
+    @Singleton
+    fun provideProgressForwarder(
+        config: UnfoldTransitionConfig,
+        progressForwarder: Provider<UnfoldTransitionProgressForwarder>
+    ): Optional<UnfoldTransitionProgressForwarder> {
+        if (!config.isEnabled) {
+            return Optional.empty()
+        }
+        return Optional.of(progressForwarder.get())
+    }
+}
+
+/**
+ * Provides [FoldStateProvider]. The [UnfoldBg] annotated binding sends progress in the [UnfoldBg]
+ * handler.
+ */
+@Module
+internal class FoldStateProviderModule {
+    @Provides
+    @Singleton
+    fun provideFoldStateProvider(
+        factory: DeviceFoldStateProvider.Factory,
+        @UnfoldMain hingeAngleProvider: HingeAngleProvider,
+        @UnfoldMain rotationChangeProvider: RotationChangeProvider,
+        @UnfoldMain mainHandler: Handler,
+    ): FoldStateProvider =
+        factory.create(
+            hingeAngleProvider,
+            rotationChangeProvider,
+            progressHandler = mainHandler
+        )
+
+    @Provides
+    @Singleton
+    @UnfoldBg
+    fun provideBgFoldStateProvider(
+        factory: DeviceFoldStateProvider.Factory,
+        @UnfoldBg hingeAngleProvider: HingeAngleProvider,
+        @UnfoldBg rotationChangeProvider: RotationChangeProvider,
+        @UnfoldBg bgHandler: Handler,
+    ): FoldStateProvider =
+        factory.create(
+            hingeAngleProvider,
+            rotationChangeProvider,
+            progressHandler = bgHandler
+        )
+}
+
+/** Provides bindings for both [UnfoldMain] and [UnfoldBg] [HingeAngleProvider]. */
+@Module
+internal class HingeAngleProviderInternalModule {
+    @Provides
+    @UnfoldMain
     fun hingeAngleProvider(
         config: UnfoldTransitionConfig,
-        hingeAngleSensorProvider: Provider<HingeSensorAngleProvider>
+        @UnfoldMain handler: Handler,
+        hingeAngleSensorProvider: HingeSensorAngleProvider.Factory
     ): HingeAngleProvider {
         return if (config.isHingeAngleEnabled) {
-            hingeAngleSensorProvider.get()
+            hingeAngleSensorProvider.create(handler)
         } else {
             EmptyHingeAngleProvider
         }
     }
 
     @Provides
-    @Singleton
-    fun provideProgressForwarder(
-            config: UnfoldTransitionConfig,
-            progressForwarder: Provider<UnfoldTransitionProgressForwarder>
-    ): Optional<UnfoldTransitionProgressForwarder> {
-        if (!config.isEnabled) {
-            return Optional.empty()
+    @UnfoldBg
+    fun hingeAngleProviderBg(
+        config: UnfoldTransitionConfig,
+        @UnfoldBg handler: Handler,
+        hingeAngleSensorProvider: HingeSensorAngleProvider.Factory
+    ): HingeAngleProvider {
+        return if (config.isHingeAngleEnabled) {
+            hingeAngleSensorProvider.create(handler)
+        } else {
+            EmptyHingeAngleProvider
         }
-        return Optional.of(progressForwarder.get())
+    }
+}
+
+@Module
+internal class UnfoldRotationProviderInternalModule {
+    @Provides
+    @Singleton
+    @UnfoldMain
+    fun provideRotationChangeProvider(
+        rotationChangeProviderFactory: RotationChangeProvider.Factory,
+        @UnfoldMain mainHandler: Handler,
+    ): RotationChangeProvider {
+        return rotationChangeProviderFactory.create(mainHandler)
+    }
+
+    @Provides
+    @Singleton
+    @UnfoldBg
+    fun provideBgRotationChangeProvider(
+        rotationChangeProviderFactory: RotationChangeProvider.Factory,
+        @UnfoldBg bgHandler: Handler,
+    ): RotationChangeProvider {
+        return rotationChangeProviderFactory.create(bgHandler)
     }
 }
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
index 1839919..1cbaf31 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
@@ -48,6 +48,7 @@
         singleThreadBgExecutor: Executor,
         tracingTagPrefix: String,
         displayManager: DisplayManager,
+        bgHandler: Handler,
 ): UnfoldSharedComponent =
         DaggerUnfoldSharedComponent.factory()
                 .create(
@@ -62,6 +63,7 @@
                         singleThreadBgExecutor,
                         tracingTagPrefix,
                         displayManager,
+                        bgHandler,
                 )
 
 /**
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBg.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBg.kt
new file mode 100644
index 0000000..7cd4419
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBg.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.unfold.dagger
+
+import javax.inject.Qualifier
+
+/** Annotation for background computations related to unfold lib. */
+@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class UnfoldBg
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index f8f168b..907bf46 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -20,6 +20,7 @@
 import android.animation.ObjectAnimator
 import android.animation.ValueAnimator
 import android.content.Context
+import android.os.Handler
 import android.os.Trace
 import android.util.FloatProperty
 import android.util.Log
@@ -38,13 +39,25 @@
 import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
 import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
 import com.android.systemui.unfold.updates.name
-import javax.inject.Inject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 
-/** Maps fold updates to unfold transition progress using DynamicAnimation. */
+/**
+ * Maps fold updates to unfold transition progress using DynamicAnimation.
+ *
+ * Note that all variable accesses must be done in the [Handler] provided in the constructor, that
+ * might be different than [mainHandler]. When a custom handler is provided, the [SpringAnimation]
+ * uses a scheduler different than the default one.
+ */
 class PhysicsBasedUnfoldTransitionProgressProvider
-@Inject
-constructor(context: Context, private val foldStateProvider: FoldStateProvider) :
-    UnfoldTransitionProgressProvider, FoldUpdatesListener, DynamicAnimation.OnAnimationEndListener {
+@AssistedInject
+constructor(
+    context: Context,
+    private val schedulerFactory: UnfoldFrameCallbackScheduler.Factory,
+    @Assisted private val foldStateProvider: FoldStateProvider,
+    @Assisted private val progressHandler: Handler,
+) : UnfoldTransitionProgressProvider, FoldUpdatesListener, DynamicAnimation.OnAnimationEndListener {
 
     private val emphasizedInterpolator =
         loadInterpolator(context, android.R.interpolator.fast_out_extra_slow_in)
@@ -63,6 +76,7 @@
 
     private var transitionProgress: Float = 0.0f
         set(value) {
+            assertInProgressThread()
             if (isTransitionRunning) {
                 listeners.forEach { it.onTransitionProgress(value) }
             }
@@ -72,8 +86,14 @@
     private val listeners: MutableList<TransitionProgressListener> = mutableListOf()
 
     init {
-        foldStateProvider.addCallback(this)
-        foldStateProvider.start()
+        progressHandler.post {
+            // The scheduler needs to be created in the progress handler in order to get the correct
+            // choreographer and frame callbacks. This is because the choreographer can be get only
+            // as a thread local.
+            springAnimation.scheduler = schedulerFactory.create()
+            foldStateProvider.addCallback(this)
+            foldStateProvider.start()
+        }
     }
 
     override fun destroy() {
@@ -81,6 +101,8 @@
     }
 
     override fun onHingeAngleUpdate(angle: Float) {
+        assertInProgressThread()
+
         if (!isTransitionRunning || isAnimatedCancelRunning) return
         val progress = saturate(angle / FINAL_HINGE_ANGLE_POSITION)
         springAnimation.animateToFinalPosition(progress)
@@ -90,6 +112,7 @@
         if (amount < low) low else if (amount > high) high else amount
 
     override fun onFoldUpdate(@FoldUpdate update: Int) {
+        assertInProgressThread()
         when (update) {
             FOLD_UPDATE_FINISH_FULL_OPEN,
             FOLD_UPDATE_FINISH_HALF_OPEN -> {
@@ -148,6 +171,7 @@
     }
 
     private fun cancelTransition(endValue: Float, animate: Boolean) {
+        assertInProgressThread()
         if (isTransitionRunning && animate) {
             if (endValue == 1.0f && !isAnimatedCancelRunning) {
                 listeners.forEach { it.onTransitionFinishing() }
@@ -165,7 +189,6 @@
             isAnimatedCancelRunning = false
             isTransitionRunning = false
             springAnimation.cancel()
-
             cannedAnimator?.removeAllListeners()
             cannedAnimator?.cancel()
             cannedAnimator = null
@@ -182,7 +205,7 @@
         animation: DynamicAnimation<out DynamicAnimation<*>>,
         canceled: Boolean,
         value: Float,
-        velocity: Float
+        velocity: Float,
     ) {
         if (isAnimatedCancelRunning) {
             cancelTransition(value, animate = false)
@@ -202,6 +225,7 @@
     }
 
     private fun startTransition(startValue: Float) {
+        assertInProgressThread()
         if (!isTransitionRunning) onStartTransition()
 
         springAnimation.apply {
@@ -221,14 +245,16 @@
     }
 
     override fun addCallback(listener: TransitionProgressListener) {
-        listeners.add(listener)
+        progressHandler.post { listeners.add(listener) }
     }
 
     override fun removeCallback(listener: TransitionProgressListener) {
-        listeners.remove(listener)
+        progressHandler.post { listeners.remove(listener) }
     }
 
     private fun startCannedCancelAnimation() {
+        assertInProgressThread()
+
         cannedAnimator?.cancel()
         cannedAnimator = null
 
@@ -264,7 +290,7 @@
 
         override fun setValue(
             provider: PhysicsBasedUnfoldTransitionProgressProvider,
-            value: Float
+            value: Float,
         ) {
             provider.transitionProgress = value
         }
@@ -272,6 +298,25 @@
         override fun get(provider: PhysicsBasedUnfoldTransitionProgressProvider): Float =
             provider.transitionProgress
     }
+
+    private fun assertInProgressThread() {
+        check(progressHandler.looper.isCurrentThread) {
+            val progressThread = progressHandler.looper.thread
+            val thisThread = Thread.currentThread()
+            """should be called from the progress thread.
+                progressThread=$progressThread tid=${progressThread.id}
+                Thread.currentThread()=$thisThread tid=${thisThread.id}"""
+                .trimMargin()
+        }
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(
+            foldStateProvider: FoldStateProvider,
+            handler: Handler,
+        ): PhysicsBasedUnfoldTransitionProgressProvider
+    }
 }
 
 private const val TAG = "PhysicsBasedUnfoldTransitionProgressProvider"
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldFrameCallbackScheduler.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldFrameCallbackScheduler.kt
new file mode 100644
index 0000000..1dffd84
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldFrameCallbackScheduler.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.unfold.progress
+
+import android.os.Looper
+import android.view.Choreographer
+import androidx.dynamicanimation.animation.FrameCallbackScheduler
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/**
+ * Scheduler that posts animation progresses on a thread different than the ui one.
+ *
+ * The following is taken from [AnimationHandler.FrameCallbackScheduler16]. It is extracted here as
+ * there are no guarantees which implementation the [DynamicAnimation] class would use otherwise.
+ * This allows classes using [DynamicAnimation] to be created in any thread, but still use the
+ * scheduler for a specific thread.
+ *
+ * Technically the [AssistedInject] is not needed: it's just to have a nicer factory with a
+ * documentation snippet instead of using a plain dagger provider.
+ */
+class UnfoldFrameCallbackScheduler @AssistedInject constructor() : FrameCallbackScheduler {
+
+    private val choreographer = Choreographer.getInstance()
+    private val looper =
+        Looper.myLooper() ?: error("This should be created in a thread with a looper.")
+
+    override fun postFrameCallback(frameCallback: Runnable) {
+        choreographer.postFrameCallback { frameCallback.run() }
+    }
+
+    override fun isCurrentThread(): Boolean {
+        return looper.isCurrentThread
+    }
+
+    @AssistedFactory
+    interface Factory {
+        /**
+         * Creates a [FrameCallbackScheduler] that uses [Choreographer] to post frame callbacks.
+         *
+         * Note that the choreographer used depends on the thread this [create] is called on, as it
+         * is get from a thread static attribute.
+         */
+        fun create(): UnfoldFrameCallbackScheduler
+    }
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index 003013e..77f637b 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -23,37 +23,34 @@
 import androidx.core.util.Consumer
 import com.android.systemui.unfold.compat.INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
-import com.android.systemui.unfold.dagger.UnfoldMain
-import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
-import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
-import com.android.systemui.unfold.updates.RotationChangeProvider.RotationListener
 import com.android.systemui.unfold.updates.hinge.FULLY_CLOSED_DEGREES
 import com.android.systemui.unfold.updates.hinge.FULLY_OPEN_DEGREES
 import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
 import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
 import com.android.systemui.unfold.util.CurrentActivityTypeProvider
 import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityProvider
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.concurrent.CopyOnWriteArrayList
 import java.util.concurrent.Executor
-import javax.inject.Inject
 
 class DeviceFoldStateProvider
-@Inject
+@AssistedInject
 constructor(
     config: UnfoldTransitionConfig,
-    private val hingeAngleProvider: HingeAngleProvider,
+    private val context: Context,
     private val screenStatusProvider: ScreenStatusProvider,
-    private val foldProvider: FoldProvider,
     private val activityTypeProvider: CurrentActivityTypeProvider,
     private val unfoldKeyguardVisibilityProvider: UnfoldKeyguardVisibilityProvider,
-    private val rotationChangeProvider: RotationChangeProvider,
-    private val context: Context,
-    @UnfoldMain private val mainExecutor: Executor,
-    @UnfoldMain private val handler: Handler
+    private val foldProvider: FoldProvider,
+    @Assisted private val hingeAngleProvider: HingeAngleProvider,
+    @Assisted private val rotationChangeProvider: RotationChangeProvider,
+    @Assisted private val progressHandler: Handler,
 ) : FoldStateProvider {
+    private val outputListeners = CopyOnWriteArrayList<FoldStateProvider.FoldUpdatesListener>()
 
-    private val outputListeners: MutableList<FoldUpdatesListener> = mutableListOf()
-
-    @FoldUpdate private var lastFoldUpdate: Int? = null
+    @FoldStateProvider.FoldUpdate private var lastFoldUpdate: Int? = null
 
     @FloatRange(from = 0.0, to = 180.0) private var lastHingeAngle: Float = 0f
     @FloatRange(from = 0.0, to = 180.0) private var lastHingeAngleBeforeTransition: Float = 0f
@@ -61,11 +58,9 @@
     private val hingeAngleListener = HingeAngleListener()
     private val screenListener = ScreenStatusListener()
     private val foldStateListener = FoldStateListener()
-    private val mainLooper = handler.looper
     private val timeoutRunnable = Runnable { cancelAnimation() }
-    private val rotationListener = RotationListener {
-        if (isTransitionInProgress) cancelAnimation()
-    }
+    private val rotationListener = FoldRotationListener()
+    private val progressExecutor = Executor { progressHandler.post(it) }
 
     /**
      * Time after which [FOLD_UPDATE_FINISH_HALF_OPEN] is emitted following a
@@ -80,9 +75,9 @@
     private var isStarted = false
 
     override fun start() {
-        assertMainThread()
         if (isStarted) return
-        foldProvider.registerCallback(foldStateListener, mainExecutor)
+        foldProvider.registerCallback(foldStateListener, progressExecutor)
+        // TODO(b/277879146): get callbacks in the background
         screenStatusProvider.addCallback(screenListener)
         hingeAngleProvider.addCallback(hingeAngleListener)
         rotationChangeProvider.addCallback(rotationListener)
@@ -91,7 +86,6 @@
     }
 
     override fun stop() {
-        assertMainThread()
         screenStatusProvider.removeCallback(screenListener)
         foldProvider.unregisterCallback(foldStateListener)
         hingeAngleProvider.removeCallback(hingeAngleListener)
@@ -101,11 +95,11 @@
         isStarted = false
     }
 
-    override fun addCallback(listener: FoldUpdatesListener) {
+    override fun addCallback(listener: FoldStateProvider.FoldUpdatesListener) {
         outputListeners.add(listener)
     }
 
-    override fun removeCallback(listener: FoldUpdatesListener) {
+    override fun removeCallback(listener: FoldStateProvider.FoldUpdatesListener) {
         outputListeners.remove(listener)
     }
 
@@ -121,6 +115,7 @@
                 lastFoldUpdate == FOLD_UPDATE_START_CLOSING
 
     private fun onHingeAngle(angle: Float) {
+        assertInProgressThread()
         if (DEBUG) {
             Log.d(
                 TAG,
@@ -131,14 +126,14 @@
         }
 
         val currentDirection =
-                if (angle < lastHingeAngle) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING
+            if (angle < lastHingeAngle) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING
         if (isTransitionInProgress && currentDirection != lastFoldUpdate) {
             lastHingeAngleBeforeTransition = lastHingeAngle
         }
 
         val isClosing = angle < lastHingeAngleBeforeTransition
         val transitionUpdate =
-                if (isClosing) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING
+            if (isClosing) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING
         val angleChangeSurpassedThreshold =
             Math.abs(angle - lastHingeAngleBeforeTransition) > HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES
         val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES
@@ -150,12 +145,12 @@
             angleChangeSurpassedThreshold && // Do not react immediately to small changes in angle
                 eventNotAlreadyDispatched && // we haven't sent transition event already
                 !isFullyOpened && // do not send transition event if we are in fully opened hinge
-                                  // angle range as closing threshold could overlap this range
+                // angle range as closing threshold could overlap this range
                 screenAvailableEventSent && // do not send transition event if we are still in the
-                                            // process of turning on the inner display
+                // process of turning on the inner display
                 isClosingThresholdMet(angle) && // hinge angle is below certain threshold.
                 isOnLargeScreen // Avoids sending closing event when on small screen.
-                                // Start event is sent regardless due to hall sensor.
+        // Start event is sent regardless due to hall sensor.
         ) {
             notifyFoldUpdate(transitionUpdate, lastHingeAngle)
         }
@@ -202,6 +197,7 @@
 
     private inner class FoldStateListener : FoldProvider.FoldCallback {
         override fun onFoldUpdated(isFolded: Boolean) {
+            assertInProgressThread()
             this@DeviceFoldStateProvider.isFolded = isFolded
             lastHingeAngle = FULLY_CLOSED_DEGREES
 
@@ -218,7 +214,14 @@
         }
     }
 
-    private fun notifyFoldUpdate(@FoldUpdate update: Int, angle: Float) {
+    private inner class FoldRotationListener : RotationChangeProvider.RotationListener {
+        override fun onRotationChanged(newRotation: Int) {
+            assertInProgressThread()
+            if (isTransitionInProgress) cancelAnimation()
+        }
+    }
+
+    private fun notifyFoldUpdate(@FoldStateProvider.FoldUpdate update: Int, angle: Float) {
         if (DEBUG) {
             Log.d(TAG, update.name())
         }
@@ -236,11 +239,11 @@
         if (isTransitionInProgress) {
             cancelTimeout()
         }
-        handler.postDelayed(timeoutRunnable, halfOpenedTimeoutMillis.toLong())
+        progressHandler.postDelayed(timeoutRunnable, halfOpenedTimeoutMillis.toLong())
     }
 
     private fun cancelTimeout() {
-        handler.removeCallbacks(timeoutRunnable)
+        progressHandler.removeCallbacks(timeoutRunnable)
     }
 
     private fun cancelAnimation(): Unit =
@@ -249,42 +252,61 @@
     private inner class ScreenStatusListener : ScreenStatusProvider.ScreenListener {
 
         override fun onScreenTurnedOn() {
-            // Trigger this event only if we are unfolded and this is the first screen
-            // turned on event since unfold started. This prevents running the animation when
-            // turning on the internal display using the power button.
-            // Initially isUnfoldHandled is true so it will be reset to false *only* when we
-            // receive 'folded' event. If SystemUI started when device is already folded it will
-            // still receive 'folded' event on startup.
-            if (!isFolded && !isUnfoldHandled) {
-                outputListeners.forEach { it.onUnfoldedScreenAvailable() }
-                isUnfoldHandled = true
+            executeInProgressThread {
+                // Trigger this event only if we are unfolded and this is the first screen
+                // turned on event since unfold started. This prevents running the animation when
+                // turning on the internal display using the power button.
+                // Initially isUnfoldHandled is true so it will be reset to false *only* when we
+                // receive 'folded' event. If SystemUI started when device is already folded it will
+                // still receive 'folded' event on startup.
+                if (!isFolded && !isUnfoldHandled) {
+                    outputListeners.forEach { it.onUnfoldedScreenAvailable() }
+                    isUnfoldHandled = true
+                }
             }
         }
 
         override fun markScreenAsTurnedOn() {
-            if (!isFolded) {
-                isUnfoldHandled = true
+            executeInProgressThread {
+                if (!isFolded) {
+                    isUnfoldHandled = true
+                }
             }
         }
 
         override fun onScreenTurningOn() {
-            isScreenOn = true
-            updateHingeAngleProviderState()
+            executeInProgressThread {
+                isScreenOn = true
+                updateHingeAngleProviderState()
+            }
         }
 
         override fun onScreenTurningOff() {
-            isScreenOn = false
-            updateHingeAngleProviderState()
+            executeInProgressThread {
+                isScreenOn = false
+                updateHingeAngleProviderState()
+            }
+        }
+
+        /**
+         * Needed just for compatibility while not all data sources are providing data in the
+         * background.
+         *
+         * TODO(b/277879146): Remove once ScreeStatusProvider provides in the background.
+         */
+        private fun executeInProgressThread(f: () -> Unit) {
+            progressHandler.post { f() }
         }
     }
 
     private fun isOnLargeScreen(): Boolean {
-      return context.resources.configuration.smallestScreenWidthDp >
-          INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP
+        return context.resources.configuration.smallestScreenWidthDp >
+            INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP
     }
 
     /** While the screen is off or the device is folded, hinge angle updates are not needed. */
     private fun updateHingeAngleProviderState() {
+        assertInProgressThread()
         if (isScreenOn && !isFolded) {
             hingeAngleProvider.start()
         } else {
@@ -294,20 +316,34 @@
 
     private inner class HingeAngleListener : Consumer<Float> {
         override fun accept(angle: Float) {
+            assertInProgressThread()
             onHingeAngle(angle)
         }
     }
 
-    private fun assertMainThread() {
-        check(mainLooper.isCurrentThread) {
-            ("should be called from the main thread." +
-                    " sMainLooper.threadName=" + mainLooper.thread.name +
-                    " Thread.currentThread()=" + Thread.currentThread().name)
+    private fun assertInProgressThread() {
+        check(progressHandler.looper.isCurrentThread) {
+            val progressThread = progressHandler.looper.thread
+            val thisThread = Thread.currentThread()
+            """should be called from the progress thread.
+                progressThread=$progressThread tid=${progressThread.id}
+                Thread.currentThread()=$thisThread tid=${thisThread.id}"""
+                .trimMargin()
         }
     }
+
+    @AssistedFactory
+    interface Factory {
+        /** Creates a [DeviceFoldStateProvider] using the provided dependencies. */
+        fun create(
+            hingeAngleProvider: HingeAngleProvider,
+            rotationChangeProvider: RotationChangeProvider,
+            progressHandler: Handler,
+        ): DeviceFoldStateProvider
+    }
 }
 
-fun @receiver:FoldUpdate Int.name() =
+fun @receiver:FoldStateProvider.FoldUpdate Int.name() =
     when (this) {
         FOLD_UPDATE_START_OPENING -> "START_OPENING"
         FOLD_UPDATE_START_CLOSING -> "START_CLOSING"
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt
index ce8f1a1..82ea362 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt
@@ -20,20 +20,21 @@
 import android.hardware.display.DisplayManager
 import android.os.Handler
 import android.os.RemoteException
-import com.android.systemui.unfold.dagger.UnfoldMain
 import com.android.systemui.unfold.util.CallbackController
-import javax.inject.Inject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 
 /**
- * Allows to subscribe to rotation changes. Updates are provided for the display associated
- * to [context].
+ * Allows to subscribe to rotation changes. Updates are provided for the display associated to
+ * [context].
  */
 class RotationChangeProvider
-@Inject
+@AssistedInject
 constructor(
     private val displayManager: DisplayManager,
     private val context: Context,
-    @UnfoldMain private val mainHandler: Handler,
+    @Assisted private val handler: Handler,
 ) : CallbackController<RotationChangeProvider.RotationListener> {
 
     private val listeners = mutableListOf<RotationListener>()
@@ -42,7 +43,7 @@
     private var lastRotation: Int? = null
 
     override fun addCallback(listener: RotationListener) {
-        mainHandler.post {
+        handler.post {
             if (listeners.isEmpty()) {
                 subscribeToRotation()
             }
@@ -51,7 +52,7 @@
     }
 
     override fun removeCallback(listener: RotationListener) {
-        mainHandler.post {
+        handler.post {
             listeners -= listener
             if (listeners.isEmpty()) {
                 unsubscribeToRotation()
@@ -62,7 +63,7 @@
 
     private fun subscribeToRotation() {
         try {
-            displayManager.registerDisplayListener(displayListener, mainHandler)
+            displayManager.registerDisplayListener(displayListener, handler)
         } catch (e: RemoteException) {
             throw e.rethrowFromSystemServer()
         }
@@ -100,4 +101,10 @@
 
         override fun onDisplayRemoved(displayId: Int) {}
     }
+
+    @AssistedFactory
+    interface Factory {
+        /** Creates a new [RotationChangeProvider] that provides updated using [handler]. */
+        fun create(handler: Handler): RotationChangeProvider
+    }
 }
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
index 89fb12e..14c4cc0 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
@@ -18,21 +18,26 @@
 import android.hardware.SensorEvent
 import android.hardware.SensorEventListener
 import android.hardware.SensorManager
+import android.os.Handler
 import android.os.Trace
 import androidx.core.util.Consumer
 import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.concurrent.CopyOnWriteArrayList
 import java.util.concurrent.Executor
-import javax.inject.Inject
 
 internal class HingeSensorAngleProvider
-@Inject
+@AssistedInject
 constructor(
     private val sensorManager: SensorManager,
-    @UnfoldSingleThreadBg private val singleThreadBgExecutor: Executor
+    @UnfoldSingleThreadBg private val singleThreadBgExecutor: Executor,
+    @Assisted private val listenerHandler: Handler,
 ) : HingeAngleProvider {
 
     private val sensorListener = HingeAngleSensorListener()
-    private val listeners: MutableList<Consumer<Float>> = arrayListOf()
+    private val listeners: MutableList<Consumer<Float>> = CopyOnWriteArrayList()
     var started = false
 
     override fun start() {
@@ -43,7 +48,8 @@
             sensorManager.registerListener(
                 sensorListener,
                 sensor,
-                SensorManager.SENSOR_DELAY_FASTEST
+                SensorManager.SENSOR_DELAY_FASTEST,
+                listenerHandler
             )
             Trace.endSection()
 
@@ -75,4 +81,10 @@
             listeners.forEach { it.accept(event.values[0]) }
         }
     }
+
+    @AssistedFactory
+    interface Factory {
+        /** Creates an [HingeSensorAngleProvider] that sends updates using [handler]. */
+        fun create(handler: Handler): HingeSensorAngleProvider
+    }
 }
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt
index d8bc018..a31896a 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt
@@ -16,7 +16,9 @@
 
 import android.os.Trace
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
-import javax.inject.Inject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import javax.inject.Qualifier
 
 /**
@@ -26,11 +28,11 @@
  * for each fold/unfold: in (1) systemui and (2) launcher process.
  */
 class ATraceLoggerTransitionProgressListener
-@Inject
-internal constructor(@UnfoldTransitionATracePrefix tracePrefix: String) :
+@AssistedInject
+internal constructor(@UnfoldTransitionATracePrefix tracePrefix: String, @Assisted details: String) :
     TransitionProgressListener {
 
-    private val traceName = "$tracePrefix#$UNFOLD_TRANSITION_TRACE_NAME"
+    private val traceName = "$tracePrefix$details#$UNFOLD_TRANSITION_TRACE_NAME"
 
     override fun onTransitionStarted() {
         Trace.beginAsyncSection(traceName, /* cookie= */ 0)
@@ -43,6 +45,12 @@
     override fun onTransitionProgress(progress: Float) {
         Trace.setCounter(traceName, (progress * 100).toLong())
     }
+
+    @AssistedFactory
+    interface Factory {
+        /** Creates an [ATraceLoggerTransitionProgressListener] with [details] in the track name. */
+        fun create(details: String): ATraceLoggerTransitionProgressListener
+    }
 }
 
 private const val UNFOLD_TRANSITION_TRACE_NAME = "FoldUnfoldTransitionInProgress"