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.SyncRtSurfaceTransactionApplier
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.animation.AnimationUtils
import android.view.animation.PathInterpolator
@@ -112,7 +113,7 @@ class ActivityLaunchAnimator(context: Context) {
@PublishedApi
internal fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) {
if (Looper.myLooper() != Looper.getMainLooper()) {
this.getRootView().context.mainExecutor.execute {
this.launchContainer.context.mainExecutor.execute {
this.onIntentStarted(willAnimate)
}
} else {
@@ -166,15 +167,19 @@ class ActivityLaunchAnimator(context: Context) {
}
/**
* Return the root [View] that contains the view that started the intent and will be
* animating together with the window.
* The container in which the view that started the intent will be animating together with
* the opening window.
*
* This view will be used to:
* This will be used to:
* - Get the associated [Context].
* - Compute whether we are expanding fully above the current window.
* - 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
@@ -272,9 +277,9 @@ class ActivityLaunchAnimator(context: Context) {
@VisibleForTesting
inner class Runner(private val controller: Controller) : IRemoteAnimationRunner.Stub() {
private val rootView = controller.getRootView()
@PublishedApi internal val context = rootView.context
private val transactionApplier = SyncRtSurfaceTransactionApplier(rootView)
private val launchContainer = controller.launchContainer
@PublishedApi internal val context = launchContainer.context
private val transactionApplier = SyncRtSurfaceTransactionApplier(launchContainer)
private var animator: ValueAnimator? = null
private var windowCrop = Rect()
@@ -291,11 +296,11 @@ class ActivityLaunchAnimator(context: Context) {
@PublishedApi
internal fun postTimeout() {
rootView.postDelayed(onTimeout, LAUNCH_TIMEOUT)
launchContainer.postDelayed(onTimeout, LAUNCH_TIMEOUT)
}
private fun removeTimeout() {
rootView.removeCallbacks(onTimeout)
launchContainer.removeCallbacks(onTimeout)
}
override fun onAnimationStart(
@@ -369,11 +374,11 @@ class ActivityLaunchAnimator(context: Context) {
val endWidth = endRight - endLeft
// 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] &&
endBottom >= rootViewLocation[1] + rootView.height &&
endBottom >= rootViewLocation[1] + launchContainer.height &&
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.
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.View
import android.view.ViewGroup
import android.view.ViewGroupOverlay
import android.widget.FrameLayout
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. */
private val ghostedView: View
) : ActivityLaunchAnimator.Controller {
/** The root view to which we will add the ghost view and expanding background. */
private val rootView = ghostedView.rootView as ViewGroup
private val rootViewOverlay = rootView.overlay
/** The container to which we will add the ghost view and expanding background. */
override var launchContainer = ghostedView.rootView as ViewGroup
private val launchContainerOverlay: ViewGroupOverlay
get() = launchContainer.overlay
/** The ghost view that is drawn and animated instead of the ghosted view. */
private var ghostView: GhostView? = null
@@ -42,7 +44,7 @@ open class GhostedViewLaunchAnimatorController(
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.
*/
private var backgroundView: FrameLayout? = null
@@ -96,10 +98,6 @@ open class GhostedViewLaunchAnimatorController(
return gradient.cornerRadii?.get(CORNER_RADIUS_BOTTOM_INDEX) ?: gradient.cornerRadius
}
override fun getRootView(): View {
return rootView
}
override fun createAnimatorState(): ActivityLaunchAnimator.State {
val location = ghostedView.locationOnScreen
return ActivityLaunchAnimator.State(
@@ -113,10 +111,10 @@ open class GhostedViewLaunchAnimatorController(
}
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
backgroundView = FrameLayout(rootView.context).apply {
backgroundView = FrameLayout(launchContainer.context).apply {
forceHasOverlappingRendering(false)
}
rootViewOverlay.add(backgroundView)
launchContainerOverlay.add(backgroundView)
// 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.
@@ -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
// 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)
}
@@ -169,7 +167,7 @@ open class GhostedViewLaunchAnimatorController(
backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
GhostView.removeGhost(ghostedView)
rootViewOverlay.remove(backgroundView)
launchContainerOverlay.remove(backgroundView)
ghostedView.invalidate()
}

View File

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

View File

@@ -1,6 +1,6 @@
package com.android.systemui.statusbar.notification
import android.view.View
import android.view.ViewGroup
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.statusbar.NotificationShadeDepthController
@@ -45,7 +45,11 @@ class NotificationLaunchAnimatorController(
) : ActivityLaunchAnimator.Controller {
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 {
// 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.accessibility.floatingmenu.AccessibilityFloatingMenuController;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DelegateLaunchAnimatorController;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.camera.CameraIntents;
@@ -2785,13 +2786,8 @@ public class StatusBar extends SystemUI implements DemoMode,
final boolean afterKeyguardGone = mActivityIntentHelper.wouldLaunchResolverActivity(
intent, mLockscreenUserManager.getCurrentUserId());
ActivityLaunchAnimator.Controller animController = null;
if (animationController != null) {
animController = dismissShade ? new StatusBarLaunchAnimatorController(
animationController, this, true /* isLaunchForActivity */)
: animationController;
}
final ActivityLaunchAnimator.Controller animCallbackForLambda = animController;
ActivityLaunchAnimator.Controller animController = wrapAnimationController(
animationController, dismissShade);
// If we animate, we will dismiss the shade only once the animation is done. This is taken
// care of by the StatusBarLaunchAnimationController.
@@ -2804,7 +2800,7 @@ public class StatusBar extends SystemUI implements DemoMode,
intent.addFlags(flags);
int[] result = new int[]{ActivityManager.START_CANCELED};
mActivityLaunchAnimator.startIntentWithAnimation(animCallbackForLambda,
mActivityLaunchAnimator.startIntentWithAnimation(animController,
areLaunchAnimationsEnabled(), (adapter) -> {
ActivityOptions options = new ActivityOptions(
getActivityOptions(mDisplayId, adapter));
@@ -2858,6 +2854,46 @@ public class StatusBar extends SystemUI implements DemoMode,
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() {
mStatusBarKeyguardViewManager.readyForKeyguardDone();
}

View File

@@ -29,6 +29,7 @@ import android.view.Gravity;
import android.view.ViewGroup;
import android.view.WindowManager;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.SuperStatusBarViewFactory;
@@ -51,6 +52,7 @@ public class StatusBarWindowController {
private final State mCurrentState = new State();
private ViewGroup mStatusBarView;
private ViewGroup mLaunchAnimationContainer;
private WindowManager.LayoutParams mLp;
private final WindowManager.LayoutParams mLpChanged;
@@ -62,6 +64,8 @@ public class StatusBarWindowController {
mWindowManager = windowManager;
mSuperStatusBarViewFactory = superStatusBarViewFactory;
mStatusBarView = mSuperStatusBarViewFactory.getStatusBarWindowView();
mLaunchAnimationContainer = mStatusBarView.findViewById(
R.id.status_bar_launch_animation_container);
mLpChanged = new WindowManager.LayoutParams();
mResources = resources;
@@ -124,13 +128,38 @@ public class StatusBarWindowController {
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) {
applyForceStatusBarVisibleFlag(state);
applyHeight();
applyHeight(state);
if (mLp != null && mLp.copyFrom(mLpChanged) != 0) {
mWindowManager.updateViewLayout(mStatusBarView, mLp);
}
@@ -138,10 +167,11 @@ public class StatusBarWindowController {
private static class State {
boolean mForceStatusBarVisible;
boolean mIsLaunchAnimationRunning;
}
private void applyForceStatusBarVisibleFlag(State state) {
if (state.mForceStatusBarVisible) {
if (state.mForceStatusBarVisible || state.mIsLaunchAnimationRunning) {
mLpChanged.privateFlags |= PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
} else {
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.RemoteAnimationTarget
import android.view.SurfaceControl
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import junit.framework.Assert.assertFalse
@@ -36,8 +37,8 @@ import kotlin.concurrent.thread
@RunWithLooper
class ActivityLaunchAnimatorTest : SysuiTestCase() {
private val activityLaunchAnimator = ActivityLaunchAnimator(mContext)
private val rootView = View(mContext)
@Spy private val controller = TestLaunchAnimatorController(rootView)
private val launchContainer = LinearLayout(mContext)
@Spy private val controller = TestLaunchAnimatorController(launchContainer)
@Mock lateinit var iCallback: IRemoteAnimationFinishedCallback
@get:Rule val rule = MockitoJUnit.rule()
@@ -146,10 +147,8 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
* outside of the main thread.
*/
private class TestLaunchAnimatorController(
private val rootView: View
override var launchContainer: ViewGroup
) : ActivityLaunchAnimator.Controller {
override fun getRootView(): View = rootView
override fun createAnimatorState() = ActivityLaunchAnimator.State(
top = 100,
bottom = 200,