Merge "Add an extra colored layer when animating launches (1/2)" into sc-dev

This commit is contained in:
TreeHugger Robot
2021-06-01 20:44:38 +00:00
committed by Android (Google) Code Review
7 changed files with 170 additions and 81 deletions

View File

@@ -135,7 +135,7 @@ public class SplashscreenContentDrawer {
com.android.wm.shell.R.dimen.starting_surface_exit_animation_window_shift_length);
}
private int getSystemBGColor() {
private static int getSystemBGColor() {
final Context systemContext = ActivityThread.currentApplication();
if (systemContext == null) {
Slog.e(TAG, "System context does not exist!");
@@ -145,17 +145,18 @@ public class SplashscreenContentDrawer {
return res.getColor(com.android.wm.shell.R.color.splash_window_background_default);
}
private Drawable createDefaultBackgroundDrawable() {
private static Drawable createDefaultBackgroundDrawable() {
return new ColorDrawable(getSystemBGColor());
}
private @ColorInt int peekWindowBGColor(Context context) {
/** Extract the window background color from {@code attrs}. */
public static int peekWindowBGColor(Context context, SplashScreenWindowAttrs attrs) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "peekWindowBGColor");
final Drawable themeBGDrawable;
if (mTmpAttrs.mWindowBgColor != 0) {
themeBGDrawable = new ColorDrawable(mTmpAttrs.mWindowBgColor);
} else if (mTmpAttrs.mWindowBgResId != 0) {
themeBGDrawable = context.getDrawable(mTmpAttrs.mWindowBgResId);
if (attrs.mWindowBgColor != 0) {
themeBGDrawable = new ColorDrawable(attrs.mWindowBgColor);
} else if (attrs.mWindowBgResId != 0) {
themeBGDrawable = context.getDrawable(attrs.mWindowBgResId);
} else {
themeBGDrawable = createDefaultBackgroundDrawable();
Slog.w(TAG, "Window background does not exist, using " + themeBGDrawable);
@@ -165,7 +166,7 @@ public class SplashscreenContentDrawer {
return estimatedWindowBGColor;
}
private int estimateWindowBGColor(Drawable themeBGDrawable) {
private static int estimateWindowBGColor(Drawable themeBGDrawable) {
final DrawableColorTester themeBGTester =
new DrawableColorTester(themeBGDrawable, true /* filterTransparent */);
if (themeBGTester.nonTransparentRatio() == 0) {
@@ -183,7 +184,7 @@ public class SplashscreenContentDrawer {
getWindowAttrs(context, mTmpAttrs);
final StartingWindowViewBuilder builder = new StartingWindowViewBuilder();
final int themeBGColor = peekWindowBGColor(context);
final int themeBGColor = peekWindowBGColor(context, this.mTmpAttrs);
// TODO (b/173975965) Tracking the performance on improved splash screen.
return builder
.setContext(context)
@@ -193,7 +194,11 @@ public class SplashscreenContentDrawer {
.build();
}
private static void getWindowAttrs(Context context, SplashScreenWindowAttrs attrs) {
/**
* Get the {@link SplashScreenWindowAttrs} from {@code context} and fill them into
* {@code attrs}.
*/
public static void getWindowAttrs(Context context, SplashScreenWindowAttrs attrs) {
final TypedArray typedArray = context.obtainStyledAttributes(
com.android.internal.R.styleable.Window);
attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
@@ -216,7 +221,8 @@ public class SplashscreenContentDrawer {
}
}
private static class SplashScreenWindowAttrs {
/** The configuration of the splash screen window. */
public static class SplashScreenWindowAttrs {
private int mWindowBgResId = 0;
private int mWindowBgColor = Color.TRANSPARENT;
private Drawable mReplaceIcon = null;

View File

@@ -36,6 +36,7 @@ android_library {
static_libs: [
"PluginCoreLib",
"WindowManager-Shell",
],
manifest: "AndroidManifest.xml",

View File

@@ -5,13 +5,18 @@ import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.app.ActivityManager
import android.app.ActivityTaskManager
import android.app.AppGlobals
import android.app.PendingIntent
import android.content.Context
import android.graphics.Matrix
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.drawable.GradientDrawable
import android.os.Looper
import android.os.RemoteException
import android.os.UserHandle
import android.util.Log
import android.util.MathUtils
import android.view.IRemoteAnimationFinishedCallback
@@ -26,6 +31,8 @@ import android.view.animation.AnimationUtils
import android.view.animation.PathInterpolator
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.wm.shell.startingsurface.SplashscreenContentDrawer
import com.android.wm.shell.startingsurface.SplashscreenContentDrawer.SplashScreenWindowAttrs
import kotlin.math.roundToInt
/**
@@ -40,9 +47,9 @@ class ActivityLaunchAnimator(
companion object {
const val ANIMATION_DURATION = 500L
const val ANIMATION_DURATION_FADE_OUT_CONTENT = 183L
const val ANIMATION_DURATION_FADE_IN_WINDOW = 217L
const val ANIMATION_DELAY_FADE_IN_WINDOW = 167L
private const val ANIMATION_DURATION_FADE_OUT_CONTENT = 150L
private const val ANIMATION_DURATION_FADE_IN_WINDOW = 183L
private const val ANIMATION_DELAY_FADE_IN_WINDOW = ANIMATION_DURATION_FADE_OUT_CONTENT
private const val ANIMATION_DURATION_NAV_FADE_IN = 266L
private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L
private const val ANIMATION_DELAY_NAV_FADE_IN =
@@ -54,6 +61,8 @@ class ActivityLaunchAnimator(
private val NAV_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0f, 1f)
private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f)
private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC)
/**
* Given the [linearProgress] of a launch animation, return the linear progress of the
* sub-animation starting [delay] ms after the launch animation and that lasts [duration].
@@ -68,6 +77,8 @@ class ActivityLaunchAnimator(
}
}
private val packageManager = AppGlobals.getPackageManager()
/** The interpolator used for the width, height, Y position and corner radius. */
private val animationInterpolator = AnimationUtils.loadInterpolator(context,
R.interpolator.launch_animation_interpolator_y)
@@ -76,6 +87,8 @@ class ActivityLaunchAnimator(
private val animationInterpolatorX = AnimationUtils.loadInterpolator(context,
R.interpolator.launch_animation_interpolator_x)
private val cornerRadii = FloatArray(8)
/**
* Start an intent and animate the opening window. The intent will be started by running
* [intentStarter], which should use the provided [RemoteAnimationAdapter] and return the launch
@@ -288,10 +301,7 @@ class ActivityLaunchAnimator(
var right: Int,
var topCornerRadius: Float = 0f,
var bottomCornerRadius: Float = 0f,
var contentAlpha: Float = 1f,
var backgroundAlpha: Float = 1f
var bottomCornerRadius: Float = 0f
) {
private val startTop = top
private val startBottom = bottom
@@ -331,6 +341,9 @@ class ActivityLaunchAnimator(
val centerY: Float
get() = top + height / 2f
/** Whether the expanded view should be visible or hidden. */
var visible: Boolean = true
}
@VisibleForTesting
@@ -452,22 +465,39 @@ class ActivityLaunchAnimator(
0f
}
// We add an extra layer with the same color as the app splash screen background color,
// which is usually the same color of the app background. We first fade in this layer
// to hide the expanding view, then we fade it out with SRC mode to draw a hole in the
// launch container and reveal the opening window.
val windowBackgroundColor = extractSplashScreenBackgroundColor(window)
val windowBackgroundLayer = GradientDrawable().apply {
setColor(windowBackgroundColor)
alpha = 0
}
// Update state.
val animator = ValueAnimator.ofFloat(0f, 1f)
this.animator = animator
animator.duration = ANIMATION_DURATION
animator.interpolator = Interpolators.LINEAR
val launchContainerOverlay = launchContainer.overlay
animator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
Log.d(TAG, "Animation started")
controller.onLaunchAnimationStart(isExpandingFullyAbove)
// Add the drawable to the launch container overlay. Overlays always draw
// drawables after views, so we know that it will be drawn above any view added
// by the controller.
launchContainerOverlay.add(windowBackgroundLayer)
}
override fun onAnimationEnd(animation: Animator?) {
Log.d(TAG, "Animation ended")
iCallback?.invoke()
controller.onLaunchAnimationEnd(isExpandingFullyAbove)
launchContainerOverlay.remove(windowBackgroundLayer)
}
})
@@ -491,24 +521,61 @@ class ActivityLaunchAnimator(
state.bottomCornerRadius =
MathUtils.lerp(startBottomCornerRadius, endRadius, progress)
val contentAlphaProgress = getProgress(linearProgress, 0,
ANIMATION_DURATION_FADE_OUT_CONTENT)
state.contentAlpha =
1 - CONTENT_FADE_OUT_INTERPOLATOR.getInterpolation(contentAlphaProgress)
val backgroundAlphaProgress = getProgress(linearProgress,
ANIMATION_DELAY_FADE_IN_WINDOW, ANIMATION_DURATION_FADE_IN_WINDOW)
state.backgroundAlpha =
1 - WINDOW_FADE_IN_INTERPOLATOR.getInterpolation(backgroundAlphaProgress)
// The expanding view can/should be hidden once it is completely coverred by the
// windowBackgroundLayer.
state.visible =
getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT) < 1
applyStateToWindow(window, state)
applyStateToWindowBackgroundLayer(windowBackgroundLayer, state, linearProgress)
navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
// If we started expanding the view, we make it 1 pixel smaller on all sides to
// avoid artefacts on the corners caused by anti-aliasing of the view background and
// the window background layer.
if (state.top != startTop && state.left != startLeft &&
state.bottom != startBottom && state.right != startRight) {
state.top += 1
state.left += 1
state.right -= 1
state.bottom -= 1
}
controller.onLaunchAnimationProgress(state, progress, linearProgress)
}
animator.start()
}
/** Extract the background color of the app splash screen. */
private fun extractSplashScreenBackgroundColor(window: RemoteAnimationTarget): Int {
val taskInfo = window.taskInfo
val windowPackage = taskInfo.topActivity.packageName
val userId = taskInfo.userId
val windowContext = context.createPackageContextAsUser(
windowPackage, Context.CONTEXT_RESTRICTED, UserHandle.of(userId))
val activityInfo = taskInfo.topActivityInfo
val splashScreenThemeName = packageManager.getSplashScreenTheme(windowPackage, userId)
val splashScreenThemeId = if (splashScreenThemeName != null) {
windowContext.resources.getIdentifier(splashScreenThemeName, null, null)
} else {
0
}
val themeResId = when {
splashScreenThemeId != 0 -> splashScreenThemeId
activityInfo.themeResource != 0 -> activityInfo.themeResource
else -> com.android.internal.R.style.Theme_DeviceDefault_DayNight
}
if (themeResId != windowContext.themeResId) {
windowContext.setTheme(themeResId)
}
val windowAttrs = SplashScreenWindowAttrs()
SplashscreenContentDrawer.getWindowAttrs(windowContext, windowAttrs)
return SplashscreenContentDrawer.peekWindowBGColor(windowContext, windowAttrs)
}
private fun applyStateToWindow(window: RemoteAnimationTarget, state: State) {
val screenBounds = window.screenSpaceBounds
val centerX = (screenBounds.left + screenBounds.right) / 2f
@@ -563,6 +630,41 @@ class ActivityLaunchAnimator(
transactionApplier.scheduleApply(params)
}
private fun applyStateToWindowBackgroundLayer(
drawable: GradientDrawable,
state: State,
linearProgress: Float
) {
// Update position.
drawable.setBounds(state.left, state.top, state.right, state.bottom)
// Update radius.
cornerRadii[0] = state.topCornerRadius
cornerRadii[1] = state.topCornerRadius
cornerRadii[2] = state.topCornerRadius
cornerRadii[3] = state.topCornerRadius
cornerRadii[4] = state.bottomCornerRadius
cornerRadii[5] = state.bottomCornerRadius
cornerRadii[6] = state.bottomCornerRadius
cornerRadii[7] = state.bottomCornerRadius
drawable.cornerRadii = cornerRadii
// We first fade in the background layer to hide the expanding view, then fade it out
// with SRC mode to draw a hole punch in the status bar and reveal the opening window.
val fadeInProgress = getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT)
if (fadeInProgress < 1) {
val alpha = CONTENT_FADE_OUT_INTERPOLATOR.getInterpolation(fadeInProgress)
drawable.alpha = (alpha * 0xFF).roundToInt()
drawable.setXfermode(null)
} else {
val fadeOutProgress = getProgress(linearProgress,
ANIMATION_DELAY_FADE_IN_WINDOW, ANIMATION_DURATION_FADE_IN_WINDOW)
val alpha = 1 - WINDOW_FADE_IN_INTERPOLATOR.getInterpolation(fadeOutProgress)
drawable.alpha = (alpha * 0xFF).roundToInt()
drawable.setXfermode(SRC_MODE)
}
}
private fun applyStateToNavigationBar(
navigationBar: RemoteAnimationTarget,
state: State,

View File

@@ -4,8 +4,6 @@ import android.graphics.Canvas
import android.graphics.ColorFilter
import android.graphics.Matrix
import android.graphics.PixelFormat
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
@@ -111,9 +109,7 @@ open class GhostedViewLaunchAnimatorController(
}
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
backgroundView = FrameLayout(launchContainer.context).apply {
forceHasOverlappingRendering(false)
}
backgroundView = FrameLayout(launchContainer.context)
launchContainerOverlay.add(backgroundView)
// We wrap the ghosted view background and use it to draw the expandable background. Its
@@ -125,9 +121,7 @@ open class GhostedViewLaunchAnimatorController(
// Create a ghost of the view that will be moving and fading out. This allows to fade out
// the content before fading out the background.
ghostView = GhostView.addGhost(ghostedView, launchContainer).apply {
setLayerType(View.LAYER_TYPE_HARDWARE, null)
}
ghostView = GhostView.addGhost(ghostedView, launchContainer)
val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX
matrix.getValues(initialGhostViewMatrixValues)
@@ -139,7 +133,18 @@ open class GhostedViewLaunchAnimatorController(
linearProgress: Float
) {
val ghostView = this.ghostView!!
ghostView.alpha = state.contentAlpha
val backgroundView = this.backgroundView!!
if (!state.visible) {
if (ghostView.visibility == View.VISIBLE) {
// Making the ghost view invisible will make the ghosted view visible, so order is
// important here.
ghostView.visibility = View.INVISIBLE
ghostedView.visibility = View.INVISIBLE
backgroundView.visibility = View.INVISIBLE
}
return
}
val scale = min(state.widthRatio, state.heightRatio)
ghostViewMatrix.setValues(initialGhostViewMatrixValues)
@@ -150,14 +155,12 @@ open class GhostedViewLaunchAnimatorController(
)
ghostView.animationMatrix = ghostViewMatrix
val backgroundView = this.backgroundView!!
backgroundView.top = state.top
backgroundView.bottom = state.bottom
backgroundView.left = state.left
backgroundView.right = state.right
val backgroundDrawable = backgroundDrawable!!
backgroundDrawable.alpha = (0xFF * state.backgroundAlpha).toInt()
backgroundDrawable.wrapped?.let {
setBackgroundCornerRadius(it, state.topCornerRadius, state.bottomCornerRadius)
}
@@ -168,6 +171,7 @@ open class GhostedViewLaunchAnimatorController(
GhostView.removeGhost(ghostedView)
launchContainerOverlay.remove(backgroundView)
ghostedView.visibility = View.VISIBLE
ghostedView.invalidate()
}
@@ -203,10 +207,6 @@ open class GhostedViewLaunchAnimatorController(
}
private class WrappedDrawable(val wrapped: Drawable?) : Drawable() {
companion object {
private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC)
}
private var currentAlpha = 0xFF
private var previousBounds = Rect()
@@ -220,7 +220,6 @@ open class GhostedViewLaunchAnimatorController(
wrapped.alpha = currentAlpha
wrapped.bounds = bounds
setXfermode(wrapped, SRC_MODE)
applyBackgroundRadii()
wrapped.draw(canvas)
@@ -230,7 +229,6 @@ open class GhostedViewLaunchAnimatorController(
// background.
wrapped.alpha = 0
wrapped.bounds = previousBounds
setXfermode(wrapped, null)
restoreBackgroundRadii()
}
@@ -257,27 +255,6 @@ open class GhostedViewLaunchAnimatorController(
wrapped?.colorFilter = filter
}
private fun setXfermode(background: Drawable, mode: PorterDuffXfermode?) {
if (background is InsetDrawable) {
background.drawable?.let { setXfermode(it, mode) }
return
}
if (background !is LayerDrawable) {
background.setXfermode(mode)
return
}
// We set the xfermode on the first layer that is not a mask. Most of the time it will
// be the "background layer".
for (i in 0 until background.numberOfLayers) {
if (background.getId(i) != android.R.id.mask) {
background.getDrawable(i).setXfermode(mode)
break
}
}
}
fun setBackgroundRadius(topCornerRadius: Float, bottomCornerRadius: Float) {
updateRadii(cornerRadii, topCornerRadius, bottomCornerRadius)
invalidateSelf()

View File

@@ -73,7 +73,6 @@ import com.android.internal.widget.CachingIconView;
import com.android.internal.widget.CallLayout;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.plugins.FalsingManager;
@@ -2020,6 +2019,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (params == null) {
return;
}
if (!params.getVisible()) {
if (getVisibility() == View.VISIBLE) {
setVisibility(View.INVISIBLE);
}
return;
}
float zProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
params.getProgress(0, 50));
float translationZ = MathUtils.lerp(params.getStartTranslationZ(),
@@ -2077,10 +2084,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
contentView = mGuts;
}
if (expandAnimationRunning) {
contentView.animate()
.alpha(0f)
.setDuration(ActivityLaunchAnimator.ANIMATION_DURATION_FADE_OUT_CONTENT)
.setInterpolator(ActivityLaunchAnimator.CONTENT_FADE_OUT_INTERPOLATOR);
setAboveShelf(true);
mExpandAnimationRunning = true;
getViewState().cancelAnimations(this);
@@ -2088,6 +2091,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
} else {
mExpandAnimationRunning = false;
setAboveShelf(isAboveShelf());
setVisibility(View.VISIBLE);
if (mGuts != null) {
mGuts.setAlpha(1.0f);
}

View File

@@ -20,7 +20,6 @@ import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
@@ -30,8 +29,6 @@ import android.view.View;
import com.android.internal.util.ArrayUtils;
import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.notification.ExpandAnimationParameters;
/**
@@ -281,11 +278,6 @@ public class NotificationBackgroundView extends View {
public void setExpandAnimationParams(ExpandAnimationParameters params) {
mActualHeight = params.getHeight();
mActualWidth = params.getWidth();
float alphaProgress = Interpolators.ALPHA_IN.getInterpolation(
params.getProgress(
ActivityLaunchAnimator.ANIMATION_DELAY_FADE_IN_WINDOW /* delay */,
ActivityLaunchAnimator.ANIMATION_DURATION_FADE_IN_WINDOW /* duration */));
mBackground.setAlpha((int) (mDrawableAlpha * (1.0f - alphaProgress)));
invalidate();
}
@@ -294,8 +286,6 @@ public class NotificationBackgroundView extends View {
if (mBackground instanceof LayerDrawable) {
GradientDrawable gradientDrawable =
(GradientDrawable) ((LayerDrawable) mBackground).getDrawable(0);
gradientDrawable.setXfermode(
running ? new PorterDuffXfermode(PorterDuff.Mode.SRC) : null);
// Speed optimization: disable AA if transfer mode is not SRC_OVER. AA is not easy to
// spot during animation anyways.
gradientDrawable.setAntiAlias(!running);

View File

@@ -2,6 +2,9 @@ package com.android.systemui.animation
import android.app.ActivityManager
import android.app.WindowConfiguration
import android.content.ComponentName
import android.content.pm.ActivityInfo
import android.content.pm.ApplicationInfo
import android.graphics.Point
import android.graphics.Rect
import android.os.Looper
@@ -167,10 +170,16 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
private fun fakeWindow(): RemoteAnimationTarget {
val bounds = Rect(10 /* left */, 20 /* top */, 30 /* right */, 40 /* bottom */)
val taskInfo = ActivityManager.RunningTaskInfo()
taskInfo.topActivity = ComponentName("com.android.systemui", "FakeActivity")
taskInfo.topActivityInfo = ActivityInfo().apply {
applicationInfo = ApplicationInfo()
}
return RemoteAnimationTarget(
0, RemoteAnimationTarget.MODE_OPENING, SurfaceControl(), false, Rect(), Rect(), 0,
Point(), Rect(), bounds, WindowConfiguration(), false, SurfaceControl(), Rect(),
ActivityManager.RunningTaskInfo()
taskInfo
)
}
}