Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 06ab363b authored by Aaron Liu's avatar Aaron Liu Committed by Android (Google) Code Review
Browse files

Merge "[Bouncer] Make bouncer view a ConstraintLayout" into tm-qpr-dev

parents 67e174c8 9b46e127
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -24,7 +24,6 @@
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    androidprv:layout_maxWidth="@dimen/keyguard_security_width"
    androidprv:layout_maxHeight="@dimen/keyguard_security_height"
    android:layout_gravity="center_horizontal|bottom"
    android:gravity="bottom"
    >
+21 −2
Original line number Diff line number Diff line
@@ -15,6 +15,12 @@
 */
package com.android.keyguard;

import static androidx.constraintlayout.widget.ConstraintSet.BOTTOM;
import static androidx.constraintlayout.widget.ConstraintSet.END;
import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
import static androidx.constraintlayout.widget.ConstraintSet.START;
import static androidx.constraintlayout.widget.ConstraintSet.TOP;

import android.annotation.Nullable;
import android.app.admin.IKeyguardCallback;
import android.app.admin.IKeyguardClient;
@@ -30,7 +36,10 @@ import android.util.Log;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.ViewGroup;
import android.view.View;

import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;

import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
@@ -49,7 +58,7 @@ public class AdminSecondaryLockScreenController {
    private static final int REMOTE_CONTENT_READY_TIMEOUT_MILLIS = 500;
    private final KeyguardUpdateMonitor mUpdateMonitor;
    private final Context mContext;
    private final ViewGroup mParent;
    private final ConstraintLayout mParent;
    private AdminSecurityView mView;
    private Handler mHandler;
    private IKeyguardClient mClient;
@@ -156,6 +165,7 @@ public class AdminSecondaryLockScreenController {
        mUpdateMonitor = updateMonitor;
        mKeyguardCallback = callback;
        mView = new AdminSecurityView(mContext, mSurfaceHolderCallback);
        mView.setId(View.generateViewId());
    }

    /**
@@ -167,6 +177,15 @@ public class AdminSecondaryLockScreenController {
        }
        if (!mView.isAttachedToWindow()) {
            mParent.addView(mView);
            ConstraintSet constraintSet = new ConstraintSet();
            constraintSet.clone(mParent);
            constraintSet.connect(mView.getId(), TOP, PARENT_ID, TOP);
            constraintSet.connect(mView.getId(), START, PARENT_ID, START);
            constraintSet.connect(mView.getId(), END, PARENT_ID, END);
            constraintSet.connect(mView.getId(), BOTTOM, PARENT_ID, BOTTOM);
            constraintSet.constrainHeight(mView.getId(), ConstraintSet.MATCH_CONSTRAINT);
            constraintSet.constrainWidth(mView.getId(), ConstraintSet.MATCH_CONSTRAINT);
            constraintSet.applyTo(mParent);
        }
    }

+85 −291

File changed.

Preview size limit exceeded, changes collapsed.

+210 −0
Original line number Diff line number Diff line
/*
 * 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.keyguard

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.graphics.Rect
import android.transition.Transition
import android.transition.TransitionValues
import android.util.MathUtils
import android.view.View
import android.view.ViewGroup
import android.view.animation.AnimationUtils
import com.android.internal.R.interpolator.fast_out_extra_slow_in
import com.android.systemui.R
import com.android.systemui.animation.Interpolators

/** Animates constraint layout changes for the security view. */
class KeyguardSecurityViewTransition : Transition() {

    companion object {
        const val PROP_BOUNDS = "securityViewLocation:bounds"

        // The duration of the animation to switch security sides.
        const val SECURITY_SHIFT_ANIMATION_DURATION_MS = 500L

        // How much of the switch sides animation should be dedicated to fading the security out.
        // The remainder will fade it back in again.
        const val SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION = 0.2f
    }

    private fun captureValues(values: TransitionValues) {
        val boundsRect = Rect()
        boundsRect.left = values.view.left
        boundsRect.top = values.view.top
        boundsRect.right = values.view.right
        boundsRect.bottom = values.view.bottom
        values.values[PROP_BOUNDS] = boundsRect
    }

    override fun getTransitionProperties(): Array<String>? {
        return arrayOf(PROP_BOUNDS)
    }

    override fun captureEndValues(transitionValues: TransitionValues?) {
        transitionValues?.let { captureValues(it) }
    }

    override fun captureStartValues(transitionValues: TransitionValues?) {
        transitionValues?.let { captureValues(it) }
    }

    override fun createAnimator(
        sceneRoot: ViewGroup?,
        startValues: TransitionValues?,
        endValues: TransitionValues?
    ): Animator? {
        if (sceneRoot == null || startValues == null || endValues == null) {
            return null
        }

        // This animation is a bit fun to implement. The bouncer needs to move, and fade
        // in/out at the same time. The issue is, the bouncer should only move a short
        // amount (120dp or so), but obviously needs to go from one side of the screen to
        // the other. This needs a pretty custom animation.
        //
        // This works as follows. It uses a ValueAnimation to simply drive the animation
        // progress. This animator is responsible for both the translation of the bouncer,
        // and the current fade. It will fade the bouncer out while also moving it along the
        // 120dp path. Once the bouncer is fully faded out though, it will "snap" the
        // bouncer closer to its destination, then fade it back in again. The effect is that
        // the bouncer will move from 0 -> X while fading out, then
        // (destination - X) -> destination while fading back in again.
        // TODO(b/208250221): Make this animation properly abortable.
        val positionInterpolator =
            AnimationUtils.loadInterpolator(sceneRoot.context, fast_out_extra_slow_in)
        val fadeOutInterpolator = Interpolators.FAST_OUT_LINEAR_IN
        val fadeInInterpolator = Interpolators.LINEAR_OUT_SLOW_IN
        var runningSecurityShiftAnimator = ValueAnimator.ofFloat(0.0f, 1.0f)
        runningSecurityShiftAnimator.duration = SECURITY_SHIFT_ANIMATION_DURATION_MS
        runningSecurityShiftAnimator.interpolator = Interpolators.LINEAR
        val startRect = startValues.values[PROP_BOUNDS] as Rect
        val endRect = endValues.values[PROP_BOUNDS] as Rect
        val v = startValues.view
        val totalTranslation: Int =
            sceneRoot.resources.getDimension(R.dimen.security_shift_animation_translation).toInt()
        val shouldRestoreLayerType =
            (v.hasOverlappingRendering() && v.layerType != View.LAYER_TYPE_HARDWARE)
        if (shouldRestoreLayerType) {
            v.setLayerType(View.LAYER_TYPE_HARDWARE, /* paint= */ null)
        }
        val initialAlpha: Float = v.alpha
        runningSecurityShiftAnimator.addListener(
            object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator) {
                    runningSecurityShiftAnimator = null
                }
            }
        )

        var finishedFadingOutNonSecurityView = false

        runningSecurityShiftAnimator.addUpdateListener { animation: ValueAnimator ->
            val switchPoint = SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION
            val isFadingOut = animation.animatedFraction < switchPoint
            val opacity: Float
            var currentTranslation =
                (positionInterpolator.getInterpolation(animation.animatedFraction) *
                        totalTranslation)
                    .toInt()
            var translationRemaining = totalTranslation - currentTranslation
            val leftAlign = endRect.left < startRect.left
            if (leftAlign) {
                currentTranslation = -currentTranslation
                translationRemaining = -translationRemaining
            }

            if (isFadingOut) {
                // The bouncer fades out over the first X%.
                val fadeOutFraction =
                    MathUtils.constrainedMap(
                        /* rangeMin= */ 1.0f,
                        /* rangeMax= */ 0.0f,
                        /* valueMin= */ 0.0f,
                        /* valueMax= */ switchPoint,
                        animation.animatedFraction
                    )
                opacity = fadeOutInterpolator.getInterpolation(fadeOutFraction)

                // When fading out, the alpha needs to start from the initial opacity of the
                // view flipper, otherwise we get a weird bit of jank as it ramps back to
                // 100%.
                v.alpha = opacity * initialAlpha
                if (v is KeyguardSecurityViewFlipper) {
                    v.setLeftTopRightBottom(
                        startRect.left + currentTranslation,
                        startRect.top,
                        startRect.right + currentTranslation,
                        startRect.bottom
                    )
                }
            } else {
                // And in again over the remaining (100-X)%.
                val fadeInFraction =
                    MathUtils.constrainedMap(
                        /* rangeMin= */ 0.0f,
                        /* rangeMax= */ 1.0f,
                        /* valueMin= */ switchPoint,
                        /* valueMax= */ 1.0f,
                        animation.animatedFraction
                    )
                opacity = fadeInInterpolator.getInterpolation(fadeInFraction)
                v.alpha = opacity

                // Fading back in, animate towards the destination.
                if (v is KeyguardSecurityViewFlipper) {
                    v.setLeftTopRightBottom(
                        endRect.left - translationRemaining,
                        endRect.top,
                        endRect.right - translationRemaining,
                        endRect.bottom
                    )
                }
            }
            if (animation.animatedFraction == 1.0f && shouldRestoreLayerType) {
                v.setLayerType(View.LAYER_TYPE_NONE, /* paint= */ null)
            }

            // For views that are not the security view flipper, we do not want to apply
            // an x translation animation. Instead, we want to fade out, move to final position and
            // then fade in.
            if (v !is KeyguardSecurityViewFlipper) {
                // Opacity goes close to 0 but does not fully get to 0.
                if (opacity - 0.001f < 0f) {
                    v.setLeftTopRightBottom(
                        endRect.left,
                        endRect.top,
                        endRect.right,
                        endRect.bottom
                    )
                    finishedFadingOutNonSecurityView = true
                } else if (!finishedFadingOutNonSecurityView) {
                    v.setLeftTopRightBottom(
                        startRect.left,
                        startRect.top,
                        startRect.right,
                        startRect.bottom
                    )
                }
            }
        }
        runningSecurityShiftAnimator.start()
        return runningSecurityShiftAnimator
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import android.testing.TestableLooper.RunWithLooper;
import android.testing.ViewUtils;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceView;
import android.view.View;

import androidx.test.filters.SmallTest;

@@ -84,6 +85,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase {
        MockitoAnnotations.initMocks(this);

        mKeyguardSecurityContainer = spy(new KeyguardSecurityContainer(mContext));
        mKeyguardSecurityContainer.setId(View.generateViewId());
        ViewUtils.attachView(mKeyguardSecurityContainer);

        mTestableLooper = TestableLooper.get(this);
Loading