Modifies Bubbles to use the FloatingContentCoordinator.

Test: atest SystemUITests
Bug: 138115889
Change-Id: I599024a140f9c9c2e54e835ba3f32d180e07c39a
This commit is contained in:
Joshua Tsuji
2020-02-13 16:14:29 -05:00
parent cd1ca07b3e
commit 7155bf102f
8 changed files with 182 additions and 25 deletions

View File

@@ -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.

View File

@@ -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;

View File

@@ -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 */);
}
});

View File

@@ -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);
}
}

View File

@@ -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<NotificationEntryListener> mEntryListenerCaptor;
@@ -243,7 +246,8 @@ public class BubbleControllerTest extends SysuiTestCase {
mNotificationEntryManager,
mNotifPipeline,
mFeatureFlagsOldPipeline,
mDumpController);
mDumpController,
mFloatingContentCoordinator);
mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener);
mBubbleController.setExpandListener(mBubbleExpandListener);

View File

@@ -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<NotifCollectionListener> 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);

View File

@@ -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);
}
}

View File

@@ -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,