Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +47 −3 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ package com.android.systemui.bubbles; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL; import static android.service.notification.NotificationListenerService.REASON_CANCEL; Loading Loading @@ -49,6 +52,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.provider.Settings; import android.service.notification.StatusBarNotification; import android.service.notification.ZenModeConfig; import android.util.Log; import android.view.Display; import android.view.IPinnedStackController; Loading @@ -75,6 +79,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag; import com.android.systemui.statusbar.phone.StatusBarWindowController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ZenModeController; import java.lang.annotation.Retention; import java.lang.annotation.Target; Loading Loading @@ -140,6 +145,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // Bubbles get added to the status bar view private final StatusBarWindowController mStatusBarWindowController; private final ZenModeController mZenModeController; private StatusBarStateListener mStatusBarStateListener; private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; Loading Loading @@ -201,17 +207,31 @@ public class BubbleController implements ConfigurationController.ConfigurationLi @Inject public BubbleController(Context context, StatusBarWindowController statusBarWindowController, BubbleData data, ConfigurationController configurationController, NotificationInterruptionStateProvider interruptionStateProvider) { NotificationInterruptionStateProvider interruptionStateProvider, ZenModeController zenModeController) { this(context, statusBarWindowController, data, null /* synchronizer */, configurationController, interruptionStateProvider); configurationController, interruptionStateProvider, zenModeController); } public BubbleController(Context context, StatusBarWindowController statusBarWindowController, BubbleData data, @Nullable BubbleStackView.SurfaceSynchronizer synchronizer, ConfigurationController configurationController, NotificationInterruptionStateProvider interruptionStateProvider) { NotificationInterruptionStateProvider interruptionStateProvider, ZenModeController zenModeController) { mContext = context; mNotificationInterruptionStateProvider = interruptionStateProvider; mZenModeController = zenModeController; mZenModeController.addCallback(new ZenModeController.Callback() { @Override public void onZenChanged(int zen) { updateStackViewForZenConfig(); } @Override public void onConfigChanged(ZenModeConfig config) { updateStackViewForZenConfig(); } }); configurationController.addCallback(this /* configurationListener */); Loading Loading @@ -257,6 +277,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (mExpandListener != null) { mStackView.setExpandListener(mExpandListener); } updateStackViewForZenConfig(); } } Loading Loading @@ -560,6 +582,28 @@ public class BubbleController implements ConfigurationController.ConfigurationLi }; /** * Updates the stack view's suppression flags from the latest config from the zen (do not * disturb) controller. */ private void updateStackViewForZenConfig() { final int suppressedEffects = mZenModeController.getConfig().suppressedVisualEffects; final boolean hideNotificationDotsSelected = (suppressedEffects & SUPPRESSED_EFFECT_BADGE) != 0; final boolean dontPopNotifsOnScreenSelected = (suppressedEffects & SUPPRESSED_EFFECT_PEEK) != 0; final boolean hideFromPullDownShadeSelected = (suppressedEffects & SUPPRESSED_EFFECT_NOTIFICATION_LIST) != 0; final boolean dndEnabled = mZenModeController.getZen() != Settings.Global.ZEN_MODE_OFF; mStackView.setSuppressNewDot( dndEnabled && hideNotificationDotsSelected); mStackView.setSuppressFlyout( dndEnabled && (dontPopNotifsOnScreenSelected || hideFromPullDownShadeSelected)); } /** * Lets any listeners know if bubble state has changed. * Updates the visibility of the bubbles based on current state. * Does not un-bubble, just hides or un-hides. Notifies any * {@link BubbleStateChangeListener}s of visibility changes. Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +63 −7 Original line number Diff line number Diff line Loading @@ -285,6 +285,9 @@ public class BubbleStackView extends FrameLayout { private BubbleDismissView mDismissContainer; private Runnable mAfterMagnet; private boolean mSuppressNewDot = false; private boolean mSuppressFlyout = false; public BubbleStackView(Context context, BubbleData data, @Nullable SurfaceSynchronizer synchronizer) { super(context); Loading Loading @@ -684,6 +687,9 @@ public class BubbleStackView extends FrameLayout { mBubbleContainer.addView(bubble.iconView, 0, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); ViewClippingUtil.setClippingDeactivated(bubble.iconView, true, mClippingParameters); if (bubble.iconView != null) { bubble.iconView.setSuppressDot(mSuppressNewDot, false /* animate */); } animateInFlyoutForBubble(bubble); requestUpdate(); logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__POSTED); Loading Loading @@ -1301,6 +1307,29 @@ public class BubbleStackView extends FrameLayout { } } /** Sets whether all bubbles in the stack should not show the 'new' dot. */ void setSuppressNewDot(boolean suppressNewDot) { mSuppressNewDot = suppressNewDot; for (int i = 0; i < mBubbleContainer.getChildCount(); i++) { BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i); bv.setSuppressDot(suppressNewDot, true /* animate */); } } /** * Sets whether the flyout should not appear, even if the notif otherwise would generate one. */ void setSuppressFlyout(boolean suppressFlyout) { mSuppressFlyout = suppressFlyout; } /** * Callback to run after the flyout hides. Also called if a new flyout is shown before the * previous one animates out. */ private Runnable mAfterFlyoutHides; /** * Animates in the flyout for the given bubble, if available, and then hides it after some time. */ Loading @@ -1312,22 +1341,44 @@ public class BubbleStackView extends FrameLayout { if (updateMessage != null && !isExpanded() && !mIsExpansionAnimating && !mIsGestureInProgress) { && !mIsGestureInProgress && !mSuppressFlyout) { if (bubble.iconView != null) { bubble.iconView.setSuppressDot(true /* suppressDot */, false /* animate */); // Temporarily suppress the dot while the flyout is visible. bubble.iconView.setSuppressDot( true /* suppressDot */, false /* animate */); mFlyoutDragDeltaX = 0f; mFlyout.setAlpha(0f); if (mAfterFlyoutHides != null) { mAfterFlyoutHides.run(); } mAfterFlyoutHides = () -> { // If we're going to suppress the dot, make it visible first so it'll // visibly animate away. if (mSuppressNewDot) { bubble.iconView.setSuppressDot( false /* suppressDot */, false /* animate */); } // Reset dot suppression. If we're not suppressing due to DND, then // stop suppressing it with no animation (since the flyout has // transformed into the dot). If we are suppressing due to DND, animate // it away. bubble.iconView.setSuppressDot( mSuppressNewDot /* suppressDot */, mSuppressNewDot /* animate */); }; // Post in case layout isn't complete and getWidth returns 0. post(() -> mFlyout.showFlyout( updateMessage, mStackAnimationController.getStackPosition(), getWidth(), mStackAnimationController.isStackOnLeftSide(), bubble.iconView.getBadgeColor(), () -> { bubble.iconView.setSuppressDot( false /* suppressDot */, false /* animate */); })); bubble.iconView.getBadgeColor(), mAfterFlyoutHides)); } mFlyout.removeCallbacks(mHideFlyout); mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER); logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT); Loading @@ -1336,6 +1387,10 @@ public class BubbleStackView extends FrameLayout { /** Hide the flyout immediately and cancel any pending hide runnables. */ private void hideFlyoutImmediate() { if (mAfterFlyoutHides != null) { mAfterFlyoutHides.run(); } mFlyout.removeCallbacks(mHideFlyout); mFlyout.hideFlyout(); } Loading Loading @@ -1438,6 +1493,7 @@ public class BubbleStackView extends FrameLayout { int bubbsCount = mBubbleContainer.getChildCount(); for (int i = 0; i < bubbsCount; i++) { BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i); bv.updateDotVisibility(true /* animate */); bv.setZ((BubbleController.MAX_BUBBLES * getResources().getDimensionPixelSize(R.dimen.bubble_elevation)) - i); Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java +19 −15 Original line number Diff line number Diff line Loading @@ -138,19 +138,6 @@ public class BubbleView extends FrameLayout { updateDotVisibility(animate, null /* after */); } /** * Changes the dot's visibility to match the bubble view's state, running the provided callback * after animation if requested. */ void updateDotVisibility(boolean animate, Runnable after) { boolean showDot = getEntry().showInShadeWhenBubble() && !mSuppressDot; if (animate) { animateDot(showDot, after); } else { mBadgedImageView.setShowDot(showDot); } } /** * Sets whether or not to hide the dot even if we'd otherwise show it. This is used while the Loading @@ -177,18 +164,35 @@ public class BubbleView extends FrameLayout { return mBadgedImageView.getDotPosition(); } /** * Changes the dot's visibility to match the bubble view's state, running the provided callback * after animation if requested. */ private void updateDotVisibility(boolean animate, Runnable after) { boolean showDot = getEntry().showInShadeWhenBubble() && !mSuppressDot; if (animate) { animateDot(showDot, after); } else { mBadgedImageView.setShowDot(showDot); } } /** * Animates the badge to show or hide. */ private void animateDot(boolean showDot, Runnable after) { if (mBadgedImageView.isShowingDot() != showDot) { mBadgedImageView.setShowDot(showDot); if (showDot) { mBadgedImageView.setShowDot(true); } mBadgedImageView.clearAnimation(); mBadgedImageView.animate().setDuration(200) .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) .setUpdateListener((valueAnimator) -> { float fraction = valueAnimator.getAnimatedFraction(); fraction = showDot ? fraction : 1 - fraction; fraction = showDot ? fraction : 1f - fraction; mBadgedImageView.setDotScale(fraction); }).withEndAction(() -> { if (!showDot) { Loading packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java +20 −8 Original line number Diff line number Diff line Loading @@ -86,7 +86,10 @@ public class StackAnimationController extends * we need to keep track of it separately from the first bubble's translation in case there are * no bubbles, or the first bubble was just added and being animated to its new position. */ private PointF mStackPosition = new PointF(); private PointF mStackPosition = new PointF(-1, -1); /** Whether or not the stack's start position has been set. */ private boolean mStackMovedToStartPosition = false; /** The most recent position in which the stack was resting on the edge of the screen. */ private PointF mRestingStackPosition; Loading Loading @@ -193,9 +196,10 @@ public class StackAnimationController extends /** Whether the stack is on the left side of the screen. */ public boolean isStackOnLeftSide() { if (mLayout == null) { if (mLayout == null || !isStackPositionSet()) { return false; } float stackCenter = mStackPosition.x + mIndividualBubbleSize / 2; float screenCenter = mLayout.getWidth() / 2; return stackCenter < screenCenter; Loading Loading @@ -630,10 +634,9 @@ public class StackAnimationController extends @Override void onChildAdded(View child, int index) { if (mLayout.getChildCount() == 1) { // If this is the first child added, position the stack in its starting position before // animating in. moveStackToStartPosition(() -> animateInBubble(child)); } else if (mLayout.indexOfChild(child) == 0) { // If this is the first child added, position the stack in its starting position. moveStackToStartPosition(); } else if (isStackPositionSet() && mLayout.indexOfChild(child) == 0) { // Otherwise, animate the bubble in if it's the newest bubble. If we're adding a bubble // to the back of the stack, it'll be largely invisible so don't bother animating it in. animateInBubble(child); Loading @@ -657,16 +660,21 @@ public class StackAnimationController extends } /** Moves the stack, without any animation, to the starting position. */ private void moveStackToStartPosition(Runnable after) { private void moveStackToStartPosition() { // Post to ensure that the layout's width and height have been calculated. mLayout.setVisibility(View.INVISIBLE); mLayout.post(() -> { mStackMovedToStartPosition = true; setStackPosition( mRestingStackPosition == null ? getDefaultStartPosition() : mRestingStackPosition); mLayout.setVisibility(View.VISIBLE); after.run(); // Animate in the top bubble now that we're visible. if (mLayout.getChildCount() > 0) { animateInBubble(mLayout.getChildAt(0)); } }); } Loading Loading @@ -718,6 +726,10 @@ public class StackAnimationController extends getAllowableStackPositionRegion().top + mStackStartingVerticalOffset); } private boolean isStackPositionSet() { return mStackMovedToStartPosition; } /** Animates in the given bubble. */ private void animateInBubble(View child) { child.setTranslationY(mStackPosition.y); Loading packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +14 −3 Original line number Diff line number Diff line Loading @@ -47,6 +47,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.drawable.Icon; import android.service.notification.ZenModeConfig; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.WindowManager; Loading @@ -69,6 +70,7 @@ import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.StatusBarWindowController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.ZenModeController; import org.junit.Before; import org.junit.Test; Loading Loading @@ -99,6 +101,10 @@ public class BubbleControllerTest extends SysuiTestCase { private DozeParameters mDozeParameters; @Mock private ConfigurationController mConfigurationController; @Mock private ZenModeController mZenModeController; @Mock private ZenModeConfig mZenModeConfig; private FrameLayout mStatusBarView; @Captor Loading Loading @@ -162,6 +168,9 @@ public class BubbleControllerTest extends SysuiTestCase { when(mNotificationEntryManager.getNotificationData()).thenReturn(mNotificationData); when(mNotificationData.getChannel(mRow.getEntry().key)).thenReturn(mRow.getEntry().channel); mZenModeConfig.suppressedVisualEffects = 0; when(mZenModeController.getConfig()).thenReturn(mZenModeConfig); TestableNotificationInterruptionStateProvider interruptionStateProvider = new TestableNotificationInterruptionStateProvider(mContext); interruptionStateProvider.setUpWithPresenter( Loading @@ -170,7 +179,8 @@ public class BubbleControllerTest extends SysuiTestCase { mock(NotificationInterruptionStateProvider.HeadsUpSuppressor.class)); mBubbleData = new BubbleData(mContext); mBubbleController = new TestableBubbleController(mContext, mStatusBarWindowController, mBubbleData, mConfigurationController, interruptionStateProvider); mBubbleData, mConfigurationController, interruptionStateProvider, mZenModeController); mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener); mBubbleController.setExpandListener(mBubbleExpandListener); Loading Loading @@ -628,9 +638,10 @@ public class BubbleControllerTest extends SysuiTestCase { TestableBubbleController(Context context, StatusBarWindowController statusBarWindowController, BubbleData data, ConfigurationController configurationController, NotificationInterruptionStateProvider interruptionStateProvider) { NotificationInterruptionStateProvider interruptionStateProvider, ZenModeController zenModeController) { super(context, statusBarWindowController, data, Runnable::run, configurationController, interruptionStateProvider); configurationController, interruptionStateProvider, zenModeController); } @Override Loading Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +47 −3 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ package com.android.systemui.bubbles; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL; import static android.service.notification.NotificationListenerService.REASON_CANCEL; Loading Loading @@ -49,6 +52,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.provider.Settings; import android.service.notification.StatusBarNotification; import android.service.notification.ZenModeConfig; import android.util.Log; import android.view.Display; import android.view.IPinnedStackController; Loading @@ -75,6 +79,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag; import com.android.systemui.statusbar.phone.StatusBarWindowController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ZenModeController; import java.lang.annotation.Retention; import java.lang.annotation.Target; Loading Loading @@ -140,6 +145,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // Bubbles get added to the status bar view private final StatusBarWindowController mStatusBarWindowController; private final ZenModeController mZenModeController; private StatusBarStateListener mStatusBarStateListener; private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; Loading Loading @@ -201,17 +207,31 @@ public class BubbleController implements ConfigurationController.ConfigurationLi @Inject public BubbleController(Context context, StatusBarWindowController statusBarWindowController, BubbleData data, ConfigurationController configurationController, NotificationInterruptionStateProvider interruptionStateProvider) { NotificationInterruptionStateProvider interruptionStateProvider, ZenModeController zenModeController) { this(context, statusBarWindowController, data, null /* synchronizer */, configurationController, interruptionStateProvider); configurationController, interruptionStateProvider, zenModeController); } public BubbleController(Context context, StatusBarWindowController statusBarWindowController, BubbleData data, @Nullable BubbleStackView.SurfaceSynchronizer synchronizer, ConfigurationController configurationController, NotificationInterruptionStateProvider interruptionStateProvider) { NotificationInterruptionStateProvider interruptionStateProvider, ZenModeController zenModeController) { mContext = context; mNotificationInterruptionStateProvider = interruptionStateProvider; mZenModeController = zenModeController; mZenModeController.addCallback(new ZenModeController.Callback() { @Override public void onZenChanged(int zen) { updateStackViewForZenConfig(); } @Override public void onConfigChanged(ZenModeConfig config) { updateStackViewForZenConfig(); } }); configurationController.addCallback(this /* configurationListener */); Loading Loading @@ -257,6 +277,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (mExpandListener != null) { mStackView.setExpandListener(mExpandListener); } updateStackViewForZenConfig(); } } Loading Loading @@ -560,6 +582,28 @@ public class BubbleController implements ConfigurationController.ConfigurationLi }; /** * Updates the stack view's suppression flags from the latest config from the zen (do not * disturb) controller. */ private void updateStackViewForZenConfig() { final int suppressedEffects = mZenModeController.getConfig().suppressedVisualEffects; final boolean hideNotificationDotsSelected = (suppressedEffects & SUPPRESSED_EFFECT_BADGE) != 0; final boolean dontPopNotifsOnScreenSelected = (suppressedEffects & SUPPRESSED_EFFECT_PEEK) != 0; final boolean hideFromPullDownShadeSelected = (suppressedEffects & SUPPRESSED_EFFECT_NOTIFICATION_LIST) != 0; final boolean dndEnabled = mZenModeController.getZen() != Settings.Global.ZEN_MODE_OFF; mStackView.setSuppressNewDot( dndEnabled && hideNotificationDotsSelected); mStackView.setSuppressFlyout( dndEnabled && (dontPopNotifsOnScreenSelected || hideFromPullDownShadeSelected)); } /** * Lets any listeners know if bubble state has changed. * Updates the visibility of the bubbles based on current state. * Does not un-bubble, just hides or un-hides. Notifies any * {@link BubbleStateChangeListener}s of visibility changes. Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +63 −7 Original line number Diff line number Diff line Loading @@ -285,6 +285,9 @@ public class BubbleStackView extends FrameLayout { private BubbleDismissView mDismissContainer; private Runnable mAfterMagnet; private boolean mSuppressNewDot = false; private boolean mSuppressFlyout = false; public BubbleStackView(Context context, BubbleData data, @Nullable SurfaceSynchronizer synchronizer) { super(context); Loading Loading @@ -684,6 +687,9 @@ public class BubbleStackView extends FrameLayout { mBubbleContainer.addView(bubble.iconView, 0, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); ViewClippingUtil.setClippingDeactivated(bubble.iconView, true, mClippingParameters); if (bubble.iconView != null) { bubble.iconView.setSuppressDot(mSuppressNewDot, false /* animate */); } animateInFlyoutForBubble(bubble); requestUpdate(); logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__POSTED); Loading Loading @@ -1301,6 +1307,29 @@ public class BubbleStackView extends FrameLayout { } } /** Sets whether all bubbles in the stack should not show the 'new' dot. */ void setSuppressNewDot(boolean suppressNewDot) { mSuppressNewDot = suppressNewDot; for (int i = 0; i < mBubbleContainer.getChildCount(); i++) { BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i); bv.setSuppressDot(suppressNewDot, true /* animate */); } } /** * Sets whether the flyout should not appear, even if the notif otherwise would generate one. */ void setSuppressFlyout(boolean suppressFlyout) { mSuppressFlyout = suppressFlyout; } /** * Callback to run after the flyout hides. Also called if a new flyout is shown before the * previous one animates out. */ private Runnable mAfterFlyoutHides; /** * Animates in the flyout for the given bubble, if available, and then hides it after some time. */ Loading @@ -1312,22 +1341,44 @@ public class BubbleStackView extends FrameLayout { if (updateMessage != null && !isExpanded() && !mIsExpansionAnimating && !mIsGestureInProgress) { && !mIsGestureInProgress && !mSuppressFlyout) { if (bubble.iconView != null) { bubble.iconView.setSuppressDot(true /* suppressDot */, false /* animate */); // Temporarily suppress the dot while the flyout is visible. bubble.iconView.setSuppressDot( true /* suppressDot */, false /* animate */); mFlyoutDragDeltaX = 0f; mFlyout.setAlpha(0f); if (mAfterFlyoutHides != null) { mAfterFlyoutHides.run(); } mAfterFlyoutHides = () -> { // If we're going to suppress the dot, make it visible first so it'll // visibly animate away. if (mSuppressNewDot) { bubble.iconView.setSuppressDot( false /* suppressDot */, false /* animate */); } // Reset dot suppression. If we're not suppressing due to DND, then // stop suppressing it with no animation (since the flyout has // transformed into the dot). If we are suppressing due to DND, animate // it away. bubble.iconView.setSuppressDot( mSuppressNewDot /* suppressDot */, mSuppressNewDot /* animate */); }; // Post in case layout isn't complete and getWidth returns 0. post(() -> mFlyout.showFlyout( updateMessage, mStackAnimationController.getStackPosition(), getWidth(), mStackAnimationController.isStackOnLeftSide(), bubble.iconView.getBadgeColor(), () -> { bubble.iconView.setSuppressDot( false /* suppressDot */, false /* animate */); })); bubble.iconView.getBadgeColor(), mAfterFlyoutHides)); } mFlyout.removeCallbacks(mHideFlyout); mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER); logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT); Loading @@ -1336,6 +1387,10 @@ public class BubbleStackView extends FrameLayout { /** Hide the flyout immediately and cancel any pending hide runnables. */ private void hideFlyoutImmediate() { if (mAfterFlyoutHides != null) { mAfterFlyoutHides.run(); } mFlyout.removeCallbacks(mHideFlyout); mFlyout.hideFlyout(); } Loading Loading @@ -1438,6 +1493,7 @@ public class BubbleStackView extends FrameLayout { int bubbsCount = mBubbleContainer.getChildCount(); for (int i = 0; i < bubbsCount; i++) { BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i); bv.updateDotVisibility(true /* animate */); bv.setZ((BubbleController.MAX_BUBBLES * getResources().getDimensionPixelSize(R.dimen.bubble_elevation)) - i); Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java +19 −15 Original line number Diff line number Diff line Loading @@ -138,19 +138,6 @@ public class BubbleView extends FrameLayout { updateDotVisibility(animate, null /* after */); } /** * Changes the dot's visibility to match the bubble view's state, running the provided callback * after animation if requested. */ void updateDotVisibility(boolean animate, Runnable after) { boolean showDot = getEntry().showInShadeWhenBubble() && !mSuppressDot; if (animate) { animateDot(showDot, after); } else { mBadgedImageView.setShowDot(showDot); } } /** * Sets whether or not to hide the dot even if we'd otherwise show it. This is used while the Loading @@ -177,18 +164,35 @@ public class BubbleView extends FrameLayout { return mBadgedImageView.getDotPosition(); } /** * Changes the dot's visibility to match the bubble view's state, running the provided callback * after animation if requested. */ private void updateDotVisibility(boolean animate, Runnable after) { boolean showDot = getEntry().showInShadeWhenBubble() && !mSuppressDot; if (animate) { animateDot(showDot, after); } else { mBadgedImageView.setShowDot(showDot); } } /** * Animates the badge to show or hide. */ private void animateDot(boolean showDot, Runnable after) { if (mBadgedImageView.isShowingDot() != showDot) { mBadgedImageView.setShowDot(showDot); if (showDot) { mBadgedImageView.setShowDot(true); } mBadgedImageView.clearAnimation(); mBadgedImageView.animate().setDuration(200) .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) .setUpdateListener((valueAnimator) -> { float fraction = valueAnimator.getAnimatedFraction(); fraction = showDot ? fraction : 1 - fraction; fraction = showDot ? fraction : 1f - fraction; mBadgedImageView.setDotScale(fraction); }).withEndAction(() -> { if (!showDot) { Loading
packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java +20 −8 Original line number Diff line number Diff line Loading @@ -86,7 +86,10 @@ public class StackAnimationController extends * we need to keep track of it separately from the first bubble's translation in case there are * no bubbles, or the first bubble was just added and being animated to its new position. */ private PointF mStackPosition = new PointF(); private PointF mStackPosition = new PointF(-1, -1); /** Whether or not the stack's start position has been set. */ private boolean mStackMovedToStartPosition = false; /** The most recent position in which the stack was resting on the edge of the screen. */ private PointF mRestingStackPosition; Loading Loading @@ -193,9 +196,10 @@ public class StackAnimationController extends /** Whether the stack is on the left side of the screen. */ public boolean isStackOnLeftSide() { if (mLayout == null) { if (mLayout == null || !isStackPositionSet()) { return false; } float stackCenter = mStackPosition.x + mIndividualBubbleSize / 2; float screenCenter = mLayout.getWidth() / 2; return stackCenter < screenCenter; Loading Loading @@ -630,10 +634,9 @@ public class StackAnimationController extends @Override void onChildAdded(View child, int index) { if (mLayout.getChildCount() == 1) { // If this is the first child added, position the stack in its starting position before // animating in. moveStackToStartPosition(() -> animateInBubble(child)); } else if (mLayout.indexOfChild(child) == 0) { // If this is the first child added, position the stack in its starting position. moveStackToStartPosition(); } else if (isStackPositionSet() && mLayout.indexOfChild(child) == 0) { // Otherwise, animate the bubble in if it's the newest bubble. If we're adding a bubble // to the back of the stack, it'll be largely invisible so don't bother animating it in. animateInBubble(child); Loading @@ -657,16 +660,21 @@ public class StackAnimationController extends } /** Moves the stack, without any animation, to the starting position. */ private void moveStackToStartPosition(Runnable after) { private void moveStackToStartPosition() { // Post to ensure that the layout's width and height have been calculated. mLayout.setVisibility(View.INVISIBLE); mLayout.post(() -> { mStackMovedToStartPosition = true; setStackPosition( mRestingStackPosition == null ? getDefaultStartPosition() : mRestingStackPosition); mLayout.setVisibility(View.VISIBLE); after.run(); // Animate in the top bubble now that we're visible. if (mLayout.getChildCount() > 0) { animateInBubble(mLayout.getChildAt(0)); } }); } Loading Loading @@ -718,6 +726,10 @@ public class StackAnimationController extends getAllowableStackPositionRegion().top + mStackStartingVerticalOffset); } private boolean isStackPositionSet() { return mStackMovedToStartPosition; } /** Animates in the given bubble. */ private void animateInBubble(View child) { child.setTranslationY(mStackPosition.y); Loading
packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +14 −3 Original line number Diff line number Diff line Loading @@ -47,6 +47,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.drawable.Icon; import android.service.notification.ZenModeConfig; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.WindowManager; Loading @@ -69,6 +70,7 @@ import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.StatusBarWindowController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.ZenModeController; import org.junit.Before; import org.junit.Test; Loading Loading @@ -99,6 +101,10 @@ public class BubbleControllerTest extends SysuiTestCase { private DozeParameters mDozeParameters; @Mock private ConfigurationController mConfigurationController; @Mock private ZenModeController mZenModeController; @Mock private ZenModeConfig mZenModeConfig; private FrameLayout mStatusBarView; @Captor Loading Loading @@ -162,6 +168,9 @@ public class BubbleControllerTest extends SysuiTestCase { when(mNotificationEntryManager.getNotificationData()).thenReturn(mNotificationData); when(mNotificationData.getChannel(mRow.getEntry().key)).thenReturn(mRow.getEntry().channel); mZenModeConfig.suppressedVisualEffects = 0; when(mZenModeController.getConfig()).thenReturn(mZenModeConfig); TestableNotificationInterruptionStateProvider interruptionStateProvider = new TestableNotificationInterruptionStateProvider(mContext); interruptionStateProvider.setUpWithPresenter( Loading @@ -170,7 +179,8 @@ public class BubbleControllerTest extends SysuiTestCase { mock(NotificationInterruptionStateProvider.HeadsUpSuppressor.class)); mBubbleData = new BubbleData(mContext); mBubbleController = new TestableBubbleController(mContext, mStatusBarWindowController, mBubbleData, mConfigurationController, interruptionStateProvider); mBubbleData, mConfigurationController, interruptionStateProvider, mZenModeController); mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener); mBubbleController.setExpandListener(mBubbleExpandListener); Loading Loading @@ -628,9 +638,10 @@ public class BubbleControllerTest extends SysuiTestCase { TestableBubbleController(Context context, StatusBarWindowController statusBarWindowController, BubbleData data, ConfigurationController configurationController, NotificationInterruptionStateProvider interruptionStateProvider) { NotificationInterruptionStateProvider interruptionStateProvider, ZenModeController zenModeController) { super(context, statusBarWindowController, data, Runnable::run, configurationController, interruptionStateProvider); configurationController, interruptionStateProvider, zenModeController); } @Override Loading