Merge "Support hinge angle sensor in unfold animation" into sc-v2-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
6de15e5981
@@ -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
|
||||
|
||||
@@ -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" />
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,6 +19,7 @@ import android.annotation.IntDef
|
||||
|
||||
interface UnfoldTransitionConfig {
|
||||
val isEnabled: Boolean
|
||||
val isHingeAngleEnabled: Boolean
|
||||
|
||||
@AnimationMode
|
||||
val mode: Int
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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]) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user