Merge "Align bubble behavior with DND settings." into qt-dev

This commit is contained in:
Josh Tsuji
2019-05-24 15:38:02 +00:00
committed by Android (Google) Code Review
4 changed files with 154 additions and 28 deletions

View File

@@ -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.content.pm.ActivityInfo.DOCUMENT_LAUNCH_ALWAYS;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
@@ -51,6 +54,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;
@@ -78,6 +82,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;
@@ -143,6 +148,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;
@@ -204,17 +210,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 */);
@@ -260,6 +280,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
if (mExpandListener != null) {
mStackView.setExpandListener(mExpandListener);
}
updateStackViewForZenConfig();
}
}
@@ -566,6 +588,35 @@ 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 ZenModeConfig zenModeConfig = mZenModeController.getConfig();
if (zenModeConfig == null || mStackView == null) {
return;
}
final int suppressedEffects = zenModeConfig.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.

View File

@@ -287,6 +287,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);
@@ -690,6 +693,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);
@@ -1308,6 +1314,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.
*/
@@ -1319,22 +1348,48 @@ 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 (bubble.iconView == null) {
return;
}
// 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);
@@ -1343,6 +1398,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();
}
@@ -1445,6 +1504,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);

View File

@@ -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
@@ -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) {

View File

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