Merge "Prevent SysUI from crashing during Ghost animation" into sc-dev am: e388c26283
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15235792 Change-Id: I0c60205096c646fe6c3c0423e42f0fca2902fd02
This commit is contained in:
committed by
Automerger Merge Worker
commit
44b439bae8
@@ -34,6 +34,8 @@ import com.android.wm.shell.startingsurface.SplashscreenContentDrawer
|
||||
import com.android.wm.shell.startingsurface.StartingSurface
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
private const val TAG = "ActivityLaunchAnimator"
|
||||
|
||||
/**
|
||||
* A class that allows activities to be started in a seamless way from a view that is transforming
|
||||
* nicely into the starting window.
|
||||
@@ -43,8 +45,6 @@ class ActivityLaunchAnimator(
|
||||
private val startingSurface: StartingSurface?,
|
||||
context: Context
|
||||
) {
|
||||
private val TAG = this::class.java.simpleName
|
||||
|
||||
companion object {
|
||||
const val ANIMATION_DURATION = 500L
|
||||
private const val ANIMATION_DURATION_FADE_OUT_CONTENT = 150L
|
||||
@@ -233,11 +233,21 @@ class ActivityLaunchAnimator(
|
||||
/**
|
||||
* Return a [Controller] that will animate and expand [view] into the opening window.
|
||||
*
|
||||
* Important: The view must be attached to the window when calling this function and
|
||||
* during the animation.
|
||||
* Important: The view must be attached to a [ViewGroup] when calling this function and
|
||||
* during the animation. For safety, this method will return null when it is not.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun fromView(view: View, cujType: Int? = null): Controller {
|
||||
fun fromView(view: View, cujType: Int? = null): Controller? {
|
||||
if (view.parent !is ViewGroup) {
|
||||
// TODO(b/192194319): Throw instead of just logging.
|
||||
Log.wtf(
|
||||
TAG,
|
||||
"Skipping animation as view $view is not attached to a ViewGroup",
|
||||
Exception()
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
return GhostedViewLaunchAnimatorController(view, cujType)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.graphics.drawable.InsetDrawable
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.util.Log
|
||||
import android.view.GhostView
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@@ -17,13 +18,15 @@ import android.widget.FrameLayout
|
||||
import com.android.internal.jank.InteractionJankMonitor
|
||||
import kotlin.math.min
|
||||
|
||||
private const val TAG = "GhostedViewLaunchAnimatorController"
|
||||
|
||||
/**
|
||||
* A base implementation of [ActivityLaunchAnimator.Controller] which creates a [ghost][GhostView]
|
||||
* of [ghostedView] as well as an expandable background view, which are drawn and animated instead
|
||||
* of the ghosted view.
|
||||
*
|
||||
* Important: [ghostedView] must be attached to the window when calling this function and during the
|
||||
* animation.
|
||||
* Important: [ghostedView] must be attached to a [ViewGroup] when calling this function and during
|
||||
* the animation.
|
||||
*
|
||||
* Note: Avoid instantiating this directly and call [ActivityLaunchAnimator.Controller.fromView]
|
||||
* whenever possible instead.
|
||||
@@ -113,6 +116,13 @@ open class GhostedViewLaunchAnimatorController(
|
||||
}
|
||||
|
||||
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
|
||||
if (ghostedView.parent !is ViewGroup) {
|
||||
// This should usually not happen, but let's make sure we don't crash if the view was
|
||||
// detached right before we started the animation.
|
||||
Log.w(TAG, "Skipping animation as ghostedView is not attached to a ViewGroup")
|
||||
return
|
||||
}
|
||||
|
||||
backgroundView = FrameLayout(launchContainer.context)
|
||||
launchContainerOverlay.add(backgroundView)
|
||||
|
||||
@@ -138,7 +148,7 @@ open class GhostedViewLaunchAnimatorController(
|
||||
progress: Float,
|
||||
linearProgress: Float
|
||||
) {
|
||||
val ghostView = this.ghostView!!
|
||||
val ghostView = this.ghostView ?: return
|
||||
val backgroundView = this.backgroundView!!
|
||||
|
||||
if (!state.visible) {
|
||||
@@ -173,6 +183,11 @@ open class GhostedViewLaunchAnimatorController(
|
||||
}
|
||||
|
||||
override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
|
||||
if (ghostView == null) {
|
||||
// We didn't actually run the animation.
|
||||
return
|
||||
}
|
||||
|
||||
cujType?.let { InteractionJankMonitor.getInstance().end(it) }
|
||||
|
||||
backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
|
||||
|
||||
@@ -475,6 +475,13 @@ public class MediaControlPanel {
|
||||
@Nullable
|
||||
private ActivityLaunchAnimator.Controller buildLaunchAnimatorController(
|
||||
TransitionLayout player) {
|
||||
if (!(player.getParent() instanceof ViewGroup)) {
|
||||
// TODO(b/192194319): Throw instead of just logging.
|
||||
Log.wtf(TAG, "Skipping player animation as it is not attached to a ViewGroup",
|
||||
new Exception());
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO(b/174236650): Make sure that the carousel indicator also fades out.
|
||||
// TODO(b/174236650): Instrument the animation to measure jank.
|
||||
return new GhostedViewLaunchAnimatorController(player,
|
||||
|
||||
@@ -514,7 +514,8 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
|
||||
InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON
|
||||
);
|
||||
ActivityLaunchAnimator.Controller animationController =
|
||||
new StatusBarLaunchAnimatorController(viewController, mStatusBar,
|
||||
viewController == null ? null
|
||||
: new StatusBarLaunchAnimatorController(viewController, mStatusBar,
|
||||
true /* isActivityIntent */);
|
||||
|
||||
mActivityLaunchAnimator.startIntentWithAnimation(animationController, animate,
|
||||
|
||||
@@ -14,6 +14,7 @@ import android.view.IRemoteAnimationFinishedCallback
|
||||
import android.view.RemoteAnimationAdapter
|
||||
import android.view.RemoteAnimationTarget
|
||||
import android.view.SurfaceControl
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import androidx.test.filters.SmallTest
|
||||
@@ -177,6 +178,11 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
|
||||
verify(controller).onLaunchAnimationStart(anyBoolean())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun controllerFromOrphanViewReturnsNull() {
|
||||
assertNull(ActivityLaunchAnimator.Controller.fromView(View(mContext)))
|
||||
}
|
||||
|
||||
private fun fakeWindow(): RemoteAnimationTarget {
|
||||
val bounds = Rect(10 /* left */, 20 /* top */, 30 /* right */, 40 /* bottom */)
|
||||
val taskInfo = ActivityManager.RunningTaskInfo()
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.animation
|
||||
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper
|
||||
import android.widget.LinearLayout
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@TestableLooper.RunWithLooper
|
||||
class GhostedViewLaunchAnimatorControllerTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun animatingOrphanViewDoesNotCrash() {
|
||||
val ghostedView = LinearLayout(mContext)
|
||||
val controller = GhostedViewLaunchAnimatorController(ghostedView)
|
||||
val state = ActivityLaunchAnimator.State(top = 0, bottom = 0, left = 0, right = 0)
|
||||
|
||||
controller.onIntentStarted(willAnimate = true)
|
||||
controller.onLaunchAnimationStart(isExpandingFullyAbove = true)
|
||||
controller.onLaunchAnimationProgress(state, progress = 0f, linearProgress = 0f)
|
||||
controller.onLaunchAnimationEnd(isExpandingFullyAbove = true)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user