summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt20
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt42
6 files changed, 90 insertions, 9 deletions
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index ac9298dc9e89..c7d28c3e6180 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -35,6 +35,8 @@ import com.android.wm.shell.startingsurface.SplashscreenContentDrawer
import com.android.wm.shell.startingsurface.SplashscreenContentDrawer.SplashScreenWindowAttrs
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 keyguardHandler: KeyguardHandler,
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)
}
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index ffb7ab4eff7c..b4ffb3f6cf4e 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -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
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 902e8c28a85d..15a70831b2f9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -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,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 98b9cc9bc716..9a6dd38ffca5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -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,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
index d01cdd45181f..a26f110590ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
@@ -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
@@ -175,6 +176,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()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
new file mode 100644
index 000000000000..8cba25dc1b92
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
@@ -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)
+ }
+} \ No newline at end of file