Cancel unfold animation when rotation changes
In DeviceFoldStateProvider.kt, FOLD_UPDATE_FINISH_HALF_OPEN is emitted if the rotation changes while the animation is in progress. This targets the case when the device goes to tabletop mode during the transition. + Using RotationChangeProvider to fix problems when the new orientation change was received in a background thread unexpectedly. This fixes: (1) wrong launcher icons movement after the rotation, and (2) wrong dark overlay scrim after rotation. + Minor formattig fixes by ktfmt Bug: 241743859 Test: DeviceFoldStateProviderTest && NaturalRotationUnfoldProgressProviderTest && manual stress testing Change-Id: I0ff6a823709e0891a7bcd13528a0e160d6f39c98
This commit is contained in:
@@ -17,6 +17,7 @@ package com.android.systemui.shared.animation
|
||||
|
||||
import android.graphics.Point
|
||||
import android.view.Surface
|
||||
import android.view.Surface.Rotation
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
|
||||
@@ -58,14 +59,14 @@ class UnfoldMoveFromCenterAnimator @JvmOverloads constructor(
|
||||
* Updates display properties in order to calculate the initial position for the views
|
||||
* Must be called before [registerViewForAnimation]
|
||||
*/
|
||||
fun updateDisplayProperties() {
|
||||
@JvmOverloads
|
||||
fun updateDisplayProperties(@Rotation rotation: Int = windowManager.defaultDisplay.rotation) {
|
||||
windowManager.defaultDisplay.getSize(screenSize)
|
||||
|
||||
// Simple implementation to get current fold orientation,
|
||||
// this might not be correct on all devices
|
||||
// TODO: use JetPack WindowManager library to get the fold orientation
|
||||
isVerticalFold = windowManager.defaultDisplay.rotation == Surface.ROTATION_0 ||
|
||||
windowManager.defaultDisplay.rotation == Surface.ROTATION_180
|
||||
isVerticalFold = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,12 +15,11 @@
|
||||
package com.android.systemui.unfold.util
|
||||
|
||||
import android.content.Context
|
||||
import android.os.RemoteException
|
||||
import android.view.IRotationWatcher
|
||||
import android.view.IWindowManager
|
||||
import android.view.Surface
|
||||
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
|
||||
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
|
||||
import com.android.systemui.unfold.updates.RotationChangeProvider
|
||||
import com.android.systemui.unfold.updates.RotationChangeProvider.RotationListener
|
||||
|
||||
/**
|
||||
* [UnfoldTransitionProgressProvider] that emits transition progress only when the display has
|
||||
@@ -29,27 +28,21 @@ import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionPr
|
||||
*/
|
||||
class NaturalRotationUnfoldProgressProvider(
|
||||
private val context: Context,
|
||||
private val windowManagerInterface: IWindowManager,
|
||||
private val rotationChangeProvider: RotationChangeProvider,
|
||||
unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider
|
||||
) : UnfoldTransitionProgressProvider {
|
||||
|
||||
private val scopedUnfoldTransitionProgressProvider =
|
||||
ScopedUnfoldTransitionProgressProvider(unfoldTransitionProgressProvider)
|
||||
private val rotationWatcher = RotationWatcher()
|
||||
|
||||
private var isNaturalRotation: Boolean = false
|
||||
|
||||
fun init() {
|
||||
try {
|
||||
windowManagerInterface.watchRotation(rotationWatcher, context.display.displayId)
|
||||
} catch (e: RemoteException) {
|
||||
throw e.rethrowFromSystemServer()
|
||||
}
|
||||
|
||||
onRotationChanged(context.display.rotation)
|
||||
rotationChangeProvider.addCallback(rotationListener)
|
||||
rotationListener.onRotationChanged(context.display.rotation)
|
||||
}
|
||||
|
||||
private fun onRotationChanged(rotation: Int) {
|
||||
private val rotationListener = RotationListener { rotation ->
|
||||
val isNewRotationNatural =
|
||||
rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
|
||||
|
||||
@@ -60,12 +53,7 @@ class NaturalRotationUnfoldProgressProvider(
|
||||
}
|
||||
|
||||
override fun destroy() {
|
||||
try {
|
||||
windowManagerInterface.removeRotationWatcher(rotationWatcher)
|
||||
} catch (e: RemoteException) {
|
||||
e.rethrowFromSystemServer()
|
||||
}
|
||||
|
||||
rotationChangeProvider.removeCallback(rotationListener)
|
||||
scopedUnfoldTransitionProgressProvider.destroy()
|
||||
}
|
||||
|
||||
@@ -76,10 +64,4 @@ class NaturalRotationUnfoldProgressProvider(
|
||||
override fun removeCallback(listener: TransitionProgressListener) {
|
||||
scopedUnfoldTransitionProgressProvider.removeCallback(listener)
|
||||
}
|
||||
|
||||
private inner class RotationWatcher : IRotationWatcher.Stub() {
|
||||
override fun onRotationChanged(rotation: Int) {
|
||||
this@NaturalRotationUnfoldProgressProvider.onRotationChanged(rotation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,6 @@ import android.os.Trace
|
||||
import android.view.Choreographer
|
||||
import android.view.Display
|
||||
import android.view.DisplayInfo
|
||||
import android.view.IRotationWatcher
|
||||
import android.view.IWindowManager
|
||||
import android.view.Surface
|
||||
import android.view.SurfaceControl
|
||||
import android.view.SurfaceControlViewHost
|
||||
@@ -40,6 +38,7 @@ import com.android.systemui.statusbar.LightRevealEffect
|
||||
import com.android.systemui.statusbar.LightRevealScrim
|
||||
import com.android.systemui.statusbar.LinearLightRevealEffect
|
||||
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
|
||||
import com.android.systemui.unfold.updates.RotationChangeProvider
|
||||
import com.android.systemui.util.traceSection
|
||||
import com.android.wm.shell.displayareahelper.DisplayAreaHelper
|
||||
import java.util.Optional
|
||||
@@ -58,7 +57,7 @@ constructor(
|
||||
private val displayAreaHelper: Optional<DisplayAreaHelper>,
|
||||
@Main private val executor: Executor,
|
||||
@UiBackground private val backgroundExecutor: Executor,
|
||||
private val windowManagerInterface: IWindowManager
|
||||
private val rotationChangeProvider: RotationChangeProvider,
|
||||
) {
|
||||
|
||||
private val transitionListener = TransitionListener()
|
||||
@@ -78,7 +77,7 @@ constructor(
|
||||
fun init() {
|
||||
deviceStateManager.registerCallback(executor, FoldListener())
|
||||
unfoldTransitionProgressProvider.addCallback(transitionListener)
|
||||
windowManagerInterface.watchRotation(rotationWatcher, context.display.displayId)
|
||||
rotationChangeProvider.addCallback(rotationWatcher)
|
||||
|
||||
val containerBuilder =
|
||||
SurfaceControl.Builder(SurfaceSession())
|
||||
@@ -86,7 +85,9 @@ constructor(
|
||||
.setName("unfold-overlay-container")
|
||||
|
||||
displayAreaHelper.get().attachToRootDisplayArea(
|
||||
Display.DEFAULT_DISPLAY, containerBuilder) { builder ->
|
||||
Display.DEFAULT_DISPLAY,
|
||||
containerBuilder
|
||||
) { builder ->
|
||||
executor.execute {
|
||||
overlayContainer = builder.build()
|
||||
|
||||
@@ -244,8 +245,8 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private inner class RotationWatcher : IRotationWatcher.Stub() {
|
||||
override fun onRotationChanged(newRotation: Int) =
|
||||
private inner class RotationWatcher : RotationChangeProvider.RotationListener {
|
||||
override fun onRotationChanged(newRotation: Int) {
|
||||
traceSection("UnfoldLightRevealOverlayAnimation#onRotationChanged") {
|
||||
if (currentRotation != newRotation) {
|
||||
currentRotation = newRotation
|
||||
@@ -253,6 +254,7 @@ constructor(
|
||||
root?.relayout(getLayoutParams())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inner class FoldListener :
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
package com.android.systemui.unfold
|
||||
|
||||
import android.content.Context
|
||||
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.RotationChangeProvider
|
||||
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
|
||||
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
|
||||
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
|
||||
@@ -65,11 +65,11 @@ class UnfoldTransitionModule {
|
||||
@Singleton
|
||||
fun provideNaturalRotationProgressProvider(
|
||||
context: Context,
|
||||
windowManager: IWindowManager,
|
||||
rotationChangeProvider: RotationChangeProvider,
|
||||
unfoldTransitionProgressProvider: Optional<UnfoldTransitionProgressProvider>
|
||||
): Optional<NaturalRotationUnfoldProgressProvider> =
|
||||
unfoldTransitionProgressProvider.map { provider ->
|
||||
NaturalRotationUnfoldProgressProvider(context, windowManager, provider)
|
||||
NaturalRotationUnfoldProgressProvider(context, rotationChangeProvider, provider)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -25,16 +25,21 @@ 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.RotationChangeProvider.RotationListener
|
||||
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.util.mockito.any
|
||||
import com.android.systemui.util.mockito.capture
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import java.util.concurrent.Executor
|
||||
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
|
||||
|
||||
@@ -48,6 +53,12 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
|
||||
@Mock
|
||||
private lateinit var handler: Handler
|
||||
|
||||
@Mock
|
||||
private lateinit var rotationChangeProvider: RotationChangeProvider
|
||||
|
||||
@Captor
|
||||
private lateinit var rotationListener: ArgumentCaptor<RotationListener>
|
||||
|
||||
private val foldProvider = TestFoldProvider()
|
||||
private val screenOnStatusProvider = TestScreenOnStatusProvider()
|
||||
private val testHingeAngleProvider = TestHingeAngleProvider()
|
||||
@@ -76,6 +87,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
|
||||
screenOnStatusProvider,
|
||||
foldProvider,
|
||||
activityTypeProvider,
|
||||
rotationChangeProvider,
|
||||
context.mainExecutor,
|
||||
handler
|
||||
)
|
||||
@@ -92,6 +104,8 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
|
||||
})
|
||||
foldStateProvider.start()
|
||||
|
||||
verify(rotationChangeProvider).addCallback(capture(rotationListener))
|
||||
|
||||
whenever(handler.postDelayed(any<Runnable>(), any())).then { invocationOnMock ->
|
||||
scheduledRunnable = invocationOnMock.getArgument<Runnable>(0)
|
||||
scheduledRunnableDelay = invocationOnMock.getArgument<Long>(1)
|
||||
@@ -372,6 +386,27 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
|
||||
assertThat(testHingeAngleProvider.isStarted).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onRotationChanged_whileInProgress_cancelled() {
|
||||
setFoldState(folded = false)
|
||||
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING)
|
||||
|
||||
rotationListener.value.onRotationChanged(1)
|
||||
|
||||
assertThat(foldUpdates).containsExactly(
|
||||
FOLD_UPDATE_START_OPENING, FOLD_UPDATE_FINISH_HALF_OPEN)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onRotationChanged_whileNotInProgress_noUpdates() {
|
||||
setFoldState(folded = true)
|
||||
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_FINISH_CLOSED)
|
||||
|
||||
rotationListener.value.onRotationChanged(1)
|
||||
|
||||
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_FINISH_CLOSED)
|
||||
}
|
||||
|
||||
private fun setupForegroundActivityType(isHomeActivity: Boolean?) {
|
||||
whenever(activityTypeProvider.isHomeActivity).thenReturn(isHomeActivity)
|
||||
}
|
||||
|
||||
@@ -16,18 +16,19 @@
|
||||
package com.android.systemui.unfold.util
|
||||
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.view.IRotationWatcher
|
||||
import android.view.IWindowManager
|
||||
import android.view.Surface
|
||||
import androidx.test.filters.SmallTest
|
||||
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.unfold.updates.RotationChangeProvider
|
||||
import com.android.systemui.unfold.updates.RotationChangeProvider.RotationListener
|
||||
import com.android.systemui.util.mockito.capture
|
||||
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.clearInvocations
|
||||
import org.mockito.Mockito.never
|
||||
@@ -38,32 +39,26 @@ import org.mockito.MockitoAnnotations
|
||||
@SmallTest
|
||||
class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
|
||||
|
||||
@Mock
|
||||
lateinit var windowManager: IWindowManager
|
||||
@Mock lateinit var rotationChangeProvider: RotationChangeProvider
|
||||
|
||||
private val sourceProvider = TestUnfoldTransitionProvider()
|
||||
|
||||
@Mock
|
||||
lateinit var transitionListener: TransitionProgressListener
|
||||
@Mock lateinit var transitionListener: TransitionProgressListener
|
||||
|
||||
@Captor private lateinit var rotationListenerCaptor: ArgumentCaptor<RotationListener>
|
||||
|
||||
lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
|
||||
|
||||
private val rotationWatcherCaptor =
|
||||
ArgumentCaptor.forClass(IRotationWatcher.Stub::class.java)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
progressProvider = NaturalRotationUnfoldProgressProvider(
|
||||
context,
|
||||
windowManager,
|
||||
sourceProvider
|
||||
)
|
||||
progressProvider =
|
||||
NaturalRotationUnfoldProgressProvider(context, rotationChangeProvider, sourceProvider)
|
||||
|
||||
progressProvider.init()
|
||||
|
||||
verify(windowManager).watchRotation(rotationWatcherCaptor.capture(), any())
|
||||
verify(rotationChangeProvider).addCallback(capture(rotationListenerCaptor))
|
||||
|
||||
progressProvider.addCallback(transitionListener)
|
||||
}
|
||||
@@ -127,6 +122,6 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
private fun onRotationChanged(rotation: Int) {
|
||||
rotationWatcherCaptor.value.onRotationChanged(rotation)
|
||||
rotationListenerCaptor.value.onRotationChanged(rotation)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,10 +20,12 @@ import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.hardware.SensorManager
|
||||
import android.os.Handler
|
||||
import android.view.IWindowManager
|
||||
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.RotationChangeProvider
|
||||
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
|
||||
import com.android.systemui.unfold.util.CurrentActivityTypeProvider
|
||||
import com.android.systemui.unfold.util.UnfoldTransitionATracePrefix
|
||||
@@ -39,11 +41,11 @@ import javax.inject.Singleton
|
||||
*
|
||||
* This component is meant to be used for places that don't use dagger. By providing those
|
||||
* parameters to the factory, all dagger objects are correctly instantiated. See
|
||||
* [createUnfoldTransitionProgressProvider] for an example.
|
||||
* [createUnfoldSharedComponent] for an example.
|
||||
*/
|
||||
@Singleton
|
||||
@Component(modules = [UnfoldSharedModule::class])
|
||||
internal interface UnfoldSharedComponent {
|
||||
interface UnfoldSharedComponent {
|
||||
|
||||
@Component.Factory
|
||||
interface Factory {
|
||||
@@ -58,9 +60,11 @@ internal interface UnfoldSharedComponent {
|
||||
@BindsInstance @UnfoldMain executor: Executor,
|
||||
@BindsInstance @UnfoldBackground backgroundExecutor: Executor,
|
||||
@BindsInstance @UnfoldTransitionATracePrefix tracingTagPrefix: String,
|
||||
@BindsInstance windowManager: IWindowManager,
|
||||
@BindsInstance contentResolver: ContentResolver = context.contentResolver
|
||||
): UnfoldSharedComponent
|
||||
}
|
||||
|
||||
val unfoldTransitionProvider: Optional<UnfoldTransitionProgressProvider>
|
||||
val rotationChangeProvider: RotationChangeProvider
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ package com.android.systemui.unfold
|
||||
import android.content.Context
|
||||
import android.hardware.SensorManager
|
||||
import android.os.Handler
|
||||
import android.view.IWindowManager
|
||||
import com.android.systemui.unfold.config.UnfoldTransitionConfig
|
||||
import com.android.systemui.unfold.updates.FoldProvider
|
||||
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
|
||||
@@ -27,14 +28,15 @@ import com.android.systemui.unfold.util.CurrentActivityTypeProvider
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
/**
|
||||
* Factory for [UnfoldTransitionProgressProvider].
|
||||
* Factory for [UnfoldSharedComponent].
|
||||
*
|
||||
* This is needed as Launcher has to create the object manually. If dagger is available, this object
|
||||
* is provided in [UnfoldSharedModule].
|
||||
* This wraps the autogenerated factory (for discoverability), and is needed as Launcher has to
|
||||
* create the object manually. If dagger is available, this object is provided in
|
||||
* [UnfoldSharedModule].
|
||||
*
|
||||
* This should **never** be called from sysui, as the object is already provided in that process.
|
||||
*/
|
||||
fun createUnfoldTransitionProgressProvider(
|
||||
fun createUnfoldSharedComponent(
|
||||
context: Context,
|
||||
config: UnfoldTransitionConfig,
|
||||
screenStatusProvider: ScreenStatusProvider,
|
||||
@@ -44,8 +46,9 @@ fun createUnfoldTransitionProgressProvider(
|
||||
mainHandler: Handler,
|
||||
mainExecutor: Executor,
|
||||
backgroundExecutor: Executor,
|
||||
tracingTagPrefix: String
|
||||
): UnfoldTransitionProgressProvider =
|
||||
tracingTagPrefix: String,
|
||||
windowManager: IWindowManager,
|
||||
): UnfoldSharedComponent =
|
||||
DaggerUnfoldSharedComponent.factory()
|
||||
.create(
|
||||
context,
|
||||
@@ -57,9 +60,6 @@ fun createUnfoldTransitionProgressProvider(
|
||||
mainHandler,
|
||||
mainExecutor,
|
||||
backgroundExecutor,
|
||||
tracingTagPrefix)
|
||||
.unfoldTransitionProvider
|
||||
.orElse(null)
|
||||
?: throw IllegalStateException(
|
||||
"Trying to create " +
|
||||
"UnfoldTransitionProgressProvider when the transition is disabled")
|
||||
tracingTagPrefix,
|
||||
windowManager,
|
||||
)
|
||||
|
||||
@@ -26,7 +26,8 @@ import com.android.systemui.unfold.util.CallbackController
|
||||
*
|
||||
* onTransitionProgress callback could be called on each frame.
|
||||
*
|
||||
* Use [createUnfoldTransitionProgressProvider] to create instances of this interface
|
||||
* Use [createUnfoldSharedComponent] to create instances of this interface when dagger is not
|
||||
* available.
|
||||
*/
|
||||
interface UnfoldTransitionProgressProvider : CallbackController<TransitionProgressListener> {
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ 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
|
||||
@@ -40,22 +41,24 @@ constructor(
|
||||
private val screenStatusProvider: ScreenStatusProvider,
|
||||
private val foldProvider: FoldProvider,
|
||||
private val activityTypeProvider: CurrentActivityTypeProvider,
|
||||
private val rotationChangeProvider: RotationChangeProvider,
|
||||
@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()
|
||||
private val timeoutRunnable = TimeoutRunnable()
|
||||
private val timeoutRunnable = Runnable { cancelAnimation() }
|
||||
private val rotationListener = RotationListener {
|
||||
if (isTransitionInProgress) cancelAnimation()
|
||||
}
|
||||
|
||||
/**
|
||||
* Time after which [FOLD_UPDATE_FINISH_HALF_OPEN] is emitted following a
|
||||
@@ -72,6 +75,7 @@ constructor(
|
||||
foldProvider.registerCallback(foldStateListener, mainExecutor)
|
||||
screenStatusProvider.addCallback(screenListener)
|
||||
hingeAngleProvider.addCallback(hingeAngleListener)
|
||||
rotationChangeProvider.addCallback(rotationListener)
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
@@ -79,6 +83,7 @@ constructor(
|
||||
foldProvider.unregisterCallback(foldStateListener)
|
||||
hingeAngleProvider.removeCallback(hingeAngleListener)
|
||||
hingeAngleProvider.stop()
|
||||
rotationChangeProvider.removeCallback(rotationListener)
|
||||
}
|
||||
|
||||
override fun addCallback(listener: FoldUpdatesListener) {
|
||||
@@ -90,14 +95,15 @@ constructor(
|
||||
}
|
||||
|
||||
override val isFinishedOpening: Boolean
|
||||
get() = !isFolded &&
|
||||
get() =
|
||||
!isFolded &&
|
||||
(lastFoldUpdate == FOLD_UPDATE_FINISH_FULL_OPEN ||
|
||||
lastFoldUpdate == FOLD_UPDATE_FINISH_HALF_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) {
|
||||
@@ -168,7 +174,7 @@ constructor(
|
||||
|
||||
private fun notifyFoldUpdate(@FoldUpdate update: Int) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, stateToString(update))
|
||||
Log.d(TAG, update.name())
|
||||
}
|
||||
outputListeners.forEach { it.onFoldUpdate(update) }
|
||||
lastFoldUpdate = update
|
||||
@@ -185,6 +191,8 @@ constructor(
|
||||
handler.removeCallbacks(timeoutRunnable)
|
||||
}
|
||||
|
||||
private fun cancelAnimation(): Unit = notifyFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN)
|
||||
|
||||
private inner class ScreenStatusListener : ScreenStatusProvider.ScreenListener {
|
||||
|
||||
override fun onScreenTurnedOn() {
|
||||
@@ -225,16 +233,10 @@ constructor(
|
||||
onHingeAngle(angle)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class TimeoutRunnable : Runnable {
|
||||
override fun run() {
|
||||
notifyFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun stateToString(@FoldUpdate update: Int): String {
|
||||
return when (update) {
|
||||
fun @receiver:FoldUpdate Int.name() =
|
||||
when (this) {
|
||||
FOLD_UPDATE_START_OPENING -> "START_OPENING"
|
||||
FOLD_UPDATE_START_CLOSING -> "START_CLOSING"
|
||||
FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> "UNFOLDED_SCREEN_AVAILABLE"
|
||||
@@ -243,15 +245,12 @@ private fun stateToString(@FoldUpdate update: Int): String {
|
||||
FOLD_UPDATE_FINISH_CLOSED -> "FINISH_CLOSED"
|
||||
else -> "UNKNOWN"
|
||||
}
|
||||
}
|
||||
|
||||
private const val TAG = "DeviceFoldProvider"
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user