Merge changes from topic "cancel_unfold_anim_on_rotation" into tm-qpr-dev am: aea723c854

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20177443

Change-Id: If47354061fd99ec60f4fe8893a25e1e94f42f0c0
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Nicolò Mazzucato
2022-10-13 23:27:26 +00:00
committed by Automerger Merge Worker
12 changed files with 288 additions and 92 deletions

View File

@@ -17,6 +17,7 @@ package com.android.systemui.shared.animation
import android.graphics.Point import android.graphics.Point
import android.view.Surface import android.view.Surface
import android.view.Surface.Rotation
import android.view.View import android.view.View
import android.view.WindowManager import android.view.WindowManager
import com.android.systemui.unfold.UnfoldTransitionProgressProvider 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 * Updates display properties in order to calculate the initial position for the views
* Must be called before [registerViewForAnimation] * Must be called before [registerViewForAnimation]
*/ */
fun updateDisplayProperties() { @JvmOverloads
fun updateDisplayProperties(@Rotation rotation: Int = windowManager.defaultDisplay.rotation) {
windowManager.defaultDisplay.getSize(screenSize) windowManager.defaultDisplay.getSize(screenSize)
// Simple implementation to get current fold orientation, // Simple implementation to get current fold orientation,
// this might not be correct on all devices // this might not be correct on all devices
// TODO: use JetPack WindowManager library to get the fold orientation // TODO: use JetPack WindowManager library to get the fold orientation
isVerticalFold = windowManager.defaultDisplay.rotation == Surface.ROTATION_0 || isVerticalFold = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
windowManager.defaultDisplay.rotation == Surface.ROTATION_180
} }
/** /**

View File

@@ -15,12 +15,11 @@
package com.android.systemui.unfold.util package com.android.systemui.unfold.util
import android.content.Context import android.content.Context
import android.os.RemoteException
import android.view.IRotationWatcher
import android.view.IWindowManager
import android.view.Surface import android.view.Surface
import com.android.systemui.unfold.UnfoldTransitionProgressProvider import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener 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 * [UnfoldTransitionProgressProvider] that emits transition progress only when the display has
@@ -29,27 +28,21 @@ import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionPr
*/ */
class NaturalRotationUnfoldProgressProvider( class NaturalRotationUnfoldProgressProvider(
private val context: Context, private val context: Context,
private val windowManagerInterface: IWindowManager, private val rotationChangeProvider: RotationChangeProvider,
unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider
) : UnfoldTransitionProgressProvider { ) : UnfoldTransitionProgressProvider {
private val scopedUnfoldTransitionProgressProvider = private val scopedUnfoldTransitionProgressProvider =
ScopedUnfoldTransitionProgressProvider(unfoldTransitionProgressProvider) ScopedUnfoldTransitionProgressProvider(unfoldTransitionProgressProvider)
private val rotationWatcher = RotationWatcher()
private var isNaturalRotation: Boolean = false private var isNaturalRotation: Boolean = false
fun init() { fun init() {
try { rotationChangeProvider.addCallback(rotationListener)
windowManagerInterface.watchRotation(rotationWatcher, context.display.displayId) rotationListener.onRotationChanged(context.display.rotation)
} catch (e: RemoteException) {
throw e.rethrowFromSystemServer()
} }
onRotationChanged(context.display.rotation) private val rotationListener = RotationListener { rotation ->
}
private fun onRotationChanged(rotation: Int) {
val isNewRotationNatural = val isNewRotationNatural =
rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
@@ -60,12 +53,7 @@ class NaturalRotationUnfoldProgressProvider(
} }
override fun destroy() { override fun destroy() {
try { rotationChangeProvider.removeCallback(rotationListener)
windowManagerInterface.removeRotationWatcher(rotationWatcher)
} catch (e: RemoteException) {
e.rethrowFromSystemServer()
}
scopedUnfoldTransitionProgressProvider.destroy() scopedUnfoldTransitionProgressProvider.destroy()
} }
@@ -76,10 +64,4 @@ class NaturalRotationUnfoldProgressProvider(
override fun removeCallback(listener: TransitionProgressListener) { override fun removeCallback(listener: TransitionProgressListener) {
scopedUnfoldTransitionProgressProvider.removeCallback(listener) scopedUnfoldTransitionProgressProvider.removeCallback(listener)
} }
private inner class RotationWatcher : IRotationWatcher.Stub() {
override fun onRotationChanged(rotation: Int) {
this@NaturalRotationUnfoldProgressProvider.onRotationChanged(rotation)
}
}
} }

View File

@@ -26,8 +26,6 @@ import android.os.Trace
import android.view.Choreographer import android.view.Choreographer
import android.view.Display import android.view.Display
import android.view.DisplayInfo import android.view.DisplayInfo
import android.view.IRotationWatcher
import android.view.IWindowManager
import android.view.Surface import android.view.Surface
import android.view.SurfaceControl import android.view.SurfaceControl
import android.view.SurfaceControlViewHost 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.LightRevealScrim
import com.android.systemui.statusbar.LinearLightRevealEffect import com.android.systemui.statusbar.LinearLightRevealEffect
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.updates.RotationChangeProvider
import com.android.systemui.util.traceSection import com.android.systemui.util.traceSection
import com.android.wm.shell.displayareahelper.DisplayAreaHelper import com.android.wm.shell.displayareahelper.DisplayAreaHelper
import java.util.Optional import java.util.Optional
@@ -58,7 +57,7 @@ constructor(
private val displayAreaHelper: Optional<DisplayAreaHelper>, private val displayAreaHelper: Optional<DisplayAreaHelper>,
@Main private val executor: Executor, @Main private val executor: Executor,
@UiBackground private val backgroundExecutor: Executor, @UiBackground private val backgroundExecutor: Executor,
private val windowManagerInterface: IWindowManager private val rotationChangeProvider: RotationChangeProvider,
) { ) {
private val transitionListener = TransitionListener() private val transitionListener = TransitionListener()
@@ -78,7 +77,7 @@ constructor(
fun init() { fun init() {
deviceStateManager.registerCallback(executor, FoldListener()) deviceStateManager.registerCallback(executor, FoldListener())
unfoldTransitionProgressProvider.addCallback(transitionListener) unfoldTransitionProgressProvider.addCallback(transitionListener)
windowManagerInterface.watchRotation(rotationWatcher, context.display.displayId) rotationChangeProvider.addCallback(rotationWatcher)
val containerBuilder = val containerBuilder =
SurfaceControl.Builder(SurfaceSession()) SurfaceControl.Builder(SurfaceSession())
@@ -86,7 +85,9 @@ constructor(
.setName("unfold-overlay-container") .setName("unfold-overlay-container")
displayAreaHelper.get().attachToRootDisplayArea( displayAreaHelper.get().attachToRootDisplayArea(
Display.DEFAULT_DISPLAY, containerBuilder) { builder -> Display.DEFAULT_DISPLAY,
containerBuilder
) { builder ->
executor.execute { executor.execute {
overlayContainer = builder.build() overlayContainer = builder.build()
@@ -244,8 +245,8 @@ constructor(
} }
} }
private inner class RotationWatcher : IRotationWatcher.Stub() { private inner class RotationWatcher : RotationChangeProvider.RotationListener {
override fun onRotationChanged(newRotation: Int) = override fun onRotationChanged(newRotation: Int) {
traceSection("UnfoldLightRevealOverlayAnimation#onRotationChanged") { traceSection("UnfoldLightRevealOverlayAnimation#onRotationChanged") {
if (currentRotation != newRotation) { if (currentRotation != newRotation) {
currentRotation = newRotation currentRotation = newRotation
@@ -254,6 +255,7 @@ constructor(
} }
} }
} }
}
private inner class FoldListener : private inner class FoldListener :
FoldStateListener( FoldStateListener(

View File

@@ -17,11 +17,11 @@
package com.android.systemui.unfold package com.android.systemui.unfold
import android.content.Context import android.content.Context
import android.view.IWindowManager
import com.android.systemui.keyguard.LifecycleScreenStatusProvider import com.android.systemui.keyguard.LifecycleScreenStatusProvider
import com.android.systemui.unfold.config.UnfoldTransitionConfig import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.system.SystemUnfoldSharedModule import com.android.systemui.unfold.system.SystemUnfoldSharedModule
import com.android.systemui.unfold.updates.FoldStateProvider 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.updates.screen.ScreenStatusProvider
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
@@ -65,11 +65,11 @@ class UnfoldTransitionModule {
@Singleton @Singleton
fun provideNaturalRotationProgressProvider( fun provideNaturalRotationProgressProvider(
context: Context, context: Context,
windowManager: IWindowManager, rotationChangeProvider: RotationChangeProvider,
unfoldTransitionProgressProvider: Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider: Optional<UnfoldTransitionProgressProvider>
): Optional<NaturalRotationUnfoldProgressProvider> = ): Optional<NaturalRotationUnfoldProgressProvider> =
unfoldTransitionProgressProvider.map { provider -> unfoldTransitionProgressProvider.map { provider ->
NaturalRotationUnfoldProgressProvider(context, windowManager, provider) NaturalRotationUnfoldProgressProvider(context, rotationChangeProvider, provider)
} }
@Provides @Provides

View File

@@ -25,16 +25,21 @@ import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig
import com.android.systemui.unfold.config.UnfoldTransitionConfig import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.system.ActivityManagerActivityTypeProvider import com.android.systemui.unfold.system.ActivityManagerActivityTypeProvider
import com.android.systemui.unfold.updates.FoldProvider.FoldCallback 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.hinge.HingeAngleProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import java.util.concurrent.Executor import java.util.concurrent.Executor
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
@@ -48,6 +53,12 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Mock @Mock
private lateinit var handler: Handler private lateinit var handler: Handler
@Mock
private lateinit var rotationChangeProvider: RotationChangeProvider
@Captor
private lateinit var rotationListener: ArgumentCaptor<RotationListener>
private val foldProvider = TestFoldProvider() private val foldProvider = TestFoldProvider()
private val screenOnStatusProvider = TestScreenOnStatusProvider() private val screenOnStatusProvider = TestScreenOnStatusProvider()
private val testHingeAngleProvider = TestHingeAngleProvider() private val testHingeAngleProvider = TestHingeAngleProvider()
@@ -76,6 +87,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
screenOnStatusProvider, screenOnStatusProvider,
foldProvider, foldProvider,
activityTypeProvider, activityTypeProvider,
rotationChangeProvider,
context.mainExecutor, context.mainExecutor,
handler handler
) )
@@ -92,6 +104,8 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
}) })
foldStateProvider.start() foldStateProvider.start()
verify(rotationChangeProvider).addCallback(capture(rotationListener))
whenever(handler.postDelayed(any<Runnable>(), any())).then { invocationOnMock -> whenever(handler.postDelayed(any<Runnable>(), any())).then { invocationOnMock ->
scheduledRunnable = invocationOnMock.getArgument<Runnable>(0) scheduledRunnable = invocationOnMock.getArgument<Runnable>(0)
scheduledRunnableDelay = invocationOnMock.getArgument<Long>(1) scheduledRunnableDelay = invocationOnMock.getArgument<Long>(1)
@@ -372,6 +386,27 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
assertThat(testHingeAngleProvider.isStarted).isFalse() 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?) { private fun setupForegroundActivityType(isHomeActivity: Boolean?) {
whenever(activityTypeProvider.isHomeActivity).thenReturn(isHomeActivity) whenever(activityTypeProvider.isHomeActivity).thenReturn(isHomeActivity)
} }

View File

@@ -0,0 +1,84 @@
/*
* 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 android.testing.AndroidTestingRunner
import android.view.IRotationWatcher
import android.view.IWindowManager
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.unfold.updates.RotationChangeProvider.RotationListener
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
class RotationChangeProviderTest : SysuiTestCase() {
private lateinit var rotationChangeProvider: RotationChangeProvider
@Mock lateinit var windowManagerInterface: IWindowManager
@Mock lateinit var listener: RotationListener
@Captor lateinit var rotationWatcher: ArgumentCaptor<IRotationWatcher>
private val fakeExecutor = FakeExecutor(FakeSystemClock())
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
rotationChangeProvider =
RotationChangeProvider(windowManagerInterface, context, fakeExecutor)
rotationChangeProvider.addCallback(listener)
fakeExecutor.runAllReady()
verify(windowManagerInterface).watchRotation(rotationWatcher.capture(), anyInt())
}
@Test
fun onRotationChanged_rotationUpdated_listenerReceivesIt() {
sendRotationUpdate(42)
verify(listener).onRotationChanged(42)
}
@Test
fun onRotationChanged_subscribersRemoved_noRotationChangeReceived() {
sendRotationUpdate(42)
verify(listener).onRotationChanged(42)
rotationChangeProvider.removeCallback(listener)
fakeExecutor.runAllReady()
sendRotationUpdate(43)
verify(windowManagerInterface).removeRotationWatcher(any())
verifyNoMoreInteractions(listener)
}
private fun sendRotationUpdate(newRotation: Int) {
rotationWatcher.value.onRotationChanged(newRotation)
fakeExecutor.runAllReady()
}
}

View File

@@ -16,18 +16,19 @@
package com.android.systemui.unfold.util package com.android.systemui.unfold.util
import android.testing.AndroidTestingRunner import android.testing.AndroidTestingRunner
import android.view.IRotationWatcher
import android.view.IWindowManager
import android.view.Surface import android.view.Surface
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.unfold.TestUnfoldTransitionProvider import com.android.systemui.unfold.TestUnfoldTransitionProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener 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.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never import org.mockito.Mockito.never
@@ -38,32 +39,26 @@ import org.mockito.MockitoAnnotations
@SmallTest @SmallTest
class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() { class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
@Mock @Mock lateinit var rotationChangeProvider: RotationChangeProvider
lateinit var windowManager: IWindowManager
private val sourceProvider = TestUnfoldTransitionProvider() private val sourceProvider = TestUnfoldTransitionProvider()
@Mock @Mock lateinit var transitionListener: TransitionProgressListener
lateinit var transitionListener: TransitionProgressListener
@Captor private lateinit var rotationListenerCaptor: ArgumentCaptor<RotationListener>
lateinit var progressProvider: NaturalRotationUnfoldProgressProvider lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
private val rotationWatcherCaptor =
ArgumentCaptor.forClass(IRotationWatcher.Stub::class.java)
@Before @Before
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
progressProvider = NaturalRotationUnfoldProgressProvider( progressProvider =
context, NaturalRotationUnfoldProgressProvider(context, rotationChangeProvider, sourceProvider)
windowManager,
sourceProvider
)
progressProvider.init() progressProvider.init()
verify(windowManager).watchRotation(rotationWatcherCaptor.capture(), any()) verify(rotationChangeProvider).addCallback(capture(rotationListenerCaptor))
progressProvider.addCallback(transitionListener) progressProvider.addCallback(transitionListener)
} }
@@ -127,6 +122,6 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
} }
private fun onRotationChanged(rotation: Int) { private fun onRotationChanged(rotation: Int) {
rotationWatcherCaptor.value.onRotationChanged(rotation) rotationListenerCaptor.value.onRotationChanged(rotation)
} }
} }

View File

@@ -20,10 +20,12 @@ import android.content.ContentResolver
import android.content.Context import android.content.Context
import android.hardware.SensorManager import android.hardware.SensorManager
import android.os.Handler import android.os.Handler
import android.view.IWindowManager
import com.android.systemui.unfold.config.UnfoldTransitionConfig import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.dagger.UnfoldBackground import com.android.systemui.unfold.dagger.UnfoldBackground
import com.android.systemui.unfold.dagger.UnfoldMain import com.android.systemui.unfold.dagger.UnfoldMain
import com.android.systemui.unfold.updates.FoldProvider 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.updates.screen.ScreenStatusProvider
import com.android.systemui.unfold.util.CurrentActivityTypeProvider import com.android.systemui.unfold.util.CurrentActivityTypeProvider
import com.android.systemui.unfold.util.UnfoldTransitionATracePrefix 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 * 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 * parameters to the factory, all dagger objects are correctly instantiated. See
* [createUnfoldTransitionProgressProvider] for an example. * [createUnfoldSharedComponent] for an example.
*/ */
@Singleton @Singleton
@Component(modules = [UnfoldSharedModule::class]) @Component(modules = [UnfoldSharedModule::class])
internal interface UnfoldSharedComponent { interface UnfoldSharedComponent {
@Component.Factory @Component.Factory
interface Factory { interface Factory {
@@ -58,9 +60,11 @@ internal interface UnfoldSharedComponent {
@BindsInstance @UnfoldMain executor: Executor, @BindsInstance @UnfoldMain executor: Executor,
@BindsInstance @UnfoldBackground backgroundExecutor: Executor, @BindsInstance @UnfoldBackground backgroundExecutor: Executor,
@BindsInstance @UnfoldTransitionATracePrefix tracingTagPrefix: String, @BindsInstance @UnfoldTransitionATracePrefix tracingTagPrefix: String,
@BindsInstance windowManager: IWindowManager,
@BindsInstance contentResolver: ContentResolver = context.contentResolver @BindsInstance contentResolver: ContentResolver = context.contentResolver
): UnfoldSharedComponent ): UnfoldSharedComponent
} }
val unfoldTransitionProvider: Optional<UnfoldTransitionProgressProvider> val unfoldTransitionProvider: Optional<UnfoldTransitionProgressProvider>
val rotationChangeProvider: RotationChangeProvider
} }

View File

@@ -20,6 +20,7 @@ package com.android.systemui.unfold
import android.content.Context import android.content.Context
import android.hardware.SensorManager import android.hardware.SensorManager
import android.os.Handler import android.os.Handler
import android.view.IWindowManager
import com.android.systemui.unfold.config.UnfoldTransitionConfig import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.updates.FoldProvider import com.android.systemui.unfold.updates.FoldProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
@@ -27,14 +28,15 @@ import com.android.systemui.unfold.util.CurrentActivityTypeProvider
import java.util.concurrent.Executor 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 * This wraps the autogenerated factory (for discoverability), and is needed as Launcher has to
* is provided in [UnfoldSharedModule]. * 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. * This should **never** be called from sysui, as the object is already provided in that process.
*/ */
fun createUnfoldTransitionProgressProvider( fun createUnfoldSharedComponent(
context: Context, context: Context,
config: UnfoldTransitionConfig, config: UnfoldTransitionConfig,
screenStatusProvider: ScreenStatusProvider, screenStatusProvider: ScreenStatusProvider,
@@ -44,8 +46,9 @@ fun createUnfoldTransitionProgressProvider(
mainHandler: Handler, mainHandler: Handler,
mainExecutor: Executor, mainExecutor: Executor,
backgroundExecutor: Executor, backgroundExecutor: Executor,
tracingTagPrefix: String tracingTagPrefix: String,
): UnfoldTransitionProgressProvider = windowManager: IWindowManager,
): UnfoldSharedComponent =
DaggerUnfoldSharedComponent.factory() DaggerUnfoldSharedComponent.factory()
.create( .create(
context, context,
@@ -57,9 +60,6 @@ fun createUnfoldTransitionProgressProvider(
mainHandler, mainHandler,
mainExecutor, mainExecutor,
backgroundExecutor, backgroundExecutor,
tracingTagPrefix) tracingTagPrefix,
.unfoldTransitionProvider windowManager,
.orElse(null) )
?: throw IllegalStateException(
"Trying to create " +
"UnfoldTransitionProgressProvider when the transition is disabled")

View File

@@ -26,7 +26,8 @@ import com.android.systemui.unfold.util.CallbackController
* *
* onTransitionProgress callback could be called on each frame. * 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> { interface UnfoldTransitionProgressProvider : CallbackController<TransitionProgressListener> {

View File

@@ -24,6 +24,7 @@ import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.dagger.UnfoldMain import com.android.systemui.unfold.dagger.UnfoldMain
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener 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_CLOSED_DEGREES
import com.android.systemui.unfold.updates.hinge.FULLY_OPEN_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.hinge.HingeAngleProvider
@@ -40,22 +41,24 @@ constructor(
private val screenStatusProvider: ScreenStatusProvider, private val screenStatusProvider: ScreenStatusProvider,
private val foldProvider: FoldProvider, private val foldProvider: FoldProvider,
private val activityTypeProvider: CurrentActivityTypeProvider, private val activityTypeProvider: CurrentActivityTypeProvider,
private val rotationChangeProvider: RotationChangeProvider,
@UnfoldMain private val mainExecutor: Executor, @UnfoldMain private val mainExecutor: Executor,
@UnfoldMain private val handler: Handler @UnfoldMain private val handler: Handler
) : FoldStateProvider { ) : FoldStateProvider {
private val outputListeners: MutableList<FoldUpdatesListener> = mutableListOf() private val outputListeners: MutableList<FoldUpdatesListener> = mutableListOf()
@FoldUpdate @FoldUpdate private var lastFoldUpdate: Int? = null
private var lastFoldUpdate: Int? = null
@FloatRange(from = 0.0, to = 180.0) @FloatRange(from = 0.0, to = 180.0) private var lastHingeAngle: Float = 0f
private var lastHingeAngle: Float = 0f
private val hingeAngleListener = HingeAngleListener() private val hingeAngleListener = HingeAngleListener()
private val screenListener = ScreenStatusListener() private val screenListener = ScreenStatusListener()
private val foldStateListener = FoldStateListener() 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 * Time after which [FOLD_UPDATE_FINISH_HALF_OPEN] is emitted following a
@@ -72,6 +75,7 @@ constructor(
foldProvider.registerCallback(foldStateListener, mainExecutor) foldProvider.registerCallback(foldStateListener, mainExecutor)
screenStatusProvider.addCallback(screenListener) screenStatusProvider.addCallback(screenListener)
hingeAngleProvider.addCallback(hingeAngleListener) hingeAngleProvider.addCallback(hingeAngleListener)
rotationChangeProvider.addCallback(rotationListener)
} }
override fun stop() { override fun stop() {
@@ -79,6 +83,7 @@ constructor(
foldProvider.unregisterCallback(foldStateListener) foldProvider.unregisterCallback(foldStateListener)
hingeAngleProvider.removeCallback(hingeAngleListener) hingeAngleProvider.removeCallback(hingeAngleListener)
hingeAngleProvider.stop() hingeAngleProvider.stop()
rotationChangeProvider.removeCallback(rotationListener)
} }
override fun addCallback(listener: FoldUpdatesListener) { override fun addCallback(listener: FoldUpdatesListener) {
@@ -90,7 +95,8 @@ constructor(
} }
override val isFinishedOpening: Boolean override val isFinishedOpening: Boolean
get() = !isFolded && get() =
!isFolded &&
(lastFoldUpdate == FOLD_UPDATE_FINISH_FULL_OPEN || (lastFoldUpdate == FOLD_UPDATE_FINISH_FULL_OPEN ||
lastFoldUpdate == FOLD_UPDATE_FINISH_HALF_OPEN) lastFoldUpdate == FOLD_UPDATE_FINISH_HALF_OPEN)
@@ -168,7 +174,7 @@ constructor(
private fun notifyFoldUpdate(@FoldUpdate update: Int) { private fun notifyFoldUpdate(@FoldUpdate update: Int) {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, stateToString(update)) Log.d(TAG, update.name())
} }
outputListeners.forEach { it.onFoldUpdate(update) } outputListeners.forEach { it.onFoldUpdate(update) }
lastFoldUpdate = update lastFoldUpdate = update
@@ -185,6 +191,8 @@ constructor(
handler.removeCallbacks(timeoutRunnable) handler.removeCallbacks(timeoutRunnable)
} }
private fun cancelAnimation(): Unit = notifyFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN)
private inner class ScreenStatusListener : ScreenStatusProvider.ScreenListener { private inner class ScreenStatusListener : ScreenStatusProvider.ScreenListener {
override fun onScreenTurnedOn() { override fun onScreenTurnedOn() {
@@ -225,16 +233,10 @@ constructor(
onHingeAngle(angle) onHingeAngle(angle)
} }
} }
private inner class TimeoutRunnable : Runnable {
override fun run() {
notifyFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN)
}
}
} }
private fun stateToString(@FoldUpdate update: Int): String { fun @receiver:FoldUpdate Int.name() =
return when (update) { when (this) {
FOLD_UPDATE_START_OPENING -> "START_OPENING" FOLD_UPDATE_START_OPENING -> "START_OPENING"
FOLD_UPDATE_START_CLOSING -> "START_CLOSING" FOLD_UPDATE_START_CLOSING -> "START_CLOSING"
FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> "UNFOLDED_SCREEN_AVAILABLE" 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" FOLD_UPDATE_FINISH_CLOSED -> "FINISH_CLOSED"
else -> "UNKNOWN" else -> "UNKNOWN"
} }
}
private const val TAG = "DeviceFoldProvider" private const val TAG = "DeviceFoldProvider"
private const val DEBUG = false private const val DEBUG = false
/** Threshold after which we consider the device fully unfolded. */ /** Threshold after which we consider the device fully unfolded. */
@VisibleForTesting @VisibleForTesting const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
/** Fold animation on top of apps only when the angle exceeds this threshold. */ /** Fold animation on top of apps only when the angle exceeds this threshold. */
@VisibleForTesting @VisibleForTesting const val START_CLOSING_ON_APPS_THRESHOLD_DEGREES = 60
const val START_CLOSING_ON_APPS_THRESHOLD_DEGREES = 60

View File

@@ -0,0 +1,93 @@
/*
* 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 android.content.Context
import android.os.RemoteException
import android.view.IRotationWatcher
import android.view.IWindowManager
import android.view.Surface.Rotation
import com.android.systemui.unfold.dagger.UnfoldMain
import com.android.systemui.unfold.util.CallbackController
import java.util.concurrent.Executor
import javax.inject.Inject
/**
* Allows to subscribe to rotation changes.
*
* This is needed as rotation updates from [IWindowManager] are received in a binder thread, while
* most of the times we want them in the main one. Updates are provided for the display associated
* to [context].
*/
class RotationChangeProvider
@Inject
constructor(
private val windowManagerInterface: IWindowManager,
private val context: Context,
@UnfoldMain private val mainExecutor: Executor,
) : CallbackController<RotationChangeProvider.RotationListener> {
private val listeners = mutableListOf<RotationListener>()
private val rotationWatcher = RotationWatcher()
override fun addCallback(listener: RotationListener) {
mainExecutor.execute {
if (listeners.isEmpty()) {
subscribeToRotation()
}
listeners += listener
}
}
override fun removeCallback(listener: RotationListener) {
mainExecutor.execute {
listeners -= listener
if (listeners.isEmpty()) {
unsubscribeToRotation()
}
}
}
private fun subscribeToRotation() {
try {
windowManagerInterface.watchRotation(rotationWatcher, context.displayId)
} catch (e: RemoteException) {
throw e.rethrowFromSystemServer()
}
}
private fun unsubscribeToRotation() {
try {
windowManagerInterface.removeRotationWatcher(rotationWatcher)
} catch (e: RemoteException) {
throw e.rethrowFromSystemServer()
}
}
/** Gets notified of rotation changes. */
fun interface RotationListener {
/** Called once rotation changes. */
fun onRotationChanged(@Rotation newRotation: Int)
}
private inner class RotationWatcher : IRotationWatcher.Stub() {
override fun onRotationChanged(rotation: Int) {
mainExecutor.execute { listeners.forEach { it.onRotationChanged(rotation) } }
}
}
}