Extract unfold classes to a separate library

No behaviour changes in this CL.
Extracted classes related to the fold/unfold
animation (that plays on foldable devices) to
a separate library that does not depend on
system classes. This is necessary to ease
reusing these classes in live wallpapers.

Bug: 231936088
Test: fold/unfold device
Change-Id: Ic1e1ec742867fca9c1209b930accd5738de18e39
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 3cf5bc1..9f790c6 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -47,6 +47,7 @@
     ],
     static_libs: [
         "PluginCoreLib",
+        "SystemUIUnfoldLib",
         "androidx.dynamicanimation_dynamicanimation",
         "androidx.concurrent_concurrent-futures",
         "dagger2",
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt
deleted file mode 100644
index d1b0639..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2021 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.config
-
-import android.content.Context
-import android.os.SystemProperties
-
-internal class ResourceUnfoldTransitionConfig(private val context: Context) :
-    UnfoldTransitionConfig {
-
-    override val isEnabled: Boolean
-        get() = readIsEnabledResource() && isPropertyEnabled
-
-    override val isHingeAngleEnabled: Boolean
-        get() = readIsHingeAngleEnabled()
-
-    private val isPropertyEnabled: Boolean
-        get() =
-            SystemProperties.getInt(
-                UNFOLD_TRANSITION_MODE_PROPERTY_NAME, UNFOLD_TRANSITION_PROPERTY_ENABLED) ==
-                UNFOLD_TRANSITION_PROPERTY_ENABLED
-
-    private fun readIsEnabledResource(): Boolean =
-        context.resources.getBoolean(com.android.internal.R.bool.config_unfoldTransitionEnabled)
-
-    private fun readIsHingeAngleEnabled(): Boolean =
-        context.resources.getBoolean(com.android.internal.R.bool.config_unfoldTransitionHingeAngle)
-}
-
-/**
- * Temporary persistent property to control unfold transition mode.
- *
- * See [com.android.unfold.config.AnimationMode].
- */
-private const val UNFOLD_TRANSITION_MODE_PROPERTY_NAME = "persist.unfold.transition_enabled"
-private const val UNFOLD_TRANSITION_PROPERTY_ENABLED = 1
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
new file mode 100644
index 0000000..7f2933e
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 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.system
+
+import android.app.ActivityManager
+import android.app.WindowConfiguration
+import com.android.systemui.unfold.util.CurrentActivityTypeProvider
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class ActivityManagerActivityTypeProvider @Inject constructor(
+    private val activityManager: ActivityManager
+) : CurrentActivityTypeProvider {
+
+    override val isHomeActivity: Boolean?
+        get() {
+            val activityType = activityManager.getRunningTasks(/* maxNum= */ 1)
+                    ?.getOrNull(0)?.topActivityType ?: return null
+
+            return activityType == WindowConfiguration.ACTIVITY_TYPE_HOME
+        }
+}
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
new file mode 100644
index 0000000..3b8d318
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateManagerFoldProvider.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 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.system
+
+import android.content.Context
+import android.hardware.devicestate.DeviceStateManager
+import com.android.systemui.unfold.updates.FoldProvider
+import com.android.systemui.unfold.updates.FoldProvider.FoldCallback
+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 {
+
+    private val callbacks: MutableMap<FoldCallback,
+            DeviceStateManager.DeviceStateCallback> = hashMapOf()
+
+    override fun registerCallback(callback: FoldCallback, executor: Executor) {
+        val listener = FoldStateListener(context, callback)
+        deviceStateManager.registerCallback(executor, listener)
+        callbacks[callback] = listener
+    }
+
+    override fun unregisterCallback(callback: FoldCallback) {
+        val listener = callbacks.remove(callback)
+        listener?.let {
+            deviceStateManager.unregisterCallback(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
new file mode 100644
index 0000000..24ae42a
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 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.system
+
+import android.os.Handler
+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.UnfoldBackground
+import com.android.systemui.unfold.dagger.UnfoldMain
+import com.android.systemui.unfold.updates.FoldProvider
+import com.android.systemui.unfold.util.CurrentActivityTypeProvider
+import dagger.Binds
+import dagger.Module
+import java.util.concurrent.Executor
+
+/**
+ * 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 {
+
+    @Binds
+    abstract fun activityTypeProvider(executor: ActivityManagerActivityTypeProvider):
+            CurrentActivityTypeProvider
+
+    @Binds
+    abstract fun config(config: ResourceUnfoldTransitionConfig): UnfoldTransitionConfig
+
+    @Binds
+    abstract fun foldState(provider: DeviceStateManagerFoldProvider): FoldProvider
+
+    @Binds
+    @UnfoldMain
+    abstract fun mainExecutor(@Main executor: Executor): Executor
+
+    @Binds
+    @UnfoldMain
+    abstract fun mainHandler(@Main handler: Handler): Handler
+
+    @Binds
+    @UnfoldBackground
+    abstract fun backgroundExecutor(@UiBackground executor: Executor): Executor
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt
deleted file mode 100644
index b351585..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.android.systemui.unfold.updates.hinge
-
-import androidx.core.util.Consumer
-
-internal object EmptyHingeAngleProvider : HingeAngleProvider {
-    override fun start() {}
-
-    override fun stop() {}
-
-    override fun removeCallback(listener: Consumer<Float>) {}
-
-    override fun addCallback(listener: Consumer<Float>) {}
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt
deleted file mode 100644
index 48a5b12..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.android.systemui.unfold.updates.hinge
-
-import androidx.core.util.Consumer
-import com.android.systemui.statusbar.policy.CallbackController
-
-/**
- * Emits device hinge angle values (angle between two integral parts of the device).
- *
- * The hinge angle could be from 0 to 360 degrees inclusive. For foldable devices usually 0
- * corresponds to fully closed (folded) state and 180 degrees corresponds to fully open (flat)
- * state.
- */
-interface HingeAngleProvider : CallbackController<Consumer<Float>> {
-    fun start()
-    fun stop()
-}
-
-const val FULLY_OPEN_DEGREES = 180f
-const val FULLY_CLOSED_DEGREES = 0f
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
index 53c528f..ec938b2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
@@ -1,3 +1,17 @@
+/*
+ * Copyright (C) 2022 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.util
 
 import android.content.Context
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
index d2d2361..eea6ac0e 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -20,6 +20,7 @@
 import android.view.IWindowManager
 import com.android.systemui.keyguard.LifecycleScreenStatusProvider
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.system.SystemUnfoldSharedModule
 import com.android.systemui.unfold.updates.FoldStateProvider
 import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
 import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
@@ -34,7 +35,7 @@
 import javax.inject.Named
 import javax.inject.Singleton
 
-@Module(includes = [UnfoldSharedModule::class])
+@Module(includes = [UnfoldSharedModule::class, SystemUnfoldSharedModule::class])
 class UnfoldTransitionModule {
 
     @Provides @UnfoldTransitionATracePrefix fun tracingTagPrefix() = "systemui"
@@ -62,11 +63,6 @@
 
     @Provides
     @Singleton
-    fun provideUnfoldTransitionConfig(context: Context): UnfoldTransitionConfig =
-        createConfig(context)
-
-    @Provides
-    @Singleton
     fun provideNaturalRotationProgressProvider(
         context: Context,
         windowManager: IWindowManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
index 3231415..a4a89a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
@@ -1,3 +1,17 @@
+/*
+ * Copyright (C) 2022 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.shared.animation
 
 import android.testing.AndroidTestingRunner
@@ -7,31 +21,24 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.TestUnfoldTransitionProvider
 import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 class UnfoldConstantTranslateAnimatorTest : SysuiTestCase() {
 
-    @Mock private lateinit var progressProvider: UnfoldTransitionProgressProvider
+    private val progressProvider = TestUnfoldTransitionProvider()
 
     @Mock private lateinit var parent: ViewGroup
 
-    @Captor private lateinit var progressListenerCaptor: ArgumentCaptor<TransitionProgressListener>
-
     private lateinit var animator: UnfoldConstantTranslateAnimator
-    private lateinit var progressListener: TransitionProgressListener
 
     private val viewsIdToRegister =
         setOf(
@@ -46,17 +53,14 @@
             UnfoldConstantTranslateAnimator(viewsIdToRegister, progressProvider)
 
         animator.init(parent, MAX_TRANSLATION)
-
-        verify(progressProvider).addCallback(progressListenerCaptor.capture())
-        progressListener = progressListenerCaptor.value
     }
 
     @Test
     fun onTransition_noMatchingIds() {
         // GIVEN no views matching any ids
         // WHEN the transition starts
-        progressListener.onTransitionStarted()
-        progressListener.onTransitionProgress(.1f)
+        progressProvider.onTransitionStarted()
+        progressProvider.onTransitionProgress(.1f)
 
         // THEN nothing... no exceptions
     }
@@ -86,22 +90,22 @@
         // Compare values as ints because -0f != 0f
 
         // WHEN the transition starts
-        progressListener.onTransitionStarted()
-        progressListener.onTransitionProgress(0f)
+        progressProvider.onTransitionStarted()
+        progressProvider.onTransitionProgress(0f)
 
         list.forEach { (view, direction) ->
             assertEquals((-MAX_TRANSLATION * direction).toInt(), view.translationX.toInt())
         }
 
         // WHEN the transition progresses, translation is updated
-        progressListener.onTransitionProgress(.5f)
+        progressProvider.onTransitionProgress(.5f)
         list.forEach { (view, direction) ->
             assertEquals((-MAX_TRANSLATION / 2f * direction).toInt(), view.translationX.toInt())
         }
 
         // WHEN the transition ends, translation is completed
-        progressListener.onTransitionProgress(1f)
-        progressListener.onTransitionFinished()
+        progressProvider.onTransitionProgress(1f)
+        progressProvider.onTransitionFinished()
         list.forEach { (view, _) -> assertEquals(0, view.translationX.toInt()) }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 9ab88dc..ba29e95 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -136,6 +136,7 @@
     private class UnfoldConfig : UnfoldTransitionConfig {
         override var isEnabled: Boolean = false
         override var isHingeAngleEnabled: Boolean = false
+        override val halfFoldedTimeoutMillis: Int = 0
     }
 
     private class TestTouchEventHandler : PhoneStatusBarView.TouchEventHandler {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt
index 8076b4e..39e4e64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt
@@ -25,28 +25,20 @@
 import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN
 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.updates.FoldStateProvider
 import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
+import com.android.systemui.unfold.util.TestFoldStateProvider
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
-import org.mockito.Mock
-import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class FoldStateLoggingProviderTest : SysuiTestCase() {
 
-    @Captor
-    private lateinit var foldUpdatesListener: ArgumentCaptor<FoldStateProvider.FoldUpdatesListener>
-
-    @Mock private lateinit var foldStateProvider: FoldStateProvider
-
+    private val testFoldStateProvider = TestFoldStateProvider()
     private val fakeClock = FakeSystemClock()
 
     private lateinit var foldStateLoggingProvider: FoldStateLoggingProvider
@@ -65,12 +57,10 @@
         MockitoAnnotations.initMocks(this)
 
         foldStateLoggingProvider =
-            FoldStateLoggingProviderImpl(foldStateProvider, fakeClock).apply {
+            FoldStateLoggingProviderImpl(testFoldStateProvider, fakeClock).apply {
                 addCallback(foldStateLoggingListener)
                 init()
             }
-
-        verify(foldStateProvider).addCallback(foldUpdatesListener.capture())
     }
 
     @Test
@@ -183,10 +173,10 @@
     fun uninit_removesCallback() {
         foldStateLoggingProvider.uninit()
 
-        verify(foldStateProvider).removeCallback(foldUpdatesListener.value)
+        assertThat(testFoldStateProvider.hasListeners).isFalse()
     }
 
     private fun sendFoldUpdate(@FoldUpdate foldUpdate: Int) {
-        foldUpdatesListener.value.onFoldUpdate(foldUpdate)
+        testFoldStateProvider.sendFoldUpdate(foldUpdate)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt
new file mode 100644
index 0000000..ab450e2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 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.config
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+
+/**
+ * A test that checks that we load correct resources in
+ * ResourceUnfoldTransitionConfig as we use strings there instead of R constants.
+ * Internal Android resource constants are not available in public APIs,
+ * so we can't use them there directly.
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ResourceUnfoldTransitionConfigTest : SysuiTestCase() {
+
+    private val config = ResourceUnfoldTransitionConfig()
+
+    @Test
+    fun testIsEnabled() {
+        assertThat(config.isEnabled).isEqualTo(mContext.resources
+            .getBoolean(com.android.internal.R.bool.config_unfoldTransitionEnabled))
+    }
+
+    @Test
+    fun testHingeAngleEnabled() {
+        assertThat(config.isHingeAngleEnabled).isEqualTo(mContext.resources
+            .getBoolean(com.android.internal.R.bool.config_unfoldTransitionHingeAngle))
+    }
+
+    @Test
+    fun testHalfFoldedTimeout() {
+        assertThat(config.halfFoldedTimeoutMillis).isEqualTo(mContext.resources
+            .getInteger(com.android.internal.R.integer.config_unfoldTransitionHalfFoldedTimeout))
+    }
+}
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 1f1f88b..87fca1f 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
@@ -16,82 +16,69 @@
 
 package com.android.systemui.unfold.updates
 
-import android.app.ActivityManager
-import android.app.ActivityManager.RunningTaskInfo
-import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
-import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
-import android.app.WindowConfiguration.ActivityType
-import android.hardware.devicestate.DeviceStateManager
-import android.hardware.devicestate.DeviceStateManager.FoldStateListener
 import android.os.Handler
 import android.testing.AndroidTestingRunner
 import androidx.core.util.Consumer
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig
+import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.system.ActivityManagerActivityTypeProvider
+import com.android.systemui.unfold.updates.FoldProvider.FoldCallback
 import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
 import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
 import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
-import com.android.systemui.unfold.util.FoldableDeviceStates
-import com.android.systemui.unfold.util.FoldableTestUtils
 import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import java.util.concurrent.Executor
+import org.mockito.Mockito.`when` as whenever
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class DeviceFoldStateProviderTest : SysuiTestCase() {
 
-    @Mock private lateinit var hingeAngleProvider: HingeAngleProvider
+    @Mock
+    private lateinit var activityTypeProvider: ActivityManagerActivityTypeProvider
 
-    @Mock private lateinit var screenStatusProvider: ScreenStatusProvider
+    @Mock
+    private lateinit var handler: Handler
 
-    @Mock private lateinit var deviceStateManager: DeviceStateManager
-
-    @Mock private lateinit var activityManager: ActivityManager
-
-    @Mock private lateinit var handler: Handler
-
-    @Captor private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
-
-    @Captor private lateinit var screenOnListenerCaptor: ArgumentCaptor<ScreenListener>
-
-    @Captor private lateinit var hingeAngleCaptor: ArgumentCaptor<Consumer<Float>>
+    private val foldProvider = TestFoldProvider()
+    private val screenOnStatusProvider = TestScreenOnStatusProvider()
+    private val testHingeAngleProvider = TestHingeAngleProvider()
 
     private lateinit var foldStateProvider: DeviceFoldStateProvider
 
     private val foldUpdates: MutableList<Int> = arrayListOf()
     private val hingeAngleUpdates: MutableList<Float> = arrayListOf()
 
-    private lateinit var deviceStates: FoldableDeviceStates
-
     private var scheduledRunnable: Runnable? = null
     private var scheduledRunnableDelay: Long? = null
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        overrideResource(
-            com.android.internal.R.integer.config_unfoldTransitionHalfFoldedTimeout,
-            HALF_OPENED_TIMEOUT_MILLIS.toInt())
-        deviceStates = FoldableTestUtils.findDeviceStates(context)
+
+        val config = object : UnfoldTransitionConfig by ResourceUnfoldTransitionConfig() {
+            override val halfFoldedTimeoutMillis: Int
+                get() = HALF_OPENED_TIMEOUT_MILLIS.toInt()
+        }
 
         foldStateProvider =
             DeviceFoldStateProvider(
-                context,
-                hingeAngleProvider,
-                screenStatusProvider,
-                deviceStateManager,
-                activityManager,
+                config,
+                testHingeAngleProvider,
+                screenOnStatusProvider,
+                foldProvider,
+                activityTypeProvider,
                 context.mainExecutor,
-                handler)
+                handler
+            )
 
         foldStateProvider.addCallback(
             object : FoldStateProvider.FoldUpdatesListener {
@@ -105,10 +92,6 @@
             })
         foldStateProvider.start()
 
-        verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture())
-        verify(screenStatusProvider).addCallback(screenOnListenerCaptor.capture())
-        verify(hingeAngleProvider).addCallback(hingeAngleCaptor.capture())
-
         whenever(handler.postDelayed(any<Runnable>(), any())).then { invocationOnMock ->
             scheduledRunnable = invocationOnMock.getArgument<Runnable>(0)
             scheduledRunnableDelay = invocationOnMock.getArgument<Long>(1)
@@ -125,7 +108,7 @@
         }
 
         // By default, we're on launcher.
-        setupForegroundActivityType(ACTIVITY_TYPE_HOME)
+        setupForegroundActivityType(isHomeActivity = true)
     }
 
     @Test
@@ -146,14 +129,14 @@
     fun testOnFolded_stopsHingeAngleProvider() {
         setFoldState(folded = true)
 
-        verify(hingeAngleProvider).stop()
+        assertThat(testHingeAngleProvider.isStarted).isFalse()
     }
 
     @Test
     fun testOnUnfolded_startsHingeAngleProvider() {
         setFoldState(folded = false)
 
-        verify(hingeAngleProvider).start()
+        assertThat(testHingeAngleProvider.isStarted).isTrue()
     }
 
     @Test
@@ -310,7 +293,7 @@
 
     @Test
     fun startClosingEvent_whileNotOnLauncher_doesNotTriggerBeforeThreshold() {
-        setupForegroundActivityType(ACTIVITY_TYPE_STANDARD)
+        setupForegroundActivityType(isHomeActivity = false)
         sendHingeAngleEvent(180)
 
         sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
@@ -319,8 +302,28 @@
     }
 
     @Test
+    fun startClosingEvent_whileActivityTypeNotAvailable_triggerBeforeThreshold() {
+        setupForegroundActivityType(isHomeActivity = null)
+        sendHingeAngleEvent(180)
+
+        sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
+
+        assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
+    }
+
+    @Test
+    fun startClosingEvent_whileOnLauncher_doesTriggerBeforeThreshold() {
+        setupForegroundActivityType(isHomeActivity = true)
+        sendHingeAngleEvent(180)
+
+        sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
+
+        assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
+    }
+
+    @Test
     fun startClosingEvent_whileNotOnLauncher_triggersAfterThreshold() {
-        setupForegroundActivityType(ACTIVITY_TYPE_STANDARD)
+        setupForegroundActivityType(isHomeActivity = false)
         sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES)
 
         sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES - 1)
@@ -328,9 +331,8 @@
         assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
     }
 
-    private fun setupForegroundActivityType(@ActivityType type: Int) {
-        val taskInfo = RunningTaskInfo().apply { topActivityType = type }
-        whenever(activityManager.getRunningTasks(1)).thenReturn(listOf(taskInfo))
+    private fun setupForegroundActivityType(isHomeActivity: Boolean?) {
+        whenever(activityTypeProvider.isHomeActivity).thenReturn(isHomeActivity)
     }
 
     private fun simulateTimeout(waitTime: Long = HALF_OPENED_TIMEOUT_MILLIS) {
@@ -348,16 +350,72 @@
     }
 
     private fun setFoldState(folded: Boolean) {
-        val state = if (folded) deviceStates.folded else deviceStates.unfolded
-        foldStateListenerCaptor.value.onStateChanged(state)
+        foldProvider.notifyFolded(folded)
     }
 
     private fun fireScreenOnEvent() {
-        screenOnListenerCaptor.value.onScreenTurnedOn()
+        screenOnStatusProvider.notifyScreenTurnedOn()
     }
 
     private fun sendHingeAngleEvent(angle: Int) {
-        hingeAngleCaptor.value.accept(angle.toFloat())
+        testHingeAngleProvider.notifyAngle(angle.toFloat())
+    }
+
+    private class TestFoldProvider : FoldProvider {
+        private val callbacks = arrayListOf<FoldCallback>()
+
+        override fun registerCallback(callback: FoldCallback, executor: Executor) {
+            callbacks += callback
+        }
+
+        override fun unregisterCallback(callback: FoldCallback) {
+            callbacks -= callback
+        }
+
+        fun notifyFolded(isFolded: Boolean) {
+            callbacks.forEach { it.onFoldUpdated(isFolded) }
+        }
+    }
+
+    private class TestScreenOnStatusProvider : ScreenStatusProvider {
+        private val callbacks = arrayListOf<ScreenListener>()
+
+        override fun addCallback(listener: ScreenListener) {
+            callbacks += listener
+        }
+
+        override fun removeCallback(listener: ScreenListener) {
+            callbacks -= listener
+        }
+
+        fun notifyScreenTurnedOn() {
+            callbacks.forEach { it.onScreenTurnedOn() }
+        }
+    }
+
+    private class TestHingeAngleProvider : HingeAngleProvider {
+        private val callbacks = arrayListOf<Consumer<Float>>()
+        var isStarted: Boolean = false
+
+        override fun start() {
+            isStarted = true;
+        }
+
+        override fun stop() {
+            isStarted = false;
+        }
+
+        override fun addCallback(listener: Consumer<Float>) {
+            callbacks += listener
+        }
+
+        override fun removeCallback(listener: Consumer<Float>) {
+            callbacks -= listener
+        }
+
+        fun notifyAngle(angle: Float) {
+            callbacks.forEach { it.accept(angle) }
+        }
     }
 
     companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
index a3f17aa..b2cedbf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
@@ -20,18 +20,18 @@
 import android.view.IWindowManager
 import android.view.Surface
 import androidx.test.filters.SmallTest
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.unfold.TestUnfoldTransitionProvider
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 import com.android.systemui.util.mockito.any
-import com.android.systemui.SysuiTestCase
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
-import org.mockito.Mockito.verify
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
@@ -41,16 +41,13 @@
     @Mock
     lateinit var windowManager: IWindowManager
 
-    @Mock
-    lateinit var sourceProvider: UnfoldTransitionProgressProvider
+    private val sourceProvider = TestUnfoldTransitionProvider()
 
     @Mock
     lateinit var transitionListener: TransitionProgressListener
 
     lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
 
-    private val sourceProviderListenerCaptor =
-        ArgumentCaptor.forClass(TransitionProgressListener::class.java)
     private val rotationWatcherCaptor =
         ArgumentCaptor.forClass(IRotationWatcher.Stub::class.java)
 
@@ -66,7 +63,6 @@
 
         progressProvider.init()
 
-        verify(sourceProvider).addCallback(sourceProviderListenerCaptor.capture())
         verify(windowManager).watchRotation(rotationWatcherCaptor.capture(), any())
 
         progressProvider.addCallback(transitionListener)
@@ -76,7 +72,7 @@
     fun testNaturalRotation0_sendTransitionStartedEvent_eventReceived() {
         onRotationChanged(Surface.ROTATION_0)
 
-        source.onTransitionStarted()
+        sourceProvider.onTransitionStarted()
 
         verify(transitionListener).onTransitionStarted()
     }
@@ -85,7 +81,7 @@
     fun testNaturalRotation0_sendTransitionProgressEvent_eventReceived() {
         onRotationChanged(Surface.ROTATION_0)
 
-        source.onTransitionProgress(0.5f)
+        sourceProvider.onTransitionProgress(0.5f)
 
         verify(transitionListener).onTransitionProgress(0.5f)
     }
@@ -94,7 +90,7 @@
     fun testNotNaturalRotation90_sendTransitionStartedEvent_eventNotReceived() {
         onRotationChanged(Surface.ROTATION_90)
 
-        source.onTransitionStarted()
+        sourceProvider.onTransitionStarted()
 
         verify(transitionListener, never()).onTransitionStarted()
     }
@@ -103,7 +99,7 @@
     fun testNaturalRotation90_sendTransitionProgressEvent_eventNotReceived() {
         onRotationChanged(Surface.ROTATION_90)
 
-        source.onTransitionProgress(0.5f)
+        sourceProvider.onTransitionProgress(0.5f)
 
         verify(transitionListener, never()).onTransitionProgress(0.5f)
     }
@@ -111,7 +107,7 @@
     @Test
     fun testRotationBecameUnnaturalDuringTransition_sendsTransitionFinishedEvent() {
         onRotationChanged(Surface.ROTATION_0)
-        source.onTransitionStarted()
+        sourceProvider.onTransitionStarted()
         clearInvocations(transitionListener)
 
         onRotationChanged(Surface.ROTATION_90)
@@ -122,7 +118,7 @@
     @Test
     fun testRotationBecameNaturalDuringTransition_sendsTransitionStartedEvent() {
         onRotationChanged(Surface.ROTATION_90)
-        source.onTransitionStarted()
+        sourceProvider.onTransitionStarted()
         clearInvocations(transitionListener)
 
         onRotationChanged(Surface.ROTATION_0)
@@ -133,7 +129,4 @@
     private fun onRotationChanged(rotation: Int) {
         rotationWatcherCaptor.value.onRotationChanged(rotation)
     }
-
-    private val source: TransitionProgressListener
-        get() = sourceProviderListenerCaptor.value
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
index db7a8516..fc2a78a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
@@ -21,6 +21,7 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.unfold.TestUnfoldTransitionProvider
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 import com.android.systemui.util.mockito.any
@@ -41,15 +42,11 @@
     lateinit var contentResolver: ContentResolver
 
     @Mock
-    lateinit var sourceProvider: UnfoldTransitionProgressProvider
-
-    @Mock
     lateinit var sinkProvider: TransitionProgressListener
 
-    lateinit var progressProvider: ScaleAwareTransitionProgressProvider
+    private val sourceProvider = TestUnfoldTransitionProvider()
 
-    private val sourceProviderListenerCaptor =
-            ArgumentCaptor.forClass(TransitionProgressListener::class.java)
+    lateinit var progressProvider: ScaleAwareTransitionProgressProvider
 
     private val animatorDurationScaleListenerCaptor =
             ArgumentCaptor.forClass(ContentObserver::class.java)
@@ -63,7 +60,6 @@
                 contentResolver
         )
 
-        verify(sourceProvider).addCallback(sourceProviderListenerCaptor.capture())
         verify(contentResolver).registerContentObserver(any(), any(),
                 animatorDurationScaleListenerCaptor.capture())
 
@@ -74,7 +70,7 @@
     fun onTransitionStarted_animationsEnabled_eventReceived() {
         setAnimationsEnabled(true)
 
-        source.onTransitionStarted()
+        sourceProvider.onTransitionStarted()
 
         verify(sinkProvider).onTransitionStarted()
     }
@@ -83,7 +79,7 @@
     fun onTransitionStarted_animationsNotEnabled_eventNotReceived() {
         setAnimationsEnabled(false)
 
-        source.onTransitionStarted()
+        sourceProvider.onTransitionStarted()
 
         verifyNoMoreInteractions(sinkProvider)
     }
@@ -92,7 +88,7 @@
     fun onTransitionEnd_animationsEnabled_eventReceived() {
         setAnimationsEnabled(true)
 
-        source.onTransitionFinished()
+        sourceProvider.onTransitionFinished()
 
         verify(sinkProvider).onTransitionFinished()
     }
@@ -101,7 +97,7 @@
     fun onTransitionEnd_animationsNotEnabled_eventNotReceived() {
         setAnimationsEnabled(false)
 
-        source.onTransitionFinished()
+        sourceProvider.onTransitionFinished()
 
         verifyNoMoreInteractions(sinkProvider)
     }
@@ -110,7 +106,7 @@
     fun onTransitionProgress_animationsEnabled_eventReceived() {
         setAnimationsEnabled(true)
 
-        source.onTransitionProgress(42f)
+        sourceProvider.onTransitionProgress(42f)
 
         verify(sinkProvider).onTransitionProgress(42f)
     }
@@ -119,7 +115,7 @@
     fun onTransitionProgress_animationsNotEnabled_eventNotReceived() {
         setAnimationsEnabled(false)
 
-        source.onTransitionProgress(42f)
+        sourceProvider.onTransitionProgress(42f)
 
         verifyNoMoreInteractions(sinkProvider)
     }
@@ -133,7 +129,4 @@
         ValueAnimator.setDurationScale(durationScale)
         animatorDurationScaleListenerCaptor.value.dispatchChange(/* selfChange= */false)
     }
-
-    private val source: TransitionProgressListener
-        get() = sourceProviderListenerCaptor.value
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt
index 8f851ec..a064e8c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt
@@ -24,6 +24,8 @@
 class TestFoldStateProvider : FoldStateProvider {
 
     private val listeners: MutableList<FoldUpdatesListener> = arrayListOf()
+    val hasListeners: Boolean
+        get() = listeners.isNotEmpty()
 
     override fun start() {
     }
diff --git a/packages/SystemUI/unfold/Android.bp b/packages/SystemUI/unfold/Android.bp
new file mode 100644
index 0000000..108295b
--- /dev/null
+++ b/packages/SystemUI/unfold/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2022 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+    name: "SystemUIUnfoldLib",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+        "src/**/*.aidl",
+    ],
+    static_libs: [
+        "androidx.dynamicanimation_dynamicanimation",
+        "dagger2",
+        "jsr330",
+    ],
+    java_version: "1.8",
+    min_sdk_version: "current",
+    plugins: ["dagger2-compiler"],
+}
diff --git a/packages/SystemUI/unfold/AndroidManifest.xml b/packages/SystemUI/unfold/AndroidManifest.xml
new file mode 100644
index 0000000..ee8afe1
--- /dev/null
+++ b/packages/SystemUI/unfold/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2017 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.systemui.unfold">
+
+
+</manifest>
diff --git a/packages/SystemUI/unfold/lint-baseline.xml b/packages/SystemUI/unfold/lint-baseline.xml
new file mode 100644
index 0000000..449ed2e
--- /dev/null
+++ b/packages/SystemUI/unfold/lint-baseline.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" name="" variant="all" version="7.1.0-dev">
+</issues>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedComponent.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
similarity index 79%
rename from packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
rename to packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
index 9e5aeb8..a5ec0a4 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
@@ -16,16 +16,16 @@
 
 package com.android.systemui.unfold
 
-import android.app.ActivityManager
 import android.content.ContentResolver
 import android.content.Context
 import android.hardware.SensorManager
-import android.hardware.devicestate.DeviceStateManager
 import android.os.Handler
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.dagger.qualifiers.UiBackground
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.dagger.UnfoldBackground
+import com.android.systemui.unfold.dagger.UnfoldMain
+import com.android.systemui.unfold.updates.FoldProvider
 import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
+import com.android.systemui.unfold.util.CurrentActivityTypeProvider
 import com.android.systemui.unfold.util.UnfoldTransitionATracePrefix
 import dagger.BindsInstance
 import dagger.Component
@@ -51,12 +51,12 @@
             @BindsInstance context: Context,
             @BindsInstance config: UnfoldTransitionConfig,
             @BindsInstance screenStatusProvider: ScreenStatusProvider,
-            @BindsInstance deviceStateManager: DeviceStateManager,
-            @BindsInstance activityManager: ActivityManager,
+            @BindsInstance foldProvider: FoldProvider,
+            @BindsInstance activityTypeProvider: CurrentActivityTypeProvider,
             @BindsInstance sensorManager: SensorManager,
-            @BindsInstance @Main handler: Handler,
-            @BindsInstance @Main executor: Executor,
-            @BindsInstance @UiBackground backgroundExecutor: Executor,
+            @BindsInstance @UnfoldMain handler: Handler,
+            @BindsInstance @UnfoldMain executor: Executor,
+            @BindsInstance @UnfoldBackground backgroundExecutor: Executor,
             @BindsInstance @UnfoldTransitionATracePrefix tracingTagPrefix: String,
             @BindsInstance contentResolver: ContentResolver = context.contentResolver
         ): UnfoldSharedComponent
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
similarity index 96%
rename from packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedModule.kt
rename to packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
index c612995..8f4ee4d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedModule.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.unfold
 
 import android.hardware.SensorManager
-import com.android.systemui.dagger.qualifiers.UiBackground
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.dagger.UnfoldBackground
 import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider
 import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider
 import com.android.systemui.unfold.updates.DeviceFoldStateProvider
@@ -70,7 +70,7 @@
     fun hingeAngleProvider(
         config: UnfoldTransitionConfig,
         sensorManager: SensorManager,
-        @UiBackground executor: Executor
+        @UnfoldBackground executor: Executor
     ): HingeAngleProvider =
         if (config.isHingeAngleEnabled) {
             HingeSensorAngleProvider(sensorManager, executor)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
similarity index 83%
rename from packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
rename to packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
index cc56007c..402dd84 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
@@ -17,14 +17,13 @@
 
 package com.android.systemui.unfold
 
-import android.app.ActivityManager
 import android.content.Context
 import android.hardware.SensorManager
-import android.hardware.devicestate.DeviceStateManager
 import android.os.Handler
-import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.updates.FoldProvider
 import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
+import com.android.systemui.unfold.util.CurrentActivityTypeProvider
 import java.util.concurrent.Executor
 
 /**
@@ -39,8 +38,8 @@
     context: Context,
     config: UnfoldTransitionConfig,
     screenStatusProvider: ScreenStatusProvider,
-    deviceStateManager: DeviceStateManager,
-    activityManager: ActivityManager,
+    foldProvider: FoldProvider,
+    activityTypeProvider: CurrentActivityTypeProvider,
     sensorManager: SensorManager,
     mainHandler: Handler,
     mainExecutor: Executor,
@@ -52,8 +51,8 @@
             context,
             config,
             screenStatusProvider,
-            deviceStateManager,
-            activityManager,
+            foldProvider,
+            activityTypeProvider,
             sensorManager,
             mainHandler,
             mainExecutor,
@@ -64,5 +63,3 @@
         ?: throw IllegalStateException(
             "Trying to create " +
                 "UnfoldTransitionProgressProvider when the transition is disabled")
-
-fun createConfig(context: Context): UnfoldTransitionConfig = ResourceUnfoldTransitionConfig(context)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
similarity index 93%
rename from packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
rename to packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
index 409dc95..d54481c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
@@ -15,9 +15,9 @@
  */
 package com.android.systemui.unfold
 
-import android.annotation.FloatRange
-import com.android.systemui.statusbar.policy.CallbackController
+import androidx.annotation.FloatRange
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.util.CallbackController
 
 /**
  * Interface that allows to receive unfold transition progress updates.
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt
new file mode 100644
index 0000000..2044f05
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 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.compat
+
+import android.content.Context
+import android.content.res.Configuration
+import com.android.systemui.unfold.updates.FoldProvider
+import com.android.systemui.unfold.updates.FoldProvider.FoldCallback
+import java.util.concurrent.Executor
+
+/**
+ * Fold provider that notifies about fold state based on the screen size
+ * It could be used when no activity context is available
+ * TODO(b/232369816): use Jetpack WM library when non-activity contexts supported b/169740873
+ */
+class ScreenSizeFoldProvider(private val context: Context) : FoldProvider {
+
+    private var callbacks: MutableList<FoldCallback> = arrayListOf()
+    private var lastWidth: Int = 0
+
+    override fun registerCallback(callback: FoldCallback, executor: Executor) {
+        callbacks += callback
+        onConfigurationChange(context.resources.configuration)
+    }
+
+    override fun unregisterCallback(callback: FoldCallback) {
+        callbacks -= callback
+    }
+
+    fun onConfigurationChange(newConfig: Configuration) {
+        if (lastWidth == newConfig.smallestScreenWidthDp) {
+            return
+        }
+
+        if (newConfig.smallestScreenWidthDp > INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP) {
+            callbacks.forEach { it.onFoldUpdated(false) }
+        } else {
+            callbacks.forEach { it.onFoldUpdated(true) }
+        }
+        lastWidth = newConfig.smallestScreenWidthDp
+    }
+}
+
+private const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/SizeScreenStatusProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/SizeScreenStatusProvider.kt
new file mode 100644
index 0000000..c405f31
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/SizeScreenStatusProvider.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 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.compat
+
+import com.android.systemui.unfold.updates.FoldProvider
+import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
+import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
+import java.util.concurrent.Executor
+
+class SizeScreenStatusProvider(
+    private val foldProvider: FoldProvider,
+    private val executor: Executor
+) : ScreenStatusProvider {
+
+    private val listeners: MutableList<ScreenListener> = arrayListOf()
+    private val callback = object : FoldProvider.FoldCallback {
+        override fun onFoldUpdated(isFolded: Boolean) {
+            if (!isFolded) {
+                listeners.forEach { it.onScreenTurnedOn() }
+            }
+        }
+    }
+
+    fun start() {
+        foldProvider.registerCallback(
+            callback,
+            executor
+        )
+    }
+
+    fun stop() {
+        foldProvider.unregisterCallback(callback)
+    }
+
+    override fun addCallback(listener: ScreenListener) {
+        listeners.add(listener)
+    }
+
+    override fun removeCallback(listener: ScreenListener) {
+        listeners.remove(listener)
+    }
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt
new file mode 100644
index 0000000..c513729
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 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.config
+
+import android.content.res.Resources
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class ResourceUnfoldTransitionConfig @Inject constructor() : UnfoldTransitionConfig {
+
+    override val isEnabled: Boolean by lazy {
+        val id = Resources.getSystem()
+            .getIdentifier("config_unfoldTransitionEnabled", "bool", "android")
+        Resources.getSystem().getBoolean(id)
+    }
+
+    override val isHingeAngleEnabled: Boolean by lazy {
+        val id = Resources.getSystem()
+            .getIdentifier("config_unfoldTransitionHingeAngle", "bool", "android")
+        Resources.getSystem().getBoolean(id)
+    }
+
+    override val halfFoldedTimeoutMillis: Int by lazy {
+        val id = Resources.getSystem()
+            .getIdentifier("config_unfoldTransitionHalfFoldedTimeout", "integer", "android")
+        Resources.getSystem().getInteger(id)
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt
similarity index 95%
rename from packages/SystemUI/shared/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt
rename to packages/SystemUI/unfold/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt
index 5b187b3..765e862 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt
@@ -18,4 +18,5 @@
 interface UnfoldTransitionConfig {
     val isEnabled: Boolean
     val isHingeAngleEnabled: Boolean
+    val halfFoldedTimeoutMillis: Int
 }
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBackground.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBackground.kt
new file mode 100644
index 0000000..6074795
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBackground.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 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
+
+/**
+ * Alternative to [UiBackground] qualifier annotation in unfold module.
+ * It is needed as we can't depend on SystemUI code in this module.
+ */
+@Qualifier
+@Retention(AnnotationRetention.RUNTIME)
+annotation class UnfoldBackground
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldMain.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldMain.kt
new file mode 100644
index 0000000..5553690f
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldMain.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 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
+
+/**
+ * Alternative to [Main] qualifier annotation in unfold module.
+ * It is needed as we can't depend on SystemUI code in this module.
+ */
+@Qualifier
+@Retention(AnnotationRetention.RUNTIME)
+annotation class UnfoldMain
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
similarity index 100%
rename from packages/SystemUI/shared/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
rename to packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
similarity index 97%
rename from packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
rename to packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index 04d920c..2ab28c6 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -16,7 +16,6 @@
 package com.android.systemui.unfold.progress
 
 import android.util.Log
-import android.util.MathUtils.saturate
 import androidx.dynamicanimation.animation.DynamicAnimation
 import androidx.dynamicanimation.animation.FloatPropertyCompat
 import androidx.dynamicanimation.animation.SpringAnimation
@@ -70,6 +69,9 @@
         springAnimation.animateToFinalPosition(progress)
     }
 
+    private fun saturate(amount: Float, low: Float = 0f, high: Float = 1f): Float =
+        if (amount < low) low else if (amount > high) high else amount
+
     override fun onFoldUpdate(@FoldUpdate update: Int) {
         when (update) {
             FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
similarity index 74%
rename from packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
rename to packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index 14581cc..e8038fd 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -15,46 +15,46 @@
  */
 package com.android.systemui.unfold.updates
 
-import android.annotation.FloatRange
-import android.app.ActivityManager
-import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
-import android.content.Context
-import android.hardware.devicestate.DeviceStateManager
 import android.os.Handler
 import android.util.Log
+import androidx.annotation.FloatRange
 import androidx.annotation.VisibleForTesting
 import androidx.core.util.Consumer
-import com.android.systemui.dagger.qualifiers.Main
+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.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 java.util.concurrent.Executor
 import javax.inject.Inject
 
 class DeviceFoldStateProvider
 @Inject
 constructor(
-    context: Context,
+    config: UnfoldTransitionConfig,
     private val hingeAngleProvider: HingeAngleProvider,
     private val screenStatusProvider: ScreenStatusProvider,
-    private val deviceStateManager: DeviceStateManager,
-    private val activityManager: ActivityManager,
-    @Main private val mainExecutor: Executor,
-    @Main private val handler: Handler
+    private val foldProvider: FoldProvider,
+    private val activityTypeProvider: CurrentActivityTypeProvider,
+    @UnfoldMain private val mainExecutor: Executor,
+    @UnfoldMain private val handler: Handler
 ) : FoldStateProvider {
 
     private val outputListeners: MutableList<FoldUpdatesListener> = mutableListOf()
 
-    @FoldUpdate private var lastFoldUpdate: Int? = null
+    @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 lastHingeAngle: Float = 0f
 
     private val hingeAngleListener = HingeAngleListener()
     private val screenListener = ScreenStatusListener()
-    private val foldStateListener = FoldStateListener(context)
+    private val foldStateListener = FoldStateListener()
     private val timeoutRunnable = TimeoutRunnable()
 
     /**
@@ -62,22 +62,20 @@
      * [FOLD_UPDATE_START_CLOSING] or [FOLD_UPDATE_START_OPENING] event, if an end state is not
      * reached.
      */
-    private val halfOpenedTimeoutMillis: Int =
-        context.resources.getInteger(
-            com.android.internal.R.integer.config_unfoldTransitionHalfFoldedTimeout)
+    private val halfOpenedTimeoutMillis: Int = config.halfFoldedTimeoutMillis
 
     private var isFolded = false
     private var isUnfoldHandled = true
 
     override fun start() {
-        deviceStateManager.registerCallback(mainExecutor, foldStateListener)
+        foldProvider.registerCallback(foldStateListener, mainExecutor)
         screenStatusProvider.addCallback(screenListener)
         hingeAngleProvider.addCallback(hingeAngleListener)
     }
 
     override fun stop() {
         screenStatusProvider.removeCallback(screenListener)
-        deviceStateManager.unregisterCallback(foldStateListener)
+        foldProvider.unregisterCallback(foldStateListener)
         hingeAngleProvider.removeCallback(hingeAngleListener)
         hingeAngleProvider.stop()
     }
@@ -92,13 +90,13 @@
 
     override val isFinishedOpening: Boolean
         get() = !isFolded &&
-            (lastFoldUpdate == FOLD_UPDATE_FINISH_FULL_OPEN ||
-                lastFoldUpdate == FOLD_UPDATE_FINISH_HALF_OPEN)
+                (lastFoldUpdate == FOLD_UPDATE_FINISH_FULL_OPEN ||
+                        lastFoldUpdate == FOLD_UPDATE_FINISH_HALF_OPEN)
 
     private val isTransitionInProgress: Boolean
         get() =
             lastFoldUpdate == FOLD_UPDATE_START_OPENING ||
-                lastFoldUpdate == FOLD_UPDATE_START_CLOSING
+                    lastFoldUpdate == FOLD_UPDATE_START_CLOSING
 
     private fun onHingeAngle(angle: Float) {
         if (DEBUG) {
@@ -136,39 +134,36 @@
      * apps that support table-top/HALF_FOLDED mode. Only for launcher, there is no threshold.
      */
     private fun getClosingThreshold(): Int? {
-        val activityType =
-            activityManager.getRunningTasks(/* maxNum= */ 1)?.getOrNull(0)?.topActivityType
-                ?: return null
+        val isHomeActivity = activityTypeProvider.isHomeActivity ?: return null
 
         if (DEBUG) {
-            Log.d(TAG, "activityType=" + activityType)
+            Log.d(TAG, "isHomeActivity=$isHomeActivity")
         }
 
-        return if (activityType == ACTIVITY_TYPE_HOME) {
+        return if (isHomeActivity) {
             null
         } else {
             START_CLOSING_ON_APPS_THRESHOLD_DEGREES
         }
     }
 
-    private inner class FoldStateListener(context: Context) :
-        DeviceStateManager.FoldStateListener(
-            context,
-            { folded: Boolean ->
-                isFolded = folded
-                lastHingeAngle = FULLY_CLOSED_DEGREES
+    private inner class FoldStateListener : FoldProvider.FoldCallback {
+        override fun onFoldUpdated(isFolded: Boolean) {
+            this@DeviceFoldStateProvider.isFolded = isFolded
+            lastHingeAngle = FULLY_CLOSED_DEGREES
 
-                if (folded) {
-                    hingeAngleProvider.stop()
-                    notifyFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
-                    cancelTimeout()
-                    isUnfoldHandled = false
-                } else {
-                    notifyFoldUpdate(FOLD_UPDATE_START_OPENING)
-                    rescheduleAbortAnimationTimeout()
-                    hingeAngleProvider.start()
-                }
-            })
+            if (isFolded) {
+                hingeAngleProvider.stop()
+                notifyFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
+                cancelTimeout()
+                isUnfoldHandled = false
+            } else {
+                notifyFoldUpdate(FOLD_UPDATE_START_OPENING)
+                rescheduleAbortAnimationTimeout()
+                hingeAngleProvider.start()
+            }
+        }
+    }
 
     private fun notifyFoldUpdate(@FoldUpdate update: Int) {
         if (DEBUG) {
@@ -234,7 +229,9 @@
 private const val DEBUG = false
 
 /** Threshold after which we consider the device fully unfolded. */
-@VisibleForTesting const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
+@VisibleForTesting
+const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
 
 /** Fold animation on top of apps only when the angle exceeds this threshold. */
-@VisibleForTesting const val START_CLOSING_ON_APPS_THRESHOLD_DEGREES = 60
+@VisibleForTesting
+const val START_CLOSING_ON_APPS_THRESHOLD_DEGREES = 60
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldProvider.kt
new file mode 100644
index 0000000..6e87bee
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldProvider.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 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.updates
+
+import java.util.concurrent.Executor
+
+interface FoldProvider {
+    fun registerCallback(callback: FoldCallback, executor: Executor)
+    fun unregisterCallback(callback: FoldCallback)
+
+    interface FoldCallback {
+        fun onFoldUpdated(isFolded: Boolean)
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
similarity index 91%
rename from packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
rename to packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
index 14a3a70..c7a8bf3 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
@@ -15,10 +15,10 @@
  */
 package com.android.systemui.unfold.updates
 
-import android.annotation.FloatRange
-import android.annotation.IntDef
-import com.android.systemui.statusbar.policy.CallbackController
+import androidx.annotation.FloatRange
+import androidx.annotation.IntDef
 import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
+import com.android.systemui.unfold.util.CallbackController
 
 /**
  * Allows to subscribe to main events related to fold/unfold process such as hinge angle update,
@@ -36,7 +36,6 @@
     }
 
     @IntDef(
-        prefix = ["FOLD_UPDATE_"],
         value =
             [
                 FOLD_UPDATE_START_OPENING,
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt
new file mode 100644
index 0000000..e985506
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 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.updates.hinge
+
+import androidx.core.util.Consumer
+
+internal object EmptyHingeAngleProvider : HingeAngleProvider {
+    override fun start() {}
+
+    override fun stop() {}
+
+    override fun removeCallback(listener: Consumer<Float>) {}
+
+    override fun addCallback(listener: Consumer<Float>) {}
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt
new file mode 100644
index 0000000..e464c3f
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 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.updates.hinge
+
+import androidx.core.util.Consumer
+import com.android.systemui.unfold.util.CallbackController
+
+/**
+ * Emits device hinge angle values (angle between two integral parts of the device).
+ *
+ * The hinge angle could be from 0 to 360 degrees inclusive. For foldable devices usually 0
+ * corresponds to fully closed (folded) state and 180 degrees corresponds to fully open (flat)
+ * state.
+ */
+interface HingeAngleProvider : CallbackController<Consumer<Float>> {
+    fun start()
+    fun stop()
+}
+
+const val FULLY_OPEN_DEGREES = 180f
+const val FULLY_CLOSED_DEGREES = 0f
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
similarity index 71%
rename from packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
rename to packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
index c93412b..3fc5d61 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
@@ -1,3 +1,17 @@
+/*
+ * Copyright (C) 2022 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.updates.hinge
 
 import android.hardware.Sensor
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
similarity index 93%
rename from packages/SystemUI/shared/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
rename to packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
index 668c694..d95e050 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
@@ -15,8 +15,8 @@
  */
 package com.android.systemui.unfold.updates.screen
 
-import com.android.systemui.statusbar.policy.CallbackController
 import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
+import com.android.systemui.unfold.util.CallbackController
 
 interface ScreenStatusProvider : CallbackController<ScreenListener> {
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt
similarity index 65%
rename from packages/SystemUI/shared/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt
rename to packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt
index 1574c8d..d8bc018 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt
@@ -1,3 +1,17 @@
+/*
+ * Copyright (C) 2022 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.util
 
 import android.os.Trace
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/CallbackController.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/CallbackController.kt
new file mode 100644
index 0000000..46ad534
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/CallbackController.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2022 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.util
+
+interface CallbackController<T> {
+    fun addCallback(listener: T)
+    fun removeCallback(listener: T)
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/CurrentActivityTypeProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/CurrentActivityTypeProvider.kt
new file mode 100644
index 0000000..d0e6cdc
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/CurrentActivityTypeProvider.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 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.util
+
+interface CurrentActivityTypeProvider {
+    val isHomeActivity: Boolean?
+}
+
+class EmptyCurrentActivityTypeProvider(override val isHomeActivity: Boolean? = null) :
+    CurrentActivityTypeProvider
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
similarity index 78%
rename from packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
rename to packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
index dfe8792..5c92b34 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
@@ -1,3 +1,17 @@
+/*
+ * Copyright (C) 2022 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.util
 
 import android.animation.ValueAnimator
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
similarity index 100%
rename from packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
rename to packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt