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 --> <!-- Indicates whether to enable an animation when unfolding a device or not -->
<bool name="config_unfoldTransitionEnabled">false</bool> <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 <!-- 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 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 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="string" name="config_foldedArea" />
<java-symbol type="bool" name="config_supportsConcurrentInternalDisplays" /> <java-symbol type="bool" name="config_supportsConcurrentInternalDisplays" />
<java-symbol type="bool" name="config_unfoldTransitionEnabled" /> <java-symbol type="bool" name="config_unfoldTransitionEnabled" />
<java-symbol type="bool" name="config_unfoldTransitionHingeAngle" />
<java-symbol type="array" name="config_perDeviceStateRotationLockDefaults" /> <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.progress.PhysicsBasedUnfoldTransitionProgressProvider
import com.android.systemui.unfold.updates.DeviceFoldStateProvider import com.android.systemui.unfold.updates.DeviceFoldStateProvider
import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider 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 com.android.systemui.unfold.updates.hinge.RotationSensorHingeAngleProvider
import java.lang.IllegalStateException import java.lang.IllegalStateException
import java.util.concurrent.Executor import java.util.concurrent.Executor
@@ -50,7 +51,13 @@ fun createUnfoldTransitionProgressProvider(
val hingeAngleProvider = val hingeAngleProvider =
if (config.mode == ANIMATION_MODE_HINGE_ANGLE) { 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 { } else {
EmptyHingeAngleProvider() EmptyHingeAngleProvider()
} }

View File

@@ -25,6 +25,9 @@ internal class ResourceUnfoldTransitionConfig(
override val isEnabled: Boolean override val isEnabled: Boolean
get() = readIsEnabled() && mode != ANIMATION_MODE_DISABLED get() = readIsEnabled() && mode != ANIMATION_MODE_DISABLED
override val isHingeAngleEnabled: Boolean
get() = readIsHingeAngleEnabled()
@AnimationMode @AnimationMode
override val mode: Int override val mode: Int
get() = SystemProperties.getInt(UNFOLD_TRANSITION_MODE_PROPERTY_NAME, get() = SystemProperties.getInt(UNFOLD_TRANSITION_MODE_PROPERTY_NAME,
@@ -32,6 +35,9 @@ internal class ResourceUnfoldTransitionConfig(
private fun readIsEnabled(): Boolean = context.resources private fun readIsEnabled(): Boolean = context.resources
.getBoolean(com.android.internal.R.bool.config_unfoldTransitionEnabled) .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 { interface UnfoldTransitionConfig {
val isEnabled: Boolean val isEnabled: Boolean
val isHingeAngleEnabled: Boolean
@AnimationMode @AnimationMode
val mode: Int val mode: Int

View File

@@ -16,6 +16,7 @@
package com.android.systemui.unfold.progress package com.android.systemui.unfold.progress
import android.os.Handler import android.os.Handler
import android.util.MathUtils.saturate
import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.FloatPropertyCompat import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringAnimation 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.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED 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_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.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE
import com.android.systemui.unfold.updates.FoldStateProvider import com.android.systemui.unfold.updates.FoldStateProvider
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate 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. * Maps fold updates to unfold transition progress using DynamicAnimation.
* *
* TODO(b/193793338) Current limitations: * TODO(b/193793338) Current limitations:
* - doesn't handle folding transition
* - doesn't handle postures * - doesn't handle postures
*/ */
internal class PhysicsBasedUnfoldTransitionProgressProvider( internal class PhysicsBasedUnfoldTransitionProgressProvider(
@@ -75,14 +76,20 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider(
override fun onHingeAngleUpdate(angle: Float) { override fun onHingeAngleUpdate(angle: Float) {
if (!isTransitionRunning || isAnimatedCancelRunning) return 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) { override fun onFoldUpdate(@FoldUpdate update: Int) {
when (update) { when (update) {
FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> { FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> {
onStartTransition()
startTransition(startValue = 0f) 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 -> { FOLD_UPDATE_FINISH_FULL_OPEN -> {
cancelTransition(endValue = 1f, animate = true) cancelTransition(endValue = 1f, animate = true)
@@ -90,13 +97,16 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider(
FOLD_UPDATE_FINISH_CLOSED -> { FOLD_UPDATE_FINISH_CLOSED -> {
cancelTransition(endValue = 0f, animate = false) cancelTransition(endValue = 0f, animate = false)
} }
FOLD_UPDATE_START_CLOSING -> {
startTransition(startValue = 1f)
}
} }
} }
private fun cancelTransition(endValue: Float, animate: Boolean) { private fun cancelTransition(endValue: Float, animate: Boolean) {
handler.removeCallbacks(timeoutRunnable) handler.removeCallbacks(timeoutRunnable)
if (animate) { if (isTransitionRunning && animate) {
isAnimatedCancelRunning = true isAnimatedCancelRunning = true
springAnimation.animateToFinalPosition(endValue) springAnimation.animateToFinalPosition(endValue)
} else { } else {
@@ -182,3 +192,4 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider(
private const val TRANSITION_TIMEOUT_MILLIS = 2000L private const val TRANSITION_TIMEOUT_MILLIS = 2000L
private const val SPRING_STIFFNESS = 200.0f private const val SPRING_STIFFNESS = 200.0f
private const val MINIMAL_VISIBLE_CHANGE = 0.001f 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) outputListeners.remove(listener)
} }
override val isFullyOpened: Boolean
get() = !isFolded && lastFoldUpdate == FOLD_UPDATE_FINISH_FULL_OPEN
private fun onHingeAngle(angle: Float) { private fun onHingeAngle(angle: Float) {
when (lastFoldUpdate) { when (lastFoldUpdate) {
FOLD_UPDATE_FINISH_FULL_OPEN -> { 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 lastFoldUpdate = FOLD_UPDATE_START_CLOSING
outputListeners.forEach { it.onFoldUpdate(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) { if (FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES) {
lastFoldUpdate = FOLD_UPDATE_FINISH_FULL_OPEN lastFoldUpdate = FOLD_UPDATE_FINISH_FULL_OPEN
outputListeners.forEach { it.onFoldUpdate(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) } outputListeners.forEach { it.onHingeAngleUpdate(angle) }
@@ -120,5 +129,5 @@ internal class DeviceFoldStateProvider(
} }
} }
private const val MOVEMENT_THRESHOLD_DEGREES = 10f private const val START_CLOSING_THRESHOLD_DEGREES = 95f
private const val FULLY_OPEN_THRESHOLD_DEGREES = 10f private const val FULLY_OPEN_THRESHOLD_DEGREES = 15f

View File

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

View File

@@ -3,6 +3,12 @@ package com.android.systemui.unfold.updates.hinge
import androidx.core.util.Consumer import androidx.core.util.Consumer
import com.android.systemui.statusbar.policy.CallbackController 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>> { internal interface HingeAngleProvider : CallbackController<Consumer<Float>> {
fun start() fun start()
fun stop() 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() { override fun onTransitionStarted() {
// When unfolding the view is added earlier, add view for folding case
if (scrimView == null) {
addOverlayView()
}
} }
} }