Don't commit snooze on horizontal swipes or when its in its expanded state

- Previously any touch outside of the snooze menu would commit the action
  (similar to how notification info gets reset) now only touches that
  aren't horizontal swipes will commit the action (if you snooze multiple
  in a row, it's jarring for the snooze item to disappear when you hit
  'down' on the next notification)
- Tracks expanded state of snooze menu
- Rather than commiting (i.e. snoozing) upon outside action when the
  menu is expanded it collapses the menu, upon next outside action snooze
  is commited

Test: manual / runtest systemui
Bug: 37253059
Change-Id: Ie0383fb8de49efe1b1bf0ed75d60cf75864f0210
This commit is contained in:
Mady Mellor
2017-03-30 13:22:03 -07:00
parent 4d09adcef1
commit c2dbe49bdc
7 changed files with 184 additions and 75 deletions

View File

@@ -95,14 +95,25 @@ public class NotificationGuts extends FrameLayout {
/**
* Called when the guts view have been told to close, typically after an outside
* interaction. Returning {@code true} here will prevent the guts view to close.
* interaction.
*
* @param save whether the state should be saved.
* @param force whether the guts view should be forced closed regardless of state.
* @return if closing the view has been handled.
*/
public boolean handleCloseControls(boolean save);
public boolean handleCloseControls(boolean save, boolean force);
/**
* @return whether the notification associated with these guts is set to be removed.
*/
public boolean willBeRemoved();
/**
* @return whether these guts are a leavebehind (e.g. {@link NotificationSnooze}).
*/
public default boolean isLeavebehind() {
return false;
}
}
public interface OnGutsClosedListener {
@@ -125,7 +136,7 @@ public class NotificationGuts extends FrameLayout {
@Override
public void run() {
if (mNeedsFalsingProtection && mExposed) {
closeControls(-1 /* x */, -1 /* y */, false /* save */);
closeControls(-1 /* x */, -1 /* y */, false /* save */, false /* force */);
}
}
};
@@ -144,6 +155,10 @@ public class NotificationGuts extends FrameLayout {
addView(mGutsContent.getContentView());
}
public GutsContent getGutsContent() {
return mGutsContent;
}
public void resetFalsingCheck() {
mHandler.removeCallbacks(mFalsingCheck);
if (mNeedsFalsingProtection && mExposed) {
@@ -197,19 +212,30 @@ public class NotificationGuts extends FrameLayout {
}
}
public void closeControls(int x, int y, boolean save) {
public void closeControls(boolean leavebehinds, boolean controls, int x, int y, boolean force) {
if (mGutsContent != null) {
if (mGutsContent.isLeavebehind() && leavebehinds) {
closeControls(x, y, true /* save */, force);
} else if (!mGutsContent.isLeavebehind() && controls) {
closeControls(x, y, true /* save */, force);
}
}
}
public void closeControls(int x, int y, boolean save, boolean force) {
if (getWindowToken() == null) {
if (mClosedListener != null) {
mClosedListener.onGutsClosed(this);
}
return;
}
if (mGutsContent == null || !mGutsContent.handleCloseControls(save)) {
if (mGutsContent == null || !mGutsContent.handleCloseControls(save, force)) {
animateClose(x, y);
}
setExposed(false, mNeedsFalsingProtection);
if (mClosedListener != null) {
mClosedListener.onGutsClosed(this);
setExposed(false, mNeedsFalsingProtection);
if (mClosedListener != null) {
mClosedListener.onGutsClosed(this);
}
}
}

View File

@@ -393,7 +393,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
}
@Override
public boolean handleCloseControls(boolean save) {
public boolean handleCloseControls(boolean save, boolean force) {
if (save && hasImportanceChanged()) {
if (mCheckSaveListener != null) {
mCheckSaveListener.checkSave(() -> { saveImportance(); });

View File

@@ -103,6 +103,18 @@ public class NotificationSnooze extends LinearLayout
createOptionViews();
}
public boolean isExpanded() {
return mExpanded;
}
public void setSnoozeListener(NotificationSwipeActionHelper listener) {
mSnoozeListener = listener;
}
public void setStatusBarNotification(StatusBarNotification sbn) {
mSbn = sbn;
}
private ArrayList<SnoozeOption> getDefaultSnoozeOptions() {
ArrayList<SnoozeOption> options = new ArrayList<>();
options.add(createOption(R.string.snooze_option_15_min, 15));
@@ -201,7 +213,7 @@ public class NotificationSnooze extends LinearLayout
final int x = targetLoc[0] - parentLoc[0] + centerX;
final int y = targetLoc[1] - parentLoc[1] + centerY;
showSnoozeOptions(false);
mGutsContainer.closeControls(x, y, false /* save */);
mGutsContainer.closeControls(x, y, false /* save */, false /* force */);
}
}
@@ -222,29 +234,31 @@ public class NotificationSnooze extends LinearLayout
return this;
}
public void setStatusBarNotification(StatusBarNotification sbn) {
mSbn = sbn;
}
@Override
public void setGutsParent(NotificationGuts guts) {
mGutsContainer = guts;
}
public void setSnoozeListener(NotificationSwipeActionHelper listener) {
mSnoozeListener = listener;
}
@Override
public boolean handleCloseControls(boolean save) {
// When snooze is closed (i.e. there was interaction outside of the notification)
// then we commit the snooze action.
if (mSnoozeListener != null && mSelectedOption != null) {
public boolean handleCloseControls(boolean save, boolean force) {
if (mExpanded && !force) {
// Collapse expanded state on outside touch
showSnoozeOptions(false);
return true;
} else if (mSnoozeListener != null && mSelectedOption != null) {
// Snooze option selected so commit it
mSnoozing = true;
mSnoozeListener.snooze(mSbn, mSelectedOption);
return true;
} else {
// The view should actually be closed
setSelected(mSnoozeOptions.get(0));
return false; // Return false here so that guts handles closing the view
}
// The view should be closed
return false;
}
@Override
public boolean isLeavebehind() {
return true;
}
}

View File

@@ -524,7 +524,8 @@ public class NotificationPanelView extends PanelView implements
mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
}
closeQs();
mStatusBar.dismissPopups();
mStatusBar.closeAndSaveGuts(true /* leavebehind */, true /* force */,
true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, false /* animate */,
true /* cancelAnimators */);
mNotificationStackScroller.resetScrollPosition();
@@ -1015,6 +1016,7 @@ public class NotificationPanelView extends PanelView implements
float height = mQsExpansionHeight - overscrollAmount;
setQsExpansion(height);
requestPanelHeightUpdate();
mNotificationStackScroller.checkSnoozeLeavebehind();
}
private void setQsExpanded(boolean expanded) {
@@ -1381,6 +1383,7 @@ public class NotificationPanelView extends PanelView implements
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mNotificationStackScroller.resetCheckSnoozeLeavebehind();
mQsExpansionAnimator = null;
if (onFinishRunnable != null) {
onFinishRunnable.run();

View File

@@ -2996,8 +2996,9 @@ public class StatusBar extends SystemUI implements DemoMode,
mStatusBarWindowManager.setPanelVisible(false);
mStatusBarWindowManager.setForceStatusBarVisible(false);
// Close any "App info" popups that might have snuck on-screen
dismissPopups();
// Close any guts that might be visible
closeAndSaveGuts(true /* removeLeavebehind */, true /* force */, true /* removeControls */,
-1 /* x */, -1 /* y */, true /* resetMenu */);
runPostCollapseRunnables();
setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
@@ -4617,6 +4618,7 @@ public class StatusBar extends SystemUI implements DemoMode,
public void onDragDownReset() {
mStackScroller.setDimmed(true /* dimmed */, true /* animated */);
mStackScroller.resetScrollPosition();
mStackScroller.resetCheckSnoozeLeavebehind();
}
@Override
@@ -4627,6 +4629,7 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void onTouchSlopExceeded() {
mStackScroller.removeLongPressCallback();
mStackScroller.checkSnoozeLeavebehind();
}
@Override
@@ -5800,8 +5803,10 @@ public class StatusBar extends SystemUI implements DemoMode,
if (!g.willBeRemoved() && !row.isRemoved()) {
mStackScroller.onHeightChanged(row, !isPanelFullyCollapsed() /* needsAnimation */);
}
mNotificationGutsExposed = null;
mGutsMenuItem = null;
if (mNotificationGutsExposed == g) {
mNotificationGutsExposed = null;
mGutsMenuItem = null;
}
});
View gutsView = item.getGutsView();
@@ -5897,7 +5902,8 @@ public class StatusBar extends SystemUI implements DemoMode,
final int centerY = done.getHeight() / 2;
final int x = doneLocation[0] - rowLocation[0] + centerX;
final int y = doneLocation[1] - rowLocation[1] + centerY;
dismissPopups(x, y);
closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
true /* removeControls */, x, y, true /* resetMenu */);
}
protected SwipeHelper.LongPressListener getNotificationLongClicker() {
@@ -5917,6 +5923,12 @@ public class StatusBar extends SystemUI implements DemoMode,
if (row.isDark()) {
return false;
}
if (row.areGutsExposed()) {
closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
true /* removeControls */, -1 /* x */, -1 /* y */,
true /* resetMenu */);
return false;
}
bindGuts(row, item);
NotificationGuts guts = row.getGuts();
@@ -5926,12 +5938,6 @@ public class StatusBar extends SystemUI implements DemoMode,
return false;
}
// Already showing?
if (guts.getVisibility() == View.VISIBLE) {
dismissPopups(x, y);
return false;
}
mMetricsLogger.action(MetricsEvent.ACTION_NOTE_CONTROLS);
// ensure that it's laid but not visible until actually laid out
@@ -5945,8 +5951,9 @@ public class StatusBar extends SystemUI implements DemoMode,
+ "window");
return;
}
dismissPopups(-1 /* x */, -1 /* y */, false /* resetMenu */,
false /* animate */);
closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
true /* removeControls */, -1 /* x */, -1 /* y */,
false /* resetMenu */);
guts.setVisibility(View.VISIBLE);
final double horz = Math.max(guts.getWidth() - x, x);
final double vert = Math.max(guts.getHeight() - y, y);
@@ -5986,20 +5993,23 @@ public class StatusBar extends SystemUI implements DemoMode,
return mNotificationGutsExposed;
}
public void dismissPopups() {
dismissPopups(-1 /* x */, -1 /* y */, true /* resetMenu */, false /* animate */);
}
private void dismissPopups(int x, int y) {
dismissPopups(x, y, true /* resetMenu */, false /* animate */);
}
public void dismissPopups(int x, int y, boolean resetMenu, boolean animate) {
/**
* Closes guts or notification menus that might be visible and saves any changes.
*
* @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed.
* @param force true if guts should be closed regardless of state (used for snooze only).
* @param removeControls true if controls (e.g. info) should be closed.
* @param x if closed based on touch location, this is the x touch location.
* @param y if closed based on touch location, this is the y touch location.
* @param resetMenu if any notification menus that might be revealed should be closed.
*/
public void closeAndSaveGuts(boolean removeLeavebehinds, boolean force, boolean removeControls,
int x, int y, boolean resetMenu) {
if (mNotificationGutsExposed != null) {
mNotificationGutsExposed.closeControls(x, y, true /* save */);
mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force);
}
if (resetMenu) {
mStackScroller.resetExposedMenuView(animate, true /* force */);
mStackScroller.resetExposedMenuView(false /* animate */, true /* force */);
}
}
@@ -6521,7 +6531,8 @@ public class StatusBar extends SystemUI implements DemoMode,
if (mVisible != visible) {
mVisible = visible;
if (!visible) {
dismissPopups();
closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
}
}
updateVisibleToUser();

View File

@@ -75,6 +75,7 @@ import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationGuts;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.NotificationSnooze;
import com.android.systemui.statusbar.StackScrollerDecorView;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.FakeShadowView;
@@ -234,6 +235,7 @@ public class NotificationStackScrollLayout extends ViewGroup
private NotificationMenuRowPlugin mCurrMenuRow;
private View mTranslatingParentView;
private View mMenuExposedView;
boolean mCheckForLeavebehind;
/**
* Should in this touch motion only be scrolling allowed? It's true when the scroller was
@@ -1310,6 +1312,22 @@ public class NotificationStackScrollLayout extends ViewGroup
&& !mDisallowDismissInThisMotion) {
horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
}
// Check if we need to clear any snooze leavebehinds
NotificationGuts guts = mStatusBar.getExposedGuts();
if (guts != null && !isTouchInView(ev, guts)
&& guts.getGutsContent() instanceof NotificationSnooze) {
NotificationSnooze ns = (NotificationSnooze) guts.getGutsContent();
if ((ns.isExpanded() && isCancelOrUp)
|| (!horizontalSwipeWantsIt && scrollerWantsIt)) {
// If the leavebehind is expanded we clear it on the next up event, otherwise we
// clear it on the next non-horizontal swipe or expand event.
checkSnoozeLeavebehind();
}
}
if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
mCheckForLeavebehind = true;
}
return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev);
}
@@ -1429,6 +1447,8 @@ public class NotificationStackScrollLayout extends ViewGroup
// existing overScroll, we have to scroll the view
customOverScrollBy((int) scrollAmount, mOwnScrollY,
range, getHeight() / 2);
// If we're scrolling, leavebehinds should be dismissed
checkSnoozeLeavebehind();
}
}
break;
@@ -2458,6 +2478,18 @@ public class NotificationStackScrollLayout extends ViewGroup
&& !mDisallowDismissInThisMotion) {
swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
}
// Check if we need to clear any snooze leavebehinds
boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
NotificationGuts guts = mStatusBar.getExposedGuts();
if (!isTouchInView(ev, guts) && isUp && !swipeWantsIt && !expandWantsIt
&& !scrollWantsIt) {
mCheckForLeavebehind = false;
mStatusBar.closeAndSaveGuts(true /* removeLeavebehind */, false /* force */,
false /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
}
if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
mCheckForLeavebehind = true;
}
return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev);
}
@@ -3287,13 +3319,27 @@ public class NotificationStackScrollLayout extends ViewGroup
return Math.max(mMaxLayoutHeight - mContentHeight, 0);
}
public void checkSnoozeLeavebehind() {
if (mCheckForLeavebehind) {
mStatusBar.closeAndSaveGuts(true /* removeLeavebehind */, false /* force */,
false /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
mCheckForLeavebehind = false;
}
}
public void resetCheckSnoozeLeavebehind() {
mCheckForLeavebehind = true;
}
public void onExpansionStarted() {
mIsExpansionChanging = true;
mAmbientState.setExpansionChanging(true);
checkSnoozeLeavebehind();
}
public void onExpansionStopped() {
mIsExpansionChanging = false;
resetCheckSnoozeLeavebehind();
mAmbientState.setExpansionChanging(false);
if (!mIsExpanded) {
setOwnScrollY(0);
@@ -4292,6 +4338,8 @@ public class NotificationStackScrollLayout extends ViewGroup
// of the panel early.
handleChildDismissed(view);
}
mStatusBar.closeAndSaveGuts(true /* removeLeavebehind */, false /* force */,
false /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
handleMenuCoveredOrDismissed();
}
@@ -4374,29 +4422,19 @@ public class NotificationStackScrollLayout extends ViewGroup
public void closeControlsIfOutsideTouch(MotionEvent ev) {
NotificationGuts guts = mStatusBar.getExposedGuts();
View view = null;
int height = 0;
if (guts != null) {
// Checking guts
if (guts != null && !guts.getGutsContent().isLeavebehind()) {
// Only close visible guts if they're not a leavebehind.
view = guts;
height = guts.getActualHeight();
} else if (mCurrMenuRow != null && mCurrMenuRow.isMenuVisible()
&& mTranslatingParentView != null) {
// Checking menu
view = mTranslatingParentView;
height = ((ExpandableView) mTranslatingParentView).getActualHeight();
}
if (view != null) {
final int rx = (int) ev.getRawX();
final int ry = (int) ev.getRawY();
view.getLocationOnScreen(mTempInt2);
final int x = mTempInt2[0];
final int y = mTempInt2[1];
Rect rect = new Rect(x, y, x + view.getWidth(), y + height);
if (!rect.contains(rx, ry)) {
// Touch was outside visible guts / meny notification, close what's visible
mStatusBar.dismissPopups(-1, -1, true /* resetMenu */, true /* animate */);
}
if (view != null && !isTouchInView(ev, view)) {
// Touch was outside visible guts / menu notification, close what's visible
mStatusBar.closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
true /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
resetExposedMenuView(true /* animate */, true /* force */);
}
}
@@ -4420,6 +4458,23 @@ public class NotificationStackScrollLayout extends ViewGroup
}
}
private boolean isTouchInView(MotionEvent ev, View view) {
if (view == null) {
return false;
}
final int height = (view instanceof ExpandableView)
? ((ExpandableView) view).getActualHeight()
: view.getHeight();
final int rx = (int) ev.getRawX();
final int ry = (int) ev.getRawY();
view.getLocationOnScreen(mTempInt2);
final int x = mTempInt2[0];
final int y = mTempInt2[1];
Rect rect = new Rect(x, y, x + view.getWidth(), y + height);
boolean ret = rect.contains(rx, ry);
return ret;
}
private void updateContinuousShadowDrawing() {
boolean continuousShadowUpdate = mAnimationRunning
|| !mAmbientState.getDraggedViews().isEmpty();

View File

@@ -497,7 +497,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.getImportance(), mSbn, null, null, null,
null, null);
mNotificationInfo.handleCloseControls(true);
mNotificationInfo.handleCloseControls(true, false);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
}
@@ -511,7 +511,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.getImportance(), mSbn, null, null, null,
null, null);
mNotificationInfo.handleCloseControls(true);
mNotificationInfo.handleCloseControls(true, false);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
}
@@ -571,7 +571,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
mNotificationChannel.getImportance(), mSbn, null, null, null,
null, Collections.singleton(TEST_PACKAGE_NAME));
mNotificationInfo.handleCloseControls(true);
mNotificationInfo.handleCloseControls(true, false);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
}
@@ -586,7 +586,7 @@ public class NotificationInfoTest extends SysuiTestCase {
Switch enabledSwitch = mNotificationInfo.findViewById(R.id.channel_enabled_switch);
enabledSwitch.setChecked(false);
mNotificationInfo.handleCloseControls(true);
mNotificationInfo.handleCloseControls(true, false);
ArgumentCaptor<NotificationChannel> updated =
ArgumentCaptor.forClass(NotificationChannel.class);
@@ -606,7 +606,7 @@ public class NotificationInfoTest extends SysuiTestCase {
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
enabledSwitch.setChecked(false);
mNotificationInfo.handleCloseControls(false);
mNotificationInfo.handleCloseControls(false, false);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
eq(TEST_PACKAGE_NAME), anyInt(), eq(mNotificationChannel));
}
@@ -623,7 +623,7 @@ public class NotificationInfoTest extends SysuiTestCase {
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
enabledSwitch.setChecked(false);
mNotificationInfo.handleCloseControls(true);
mNotificationInfo.handleCloseControls(true, false);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
eq(TEST_PACKAGE_NAME), anyInt(), eq(mNotificationChannel));
}
@@ -641,7 +641,7 @@ public class NotificationInfoTest extends SysuiTestCase {
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
enabledSwitch.setChecked(false);
mNotificationInfo.handleCloseControls(true);
mNotificationInfo.handleCloseControls(true, false);
verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
eq(TEST_PACKAGE_NAME), anyInt(), eq(mNotificationChannel));
}