New back panel

Test: am broadcast -a com.android.systemui.action.SET_FLAG \
        --ei id 1201 --ez value 1
Bug: 217579545
Change-Id: I789771b20fa2a748b06202fe867c4c57cd4f959a
This commit is contained in:
Peter Kalauskas
2022-04-20 21:38:07 +00:00
parent d98aab0a46
commit a674977abd
5 changed files with 1334 additions and 22 deletions

View File

@@ -48,7 +48,50 @@
<!-- The minimum display position of the arrow on the screen -->
<dimen name="navigation_edge_arrow_min_y">64dp</dimen>
<!-- The amount by which the arrow is shifted to avoid the finger-->
<dimen name="navigation_edge_finger_offset">48dp</dimen>
<dimen name="navigation_edge_finger_offset">64dp</dimen>
<!-- The thickness of the arrow -->
<dimen name="navigation_edge_arrow_thickness">4dp</dimen>
<!-- The minimum delta needed to change direction / stop triggering back -->
<dimen name="navigation_edge_minimum_x_delta_for_switch">32dp</dimen>
<!-- entry state -->
<dimen name="navigation_edge_entry_margin">4dp</dimen>
<dimen name="navigation_edge_entry_background_width">8dp</dimen>
<dimen name="navigation_edge_entry_background_height">60dp</dimen>
<dimen name="navigation_edge_entry_edge_corners">6dp</dimen>
<dimen name="navigation_edge_entry_far_corners">6dp</dimen>
<dimen name="navigation_edge_entry_arrow_length">10dp</dimen>
<dimen name="navigation_edge_entry_arrow_height">7dp</dimen>
<!-- pre-threshold -->
<dimen name="navigation_edge_pre_threshold_margin">4dp</dimen>
<dimen name="navigation_edge_pre_threshold_background_width">64dp</dimen>
<dimen name="navigation_edge_pre_threshold_background_height">60dp</dimen>
<dimen name="navigation_edge_pre_threshold_edge_corners">22dp</dimen>
<dimen name="navigation_edge_pre_threshold_far_corners">26dp</dimen>
<!-- post-threshold / active -->
<dimen name="navigation_edge_active_margin">14dp</dimen>
<dimen name="navigation_edge_active_background_width">60dp</dimen>
<dimen name="navigation_edge_active_background_height">60dp</dimen>
<dimen name="navigation_edge_active_edge_corners">30dp</dimen>
<dimen name="navigation_edge_active_far_corners">30dp</dimen>
<dimen name="navigation_edge_active_arrow_length">8dp</dimen>
<dimen name="navigation_edge_active_arrow_height">9dp</dimen>
<!-- stretch @412 dp -->
<dimen name="navigation_edge_stretch_threshold">412dp</dimen>
<dimen name="navigation_edge_stretch_margin">18dp</dimen>
<dimen name="navigation_edge_stretch_background_width">74dp</dimen>
<dimen name="navigation_edge_stretch_background_height">60dp</dimen>
<dimen name="navigation_edge_stretch_left_corners">30dp</dimen>
<dimen name="navigation_edge_stretch_right_corners">30dp</dimen>
<dimen name="navigation_edge_stretched_arrow_length">7dp</dimen>
<dimen name="navigation_edge_stretched_arrow_height">10dp</dimen>
<dimen name="navigation_edge_cancelled_arrow_length">12dp</dimen>
<dimen name="navigation_edge_cancelled_arrow_height">0dp</dimen>
<!-- Height of notification icons in the status bar -->
<dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen>

View File

@@ -0,0 +1,340 @@
package com.android.systemui.navigationbar.gestural
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Path
import android.graphics.RectF
import android.view.View
import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
import com.android.internal.util.LatencyTracker
import com.android.settingslib.Utils
import com.android.systemui.navigationbar.gestural.BackPanelController.DelayedOnAnimationEndListener
private const val TAG = "BackPanel"
private const val DEBUG = false
class BackPanel(context: Context, private val latencyTracker: LatencyTracker) : View(context) {
var arrowsPointLeft = false
set(value) {
if (field != value) {
invalidate()
field = value
}
}
// Arrow color and shape
private val arrowPath = Path()
private val arrowPaint = Paint()
// Arrow background color and shape
private var arrowBackgroundRect = RectF()
private var arrowBackgroundPaint = Paint()
// True if the panel is currently on the left of the screen
var isLeftPanel = false
/**
* Used to track back arrow latency from [android.view.MotionEvent.ACTION_DOWN] to [onDraw]
*/
private var trackingBackArrowLatency = false
/**
* The length of the arrow measured horizontally. Used for animating [arrowPath]
*/
private var arrowLength = AnimatedFloat("arrowLength", SpringForce())
/**
* The height of the arrow measured vertically from its center to its top (i.e. half the total
* height). Used for animating [arrowPath]
*/
private var arrowHeight = AnimatedFloat("arrowHeight", SpringForce())
private val backgroundWidth = AnimatedFloat(
name = "backgroundWidth",
SpringForce().apply {
stiffness = 600f
dampingRatio = 0.65f
})
private val backgroundHeight = AnimatedFloat(
name = "backgroundHeight",
SpringForce().apply {
stiffness = 600f
dampingRatio = 0.65f
})
/**
* Corners of the background closer to the edge of the screen (where the arrow appeared from).
* Used for animating [arrowBackgroundRect]
*/
private val backgroundEdgeCornerRadius = AnimatedFloat(
name = "backgroundEdgeCornerRadius",
SpringForce().apply {
stiffness = 400f
dampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY
})
/**
* Corners of the background further from the edge of the screens (toward the direction the
* arrow is being dragged). Used for animating [arrowBackgroundRect]
*/
private val backgroundDragCornerRadius = AnimatedFloat(
name = "backgroundDragCornerRadius",
SpringForce().apply {
stiffness = 2200f
dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY
})
/**
* Left/right position of the background relative to the canvas. Also corresponds with the
* background's margin relative to the screen edge. The arrow will be centered within the
* background.
*/
private var horizontalTranslation = AnimatedFloat("horizontalTranslation", SpringForce())
/**
* Canvas vertical translation. How far up/down the arrow and background appear relative to the
* canvas.
*/
private var verticalTranslation: AnimatedFloat =
AnimatedFloat("verticalTranslation", SpringForce().apply {
stiffness = SpringForce.STIFFNESS_MEDIUM
})
/**
* Use for drawing debug info. Can only be set if [DEBUG]=true
*/
var drawDebugInfo: ((canvas: Canvas) -> Unit)? = null
set(value) {
if (DEBUG) field = value
}
internal fun updateArrowPaint(arrowThickness: Float) {
// Arrow constants
arrowPaint.strokeWidth = arrowThickness
arrowPaint.color =
Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
arrowBackgroundPaint.color = Utils.getColorAccentDefaultColor(context)
}
private inner class AnimatedFloat(name: String, springForce: SpringForce) {
// The resting position when not stretched by a touch drag
private var restingPosition = 0f
// The current position as updated by the SpringAnimation
var pos = 0f
set(v) {
if (field != v) {
field = v
invalidate()
}
}
val animation: SpringAnimation
init {
val floatProp = object : FloatPropertyCompat<AnimatedFloat>(name) {
override fun setValue(animatedFloat: AnimatedFloat, value: Float) {
animatedFloat.pos = value
}
override fun getValue(animatedFloat: AnimatedFloat): Float = animatedFloat.pos
}
animation = SpringAnimation(this, floatProp)
animation.spring = springForce
}
fun snapTo(newPosition: Float) {
animation.cancel()
restingPosition = newPosition
animation.spring.finalPosition = newPosition
pos = newPosition
}
fun stretchTo(stretchAmount: Float) {
animation.animateToFinalPosition(restingPosition + stretchAmount)
}
fun updateRestingPosition(pos: Float, animated: Boolean) {
restingPosition = pos
if (animated)
animation.animateToFinalPosition(restingPosition)
else
snapTo(restingPosition)
}
}
init {
visibility = GONE
arrowPaint.apply {
style = Paint.Style.STROKE
strokeCap = Paint.Cap.SQUARE
}
arrowBackgroundPaint.apply {
style = Paint.Style.FILL
strokeJoin = Paint.Join.ROUND
strokeCap = Paint.Cap.ROUND
}
}
private fun calculateArrowPath(dx: Float, dy: Float): Path {
arrowPath.reset()
arrowPath.moveTo(dx, -dy)
arrowPath.lineTo(0f, 0f)
arrowPath.lineTo(dx, dy)
arrowPath.moveTo(dx, -dy)
return arrowPath
}
fun addEndListener(endListener: DelayedOnAnimationEndListener): Boolean {
return if (horizontalTranslation.animation.isRunning) {
horizontalTranslation.animation.addEndListener(endListener)
true
} else {
endListener.runNow()
false
}
}
fun setStretch(
arrowLengthStretch: Float,
arrowHeightStretch: Float,
backgroundWidthStretch: Float,
backgroundHeightStretch: Float,
backgroundEdgeCornerRadiusStretch: Float,
backgroundDragCornerRadiusStretch: Float,
horizontalTranslationStretch: Float
) {
arrowLength.stretchTo(arrowLengthStretch)
arrowHeight.stretchTo(arrowHeightStretch)
backgroundWidth.stretchTo(backgroundWidthStretch)
backgroundHeight.stretchTo(backgroundHeightStretch)
backgroundEdgeCornerRadius.stretchTo(backgroundEdgeCornerRadiusStretch)
backgroundDragCornerRadius.stretchTo(backgroundDragCornerRadiusStretch)
horizontalTranslation.stretchTo(horizontalTranslationStretch)
}
fun resetStretch() {
setStretch(0f, 0f, 0f, 0f, 0f, 0f, 0f)
}
/**
* Updates resting arrow and background size not accounting for stretch
*/
internal fun updateRestingArrowDimens(
backgroundWidth: Float,
backgroundHeight: Float,
backgroundEdgeCornerRadius: Float,
backgroundDragCornerRadius: Float,
arrowLength: Float,
arrowHeight: Float,
horizontalTranslation: Float,
animate: Boolean
) {
this.arrowLength.updateRestingPosition(arrowLength, animate)
this.arrowHeight.updateRestingPosition(arrowHeight, animate)
this.backgroundWidth.updateRestingPosition(backgroundWidth, animate)
this.backgroundHeight.updateRestingPosition(backgroundHeight, animate)
this.backgroundEdgeCornerRadius.updateRestingPosition(backgroundEdgeCornerRadius, animate)
this.backgroundDragCornerRadius.updateRestingPosition(backgroundDragCornerRadius, animate)
this.horizontalTranslation.updateRestingPosition(horizontalTranslation, animate)
}
fun animateVertically(yPos: Float) = verticalTranslation.stretchTo(yPos)
fun setArrowStiffness(arrowStiffness: Float, arrowDampingRatio: Float) {
arrowLength.animation.spring.apply {
stiffness = arrowStiffness
dampingRatio = arrowDampingRatio
}
arrowHeight.animation.spring.apply {
stiffness = arrowStiffness
dampingRatio = arrowDampingRatio
}
}
override fun hasOverlappingRendering() = false
override fun onDraw(canvas: Canvas) {
var edgeCorner = backgroundEdgeCornerRadius.pos
val farCorner = backgroundDragCornerRadius.pos
val halfHeight = backgroundHeight.pos / 2
canvas.save()
if (!isLeftPanel) canvas.scale(-1f, 1f, width / 2.0f, 0f)
canvas.translate(
horizontalTranslation.pos,
height * 0.5f + verticalTranslation.pos
)
val arrowBackground = arrowBackgroundRect.apply {
left = 0f
top = -halfHeight
right = backgroundWidth.pos
bottom = halfHeight
}.toPathWithRoundCorners(
topLeft = edgeCorner,
bottomLeft = edgeCorner,
topRight = farCorner,
bottomRight = farCorner
)
canvas.drawPath(arrowBackground, arrowBackgroundPaint)
val dx = arrowLength.pos
val dy = arrowHeight.pos
// How far the arrow bounding box should be from the edge of the screen. Measured from
// either the tip or the back of the arrow, whichever is closer
var arrowOffset = (backgroundWidth.pos - dx) / 2
canvas.translate(
/* dx= */ arrowOffset,
/* dy= */ 0f /* pass 0 for the y position since the canvas was already translated */
)
val arrowPointsAwayFromEdge = !arrowsPointLeft.xor(isLeftPanel)
if (arrowPointsAwayFromEdge) {
canvas.apply {
scale(-1f, 1f, 0f, 0f)
translate(-dx, 0f)
}
}
val arrowPath = calculateArrowPath(dx = dx, dy = dy)
canvas.drawPath(arrowPath, arrowPaint)
canvas.restore()
if (trackingBackArrowLatency) {
latencyTracker.onActionEnd(LatencyTracker.ACTION_SHOW_BACK_ARROW)
trackingBackArrowLatency = false
}
if (DEBUG) drawDebugInfo?.invoke(canvas)
}
fun startTrackingShowBackArrowLatency() {
latencyTracker.onActionStart(LatencyTracker.ACTION_SHOW_BACK_ARROW)
trackingBackArrowLatency = true
}
private fun RectF.toPathWithRoundCorners(
topLeft: Float = 0f,
topRight: Float = 0f,
bottomRight: Float = 0f,
bottomLeft: Float = 0f
): Path = Path().apply {
val corners = floatArrayOf(
topLeft, topLeft,
topRight, topRight,
bottomRight, bottomRight,
bottomLeft, bottomLeft
)
addRoundRect(this@toPathWithRoundCorners, corners, Path.Direction.CW)
}
}

View File

@@ -0,0 +1,735 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.navigationbar.gestural
import android.content.Context
import android.content.res.Configuration
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Point
import android.os.Handler
import android.os.SystemClock
import android.os.VibrationEffect
import android.util.Log
import android.util.MathUtils.constrain
import android.util.MathUtils.saturate
import android.view.Gravity
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.View
import android.view.WindowManager
import android.view.animation.AccelerateInterpolator
import android.view.animation.PathInterpolator
import android.window.BackEvent
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringForce
import com.android.internal.util.LatencyTracker
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.NavigationEdgeBackPlugin
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.ViewController
import com.android.wm.shell.back.BackAnimation
import java.io.PrintWriter
import javax.inject.Inject
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sign
private const val TAG = "BackPanelController"
private const val DEBUG = false
private const val ENABLE_FAILSAFE = true
private const val FAILSAFE_DELAY_MS: Long = 350
/**
* The time required between the arrow-appears vibration effect and the back-committed vibration
* effect. If the arrow is flung quickly, the phone only vibrates once. However, if the arrow is
* held on the screen for a long time, it will vibrate a second time when the back gesture is
* committed.
*/
private const val GESTURE_DURATION_FOR_CLICK_MS = 400
/**
* The min duration arrow remains on screen during a fling event.
*/
private const val FLING_PAUSE_DURATION_MS = 50L
/**
* The min duration arrow remains on screen during a fling event.
*/
private const val MIN_FLING_VELOCITY = 3000
/**
* The amount of rubber banding we do for the vertical translation
*/
private const val RUBBER_BAND_AMOUNT = 15
private const val ARROW_APPEAR_STIFFNESS = 600f
private const val ARROW_APPEAR_DAMPING_RATIO = 0.4f
private const val ARROW_DISAPPEAR_STIFFNESS = 1200f
private const val ARROW_DISAPPEAR_DAMPING_RATIO = SpringForce.DAMPING_RATIO_NO_BOUNCY
/**
* The interpolator used to rubber band
*/
private val RUBBER_BAND_INTERPOLATOR = PathInterpolator(1.0f / 5.0f, 1.0f, 1.0f, 1.0f)
private val ACCELERATE_INTERPOLATOR = AccelerateInterpolator(0.7f)
class BackPanelController private constructor(
context: Context,
private var backAnimation: BackAnimation?,
private val windowManager: WindowManager,
@Main private val mainHandler: Handler,
private val vibratorHelper: VibratorHelper,
private val configurationController: ConfigurationController,
latencyTracker: LatencyTracker
) : ViewController<BackPanel>(BackPanel(context, latencyTracker)), NavigationEdgeBackPlugin {
/**
* Injectable instance to create a new BackPanelController.
*
* Necessary because EdgeBackGestureHandler sometimes needs to create new instances of
* BackPanelController, and we need to match EdgeBackGestureHandler's context.
*/
class Factory @Inject constructor(
private val windowManager: WindowManager,
@Main private val mainHandler: Handler,
private val vibratorHelper: VibratorHelper,
private val configurationController: ConfigurationController,
private val latencyTracker: LatencyTracker
) {
/** Construct a [BackPanelController]. */
fun create(context: Context, backAnimation: BackAnimation?): BackPanelController {
val backPanelController = BackPanelController(
context,
backAnimation,
windowManager,
mainHandler,
vibratorHelper,
configurationController,
latencyTracker
)
backPanelController.init()
return backPanelController
}
}
private var params: EdgePanelParams = EdgePanelParams(resources)
private var currentState: GestureState = GestureState.GONE
private var previousState: GestureState = GestureState.GONE
// Phone should only vibrate the first time the arrow is activated
private var hasHapticPlayed = false
// Screen attributes
private lateinit var layoutParams: WindowManager.LayoutParams
private val displaySize = Point()
private lateinit var backCallback: NavigationEdgeBackPlugin.BackCallback
private var previousXTranslation = 0f
private var totalTouchDelta = 0f
private var velocityTracker: VelocityTracker? = null
set(value) {
if (field != value) field?.recycle()
field = value
}
get() {
if (field == null) field = VelocityTracker.obtain()
return field
}
// The x,y position of the first touch event
private var startX = 0f
private var startY = 0f
private val failsafeRunnable = Runnable { onFailsafe() }
private enum class GestureState {
/* Arrow is off the screen and invisible */
GONE,
/* Arrow is animating in */
ENTRY,
/* could be entry, neutral, or stretched, releasing will commit back */
ACTIVE,
/* releasing will cancel back */
INACTIVE,
/* like committed, but animation takes longer */
FLUNG,
/* back action currently occurring, arrow soon to be GONE */
COMMITTED,
/* back action currently cancelling, arrow soon to be GONE */
CANCELLED
}
/**
* Wrapper around OnAnimationEndListener which runs the given runnable after a delay. The
* runnable is not called if the animation is cancelled
*/
class DelayedOnAnimationEndListener(
private val handler: Handler,
private val runnable: Runnable,
private val delay: Long
) : DynamicAnimation.OnAnimationEndListener {
override fun onAnimationEnd(
animation: DynamicAnimation<*>,
canceled: Boolean,
value: Float,
velocity: Float
) {
animation.removeEndListener(this)
if (!canceled) {
handler.postDelayed(runnable, delay)
}
}
fun runNow() {
runnable.run()
}
}
private val setCommittedEndListener =
DelayedOnAnimationEndListener(
mainHandler,
{ updateArrowState(GestureState.COMMITTED) },
delay = FLING_PAUSE_DURATION_MS
)
private val setGoneEndListener =
DelayedOnAnimationEndListener(
mainHandler,
{
cancelFailsafe()
updateArrowState(GestureState.GONE)
},
delay = 0
)
// Vibration
private var vibrationTime: Long = 0
// Minimum size of the screen's width or height
private var screenSize = 0
/**
* Used for initialization and configuration changes
*/
private fun updateConfiguration() {
params.update(resources)
initializeBackAnimation()
mView.updateArrowPaint(params.arrowThickness)
}
private val configurationListener = object : ConfigurationController.ConfigurationListener {
override fun onConfigChanged(newConfig: Configuration?) {
updateConfiguration()
}
override fun onLayoutDirectionChanged(isLayoutRtl: Boolean) {
updateArrowDirection(isLayoutRtl)
}
}
override fun onViewAttached() {
updateConfiguration()
updateArrowDirection(configurationController.isLayoutRtl)
updateArrowState(GestureState.GONE, force = true)
updateRestingArrowDimens(animated = false, currentState)
configurationController.addCallback(configurationListener)
}
/** Update the arrow direction. The arrow should point the same way for both panels. */
private fun updateArrowDirection(isLayoutRtl: Boolean) {
mView.arrowsPointLeft = isLayoutRtl
}
override fun onViewDetached() {
configurationController.removeCallback(configurationListener)
}
override fun onMotionEvent(event: MotionEvent) {
backAnimation?.onBackMotion(
event,
event.actionMasked,
if (mView.isLeftPanel) BackEvent.EDGE_LEFT else BackEvent.EDGE_RIGHT
)
velocityTracker!!.addMovement(event)
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
resetOnDown()
startX = event.x
startY = event.y
// Reset the arrow to the side
updateArrowState(GestureState.ENTRY)
windowManager.updateViewLayout(mView, layoutParams)
mView.startTrackingShowBackArrowLatency()
}
MotionEvent.ACTION_MOVE -> handleMoveEvent(event)
MotionEvent.ACTION_UP -> {
if (currentState == GestureState.ACTIVE) {
updateArrowState(if (isFlung()) GestureState.FLUNG else GestureState.COMMITTED)
} else {
updateArrowState(GestureState.CANCELLED)
}
velocityTracker = null
}
MotionEvent.ACTION_CANCEL -> {
updateArrowState(GestureState.CANCELLED)
velocityTracker = null
}
}
}
private fun updateArrowStateOnMove(yTranslation: Float, xTranslation: Float) {
when (currentState) {
GestureState.GONE, GestureState.FLUNG, GestureState.COMMITTED, GestureState.CANCELLED ->
return
}
updateArrowState(
when {
// Check if we should transition from ENTRY to ACTIVE
currentState == GestureState.ENTRY && xTranslation > params.swipeTriggerThreshold ->
GestureState.ACTIVE
// Abort if we had continuous motion toward the edge for a while, OR the direction
// in Y is bigger than X * 2
currentState == GestureState.ACTIVE &&
((totalTouchDelta < 0 && -totalTouchDelta > params.minDeltaForSwitch) ||
(yTranslation > xTranslation * 2)) ->
GestureState.INACTIVE
// Re-activate if we had continuous motion away from the edge for a while
currentState == GestureState.INACTIVE &&
(totalTouchDelta > 0 && totalTouchDelta > params.minDeltaForSwitch) ->
GestureState.ACTIVE
// By default assume the current direction is kept
else -> currentState
}
)
}
private fun handleMoveEvent(event: MotionEvent) {
when (currentState) {
GestureState.GONE, GestureState.FLUNG, GestureState.COMMITTED,
GestureState.CANCELLED -> return
}
val x = event.x
val y = event.y
val yOffset = y - startY
// How far in the y direction we are from the original touch
val yTranslation = abs(yOffset)
// How far in the x direction we are from the original touch ignoring motion that
// occurs between the screen edge and the touch start.
val xTranslation = max(0f, if (mView.isLeftPanel) x - startX else startX - x)
// Compared to last time, how far we moved in the x direction. If <0, we are moving closer
// to the edge. If >0, we are moving further from the edge
val xDelta = xTranslation - previousXTranslation
previousXTranslation = xTranslation
if (abs(xDelta) > 0) {
if (sign(xDelta) == sign(totalTouchDelta)) {
// Direction has NOT changed, so keep counting the delta
totalTouchDelta += xDelta
} else {
// Direction has changed, so reset the delta
totalTouchDelta = xDelta
}
}
updateArrowStateOnMove(yTranslation, xTranslation)
when (currentState) {
GestureState.ACTIVE -> setActiveStretch(fullScreenStretchProgress(xTranslation))
GestureState.ENTRY ->
setEntryStretch(preThresholdStretchProgress(xTranslation))
GestureState.INACTIVE -> mView.resetStretch()
}
// set y translation
setVerticalTranslation(yOffset)
}
fun setVerticalTranslation(yOffset: Float) {
val yTranslation = abs(yOffset)
val maxYOffset = (mView.height / 2) - (params.entryBackgroundHeight / 2)
val yProgress = saturate(yTranslation / (maxYOffset * RUBBER_BAND_AMOUNT))
mView.animateVertically(
RUBBER_BAND_INTERPOLATOR.getInterpolation(yProgress) * maxYOffset * sign(
yOffset
)
)
}
/**
* @return the relative position of the drag from the time after the arrow is activated until
* the arrow is fully stretched (between 0.0 - 1.0f)
*/
fun fullScreenStretchProgress(xTranslation: Float): Float {
return saturate(
(xTranslation - params.swipeTriggerThreshold) /
(min(
params.fullyStretchedThreshold,
screenSize.toFloat()
) - params.swipeTriggerThreshold)
)
}
/**
* Tracks the relative position of the drag from the entry until the threshold where the arrow
* activates (between 0.0 - 1.0f)
*/
fun preThresholdStretchProgress(xTranslation: Float): Float {
return saturate(xTranslation / params.swipeTriggerThreshold)
}
fun setActiveStretch(progress: Float) {
val stretch = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress)
mView.setStretch(
arrowLengthStretch = stretch * (params.stretchedArrowLength - params.activeArrowLength),
arrowHeightStretch = stretch * (params.stretchedArrowHeight - params.activeArrowHeight),
backgroundWidthStretch =
stretch * (params.stretchBackgroundWidth - params.activeBackgroundWidth),
backgroundHeightStretch =
stretch * (params.stretchBackgroundHeight - params.activeBackgroundHeight),
backgroundEdgeCornerRadiusStretch =
stretch * (params.stretchEdgeCorners - params.activeEdgeCorners),
backgroundDragCornerRadiusStretch =
stretch * (params.stretchFarCorners - params.activeFarCorners),
horizontalTranslationStretch = stretch * (params.stretchMargin - params.activeMargin)
)
}
fun setEntryStretch(progress: Float) {
val bgStretch = ACCELERATE_INTERPOLATOR.getInterpolation(progress)
val arrowStretch = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress)
mView.setStretch(
arrowLengthStretch =
arrowStretch * (params.activeArrowLength - params.entryArrowLength),
arrowHeightStretch =
arrowStretch * (params.activeArrowHeight - params.entryArrowHeight),
backgroundWidthStretch =
bgStretch * (params.preThresholdBackgroundWidth - params.entryBackgroundWidth),
backgroundHeightStretch =
bgStretch * (params.preThresholdBackgroundHeight - params.entryBackgroundHeight),
backgroundEdgeCornerRadiusStretch =
bgStretch * (params.preThresholdEdgeCorners - params.entryEdgeCorners),
backgroundDragCornerRadiusStretch =
bgStretch * (params.preThresholdFarCorners - params.entryFarCorners),
horizontalTranslationStretch =
bgStretch * (params.preThresholdMargin - params.entryMargin)
)
}
fun setBackAnimation(backAnimation: BackAnimation?) {
this.backAnimation = backAnimation
initializeBackAnimation()
}
private fun initializeBackAnimation() {
backAnimation?.setSwipeThresholds(
params.swipeTriggerThreshold,
params.swipeProgressThreshold
)
}
override fun onDestroy() {
cancelFailsafe()
windowManager.removeView(mView)
}
override fun setIsLeftPanel(isLeftPanel: Boolean) {
mView.isLeftPanel = isLeftPanel
layoutParams.gravity = if (isLeftPanel) {
Gravity.LEFT or Gravity.TOP
} else {
Gravity.RIGHT or Gravity.TOP
}
}
override fun setInsets(insetLeft: Int, insetRight: Int) {
}
override fun setBackCallback(callback: NavigationEdgeBackPlugin.BackCallback) {
backCallback = callback
}
override fun setLayoutParams(layoutParams: WindowManager.LayoutParams) {
this.layoutParams = layoutParams
windowManager.addView(mView, layoutParams)
}
private fun isFlung() = velocityTracker!!.run {
computeCurrentVelocity(1000)
abs(xVelocity) > MIN_FLING_VELOCITY
}
private fun playFlingBackAnimation() {
playAnimation(setCommittedEndListener)
}
private fun playCommitBackAnimation() {
// Check if we should vibrate again
if (previousState != GestureState.FLUNG) {
backCallback.triggerBack()
velocityTracker!!.computeCurrentVelocity(1000)
val isSlow = abs(velocityTracker!!.xVelocity) < 500
val hasNotVibratedRecently =
SystemClock.uptimeMillis() - vibrationTime >= GESTURE_DURATION_FOR_CLICK_MS
if (isSlow || hasNotVibratedRecently) {
vibratorHelper.vibrate(VibrationEffect.EFFECT_CLICK)
}
}
playAnimation(setGoneEndListener)
}
private fun playCancelBackAnimation() {
backCallback.cancelBack()
playAnimation(setGoneEndListener)
}
/**
* @return true if the animation is running, false otherwise. Some transitions don't animate
*/
private fun playAnimation(endListener: DelayedOnAnimationEndListener) {
updateRestingArrowDimens(animated = true, currentState)
if (!mView.addEndListener(endListener)) {
scheduleFailsafe()
}
}
private fun resetOnDown() {
hasHapticPlayed = false
totalTouchDelta = 0f
vibrationTime = 0
cancelFailsafe()
backAnimation?.setTriggerBack(false)
}
private fun updateYPosition(touchY: Float) {
var yPosition = touchY - params.fingerOffset
yPosition = Math.max(yPosition, params.minArrowYPosition.toFloat())
yPosition -= layoutParams.height / 2.0f
layoutParams.y = constrain(yPosition.toInt(), 0, displaySize.y)
}
override fun setDisplaySize(displaySize: Point) {
this.displaySize.set(displaySize.x, displaySize.y)
screenSize = Math.min(displaySize.x, displaySize.y)
}
/**
* Updates resting arrow and background size not accounting for stretch
*/
private fun updateRestingArrowDimens(animated: Boolean, currentState: GestureState) {
mView.updateRestingArrowDimens(
backgroundWidth =
when (currentState) {
GestureState.GONE, GestureState.ENTRY -> params.entryBackgroundWidth
else -> params.activeBackgroundWidth
},
backgroundHeight =
when (currentState) {
GestureState.GONE, GestureState.ENTRY -> params.entryBackgroundHeight
else -> params.activeBackgroundHeight
},
backgroundEdgeCornerRadius =
when (currentState) {
GestureState.GONE, GestureState.ENTRY, GestureState.INACTIVE ->
params.entryEdgeCorners
else ->
params.activeEdgeCorners
},
backgroundDragCornerRadius =
when (currentState) {
GestureState.GONE, GestureState.ENTRY -> params.entryFarCorners
else -> params.activeFarCorners
},
arrowLength =
when (currentState) {
GestureState.ACTIVE, GestureState.INACTIVE, GestureState.COMMITTED,
GestureState.FLUNG -> params.activeArrowLength
GestureState.CANCELLED -> params.cancelledArrowLength
GestureState.GONE, GestureState.ENTRY -> params.entryArrowLength
},
arrowHeight =
when (currentState) {
GestureState.ACTIVE, GestureState.INACTIVE, GestureState.COMMITTED,
GestureState.FLUNG -> params.activeArrowHeight
GestureState.CANCELLED -> params.cancelledArrowHeight
GestureState.GONE, GestureState.ENTRY -> params.entryArrowHeight
},
horizontalTranslation =
when (currentState) {
GestureState.GONE -> -params.activeBackgroundWidth
// Position the cancelled/committed arrow slightly further off the screen so we
// do not see part of it bouncing
GestureState.CANCELLED, GestureState.COMMITTED ->
-params.activeBackgroundWidth * 1.5f
GestureState.FLUNG -> params.stretchMargin
GestureState.ACTIVE -> params.activeMargin
GestureState.ENTRY, GestureState.INACTIVE -> params.entryMargin
},
animate = animated
)
if (animated) {
when (currentState) {
GestureState.ENTRY, GestureState.ACTIVE, GestureState.FLUNG ->
mView.setArrowStiffness(ARROW_APPEAR_STIFFNESS, ARROW_APPEAR_DAMPING_RATIO)
else ->
mView.setArrowStiffness(
ARROW_DISAPPEAR_STIFFNESS, ARROW_DISAPPEAR_DAMPING_RATIO)
}
}
}
/**
* Update arrow state. If state has not changed, this is a no-op.
*
* Transitioning to active/inactive will indicate whether or not releasing touch will trigger
* the back action.
*/
private fun updateArrowState(newState: GestureState, force: Boolean = false) {
if (!force && currentState == newState) return
if (DEBUG) Log.d(TAG, "updateArrowState $currentState -> $newState")
previousState = currentState
currentState = newState
mView.visibility = if (currentState == GestureState.GONE) View.GONE else View.VISIBLE
when (currentState) {
// Transitioning to GONE never animates since the arrow is (presumably) already off the
// screen
GestureState.GONE -> updateRestingArrowDimens(animated = false, currentState)
GestureState.ENTRY -> {
updateYPosition(startY)
updateRestingArrowDimens(animated = true, currentState)
}
GestureState.ACTIVE -> {
backAnimation?.setTriggerBack(true)
updateRestingArrowDimens(animated = true, currentState)
// Vibrate the first time we transition to ACTIVE
if (!hasHapticPlayed) {
hasHapticPlayed = true
vibrationTime = SystemClock.uptimeMillis()
vibratorHelper.vibrate(VibrationEffect.EFFECT_TICK)
}
}
GestureState.INACTIVE -> {
backAnimation?.setTriggerBack(false)
updateRestingArrowDimens(animated = true, currentState)
}
GestureState.FLUNG -> playFlingBackAnimation()
GestureState.COMMITTED -> playCommitBackAnimation()
GestureState.CANCELLED -> playCancelBackAnimation()
}
}
private fun scheduleFailsafe() {
if (!ENABLE_FAILSAFE) return
cancelFailsafe()
if (DEBUG) Log.d(TAG, "scheduleFailsafe")
mainHandler.postDelayed(failsafeRunnable, FAILSAFE_DELAY_MS)
}
private fun cancelFailsafe() {
if (DEBUG) Log.d(TAG, "cancelFailsafe")
mainHandler.removeCallbacks(failsafeRunnable)
}
private fun onFailsafe() {
if (DEBUG) Log.d(TAG, "onFailsafe")
updateArrowState(GestureState.GONE, force = true)
}
override fun dump(pw: PrintWriter) {
pw.println("$TAG:")
pw.println(" currentState=$currentState")
pw.println(" isLeftPanel=$mView.isLeftPanel")
}
init {
if (DEBUG) mView.drawDebugInfo = { canvas ->
val debugStrings = listOf(
"$currentState",
"startX=$startX",
"startY=$startY",
"xDelta=${"%.1f".format(totalTouchDelta)}",
"xTranslation=${"%.1f".format(previousXTranslation)}",
"pre=${"%.0f".format(preThresholdStretchProgress(previousXTranslation) * 100)}%",
"post=${"%.0f".format(fullScreenStretchProgress(previousXTranslation) * 100)}%"
)
val debugPaint = Paint().apply {
color = Color.WHITE
}
val debugInfoBottom = debugStrings.size * 32f + 4f
canvas.drawRect(
4f,
4f,
canvas.width.toFloat(),
debugStrings.size * 32f + 4f,
debugPaint
)
debugPaint.apply {
color = Color.BLACK
textSize = 32f
}
var offset = 32f
for (debugText in debugStrings) {
canvas.drawText(debugText, 10f, offset, debugPaint)
offset += 32f
}
debugPaint.apply {
color = Color.RED
style = Paint.Style.STROKE
strokeWidth = 4f
}
val canvasWidth = canvas.width.toFloat()
val canvasHeight = canvas.height.toFloat()
canvas.drawRect(0f, 0f, canvasWidth, canvasHeight, debugPaint)
fun drawVerticalLine(x: Float, color: Int) {
debugPaint.color = color
val x = if (mView.isLeftPanel) x else canvasWidth - x
canvas.drawLine(x, debugInfoBottom, x, canvas.height.toFloat(), debugPaint)
}
drawVerticalLine(x = params.swipeTriggerThreshold, color = Color.BLUE)
drawVerticalLine(x = startX, color = Color.GREEN)
drawVerticalLine(x = previousXTranslation, color = Color.DKGRAY)
}
}
}

View File

@@ -62,6 +62,8 @@ import com.android.systemui.R;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -183,6 +185,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
private final PluginManager mPluginManager;
private final ProtoTracer mProtoTracer;
private final NavigationModeController mNavigationModeController;
private final BackPanelController.Factory mBackPanelControllerFactory;
private final ViewConfiguration mViewConfiguration;
private final WindowManager mWindowManager;
private final IWindowManager mWindowManagerService;
@@ -200,6 +203,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
private final Region mExcludeRegion = new Region();
private final Region mUnrestrictedExcludeRegion = new Region();
private final LatencyTracker mLatencyTracker;
private final FeatureFlags mFeatureFlags;
// The left side edge width where touch down is allowed
private int mEdgeWidthLeft;
@@ -231,6 +235,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
private boolean mIsBackGestureAllowed;
private boolean mGestureBlockingActivityRunning;
private boolean mIsInPipMode;
private boolean mIsPredictiveBackAnimEnabled;
private InputMonitor mInputMonitor;
private InputChannelCompat.InputEventReceiver mInputEventReceiver;
@@ -299,12 +304,22 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
};
EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService,
SysUiState sysUiState, PluginManager pluginManager, @Main Executor executor,
BroadcastDispatcher broadcastDispatcher, ProtoTracer protoTracer,
NavigationModeController navigationModeController, ViewConfiguration viewConfiguration,
WindowManager windowManager, IWindowManager windowManagerService,
FalsingManager falsingManager, LatencyTracker latencyTracker) {
EdgeBackGestureHandler(
Context context,
OverviewProxyService overviewProxyService,
SysUiState sysUiState,
PluginManager pluginManager,
@Main Executor executor,
BroadcastDispatcher broadcastDispatcher,
ProtoTracer protoTracer,
NavigationModeController navigationModeController,
BackPanelController.Factory backPanelControllerFactory,
ViewConfiguration viewConfiguration,
WindowManager windowManager,
IWindowManager windowManagerService,
FalsingManager falsingManager,
LatencyTracker latencyTracker,
FeatureFlags featureFlags) {
super(broadcastDispatcher);
mContext = context;
mDisplayId = context.getDisplayId();
@@ -314,11 +329,13 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
mPluginManager = pluginManager;
mProtoTracer = protoTracer;
mNavigationModeController = navigationModeController;
mBackPanelControllerFactory = backPanelControllerFactory;
mViewConfiguration = viewConfiguration;
mWindowManager = windowManager;
mWindowManagerService = windowManagerService;
mFalsingManager = falsingManager;
mLatencyTracker = latencyTracker;
mFeatureFlags = featureFlags;
ComponentName recentsComponentName = ComponentName.unflattenFromString(
context.getString(com.android.internal.R.string.config_recentsComponentName));
if (recentsComponentName != null) {
@@ -508,8 +525,9 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
Choreographer.getInstance(), this::onInputEvent);
// Add a nav bar panel window
setEdgeBackPlugin(
new NavigationBarEdgePanel(mContext, mBackAnimation, mLatencyTracker));
mIsPredictiveBackAnimEnabled =
mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_ANIM);
resetEdgeBackPlugin();
mPluginManager.addPluginListener(
this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false);
}
@@ -524,7 +542,17 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
@Override
public void onPluginDisconnected(NavigationEdgeBackPlugin plugin) {
setEdgeBackPlugin(new NavigationBarEdgePanel(mContext, mBackAnimation, mLatencyTracker));
resetEdgeBackPlugin();
}
private void resetEdgeBackPlugin() {
if (mIsPredictiveBackAnimEnabled) {
setEdgeBackPlugin(
mBackPanelControllerFactory.create(mContext, mBackAnimation));
} else {
setEdgeBackPlugin(
new NavigationBarEdgePanel(mContext, mBackAnimation, mLatencyTracker));
}
}
private void setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin) {
@@ -949,8 +977,12 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
public void setBackAnimation(BackAnimation backAnimation) {
mBackAnimation = backAnimation;
if (mEdgeBackPlugin != null && mEdgeBackPlugin instanceof NavigationBarEdgePanel) {
((NavigationBarEdgePanel) mEdgeBackPlugin).setBackAnimation(backAnimation);
if (mEdgeBackPlugin != null) {
if (mEdgeBackPlugin instanceof NavigationBarEdgePanel) {
((NavigationBarEdgePanel) mEdgeBackPlugin).setBackAnimation(backAnimation);
} else if (mEdgeBackPlugin instanceof BackPanelController) {
((BackPanelController) mEdgeBackPlugin).setBackAnimation(backAnimation);
}
}
}
@@ -968,20 +1000,29 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
private final BroadcastDispatcher mBroadcastDispatcher;
private final ProtoTracer mProtoTracer;
private final NavigationModeController mNavigationModeController;
private final BackPanelController.Factory mBackPanelControllerFactory;
private final ViewConfiguration mViewConfiguration;
private final WindowManager mWindowManager;
private final IWindowManager mWindowManagerService;
private final FalsingManager mFalsingManager;
private final LatencyTracker mLatencyTracker;
private final FeatureFlags mFeatureFlags;
@Inject
public Factory(OverviewProxyService overviewProxyService,
SysUiState sysUiState, PluginManager pluginManager, @Main Executor executor,
BroadcastDispatcher broadcastDispatcher, ProtoTracer protoTracer,
NavigationModeController navigationModeController,
ViewConfiguration viewConfiguration, WindowManager windowManager,
IWindowManager windowManagerService, FalsingManager falsingManager,
LatencyTracker latencyTracker) {
SysUiState sysUiState,
PluginManager pluginManager,
@Main Executor executor,
BroadcastDispatcher broadcastDispatcher,
ProtoTracer protoTracer,
NavigationModeController navigationModeController,
BackPanelController.Factory backPanelControllerFactory,
ViewConfiguration viewConfiguration,
WindowManager windowManager,
IWindowManager windowManagerService,
FalsingManager falsingManager,
LatencyTracker latencyTracker,
FeatureFlags featureFlags) {
mOverviewProxyService = overviewProxyService;
mSysUiState = sysUiState;
mPluginManager = pluginManager;
@@ -989,19 +1030,33 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
mBroadcastDispatcher = broadcastDispatcher;
mProtoTracer = protoTracer;
mNavigationModeController = navigationModeController;
mBackPanelControllerFactory = backPanelControllerFactory;
mViewConfiguration = viewConfiguration;
mWindowManager = windowManager;
mWindowManagerService = windowManagerService;
mFalsingManager = falsingManager;
mLatencyTracker = latencyTracker;
mFeatureFlags = featureFlags;
}
/** Construct a {@link EdgeBackGestureHandler}. */
public EdgeBackGestureHandler create(Context context) {
return new EdgeBackGestureHandler(context, mOverviewProxyService, mSysUiState,
mPluginManager, mExecutor, mBroadcastDispatcher, mProtoTracer,
mNavigationModeController, mViewConfiguration, mWindowManager,
mWindowManagerService, mFalsingManager, mLatencyTracker);
return new EdgeBackGestureHandler(
context,
mOverviewProxyService,
mSysUiState,
mPluginManager,
mExecutor,
mBroadcastDispatcher,
mProtoTracer,
mNavigationModeController,
mBackPanelControllerFactory,
mViewConfiguration,
mWindowManager,
mWindowManagerService,
mFalsingManager,
mLatencyTracker,
mFeatureFlags);
}
}

View File

@@ -0,0 +1,139 @@
package com.android.systemui.navigationbar.gestural
import android.content.res.Resources
import com.android.systemui.R
data class EdgePanelParams(private var resources: Resources) {
var arrowThickness: Float = 0f
private set
var entryArrowLength: Float = 0f
private set
var entryArrowHeight: Float = 0f
private set
var activeArrowLength: Float = 0f
private set
var activeArrowHeight: Float = 0f
private set
var stretchedArrowLength: Float = 0f
private set
var stretchedArrowHeight: Float = 0f
private set
var cancelledArrowLength: Float = 0f
private set
var cancelledArrowHeight: Float = 0f
private set
var entryMargin: Float = 0f
private set
var entryBackgroundWidth: Float = 0f
private set
var entryBackgroundHeight: Float = 0f
private set
var entryEdgeCorners: Float = 0f
private set
var entryFarCorners: Float = 0f
private set
var preThresholdMargin: Float = 0f
private set
var preThresholdBackgroundWidth: Float = 0f
private set
var preThresholdBackgroundHeight: Float = 0f
private set
var preThresholdEdgeCorners: Float = 0f
private set
var preThresholdFarCorners: Float = 0f
private set
var activeMargin: Float = 0f
private set
var activeBackgroundWidth: Float = 0f
private set
var activeBackgroundHeight: Float = 0f
private set
var activeEdgeCorners: Float = 0f
private set
var activeFarCorners: Float = 0f
private set
var fullyStretchedThreshold: Float = 0f
private set
var stretchMargin: Float = 0f
private set
var stretchBackgroundWidth: Float = 0f
private set
var stretchBackgroundHeight: Float = 0f
private set
var stretchEdgeCorners: Float = 0f
private set
var stretchFarCorners: Float = 0f
private set
// navigation bar edge constants
var arrowPaddingEnd: Int = 0
private set
// The closest to y
var minArrowYPosition: Int = 0
private set
var fingerOffset: Int = 0
private set
var swipeTriggerThreshold: Float = 0f
private set
var swipeProgressThreshold: Float = 0f
private set
// The minimum delta needed to change direction / stop triggering back
var minDeltaForSwitch: Int = 0
private set
init {
update(resources)
}
private fun getDimen(id: Int): Float {
return resources.getDimension(id)
}
private fun getPx(id: Int): Int {
return resources.getDimensionPixelSize(id)
}
fun update(resources: Resources) {
this.resources = resources
arrowThickness = getDimen(R.dimen.navigation_edge_arrow_thickness)
entryArrowLength = getDimen(R.dimen.navigation_edge_entry_arrow_length)
entryArrowHeight = getDimen(R.dimen.navigation_edge_entry_arrow_height)
activeArrowLength = getDimen(R.dimen.navigation_edge_active_arrow_length)
activeArrowHeight = getDimen(R.dimen.navigation_edge_active_arrow_height)
stretchedArrowLength = getDimen(R.dimen.navigation_edge_stretched_arrow_length)
stretchedArrowHeight = getDimen(R.dimen.navigation_edge_stretched_arrow_height)
cancelledArrowLength = getDimen(R.dimen.navigation_edge_cancelled_arrow_length)
cancelledArrowHeight = getDimen(R.dimen.navigation_edge_cancelled_arrow_height)
entryMargin = getDimen(R.dimen.navigation_edge_entry_margin)
entryBackgroundWidth = getDimen(R.dimen.navigation_edge_entry_background_width)
entryBackgroundHeight = getDimen(R.dimen.navigation_edge_entry_background_height)
entryEdgeCorners = getDimen(R.dimen.navigation_edge_entry_edge_corners)
entryFarCorners = getDimen(R.dimen.navigation_edge_entry_far_corners)
preThresholdMargin = getDimen(R.dimen.navigation_edge_pre_threshold_margin)
preThresholdBackgroundWidth =
getDimen(R.dimen.navigation_edge_pre_threshold_background_width)
preThresholdBackgroundHeight =
getDimen(R.dimen.navigation_edge_pre_threshold_background_height)
preThresholdEdgeCorners = getDimen(R.dimen.navigation_edge_pre_threshold_edge_corners)
preThresholdFarCorners = getDimen(R.dimen.navigation_edge_pre_threshold_far_corners)
activeMargin = getDimen(R.dimen.navigation_edge_active_margin)
activeBackgroundWidth = getDimen(R.dimen.navigation_edge_active_background_width)
activeBackgroundHeight = getDimen(R.dimen.navigation_edge_active_background_height)
activeEdgeCorners = getDimen(R.dimen.navigation_edge_active_edge_corners)
activeFarCorners = getDimen(R.dimen.navigation_edge_active_far_corners)
fullyStretchedThreshold = getDimen(R.dimen.navigation_edge_stretch_threshold)
stretchMargin = getDimen(R.dimen.navigation_edge_stretch_margin)
stretchBackgroundWidth = getDimen(R.dimen.navigation_edge_stretch_background_width)
stretchBackgroundHeight = getDimen(R.dimen.navigation_edge_stretch_background_height)
stretchEdgeCorners = getDimen(R.dimen.navigation_edge_stretch_left_corners)
stretchFarCorners = getDimen(R.dimen.navigation_edge_stretch_right_corners)
arrowPaddingEnd = getPx(R.dimen.navigation_edge_panel_padding)
minArrowYPosition = getPx(R.dimen.navigation_edge_arrow_min_y)
fingerOffset = getPx(R.dimen.navigation_edge_finger_offset)
swipeTriggerThreshold = getDimen(R.dimen.navigation_edge_action_drag_threshold)
swipeProgressThreshold = getDimen(R.dimen.navigation_edge_action_progress_threshold)
minDeltaForSwitch = getPx(R.dimen.navigation_edge_minimum_x_delta_for_switch)
}
}