Tune fold animation on top of apps

On top of apps, the fold animation is triggered only after a threshold. On Launcher, there is not threshold.

In this way, we don't show fold animation while apps might be handling table top/half_folded state.

+ timeout to finish the animation made shorter

Test: atest DeviceFoldStateProviderTest
Bug: 225185239
Change-Id: Idaed140cec723ab54ed8c75aaf4a8e80261dc0eb
This commit is contained in:
Nicolo' Mazzucato
2022-03-24 11:31:52 +00:00
parent 0a6a138074
commit ef3dfe2a8f
4 changed files with 78 additions and 3 deletions

View File

@@ -16,6 +16,7 @@
package com.android.systemui.unfold
import android.app.ActivityManager
import android.content.ContentResolver
import android.content.Context
import android.hardware.SensorManager
@@ -51,6 +52,7 @@ internal interface UnfoldSharedComponent {
@BindsInstance config: UnfoldTransitionConfig,
@BindsInstance screenStatusProvider: ScreenStatusProvider,
@BindsInstance deviceStateManager: DeviceStateManager,
@BindsInstance activityManager: ActivityManager,
@BindsInstance sensorManager: SensorManager,
@BindsInstance @Main handler: Handler,
@BindsInstance @Main executor: Executor,

View File

@@ -17,6 +17,7 @@
package com.android.systemui.unfold
import android.app.ActivityManager
import android.content.Context
import android.hardware.SensorManager
import android.hardware.devicestate.DeviceStateManager
@@ -39,6 +40,7 @@ fun createUnfoldTransitionProgressProvider(
config: UnfoldTransitionConfig,
screenStatusProvider: ScreenStatusProvider,
deviceStateManager: DeviceStateManager,
activityManager: ActivityManager,
sensorManager: SensorManager,
mainHandler: Handler,
mainExecutor: Executor,
@@ -51,6 +53,7 @@ fun createUnfoldTransitionProgressProvider(
config,
screenStatusProvider,
deviceStateManager,
activityManager,
sensorManager,
mainHandler,
mainExecutor,

View File

@@ -16,6 +16,8 @@
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
@@ -39,6 +41,7 @@ constructor(
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
) : FoldStateProvider {
@@ -92,10 +95,12 @@ constructor(
}
val isClosing = angle < lastHingeAngle
val closingThreshold = getClosingThreshold()
val closingThresholdMet = closingThreshold == null || angle < closingThreshold
val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES
val closingEventDispatched = lastFoldUpdate == FOLD_UPDATE_START_CLOSING
if (isClosing && !closingEventDispatched && !isFullyOpened) {
if (isClosing && closingThresholdMet && !closingEventDispatched && !isFullyOpened) {
notifyFoldUpdate(FOLD_UPDATE_START_CLOSING)
}
@@ -113,6 +118,33 @@ constructor(
outputListeners.forEach { it.onHingeAngleUpdate(angle) }
}
/**
* Fold animation should be started only after the threshold returned here.
*
* This has been introduced because the fold animation might be distracting/unwanted on top of
* 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)
?.configuration
?.windowConfiguration
?.activityType
?: return null
if (DEBUG) {
Log.d(TAG, "activityType=" + activityType)
}
return if (activityType == ACTIVITY_TYPE_HOME) {
null
} else {
START_CLOSING_ON_APPS_THRESHOLD_DEGREES
}
}
private inner class FoldStateListener(context: Context) :
DeviceStateManager.FoldStateListener(
context,
@@ -199,7 +231,10 @@ private const val DEBUG = false
* Time after which [FOLD_UPDATE_FINISH_HALF_OPEN] is emitted following a
* [FOLD_UPDATE_START_CLOSING] or [FOLD_UPDATE_START_OPENING] event, if an end state is not reached.
*/
@VisibleForTesting const val HALF_OPENED_TIMEOUT_MILLIS = 1000L
@VisibleForTesting const val HALF_OPENED_TIMEOUT_MILLIS = 600L
/** Threshold after which we consider the device fully unfolded. */
@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

View File

@@ -16,6 +16,11 @@
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
@@ -30,7 +35,6 @@ 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 java.lang.Exception
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -51,6 +55,8 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@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>
@@ -80,6 +86,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
hingeAngleProvider,
screenStatusProvider,
deviceStateManager,
activityManager,
context.mainExecutor,
handler)
@@ -113,6 +120,9 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
}
null
}
// By default, we're on launcher.
setupForegroundActivityType(ACTIVITY_TYPE_HOME)
}
@Test
@@ -258,6 +268,31 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
}
}
@Test
fun startClosingEvent_whileNotOnLauncher_doesNotTriggerBeforeThreshold() {
setupForegroundActivityType(ACTIVITY_TYPE_STANDARD)
sendHingeAngleEvent(180)
sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
assertThat(foldUpdates).isEmpty()
}
@Test
fun startClosingEvent_whileNotOnLauncher_triggersAfterThreshold() {
setupForegroundActivityType(ACTIVITY_TYPE_STANDARD)
sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES)
sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES - 1)
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 simulateTimeout(waitTime: Long = HALF_OPENED_TIMEOUT_MILLIS) {
val runnableDelay = scheduledRunnableDelay ?: throw Exception("No runnable scheduled.")
if (waitTime >= runnableDelay) {