Merge "Polish status bar launch animations." into sc-dev

This commit is contained in:
Jordan Demeulenaere
2021-05-03 14:33:59 +00:00
committed by Android (Google) Code Review
8 changed files with 132 additions and 45 deletions

View File

@@ -17,6 +17,7 @@ import android.view.RemoteAnimationAdapter
import android.view.RemoteAnimationTarget import android.view.RemoteAnimationTarget
import android.view.SyncRtSurfaceTransactionApplier import android.view.SyncRtSurfaceTransactionApplier
import android.view.View import android.view.View
import android.view.ViewGroup
import android.view.WindowManager import android.view.WindowManager
import android.view.animation.AnimationUtils import android.view.animation.AnimationUtils
import android.view.animation.PathInterpolator import android.view.animation.PathInterpolator
@@ -112,7 +113,7 @@ class ActivityLaunchAnimator(context: Context) {
@PublishedApi @PublishedApi
internal fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) { internal fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) {
if (Looper.myLooper() != Looper.getMainLooper()) { if (Looper.myLooper() != Looper.getMainLooper()) {
this.getRootView().context.mainExecutor.execute { this.launchContainer.context.mainExecutor.execute {
this.onIntentStarted(willAnimate) this.onIntentStarted(willAnimate)
} }
} else { } else {
@@ -166,15 +167,19 @@ class ActivityLaunchAnimator(context: Context) {
} }
/** /**
* Return the root [View] that contains the view that started the intent and will be * The container in which the view that started the intent will be animating together with
* animating together with the window. * the opening window.
* *
* This view will be used to: * This will be used to:
* - Get the associated [Context]. * - Get the associated [Context].
* - Compute whether we are expanding fully above the current window. * - Compute whether we are expanding fully above the current window.
* - Apply surface transactions in sync with RenderThread. * - Apply surface transactions in sync with RenderThread.
*
* This container can be changed to force this [Controller] to animate the expanding view
* inside a different location, for instance to ensure correct layering during the
* animation.
*/ */
fun getRootView(): View var launchContainer: ViewGroup
/** /**
* Return the [State] of the view that will be animated. We will animate from this state to * Return the [State] of the view that will be animated. We will animate from this state to
@@ -272,9 +277,9 @@ class ActivityLaunchAnimator(context: Context) {
@VisibleForTesting @VisibleForTesting
inner class Runner(private val controller: Controller) : IRemoteAnimationRunner.Stub() { inner class Runner(private val controller: Controller) : IRemoteAnimationRunner.Stub() {
private val rootView = controller.getRootView() private val launchContainer = controller.launchContainer
@PublishedApi internal val context = rootView.context @PublishedApi internal val context = launchContainer.context
private val transactionApplier = SyncRtSurfaceTransactionApplier(rootView) private val transactionApplier = SyncRtSurfaceTransactionApplier(launchContainer)
private var animator: ValueAnimator? = null private var animator: ValueAnimator? = null
private var windowCrop = Rect() private var windowCrop = Rect()
@@ -291,11 +296,11 @@ class ActivityLaunchAnimator(context: Context) {
@PublishedApi @PublishedApi
internal fun postTimeout() { internal fun postTimeout() {
rootView.postDelayed(onTimeout, LAUNCH_TIMEOUT) launchContainer.postDelayed(onTimeout, LAUNCH_TIMEOUT)
} }
private fun removeTimeout() { private fun removeTimeout() {
rootView.removeCallbacks(onTimeout) launchContainer.removeCallbacks(onTimeout)
} }
override fun onAnimationStart( override fun onAnimationStart(
@@ -369,11 +374,11 @@ class ActivityLaunchAnimator(context: Context) {
val endWidth = endRight - endLeft val endWidth = endRight - endLeft
// TODO(b/184121838): Ensure that we are launching on the same screen. // TODO(b/184121838): Ensure that we are launching on the same screen.
val rootViewLocation = rootView.locationOnScreen val rootViewLocation = launchContainer.locationOnScreen
val isExpandingFullyAbove = endTop <= rootViewLocation[1] && val isExpandingFullyAbove = endTop <= rootViewLocation[1] &&
endBottom >= rootViewLocation[1] + rootView.height && endBottom >= rootViewLocation[1] + launchContainer.height &&
endLeft <= rootViewLocation[0] && endLeft <= rootViewLocation[0] &&
endRight >= rootViewLocation[0] + rootView.width endRight >= rootViewLocation[0] + launchContainer.width
// TODO(b/184121838): We should somehow get the top and bottom radius of the window. // TODO(b/184121838): We should somehow get the top and bottom radius of the window.
val endRadius = if (isExpandingFullyAbove) { val endRadius = if (isExpandingFullyAbove) {

View File

@@ -0,0 +1,10 @@
package com.android.systemui.animation
/**
* A base class to easily create an implementation of [ActivityLaunchAnimator.Controller] which
* delegates most of its call to [delegate]. This is mostly useful for Java code which can't easily
* create such a delegated class.
*/
open class DelegateLaunchAnimatorController(
protected val delegate: ActivityLaunchAnimator.Controller
) : ActivityLaunchAnimator.Controller by delegate

View File

@@ -14,6 +14,7 @@ import android.graphics.drawable.LayerDrawable
import android.view.GhostView import android.view.GhostView
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewGroupOverlay
import android.widget.FrameLayout import android.widget.FrameLayout
import kotlin.math.min import kotlin.math.min
@@ -32,9 +33,10 @@ open class GhostedViewLaunchAnimatorController(
/** The view that will be ghosted and from which the background will be extracted. */ /** The view that will be ghosted and from which the background will be extracted. */
private val ghostedView: View private val ghostedView: View
) : ActivityLaunchAnimator.Controller { ) : ActivityLaunchAnimator.Controller {
/** The root view to which we will add the ghost view and expanding background. */ /** The container to which we will add the ghost view and expanding background. */
private val rootView = ghostedView.rootView as ViewGroup override var launchContainer = ghostedView.rootView as ViewGroup
private val rootViewOverlay = rootView.overlay private val launchContainerOverlay: ViewGroupOverlay
get() = launchContainer.overlay
/** The ghost view that is drawn and animated instead of the ghosted view. */ /** The ghost view that is drawn and animated instead of the ghosted view. */
private var ghostView: GhostView? = null private var ghostView: GhostView? = null
@@ -42,7 +44,7 @@ open class GhostedViewLaunchAnimatorController(
private val ghostViewMatrix = Matrix() private val ghostViewMatrix = Matrix()
/** /**
* The expanding background view that will be added to [rootView] (below [ghostView]) and * The expanding background view that will be added to [launchContainer] (below [ghostView]) and
* animate. * animate.
*/ */
private var backgroundView: FrameLayout? = null private var backgroundView: FrameLayout? = null
@@ -96,10 +98,6 @@ open class GhostedViewLaunchAnimatorController(
return gradient.cornerRadii?.get(CORNER_RADIUS_BOTTOM_INDEX) ?: gradient.cornerRadius return gradient.cornerRadii?.get(CORNER_RADIUS_BOTTOM_INDEX) ?: gradient.cornerRadius
} }
override fun getRootView(): View {
return rootView
}
override fun createAnimatorState(): ActivityLaunchAnimator.State { override fun createAnimatorState(): ActivityLaunchAnimator.State {
val location = ghostedView.locationOnScreen val location = ghostedView.locationOnScreen
return ActivityLaunchAnimator.State( return ActivityLaunchAnimator.State(
@@ -113,10 +111,10 @@ open class GhostedViewLaunchAnimatorController(
} }
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) { override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
backgroundView = FrameLayout(rootView.context).apply { backgroundView = FrameLayout(launchContainer.context).apply {
forceHasOverlappingRendering(false) forceHasOverlappingRendering(false)
} }
rootViewOverlay.add(backgroundView) launchContainerOverlay.add(backgroundView)
// We wrap the ghosted view background and use it to draw the expandable background. Its // We wrap the ghosted view background and use it to draw the expandable background. Its
// alpha will be set to 0 as soon as we start drawing the expanding background. // alpha will be set to 0 as soon as we start drawing the expanding background.
@@ -127,7 +125,7 @@ open class GhostedViewLaunchAnimatorController(
// Create a ghost of the view that will be moving and fading out. This allows to fade out // 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. // the content before fading out the background.
ghostView = GhostView.addGhost(ghostedView, rootView).apply { ghostView = GhostView.addGhost(ghostedView, launchContainer).apply {
setLayerType(View.LAYER_TYPE_HARDWARE, null) setLayerType(View.LAYER_TYPE_HARDWARE, null)
} }
@@ -169,7 +167,7 @@ open class GhostedViewLaunchAnimatorController(
backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
GhostView.removeGhost(ghostedView) GhostView.removeGhost(ghostedView)
rootViewOverlay.remove(backgroundView) launchContainerOverlay.remove(backgroundView)
ghostedView.invalidate() ghostedView.invalidate()
} }

View File

@@ -25,6 +25,11 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:fitsSystemWindows="true"> android:fitsSystemWindows="true">
<FrameLayout
android:id="@+id/status_bar_launch_animation_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<FrameLayout <FrameLayout
android:id="@+id/status_bar_container" android:id="@+id/status_bar_container"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -1,6 +1,6 @@
package com.android.systemui.statusbar.notification package com.android.systemui.statusbar.notification
import android.view.View import android.view.ViewGroup
import com.android.internal.jank.InteractionJankMonitor import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.statusbar.NotificationShadeDepthController import com.android.systemui.statusbar.NotificationShadeDepthController
@@ -45,7 +45,11 @@ class NotificationLaunchAnimatorController(
) : ActivityLaunchAnimator.Controller { ) : ActivityLaunchAnimator.Controller {
private val notificationKey = notification.entry.sbn.key private val notificationKey = notification.entry.sbn.key
override fun getRootView(): View = notification.rootView override var launchContainer: ViewGroup
get() = notification.rootView as ViewGroup
set(ignored) {
// Do nothing. Notifications are always animated inside their rootView.
}
override fun createAnimatorState(): ActivityLaunchAnimator.State { override fun createAnimatorState(): ActivityLaunchAnimator.State {
// If the notification panel is collapsed, the clip may be larger than the height. // If the notification panel is collapsed, the clip may be larger than the height.

View File

@@ -148,6 +148,7 @@ import com.android.systemui.R;
import com.android.systemui.SystemUI; import com.android.systemui.SystemUI;
import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController; import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DelegateLaunchAnimatorController;
import com.android.systemui.assist.AssistManager; import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.camera.CameraIntents; import com.android.systemui.camera.CameraIntents;
@@ -2785,13 +2786,8 @@ public class StatusBar extends SystemUI implements DemoMode,
final boolean afterKeyguardGone = mActivityIntentHelper.wouldLaunchResolverActivity( final boolean afterKeyguardGone = mActivityIntentHelper.wouldLaunchResolverActivity(
intent, mLockscreenUserManager.getCurrentUserId()); intent, mLockscreenUserManager.getCurrentUserId());
ActivityLaunchAnimator.Controller animController = null; ActivityLaunchAnimator.Controller animController = wrapAnimationController(
if (animationController != null) { animationController, dismissShade);
animController = dismissShade ? new StatusBarLaunchAnimatorController(
animationController, this, true /* isLaunchForActivity */)
: animationController;
}
final ActivityLaunchAnimator.Controller animCallbackForLambda = animController;
// If we animate, we will dismiss the shade only once the animation is done. This is taken // If we animate, we will dismiss the shade only once the animation is done. This is taken
// care of by the StatusBarLaunchAnimationController. // care of by the StatusBarLaunchAnimationController.
@@ -2804,7 +2800,7 @@ public class StatusBar extends SystemUI implements DemoMode,
intent.addFlags(flags); intent.addFlags(flags);
int[] result = new int[]{ActivityManager.START_CANCELED}; int[] result = new int[]{ActivityManager.START_CANCELED};
mActivityLaunchAnimator.startIntentWithAnimation(animCallbackForLambda, mActivityLaunchAnimator.startIntentWithAnimation(animController,
areLaunchAnimationsEnabled(), (adapter) -> { areLaunchAnimationsEnabled(), (adapter) -> {
ActivityOptions options = new ActivityOptions( ActivityOptions options = new ActivityOptions(
getActivityOptions(mDisplayId, adapter)); getActivityOptions(mDisplayId, adapter));
@@ -2858,6 +2854,46 @@ public class StatusBar extends SystemUI implements DemoMode,
afterKeyguardGone, true /* deferred */); afterKeyguardGone, true /* deferred */);
} }
@Nullable
private ActivityLaunchAnimator.Controller wrapAnimationController(
@Nullable ActivityLaunchAnimator.Controller animationController, boolean dismissShade) {
if (animationController == null) {
return null;
}
View rootView = animationController.getLaunchContainer().getRootView();
if (rootView == mSuperStatusBarViewFactory.getStatusBarWindowView()) {
// We are animating a view in the status bar. We have to make sure that the status bar
// window matches the full screen during the animation and that we are expanding the
// view below the other status bar text.
animationController.setLaunchContainer(
mStatusBarWindowController.getLaunchAnimationContainer());
return new DelegateLaunchAnimatorController(animationController) {
@Override
public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {
getDelegate().onLaunchAnimationStart(isExpandingFullyAbove);
mStatusBarWindowController.setLaunchAnimationRunning(true);
}
@Override
public void onLaunchAnimationEnd(boolean isExpandingFullyAbove) {
getDelegate().onLaunchAnimationEnd(isExpandingFullyAbove);
mStatusBarWindowController.setLaunchAnimationRunning(false);
}
};
}
if (dismissShade && rootView == mNotificationShadeWindowView) {
// We are animating a view in the shade. We have to make sure that we collapse it when
// the animation ends or is cancelled.
return new StatusBarLaunchAnimatorController(animationController, this,
true /* isLaunchForActivity */);
}
return animationController;
}
public void readyForKeyguardDone() { public void readyForKeyguardDone() {
mStatusBarKeyguardViewManager.readyForKeyguardDone(); mStatusBarKeyguardViewManager.readyForKeyguardDone();
} }

View File

@@ -29,6 +29,7 @@ import android.view.Gravity;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowManager; import android.view.WindowManager;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.SuperStatusBarViewFactory;
@@ -51,6 +52,7 @@ public class StatusBarWindowController {
private final State mCurrentState = new State(); private final State mCurrentState = new State();
private ViewGroup mStatusBarView; private ViewGroup mStatusBarView;
private ViewGroup mLaunchAnimationContainer;
private WindowManager.LayoutParams mLp; private WindowManager.LayoutParams mLp;
private final WindowManager.LayoutParams mLpChanged; private final WindowManager.LayoutParams mLpChanged;
@@ -62,6 +64,8 @@ public class StatusBarWindowController {
mWindowManager = windowManager; mWindowManager = windowManager;
mSuperStatusBarViewFactory = superStatusBarViewFactory; mSuperStatusBarViewFactory = superStatusBarViewFactory;
mStatusBarView = mSuperStatusBarViewFactory.getStatusBarWindowView(); mStatusBarView = mSuperStatusBarViewFactory.getStatusBarWindowView();
mLaunchAnimationContainer = mStatusBarView.findViewById(
R.id.status_bar_launch_animation_container);
mLpChanged = new WindowManager.LayoutParams(); mLpChanged = new WindowManager.LayoutParams();
mResources = resources; mResources = resources;
@@ -124,13 +128,38 @@ public class StatusBarWindowController {
apply(mCurrentState); apply(mCurrentState);
} }
private void applyHeight() { /**
mLpChanged.height = mBarHeight; * Return the container in which we should run launch animations started from the status bar and
* expanding into the opening window.
*
* @see #setLaunchAnimationRunning
*/
public ViewGroup getLaunchAnimationContainer() {
return mLaunchAnimationContainer;
}
/**
* Set whether a launch animation is currently running. If true, this will ensure that the
* window matches its parent height so that the animation is not clipped by the normal status
* bar height.
*/
public void setLaunchAnimationRunning(boolean isLaunchAnimationRunning) {
if (isLaunchAnimationRunning == mCurrentState.mIsLaunchAnimationRunning) {
return;
}
mCurrentState.mIsLaunchAnimationRunning = isLaunchAnimationRunning;
apply(mCurrentState);
}
private void applyHeight(State state) {
mLpChanged.height =
state.mIsLaunchAnimationRunning ? ViewGroup.LayoutParams.MATCH_PARENT : mBarHeight;
} }
private void apply(State state) { private void apply(State state) {
applyForceStatusBarVisibleFlag(state); applyForceStatusBarVisibleFlag(state);
applyHeight(); applyHeight(state);
if (mLp != null && mLp.copyFrom(mLpChanged) != 0) { if (mLp != null && mLp.copyFrom(mLpChanged) != 0) {
mWindowManager.updateViewLayout(mStatusBarView, mLp); mWindowManager.updateViewLayout(mStatusBarView, mLp);
} }
@@ -138,10 +167,11 @@ public class StatusBarWindowController {
private static class State { private static class State {
boolean mForceStatusBarVisible; boolean mForceStatusBarVisible;
boolean mIsLaunchAnimationRunning;
} }
private void applyForceStatusBarVisibleFlag(State state) { private void applyForceStatusBarVisibleFlag(State state) {
if (state.mForceStatusBarVisible) { if (state.mForceStatusBarVisible || state.mIsLaunchAnimationRunning) {
mLpChanged.privateFlags |= PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR; mLpChanged.privateFlags |= PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
} else { } else {
mLpChanged.privateFlags &= ~PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR; mLpChanged.privateFlags &= ~PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;

View File

@@ -11,7 +11,8 @@ import android.view.IRemoteAnimationFinishedCallback
import android.view.RemoteAnimationAdapter import android.view.RemoteAnimationAdapter
import android.view.RemoteAnimationTarget import android.view.RemoteAnimationTarget
import android.view.SurfaceControl import android.view.SurfaceControl
import android.view.View import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import junit.framework.Assert.assertFalse import junit.framework.Assert.assertFalse
@@ -36,8 +37,8 @@ import kotlin.concurrent.thread
@RunWithLooper @RunWithLooper
class ActivityLaunchAnimatorTest : SysuiTestCase() { class ActivityLaunchAnimatorTest : SysuiTestCase() {
private val activityLaunchAnimator = ActivityLaunchAnimator(mContext) private val activityLaunchAnimator = ActivityLaunchAnimator(mContext)
private val rootView = View(mContext) private val launchContainer = LinearLayout(mContext)
@Spy private val controller = TestLaunchAnimatorController(rootView) @Spy private val controller = TestLaunchAnimatorController(launchContainer)
@Mock lateinit var iCallback: IRemoteAnimationFinishedCallback @Mock lateinit var iCallback: IRemoteAnimationFinishedCallback
@get:Rule val rule = MockitoJUnit.rule() @get:Rule val rule = MockitoJUnit.rule()
@@ -146,10 +147,8 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
* outside of the main thread. * outside of the main thread.
*/ */
private class TestLaunchAnimatorController( private class TestLaunchAnimatorController(
private val rootView: View override var launchContainer: ViewGroup
) : ActivityLaunchAnimator.Controller { ) : ActivityLaunchAnimator.Controller {
override fun getRootView(): View = rootView
override fun createAnimatorState() = ActivityLaunchAnimator.State( override fun createAnimatorState() = ActivityLaunchAnimator.State(
top = 100, top = 100,
bottom = 200, bottom = 200,