Merge "Tune fold animation on top of apps" into tm-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
c1f95fd38b
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.android.systemui.unfold
|
package com.android.systemui.unfold
|
||||||
|
|
||||||
|
import android.app.ActivityManager
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.hardware.SensorManager
|
import android.hardware.SensorManager
|
||||||
@@ -51,6 +52,7 @@ internal interface UnfoldSharedComponent {
|
|||||||
@BindsInstance config: UnfoldTransitionConfig,
|
@BindsInstance config: UnfoldTransitionConfig,
|
||||||
@BindsInstance screenStatusProvider: ScreenStatusProvider,
|
@BindsInstance screenStatusProvider: ScreenStatusProvider,
|
||||||
@BindsInstance deviceStateManager: DeviceStateManager,
|
@BindsInstance deviceStateManager: DeviceStateManager,
|
||||||
|
@BindsInstance activityManager: ActivityManager,
|
||||||
@BindsInstance sensorManager: SensorManager,
|
@BindsInstance sensorManager: SensorManager,
|
||||||
@BindsInstance @Main handler: Handler,
|
@BindsInstance @Main handler: Handler,
|
||||||
@BindsInstance @Main executor: Executor,
|
@BindsInstance @Main executor: Executor,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package com.android.systemui.unfold
|
package com.android.systemui.unfold
|
||||||
|
|
||||||
|
import android.app.ActivityManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.hardware.SensorManager
|
import android.hardware.SensorManager
|
||||||
import android.hardware.devicestate.DeviceStateManager
|
import android.hardware.devicestate.DeviceStateManager
|
||||||
@@ -39,6 +40,7 @@ fun createUnfoldTransitionProgressProvider(
|
|||||||
config: UnfoldTransitionConfig,
|
config: UnfoldTransitionConfig,
|
||||||
screenStatusProvider: ScreenStatusProvider,
|
screenStatusProvider: ScreenStatusProvider,
|
||||||
deviceStateManager: DeviceStateManager,
|
deviceStateManager: DeviceStateManager,
|
||||||
|
activityManager: ActivityManager,
|
||||||
sensorManager: SensorManager,
|
sensorManager: SensorManager,
|
||||||
mainHandler: Handler,
|
mainHandler: Handler,
|
||||||
mainExecutor: Executor,
|
mainExecutor: Executor,
|
||||||
@@ -51,6 +53,7 @@ fun createUnfoldTransitionProgressProvider(
|
|||||||
config,
|
config,
|
||||||
screenStatusProvider,
|
screenStatusProvider,
|
||||||
deviceStateManager,
|
deviceStateManager,
|
||||||
|
activityManager,
|
||||||
sensorManager,
|
sensorManager,
|
||||||
mainHandler,
|
mainHandler,
|
||||||
mainExecutor,
|
mainExecutor,
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
package com.android.systemui.unfold.updates
|
package com.android.systemui.unfold.updates
|
||||||
|
|
||||||
import android.annotation.FloatRange
|
import android.annotation.FloatRange
|
||||||
|
import android.app.ActivityManager
|
||||||
|
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.hardware.devicestate.DeviceStateManager
|
import android.hardware.devicestate.DeviceStateManager
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
@@ -39,6 +41,7 @@ constructor(
|
|||||||
private val hingeAngleProvider: HingeAngleProvider,
|
private val hingeAngleProvider: HingeAngleProvider,
|
||||||
private val screenStatusProvider: ScreenStatusProvider,
|
private val screenStatusProvider: ScreenStatusProvider,
|
||||||
private val deviceStateManager: DeviceStateManager,
|
private val deviceStateManager: DeviceStateManager,
|
||||||
|
private val activityManager: ActivityManager,
|
||||||
@Main private val mainExecutor: Executor,
|
@Main private val mainExecutor: Executor,
|
||||||
@Main private val handler: Handler
|
@Main private val handler: Handler
|
||||||
) : FoldStateProvider {
|
) : FoldStateProvider {
|
||||||
@@ -92,10 +95,12 @@ constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val isClosing = angle < lastHingeAngle
|
val isClosing = angle < lastHingeAngle
|
||||||
|
val closingThreshold = getClosingThreshold()
|
||||||
|
val closingThresholdMet = closingThreshold == null || angle < closingThreshold
|
||||||
val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES
|
val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES
|
||||||
val closingEventDispatched = lastFoldUpdate == FOLD_UPDATE_START_CLOSING
|
val closingEventDispatched = lastFoldUpdate == FOLD_UPDATE_START_CLOSING
|
||||||
|
|
||||||
if (isClosing && !closingEventDispatched && !isFullyOpened) {
|
if (isClosing && closingThresholdMet && !closingEventDispatched && !isFullyOpened) {
|
||||||
notifyFoldUpdate(FOLD_UPDATE_START_CLOSING)
|
notifyFoldUpdate(FOLD_UPDATE_START_CLOSING)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,6 +118,33 @@ constructor(
|
|||||||
outputListeners.forEach { it.onHingeAngleUpdate(angle) }
|
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) :
|
private inner class FoldStateListener(context: Context) :
|
||||||
DeviceStateManager.FoldStateListener(
|
DeviceStateManager.FoldStateListener(
|
||||||
context,
|
context,
|
||||||
@@ -199,7 +231,10 @@ private const val DEBUG = false
|
|||||||
* Time after which [FOLD_UPDATE_FINISH_HALF_OPEN] is emitted following a
|
* 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.
|
* [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. */
|
/** 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
|
||||||
|
|||||||
@@ -16,6 +16,11 @@
|
|||||||
|
|
||||||
package com.android.systemui.unfold.updates
|
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
|
||||||
import android.hardware.devicestate.DeviceStateManager.FoldStateListener
|
import android.hardware.devicestate.DeviceStateManager.FoldStateListener
|
||||||
import android.os.Handler
|
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.unfold.util.FoldableTestUtils
|
||||||
import com.android.systemui.util.mockito.any
|
import com.android.systemui.util.mockito.any
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import java.lang.Exception
|
|
||||||
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
|
||||||
@@ -51,6 +55,8 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Mock private lateinit var deviceStateManager: DeviceStateManager
|
@Mock private lateinit var deviceStateManager: DeviceStateManager
|
||||||
|
|
||||||
|
@Mock private lateinit var activityManager: ActivityManager
|
||||||
|
|
||||||
@Mock private lateinit var handler: Handler
|
@Mock private lateinit var handler: Handler
|
||||||
|
|
||||||
@Captor private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
|
@Captor private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
|
||||||
@@ -80,6 +86,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
|
|||||||
hingeAngleProvider,
|
hingeAngleProvider,
|
||||||
screenStatusProvider,
|
screenStatusProvider,
|
||||||
deviceStateManager,
|
deviceStateManager,
|
||||||
|
activityManager,
|
||||||
context.mainExecutor,
|
context.mainExecutor,
|
||||||
handler)
|
handler)
|
||||||
|
|
||||||
@@ -113,6 +120,9 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// By default, we're on launcher.
|
||||||
|
setupForegroundActivityType(ACTIVITY_TYPE_HOME)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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) {
|
private fun simulateTimeout(waitTime: Long = HALF_OPENED_TIMEOUT_MILLIS) {
|
||||||
val runnableDelay = scheduledRunnableDelay ?: throw Exception("No runnable scheduled.")
|
val runnableDelay = scheduledRunnableDelay ?: throw Exception("No runnable scheduled.")
|
||||||
if (waitTime >= runnableDelay) {
|
if (waitTime >= runnableDelay) {
|
||||||
|
|||||||
Reference in New Issue
Block a user