Merge "Support hinge angle sensor in unfold animation" into sc-v2-dev

This commit is contained in:
Nick Chameyev
2021-09-01 09:25:47 +00:00
committed by Android (Google) Code Review
11 changed files with 101 additions and 9 deletions

View File

@@ -660,6 +660,9 @@
<!-- Indicates whether to enable an animation when unfolding a device or not -->
<bool name="config_unfoldTransitionEnabled">false</bool>
<!-- Indicates whether to enable hinge angle sensor when using unfold animation -->
<bool name="config_unfoldTransitionHingeAngle">false</bool>
<!-- Indicates that the device supports having more than one internal display on at the same
time. Only applicable to devices with more than one internal display. If this option is
set to false, DisplayManager will make additional effort to ensure no more than 1 internal

View File

@@ -3842,6 +3842,7 @@
<java-symbol type="string" name="config_foldedArea" />
<java-symbol type="bool" name="config_supportsConcurrentInternalDisplays" />
<java-symbol type="bool" name="config_unfoldTransitionEnabled" />
<java-symbol type="bool" name="config_unfoldTransitionHingeAngle" />
<java-symbol type="array" name="config_perDeviceStateRotationLockDefaults" />

View File

@@ -29,6 +29,7 @@ import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvide
import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider
import com.android.systemui.unfold.updates.DeviceFoldStateProvider
import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider
import com.android.systemui.unfold.updates.hinge.HingeSensorAngleProvider
import com.android.systemui.unfold.updates.hinge.RotationSensorHingeAngleProvider
import java.lang.IllegalStateException
import java.util.concurrent.Executor
@@ -50,7 +51,13 @@ fun createUnfoldTransitionProgressProvider(
val hingeAngleProvider =
if (config.mode == ANIMATION_MODE_HINGE_ANGLE) {
RotationSensorHingeAngleProvider(sensorManager)
// TODO: after removing temporary "config.mode" we should just
// switch between fixed timing and hinge sensor based on this flag
if (config.isHingeAngleEnabled) {
HingeSensorAngleProvider(sensorManager)
} else {
RotationSensorHingeAngleProvider(sensorManager)
}
} else {
EmptyHingeAngleProvider()
}

View File

@@ -25,6 +25,9 @@ internal class ResourceUnfoldTransitionConfig(
override val isEnabled: Boolean
get() = readIsEnabled() && mode != ANIMATION_MODE_DISABLED
override val isHingeAngleEnabled: Boolean
get() = readIsHingeAngleEnabled()
@AnimationMode
override val mode: Int
get() = SystemProperties.getInt(UNFOLD_TRANSITION_MODE_PROPERTY_NAME,
@@ -32,6 +35,9 @@ internal class ResourceUnfoldTransitionConfig(
private fun readIsEnabled(): Boolean = context.resources
.getBoolean(com.android.internal.R.bool.config_unfoldTransitionEnabled)
private fun readIsHingeAngleEnabled(): Boolean = context.resources
.getBoolean(com.android.internal.R.bool.config_unfoldTransitionHingeAngle)
}
/**

View File

@@ -19,6 +19,7 @@ import android.annotation.IntDef
interface UnfoldTransitionConfig {
val isEnabled: Boolean
val isHingeAngleEnabled: Boolean
@AnimationMode
val mode: Int

View File

@@ -16,6 +16,7 @@
package com.android.systemui.unfold.progress
import android.os.Handler
import android.util.MathUtils.saturate
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringAnimation
@@ -24,6 +25,7 @@ import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE
import com.android.systemui.unfold.updates.FoldStateProvider
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
@@ -33,7 +35,6 @@ import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
* Maps fold updates to unfold transition progress using DynamicAnimation.
*
* TODO(b/193793338) Current limitations:
* - doesn't handle folding transition
* - doesn't handle postures
*/
internal class PhysicsBasedUnfoldTransitionProgressProvider(
@@ -75,14 +76,20 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider(
override fun onHingeAngleUpdate(angle: Float) {
if (!isTransitionRunning || isAnimatedCancelRunning) return
springAnimation.animateToFinalPosition(angle / 180f)
val progress = saturate(angle / FINAL_HINGE_ANGLE_POSITION)
springAnimation.animateToFinalPosition(progress)
}
override fun onFoldUpdate(@FoldUpdate update: Int) {
when (update) {
FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> {
onStartTransition()
startTransition(startValue = 0f)
// Stop the animation if the device has already opened by the time when
// the display is available as we won't receive the full open event anymore
if (foldStateProvider.isFullyOpened) {
cancelTransition(endValue = 1f, animate = true)
}
}
FOLD_UPDATE_FINISH_FULL_OPEN -> {
cancelTransition(endValue = 1f, animate = true)
@@ -90,13 +97,16 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider(
FOLD_UPDATE_FINISH_CLOSED -> {
cancelTransition(endValue = 0f, animate = false)
}
FOLD_UPDATE_START_CLOSING -> {
startTransition(startValue = 1f)
}
}
}
private fun cancelTransition(endValue: Float, animate: Boolean) {
handler.removeCallbacks(timeoutRunnable)
if (animate) {
if (isTransitionRunning && animate) {
isAnimatedCancelRunning = true
springAnimation.animateToFinalPosition(endValue)
} else {
@@ -182,3 +192,4 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider(
private const val TRANSITION_TIMEOUT_MILLIS = 2000L
private const val SPRING_STIFFNESS = 200.0f
private const val MINIMAL_VISIBLE_CHANGE = 0.001f
private const val FINAL_HINGE_ANGLE_POSITION = 165f

View File

@@ -68,20 +68,29 @@ internal class DeviceFoldStateProvider(
outputListeners.remove(listener)
}
override val isFullyOpened: Boolean
get() = !isFolded && lastFoldUpdate == FOLD_UPDATE_FINISH_FULL_OPEN
private fun onHingeAngle(angle: Float) {
when (lastFoldUpdate) {
FOLD_UPDATE_FINISH_FULL_OPEN -> {
if (FULLY_OPEN_DEGREES - angle > MOVEMENT_THRESHOLD_DEGREES) {
if (FULLY_OPEN_DEGREES - angle > START_CLOSING_THRESHOLD_DEGREES) {
lastFoldUpdate = FOLD_UPDATE_START_CLOSING
outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_START_CLOSING) }
}
}
FOLD_UPDATE_START_OPENING, FOLD_UPDATE_START_CLOSING -> {
FOLD_UPDATE_START_OPENING -> {
if (FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES) {
lastFoldUpdate = FOLD_UPDATE_FINISH_FULL_OPEN
outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) }
}
}
FOLD_UPDATE_START_CLOSING -> {
if (FULLY_OPEN_DEGREES - angle < START_CLOSING_THRESHOLD_DEGREES) {
lastFoldUpdate = FOLD_UPDATE_FINISH_FULL_OPEN
outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) }
}
}
}
outputListeners.forEach { it.onHingeAngleUpdate(angle) }
@@ -120,5 +129,5 @@ internal class DeviceFoldStateProvider(
}
}
private const val MOVEMENT_THRESHOLD_DEGREES = 10f
private const val FULLY_OPEN_THRESHOLD_DEGREES = 10f
private const val START_CLOSING_THRESHOLD_DEGREES = 95f
private const val FULLY_OPEN_THRESHOLD_DEGREES = 15f

View File

@@ -28,6 +28,8 @@ internal interface FoldStateProvider : CallbackController<FoldUpdatesListener> {
fun start()
fun stop()
val isFullyOpened: Boolean
interface FoldUpdatesListener {
fun onHingeAngleUpdate(@FloatRange(from = 0.0, to = 180.0) angle: Float)
fun onFoldUpdate(@FoldUpdate update: Int)

View File

@@ -3,6 +3,12 @@ package com.android.systemui.unfold.updates.hinge
import androidx.core.util.Consumer
import com.android.systemui.statusbar.policy.CallbackController
/**
* Emits device hinge angle values (angle between two integral parts of the device).
* The hinge angle could be from 0 to 360 degrees inclusive.
* For foldable devices usually 0 corresponds to fully closed (folded) state and
* 180 degrees corresponds to fully open (flat) state
*/
internal interface HingeAngleProvider : CallbackController<Consumer<Float>> {
fun start()
fun stop()

View File

@@ -0,0 +1,42 @@
package com.android.systemui.unfold.updates.hinge
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import androidx.core.util.Consumer
internal class HingeSensorAngleProvider(
private val sensorManager: SensorManager
) : HingeAngleProvider {
private val sensorListener = HingeAngleSensorListener()
private val listeners: MutableList<Consumer<Float>> = arrayListOf()
override fun start() {
val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_HINGE_ANGLE)
sensorManager.registerListener(sensorListener, sensor, SensorManager.SENSOR_DELAY_FASTEST)
}
override fun stop() {
sensorManager.unregisterListener(sensorListener)
}
override fun removeCallback(listener: Consumer<Float>) {
listeners.remove(listener)
}
override fun addCallback(listener: Consumer<Float>) {
listeners.add(listener)
}
private inner class HingeAngleSensorListener : SensorEventListener {
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
}
override fun onSensorChanged(event: SensorEvent) {
listeners.forEach { it.accept(event.values[0]) }
}
}
}

View File

@@ -58,6 +58,10 @@ class UnfoldLightRevealOverlayAnimation @Inject constructor(
}
override fun onTransitionStarted() {
// When unfolding the view is added earlier, add view for folding case
if (scrimView == null) {
addOverlayView()
}
}
}