diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java index 137531079a125..23a67e2533aa8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java @@ -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); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java index 2cf06c2ab33f6..0c2c5bc3516f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java @@ -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(); }); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java index 8b3d6d9b70214..8830c5dc1247a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java @@ -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 getDefaultSnoozeOptions() { ArrayList options = new ArrayList<>(); options.add(createOption(R.string.snooze_option_15_min, 15)); @@ -203,7 +215,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 */); } } @@ -224,29 +236,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; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index b191b91e42338..c52c11af4de50 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -529,7 +529,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(); @@ -1020,6 +1021,7 @@ public class NotificationPanelView extends PanelView implements float height = mQsExpansionHeight - overscrollAmount; setQsExpansion(height); requestPanelHeightUpdate(); + mNotificationStackScroller.checkSnoozeLeavebehind(); } private void setQsExpanded(boolean expanded) { @@ -1395,6 +1397,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(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 38d514ed7b89c..4e620a67e7af6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -3012,8 +3012,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); @@ -4664,6 +4665,7 @@ public class StatusBar extends SystemUI implements DemoMode, public void onDragDownReset() { mStackScroller.setDimmed(true /* dimmed */, true /* animated */); mStackScroller.resetScrollPosition(); + mStackScroller.resetCheckSnoozeLeavebehind(); } @Override @@ -4674,6 +4676,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onTouchSlopExceeded() { mStackScroller.removeLongPressCallback(); + mStackScroller.checkSnoozeLeavebehind(); } @Override @@ -5857,8 +5860,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(); @@ -5954,7 +5959,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() { @@ -5974,6 +5980,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(); @@ -5983,12 +5995,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 @@ -6002,8 +6008,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); @@ -6043,20 +6050,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 */); } } @@ -6589,7 +6599,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(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index 94b6aaa9ba4ad..0656d736f850b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -77,6 +77,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; @@ -240,6 +241,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 @@ -1314,6 +1316,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); } @@ -1433,6 +1451,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; @@ -2463,6 +2483,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); } @@ -3292,13 +3324,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); @@ -4300,6 +4346,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(); } @@ -4382,29 +4430,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 */); } } @@ -4428,6 +4466,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(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java index 2b14b31284932..0531ec51f64f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java @@ -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 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)); }