From 7155bf102fac826bd5f07082b571e1722555ef23 Mon Sep 17 00:00:00 2001 From: Joshua Tsuji Date: Thu, 13 Feb 2020 16:14:29 -0500 Subject: [PATCH 1/2] Modifies Bubbles to use the FloatingContentCoordinator. Test: atest SystemUITests Bug: 138115889 Change-Id: I599024a140f9c9c2e54e835ba3f32d180e07c39a --- .../systemui/bubbles/BubbleController.java | 14 +- .../systemui/bubbles/BubbleStackView.java | 14 +- .../animation/StackAnimationController.java | 120 +++++++++++++++++- .../systemui/bubbles/dagger/BubbleModule.java | 7 +- .../bubbles/BubbleControllerTest.java | 6 +- .../NewNotifPipelineBubbleControllerTest.java | 6 +- .../bubbles/TestableBubbleController.java | 6 +- .../StackAnimationControllerTest.java | 34 ++++- 8 files changed, 182 insertions(+), 25 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 1f94dbd5e59af..de707b9527bbc 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -94,6 +94,7 @@ import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.util.FloatingContentCoordinator; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -140,6 +141,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi @Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer; private final NotificationGroupManager mNotificationGroupManager; private final ShadeController mShadeController; + private final FloatingContentCoordinator mFloatingContentCoordinator; private BubbleData mBubbleData; @Nullable private BubbleStackView mStackView; @@ -284,11 +286,12 @@ public class BubbleController implements ConfigurationController.ConfigurationLi NotificationEntryManager entryManager, NotifPipeline notifPipeline, FeatureFlags featureFlags, - DumpController dumpController) { + DumpController dumpController, + FloatingContentCoordinator floatingContentCoordinator) { this(context, notificationShadeWindowController, statusBarStateController, shadeController, data, null /* synchronizer */, configurationController, interruptionStateProvider, zenModeController, notifUserManager, groupManager, entryManager, - notifPipeline, featureFlags, dumpController); + notifPipeline, featureFlags, dumpController, floatingContentCoordinator); } /** @@ -308,13 +311,15 @@ public class BubbleController implements ConfigurationController.ConfigurationLi NotificationEntryManager entryManager, NotifPipeline notifPipeline, FeatureFlags featureFlags, - DumpController dumpController) { + DumpController dumpController, + FloatingContentCoordinator floatingContentCoordinator) { dumpController.registerDumpable(TAG, this); mContext = context; mShadeController = shadeController; mNotificationInterruptionStateProvider = interruptionStateProvider; mNotifUserManager = notifUserManager; mZenModeController = zenModeController; + mFloatingContentCoordinator = floatingContentCoordinator; mZenModeController.addCallback(new ZenModeController.Callback() { @Override public void onZenChanged(int zen) { @@ -584,7 +589,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi */ private void ensureStackViewCreated() { if (mStackView == null) { - mStackView = new BubbleStackView(mContext, mBubbleData, mSurfaceSynchronizer); + mStackView = new BubbleStackView( + mContext, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator); ViewGroup nsv = mNotificationShadeWindowController.getNotificationShadeView(); int bubbleScrimIndex = nsv.indexOfChild(nsv.findViewById(R.id.scrim_for_bubble)); int stackIndex = bubbleScrimIndex + 1; // Show stack above bubble scrim. diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 9a6295a80c1c6..3d9865c052221 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -72,6 +72,7 @@ import com.android.systemui.bubbles.animation.ExpandedAnimationController; import com.android.systemui.bubbles.animation.PhysicsAnimationLayout; import com.android.systemui.bubbles.animation.StackAnimationController; import com.android.systemui.shared.system.SysUiStatsLog; +import com.android.systemui.util.FloatingContentCoordinator; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -319,7 +320,8 @@ public class BubbleStackView extends FrameLayout { private BubbleOverflow mBubbleOverflow; public BubbleStackView(Context context, BubbleData data, - @Nullable SurfaceSynchronizer synchronizer) { + @Nullable SurfaceSynchronizer synchronizer, + FloatingContentCoordinator floatingContentCoordinator) { super(context); mBubbleData = data; @@ -353,7 +355,7 @@ public class BubbleStackView extends FrameLayout { mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding); int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation); - mStackAnimationController = new StackAnimationController(); + mStackAnimationController = new StackAnimationController(floatingContentCoordinator); mExpandedAnimationController = new ExpandedAnimationController( mDisplaySize, mExpandedViewPadding, res.getConfiguration().orientation); @@ -620,16 +622,16 @@ public class BubbleStackView extends FrameLayout { mBubbleData.setExpanded(true); return true; } else if (action == R.id.action_move_top_left) { - mStackAnimationController.springStack(stackBounds.left, stackBounds.top); + mStackAnimationController.springStackAfterFling(stackBounds.left, stackBounds.top); return true; } else if (action == R.id.action_move_top_right) { - mStackAnimationController.springStack(stackBounds.right, stackBounds.top); + mStackAnimationController.springStackAfterFling(stackBounds.right, stackBounds.top); return true; } else if (action == R.id.action_move_bottom_left) { - mStackAnimationController.springStack(stackBounds.left, stackBounds.bottom); + mStackAnimationController.springStackAfterFling(stackBounds.left, stackBounds.bottom); return true; } else if (action == R.id.action_move_bottom_right) { - mStackAnimationController.springStack(stackBounds.right, stackBounds.bottom); + mStackAnimationController.springStackAfterFling(stackBounds.right, stackBounds.bottom); return true; } return false; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java index 793f8b9bc05ba..60c8c4e10cf05 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java @@ -16,8 +16,10 @@ package com.android.systemui.bubbles.animation; +import android.annotation.NonNull; import android.content.res.Resources; import android.graphics.PointF; +import android.graphics.Rect; import android.graphics.RectF; import android.util.Log; import android.view.View; @@ -31,6 +33,8 @@ import androidx.dynamicanimation.animation.SpringAnimation; import androidx.dynamicanimation.animation.SpringForce; import com.android.systemui.R; +import com.android.systemui.util.FloatingContentCoordinator; +import com.android.systemui.util.animation.PhysicsAnimator; import com.google.android.collect.Sets; @@ -95,6 +99,12 @@ public class StackAnimationController extends */ private PointF mStackPosition = new PointF(-1, -1); + /** + * The area that Bubbles will occupy after all animations end. This is used to move other + * floating content out of the way proactively. + */ + private Rect mAnimatingToBounds = new Rect(); + /** Whether or not the stack's start position has been set. */ private boolean mStackMovedToStartPosition = false; @@ -163,11 +173,70 @@ public class StackAnimationController extends /** Height of the status bar. */ private float mStatusBarHeight; + /** FloatingContentCoordinator instance for resolving floating content conflicts. */ + private FloatingContentCoordinator mFloatingContentCoordinator; + + /** + * FloatingContent instance that returns the stack's location on the screen, and moves it when + * requested. + */ + private final FloatingContentCoordinator.FloatingContent mStackFloatingContent = + new FloatingContentCoordinator.FloatingContent() { + + private final Rect mFloatingBoundsOnScreen = new Rect(); + + @Override + public void moveToBounds(@NonNull Rect bounds) { + springStack(bounds.left, bounds.top, SpringForce.STIFFNESS_LOW); + } + + @NonNull + @Override + public Rect getAllowedFloatingBoundsRegion() { + final Rect floatingBounds = getFloatingBoundsOnScreen(); + final Rect allowableStackArea = new Rect(); + getAllowableStackPositionRegion().roundOut(allowableStackArea); + allowableStackArea.right += floatingBounds.width(); + allowableStackArea.bottom += floatingBounds.height(); + return allowableStackArea; + } + + @NonNull + @Override + public Rect getFloatingBoundsOnScreen() { + if (!mAnimatingToBounds.isEmpty()) { + return mAnimatingToBounds; + } + + if (mLayout.getChildCount() > 0) { + // Calculate the bounds using stack position + bubble size so that we don't need to + // wait for the bubble views to lay out. + mFloatingBoundsOnScreen.set( + (int) mStackPosition.x, + (int) mStackPosition.y, + (int) mStackPosition.x + mBubbleSize, + (int) mStackPosition.y + mBubbleSize + mBubblePaddingTop); + } else { + mFloatingBoundsOnScreen.setEmpty(); + } + + return mFloatingBoundsOnScreen; + } + }; + + public StackAnimationController( + FloatingContentCoordinator floatingContentCoordinator) { + mFloatingContentCoordinator = floatingContentCoordinator; + } + /** * Instantly move the first bubble to the given point, and animate the rest of the stack behind * it with the 'following' effect. */ public void moveFirstBubbleWithStackFollowing(float x, float y) { + // If we're moving the bubble around, we're not animating to any bounds. + mAnimatingToBounds.setEmpty(); + // If we manually move the bubbles with the IME open, clear the return point since we don't // want the stack to snap away from the new position. mPreImeY = Float.MIN_VALUE; @@ -204,22 +273,32 @@ public class StackAnimationController extends * Note that we need new SpringForce instances per animation despite identical configs because * SpringAnimation uses SpringForce's internal (changing) velocity while the animation runs. */ - public void springStack(float destinationX, float destinationY) { + public void springStack(float destinationX, float destinationY, float stiffness) { + notifyFloatingCoordinatorStackAnimatingTo(destinationX, destinationY); + springFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_X, new SpringForce() - .setStiffness(SPRING_AFTER_FLING_STIFFNESS) + .setStiffness(stiffness) .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO), 0 /* startXVelocity */, destinationX); springFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_Y, new SpringForce() - .setStiffness(SPRING_AFTER_FLING_STIFFNESS) + .setStiffness(stiffness) .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO), 0 /* startYVelocity */, destinationY); } + /** + * Springs the stack to the specified x/y coordinates, with the stiffness used for springs after + * flings. + */ + public void springStackAfterFling(float destinationX, float destinationY) { + springStack(destinationX, destinationY, SPRING_AFTER_FLING_STIFFNESS); + } + /** * Flings the stack starting with the given velocities, springing it to the nearest edge * afterward. @@ -253,6 +332,13 @@ public class StackAnimationController extends final float minimumVelocityToReachEdge = (destinationRelativeX - x) * (FLING_FRICTION_X * 4.2f); + final float estimatedY = PhysicsAnimator.estimateFlingEndValue( + mStackPosition.y, velY, + new PhysicsAnimator.FlingConfig( + FLING_FRICTION_Y, stackBounds.top, stackBounds.bottom)); + + notifyFloatingCoordinatorStackAnimatingTo(destinationRelativeX, estimatedY); + // Use the touch event's velocity if it's sufficient, otherwise use the minimum velocity so // that it'll make it all the way to the side of the screen. final float startXVelocity = stackShouldFlingLeft @@ -426,14 +512,28 @@ public class StackAnimationController extends .setStiffness(SpringForce.STIFFNESS_LOW), /* startVel */ 0f, destinationY); + + notifyFloatingCoordinatorStackAnimatingTo(mStackPosition.x, destinationY); } } /** - * Returns the region within which the stack is allowed to rest. This goes slightly off the left + * Notifies the floating coordinator that we're moving, and sets {@link #mAnimatingToBounds} so + * we return these bounds from + * {@link FloatingContentCoordinator.FloatingContent#getFloatingBoundsOnScreen()}. + */ + private void notifyFloatingCoordinatorStackAnimatingTo(float x, float y) { + final Rect floatingBounds = mStackFloatingContent.getFloatingBoundsOnScreen(); + floatingBounds.offsetTo((int) x, (int) y); + mAnimatingToBounds = floatingBounds; + mFloatingContentCoordinator.onContentMoved(mStackFloatingContent); + } + + /** + * Returns the region that the stack position must stay within. This goes slightly off the left * and right sides of the screen, below the status bar/cutout and above the navigation bar. - * While the stack is not allowed to rest outside of these bounds, it can temporarily be - * animated or dragged beyond them. + * While the stack position is not allowed to rest outside of these bounds, it can temporarily + * be animated or dragged beyond them. */ public RectF getAllowableStackPositionRegion() { final WindowInsets insets = mLayout.getRootWindowInsets(); @@ -690,6 +790,10 @@ public class StackAnimationController extends setStackPosition(mRestingStackPosition == null ? getDefaultStartPosition() : mRestingStackPosition); + + // Remove the stack from the coordinator since we don't have any bubbles and aren't + // visible. + mFloatingContentCoordinator.onContentRemoved(mStackFloatingContent); } } @@ -741,6 +845,10 @@ public class StackAnimationController extends // Animate in the top bubble now that we're visible. if (mLayout.getChildCount() > 0) { + // Add the stack to the floating content coordinator now that we have a bubble and + // are visible. + mFloatingContentCoordinator.onContentAdded(mStackFloatingContent); + animateInBubble(mLayout.getChildAt(0), 0 /* index */); } }); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java index 0337ee37bd374..f057d0b652946 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java @@ -32,6 +32,7 @@ import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.util.FloatingContentCoordinator; import javax.inject.Singleton; @@ -60,7 +61,8 @@ public interface BubbleModule { NotificationEntryManager entryManager, NotifPipeline notifPipeline, FeatureFlags featureFlags, - DumpController dumpController) { + DumpController dumpController, + FloatingContentCoordinator floatingContentCoordinator) { return new BubbleController( context, notificationShadeWindowController, @@ -76,6 +78,7 @@ public interface BubbleModule { entryManager, notifPipeline, featureFlags, - dumpController); + dumpController, + floatingContentCoordinator); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index fc79fcb9dc665..daea7a7a8dc99 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -84,6 +84,7 @@ import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.util.FloatingContentCoordinator; import com.android.systemui.util.InjectionInflationController; import org.junit.Before; @@ -129,6 +130,8 @@ public class BubbleControllerTest extends SysuiTestCase { private SysuiStatusBarStateController mStatusBarStateController; @Mock private KeyguardBypassController mKeyguardBypassController; + @Mock + private FloatingContentCoordinator mFloatingContentCoordinator; @Captor private ArgumentCaptor mEntryListenerCaptor; @@ -243,7 +246,8 @@ public class BubbleControllerTest extends SysuiTestCase { mNotificationEntryManager, mNotifPipeline, mFeatureFlagsOldPipeline, - mDumpController); + mDumpController, + mFloatingContentCoordinator); mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener); mBubbleController.setExpandListener(mBubbleExpandListener); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java index 24f8a7b52f917..b412ca5b47c4d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java @@ -79,6 +79,7 @@ import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.util.FloatingContentCoordinator; import com.android.systemui.util.InjectionInflationController; import org.junit.Before; @@ -126,6 +127,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { private SysuiStatusBarStateController mStatusBarStateController; @Mock private KeyguardBypassController mKeyguardBypassController; + @Mock + private FloatingContentCoordinator mFloatingContentCoordinator; @Captor private ArgumentCaptor mNotifListenerCaptor; @@ -232,7 +235,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { mNotificationEntryManager, mNotifPipeline, mFeatureFlagsNewPipeline, - mDumpController); + mDumpController, + mFloatingContentCoordinator); mBubbleController.addNotifCallback(mNotifCallback); mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener); mBubbleController.setExpandListener(mBubbleExpandListener); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java index 338abf59c9186..f9849f46d24dc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java @@ -30,6 +30,7 @@ import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.util.FloatingContentCoordinator; /** * Testable BubbleController subclass that immediately synchronizes surfaces. @@ -50,12 +51,13 @@ public class TestableBubbleController extends BubbleController { NotificationEntryManager entryManager, NotifPipeline notifPipeline, FeatureFlags featureFlags, - DumpController dumpController) { + DumpController dumpController, + FloatingContentCoordinator floatingContentCoordinator) { super(context, notificationShadeWindowController, statusBarStateController, shadeController, data, Runnable::run, configurationController, interruptionStateProvider, zenModeController, lockscreenUserManager, groupManager, entryManager, - notifPipeline, featureFlags, dumpController); + notifPipeline, featureFlags, dumpController, floatingContentCoordinator); setInflateSynchronously(true); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java index d79128ca5c788..9cc034996687a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java @@ -18,6 +18,10 @@ package com.android.systemui.bubbles.animation; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.graphics.PointF; @@ -30,13 +34,14 @@ import androidx.dynamicanimation.animation.SpringForce; import androidx.test.filters.SmallTest; import com.android.systemui.R; +import com.android.systemui.util.FloatingContentCoordinator; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.Spy; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -45,8 +50,10 @@ import java.util.concurrent.TimeUnit; @RunWith(AndroidTestingRunner.class) public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase { - @Spy - private TestableStackController mStackController = new TestableStackController(); + @Mock + private FloatingContentCoordinator mFloatingContentCoordinator; + + private TestableStackController mStackController; private int mStackOffset; private Runnable mCheckStartPosSet; @@ -54,6 +61,7 @@ public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase @Before public void setUp() throws Exception { super.setUp(); + mStackController = spy(new TestableStackController(mFloatingContentCoordinator)); mLayout.setActiveController(mStackController); addOneMoreThanBubbleLimitBubbles(); mStackOffset = mLayout.getResources().getDimensionPixelSize(R.dimen.bubble_stack_offset); @@ -288,6 +296,21 @@ public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase assertEquals(30, mStackController.getStackPosition().y, 1f); } + @Test + public void testFloatingCoordinator() { + // We should have called onContentAdded only once while adding all of the bubbles in + // setup(). + verify(mFloatingContentCoordinator, times(1)).onContentAdded(any()); + verify(mFloatingContentCoordinator, never()).onContentRemoved(any()); + + // Remove all views and verify that we called onContentRemoved only once. + while (mLayout.getChildCount() > 0) { + mLayout.removeView(mLayout.getChildAt(0)); + } + + verify(mFloatingContentCoordinator, times(1)).onContentRemoved(any()); + } + /** * Checks every child view to make sure it's stacked at the given coordinates, off to the left * or right side depending on offset multiplier. @@ -328,6 +351,11 @@ public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase * Testable version of the stack controller that dispatches its animations on the main thread. */ private class TestableStackController extends StackAnimationController { + TestableStackController( + FloatingContentCoordinator floatingContentCoordinator) { + super(floatingContentCoordinator); + } + @Override protected void flingThenSpringFirstBubbleWithStackFollowing( DynamicAnimation.ViewProperty property, float vel, float friction, From c81ff3d4e90f0b72708a5e4685286a2a00692be8 Mon Sep 17 00:00:00 2001 From: Joshua Tsuji Date: Thu, 13 Feb 2020 14:48:40 -0500 Subject: [PATCH 2/2] Modifies PIP to use the FloatingContentCoordinator. Test: atest SystemUITests Bug: 138115889 Change-Id: I639852a498676230e2318e4ba78c5a4333f6df02 --- .../android/systemui/pip/BasePipManager.java | 1 + .../systemui/pip/phone/PipManager.java | 7 +- .../systemui/pip/phone/PipMotionHelper.java | 153 ++++++++++++++---- .../systemui/pip/phone/PipTouchHandler.java | 41 +++-- 4 files changed, 144 insertions(+), 58 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/pip/BasePipManager.java b/packages/SystemUI/src/com/android/systemui/pip/BasePipManager.java index 383d459b7ed1f..adee7f23e709e 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/BasePipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/BasePipManager.java @@ -20,6 +20,7 @@ import android.content.res.Configuration; import java.io.PrintWriter; + public interface BasePipManager { void showPictureInPictureMenu(); default void expandPip() {} diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java index b5c8d66947caf..cb94e28e3467f 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java @@ -46,6 +46,7 @@ import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.WindowManagerWrapper; +import com.android.systemui.util.FloatingContentCoordinator; import com.android.systemui.wm.DisplayChangeController; import com.android.systemui.wm.DisplayController; @@ -229,7 +230,8 @@ public class PipManager implements BasePipManager { @Inject public PipManager(Context context, BroadcastDispatcher broadcastDispatcher, - DisplayController displayController) { + DisplayController displayController, + FloatingContentCoordinator floatingContentCoordinator) { mContext = context; mActivityManager = ActivityManager.getService(); mActivityTaskManager = ActivityTaskManager.getService(); @@ -247,7 +249,8 @@ public class PipManager implements BasePipManager { mMenuController = new PipMenuActivityController(context, mActivityManager, mMediaController, mInputConsumerController); mTouchHandler = new PipTouchHandler(context, mActivityManager, mActivityTaskManager, - mMenuController, mInputConsumerController, mPipBoundsHandler); + mMenuController, mInputConsumerController, mPipBoundsHandler, + floatingContentCoordinator); mAppOpsListener = new PipAppOpsListener(context, mActivityManager, mTouchHandler.getMotionHelper()); displayController.addDisplayChangingController(mRotationController); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java index 3ae627d27def0..c6e28522ccbd6 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java @@ -19,6 +19,7 @@ package com.android.systemui.pip.phone; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager.StackInfo; import android.app.IActivityManager; @@ -41,6 +42,7 @@ import com.android.internal.os.SomeArgs; import com.android.systemui.pip.PipSnapAlgorithm; import com.android.systemui.shared.system.WindowManagerWrapper; import com.android.systemui.statusbar.FlingAnimationUtils; +import com.android.systemui.util.FloatingContentCoordinator; import com.android.systemui.util.animation.FloatProperties; import com.android.systemui.util.animation.PhysicsAnimator; @@ -49,7 +51,8 @@ import java.io.PrintWriter; /** * A helper to animate and manipulate the PiP. */ -public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Callback { +public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Callback, + FloatingContentCoordinator.FloatingContent { private static final String TAG = "PipMotionHelper"; private static final boolean DEBUG = false; @@ -85,6 +88,12 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call /** PIP's current bounds on the screen. */ private final Rect mBounds = new Rect(); + /** The bounds within which PIP's top-left coordinate is allowed to move. */ + private Rect mMovementBounds = new Rect(); + + /** The region that all of PIP must stay within. */ + private Rect mFloatingAllowedArea = new Rect(); + private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider = new SfVsyncFrameCallbackProvider(); @@ -93,6 +102,12 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call */ private final Rect mAnimatedBounds = new Rect(); + /** The destination bounds to which PIP is animating. */ + private Rect mAnimatingToBounds = new Rect(); + + /** Coordinator instance for resolving conflicts with other floating content. */ + private FloatingContentCoordinator mFloatingContentCoordinator; + /** * PhysicsAnimator instance for animating {@link #mAnimatedBounds} using physics animations. */ @@ -119,9 +134,15 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call new PhysicsAnimator.SpringConfig( SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY); + /** SpringConfig to use for springing PIP away from conflicting floating content. */ + private final PhysicsAnimator.SpringConfig mConflictResolutionSpringConfig = + new PhysicsAnimator.SpringConfig( + SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY); + public PipMotionHelper(Context context, IActivityManager activityManager, IActivityTaskManager activityTaskManager, PipMenuActivityController menuController, - PipSnapAlgorithm snapAlgorithm, FlingAnimationUtils flingAnimationUtils) { + PipSnapAlgorithm snapAlgorithm, FlingAnimationUtils flingAnimationUtils, + FloatingContentCoordinator floatingContentCoordinator) { mContext = context; mHandler = new Handler(ForegroundThread.get().getLooper(), this); mActivityManager = activityManager; @@ -129,9 +150,27 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call mMenuController = menuController; mSnapAlgorithm = snapAlgorithm; mFlingAnimationUtils = flingAnimationUtils; + mFloatingContentCoordinator = floatingContentCoordinator; onConfigurationChanged(); } + @NonNull + @Override + public Rect getFloatingBoundsOnScreen() { + return !mAnimatingToBounds.isEmpty() ? mAnimatingToBounds : mBounds; + } + + @NonNull + @Override + public Rect getAllowedFloatingBoundsRegion() { + return mFloatingAllowedArea; + } + + @Override + public void moveToBounds(@NonNull Rect bounds) { + animateToBounds(bounds, mConflictResolutionSpringConfig); + } + /** * Updates whenever the configuration changes. */ @@ -157,9 +196,24 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call } /** - * Tries to the move the pinned stack to the given {@param bounds}. + * Tries to move the pinned stack to the given {@param bounds}. */ void movePip(Rect toBounds) { + movePip(toBounds, false /* isDragging */); + } + + /** + * Tries to move the pinned stack to the given {@param bounds}. + * + * @param isDragging Whether this movement is the result of a drag touch gesture. If so, we + * won't notify the floating content coordinator of this move, since that will + * happen when the gesture ends. + */ + void movePip(Rect toBounds, boolean isDragging) { + if (!isDragging) { + mFloatingContentCoordinator.onContentMoved(this); + } + cancelAnimations(); resizePipUnchecked(toBounds); mBounds.set(toBounds); @@ -211,6 +265,18 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call }); } + /** Sets the movement bounds to use to constrain PIP position animations. */ + void setCurrentMovementBounds(Rect movementBounds) { + mMovementBounds.set(movementBounds); + rebuildFlingConfigs(); + + // The movement bounds represent the area within which we can move PIP's top-left position. + // The allowed area for all of PIP is those bounds plus PIP's width and height. + mFloatingAllowedArea.set(mMovementBounds); + mFloatingAllowedArea.right += mBounds.width(); + mFloatingAllowedArea.bottom += mBounds.height(); + } + /** * @return the PiP bounds. */ @@ -221,11 +287,11 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call /** * @return the closest minimized PiP bounds. */ - Rect getClosestMinimizedBounds(Rect stackBounds, Rect movementBounds) { + Rect getClosestMinimizedBounds(Rect stackBounds) { Point displaySize = new Point(); mContext.getDisplay().getRealSize(displaySize); - Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, stackBounds); - mSnapAlgorithm.applyMinimizedOffset(toBounds, movementBounds, displaySize, mStableInsets); + Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(mMovementBounds, stackBounds); + mSnapAlgorithm.applyMinimizedOffset(toBounds, mMovementBounds, displaySize, mStableInsets); return toBounds; } @@ -264,11 +330,10 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call /** * Animates the PiP to the minimized state, slightly offscreen. */ - void animateToClosestMinimizedState(Rect movementBounds, @Nullable Runnable updateAction) { - final Rect toBounds = getClosestMinimizedBounds(mBounds, movementBounds); - - prepareForBoundsAnimation(movementBounds); + void animateToClosestMinimizedState(@Nullable Runnable updateAction) { + final Rect toBounds = getClosestMinimizedBounds(mBounds); + mAnimatedBounds.set(mBounds); mAnimatedBoundsPhysicsAnimator .spring(FloatProperties.RECT_X, toBounds.left, mSpringConfig) .spring(FloatProperties.RECT_Y, toBounds.top, mSpringConfig); @@ -285,10 +350,8 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call * Flings the PiP to the closest snap target. */ void flingToSnapTarget( - float velocityX, float velocityY, Rect movementBounds, Runnable updateAction, - @Nullable Runnable endAction) { - prepareForBoundsAnimation(movementBounds); - + float velocityX, float velocityY, Runnable updateAction, @Nullable Runnable endAction) { + mAnimatedBounds.set(mBounds); mAnimatedBoundsPhysicsAnimator .flingThenSpring( FloatProperties.RECT_X, velocityX, mFlingConfigX, mSpringConfig, @@ -298,21 +361,39 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call .addUpdateListener((target, values) -> updateAction.run()) .withEndActions(endAction); + final float xEndValue = velocityX < 0 ? mMovementBounds.left : mMovementBounds.right; + final float estimatedFlingYEndValue = + PhysicsAnimator.estimateFlingEndValue(mBounds.top, velocityY, mFlingConfigY); + + setAnimatingToBounds(new Rect( + (int) xEndValue, + (int) estimatedFlingYEndValue, + (int) xEndValue + mBounds.width(), + (int) estimatedFlingYEndValue + mBounds.height())); + startBoundsAnimation(); } /** * Animates the PiP to the closest snap target. */ - void animateToClosestSnapTarget(Rect movementBounds) { - prepareForBoundsAnimation(movementBounds); + void animateToClosestSnapTarget() { + final Rect newBounds = mSnapAlgorithm.findClosestSnapBounds(mMovementBounds, mBounds); + animateToBounds(newBounds, mSpringConfig); + } - final Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds); + /** + * Animates PIP to the provided bounds, using physics animations and the given spring + * configuration + */ + void animateToBounds(Rect bounds, PhysicsAnimator.SpringConfig springConfig) { + mAnimatedBounds.set(mBounds); mAnimatedBoundsPhysicsAnimator - .spring(FloatProperties.RECT_X, toBounds.left, mSpringConfig) - .spring(FloatProperties.RECT_Y, toBounds.top, mSpringConfig); - + .spring(FloatProperties.RECT_X, bounds.left, springConfig) + .spring(FloatProperties.RECT_Y, bounds.top, springConfig); startBoundsAnimation(); + + setAnimatingToBounds(bounds); } /** @@ -323,9 +404,6 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call final boolean isFling = velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond(); final Point dismissEndPoint = getDismissEndPoint(mBounds, velocityX, velocityY, isFling); - // Set the animated bounds to start at the current bounds. We don't need to rebuild the - // fling configs here via prepareForBoundsAnimation, since animateDismiss isn't provided - // with new movement bounds. mAnimatedBounds.set(mBounds); // Animate to the dismiss end point, and then dismiss PIP. @@ -366,9 +444,11 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call currentMovementBounds); } mSnapAlgorithm.applySnapFraction(normalBounds, normalMovementBounds, savedSnapFraction); + if (minimized) { - normalBounds = getClosestMinimizedBounds(normalBounds, normalMovementBounds); + normalBounds = getClosestMinimizedBounds(normalBounds); } + if (immediate) { movePip(normalBounds); } else { @@ -400,19 +480,15 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call */ private void cancelAnimations() { mAnimatedBoundsPhysicsAnimator.cancel(); + mAnimatingToBounds.setEmpty(); } - /** - * Set new fling configs whose min/max values respect the given movement bounds, and set the - * animated bounds to PIP's current 'real' bounds. - */ - private void prepareForBoundsAnimation(Rect movementBounds) { + /** Set new fling configs whose min/max values respect the given movement bounds. */ + private void rebuildFlingConfigs() { mFlingConfigX = new PhysicsAnimator.FlingConfig( - DEFAULT_FRICTION, movementBounds.left, movementBounds.right); + DEFAULT_FRICTION, mMovementBounds.left, mMovementBounds.right); mFlingConfigY = new PhysicsAnimator.FlingConfig( - DEFAULT_FRICTION, movementBounds.top, movementBounds.bottom); - - mAnimatedBounds.set(mBounds); + DEFAULT_FRICTION, mMovementBounds.top, mMovementBounds.bottom); } /** @@ -431,6 +507,16 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call .start(); } + /** + * Notifies the floating coordinator that we're moving, and sets {@link #mAnimatingToBounds} so + * we return these bounds from + * {@link FloatingContentCoordinator.FloatingContent#getFloatingBoundsOnScreen()}. + */ + private void setAnimatingToBounds(Rect bounds) { + mAnimatingToBounds = bounds; + mFloatingContentCoordinator.onContentMoved(this); + } + /** * Directly resizes the PiP to the given {@param bounds}. */ @@ -459,6 +545,7 @@ public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Call args.arg1 = toBounds; args.argi1 = duration; mHandler.sendMessage(mHandler.obtainMessage(MSG_RESIZE_ANIMATE, args)); + setAnimatingToBounds(toBounds); } } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index 924edb6fe3127..8e588e67861c1 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -47,6 +47,7 @@ import com.android.systemui.pip.PipBoundsHandler; import com.android.systemui.pip.PipSnapAlgorithm; import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.statusbar.FlingAnimationUtils; +import com.android.systemui.util.FloatingContentCoordinator; import java.io.PrintWriter; @@ -127,6 +128,7 @@ public class PipTouchHandler { // Touch state private final PipTouchState mTouchState; private final FlingAnimationUtils mFlingAnimationUtils; + private final FloatingContentCoordinator mFloatingContentCoordinator; private final PipMotionHelper mMotionHelper; private PipTouchGesture mGesture; @@ -152,7 +154,7 @@ public class PipTouchHandler { @Override public void onPipMinimize() { setMinimizedStateInternal(true); - mMotionHelper.animateToClosestMinimizedState(mMovementBounds, null /* updateAction */); + mMotionHelper.animateToClosestMinimizedState(null /* updateAction */); } @Override @@ -172,7 +174,8 @@ public class PipTouchHandler { public PipTouchHandler(Context context, IActivityManager activityManager, IActivityTaskManager activityTaskManager, PipMenuActivityController menuController, InputConsumerController inputConsumerController, - PipBoundsHandler pipBoundsHandler) { + PipBoundsHandler pipBoundsHandler, + FloatingContentCoordinator floatingContentCoordinator) { // Initialize the Pip input consumer mContext = context; @@ -188,7 +191,7 @@ public class PipTouchHandler { 2.5f); mGesture = new DefaultPipTouchGesture(); mMotionHelper = new PipMotionHelper(mContext, mActivityManager, mActivityTaskManager, - mMenuController, mSnapAlgorithm, mFlingAnimationUtils); + mMenuController, mSnapAlgorithm, mFlingAnimationUtils, floatingContentCoordinator); mPipResizeGestureHandler = new PipResizeGestureHandler(context, pipBoundsHandler, this, mMotionHelper); mTouchState = new PipTouchState(mViewConfig, mHandler, @@ -207,6 +210,7 @@ public class PipTouchHandler { inputConsumerController.setRegistrationListener(this::onRegistrationChanged); mPipBoundsHandler = pipBoundsHandler; + mFloatingContentCoordinator = floatingContentCoordinator; mConnection = new PipAccessibilityInteractionConnection(mMotionHelper, this::onAccessibilityShowMenu, mHandler); } @@ -228,15 +232,18 @@ public class PipTouchHandler { } public void onActivityPinned() { - cleanUp(); + cleanUpDismissTarget(); mShowPipMenuOnAnimationEnd = true; mPipResizeGestureHandler.onActivityPinned(); + mFloatingContentCoordinator.onContentAdded(mMotionHelper); } public void onActivityUnpinned(ComponentName topPipActivity) { if (topPipActivity == null) { // Clean up state after the last PiP activity is removed - cleanUp(); + cleanUpDismissTarget(); + + mFloatingContentCoordinator.onContentRemoved(mMotionHelper); } mPipResizeGestureHandler.onActivityUnpinned(); } @@ -501,8 +508,7 @@ public class PipTouchHandler { if (fromController) { if (isMinimized) { // Move the PiP to the new bounds immediately if minimized - mMotionHelper.movePip(mMotionHelper.getClosestMinimizedBounds(mNormalBounds, - mMovementBounds)); + mMotionHelper.movePip(mMotionHelper.getClosestMinimizedBounds(mNormalBounds)); } } else if (mPinnedStackController != null) { try { @@ -654,7 +660,7 @@ public class PipTouchHandler { mTmpBounds.set(mMotionHelper.getBounds()); mTmpBounds.offsetTo((int) left, (int) top); - mMotionHelper.movePip(mTmpBounds); + mMotionHelper.movePip(mTmpBounds, true /* isDragging */); if (mEnableDimissDragToEdge) { updateDismissFraction(); @@ -724,7 +730,6 @@ public class PipTouchHandler { mMenuController.hideMenu(); } else { mMotionHelper.animateToClosestMinimizedState( - mMovementBounds, PipTouchHandler.this::updateDismissFraction /* updateAction */); } return true; @@ -748,16 +753,15 @@ public class PipTouchHandler { } if (isFling) { - mMotionHelper.flingToSnapTarget( - vel.x, vel.y, mMovementBounds, + mMotionHelper.flingToSnapTarget(vel.x, vel.y, PipTouchHandler.this::updateDismissFraction /* updateAction */, endAction /* endAction */); } else { - mMotionHelper.animateToClosestSnapTarget(mMovementBounds); + mMotionHelper.animateToClosestSnapTarget(); } } else if (mIsMinimized) { // This was a tap, so no longer minimized - mMotionHelper.animateToClosestSnapTarget(mMovementBounds); + mMotionHelper.animateToClosestSnapTarget(); setMinimizedStateInternal(false); } else if (mTouchState.isDoubleTap()) { // Expand to fullscreen if this is a double tap @@ -789,6 +793,7 @@ public class PipTouchHandler { : mNormalMovementBounds; mPipBoundsHandler.setMinEdgeSize( isMenuExpanded ? mExpandedShortestEdgeSize : 0); + mMotionHelper.setCurrentMovementBounds(mMovementBounds); } /** @@ -799,16 +804,6 @@ public class PipTouchHandler { mDismissViewController.destroyDismissTarget(); } - /** - * Resets some states related to the touch handling. - */ - private void cleanUp() { - if (mIsMinimized) { - setMinimizedStateInternal(false); - } - cleanUpDismissTarget(); - } - /** * @return whether the menu will resize as a part of showing the full menu. */