Merge "Polish status bar launch animations." into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
4a381a0856
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user