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:
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user