diff options
14 files changed, 433 insertions, 138 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index b58e5b3b7981..9ecae8c9bca9 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1348,10 +1348,18 @@ public final class Settings { = "android.settings.NOTIFICATION_SETTINGS"; /** + * Activity Action: Show app listing settings, filtered by those that send notifications. + * + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_ALL_APPS_NOTIFICATION_SETTINGS = + "android.settings.ALL_APPS_NOTIFICATION_SETTINGS"; + + /** * Activity Action: Show notification settings for a single app. * <p> - * Input: {@link #EXTRA_APP_PACKAGE}, the package containing the channel to display. - * Input: Optionally, {@link #EXTRA_CHANNEL_ID}, to highlight that channel. + * Input: {@link #EXTRA_APP_PACKAGE}, the package to display. * <p> * Output: Nothing. */ diff --git a/packages/SystemUI/res/layout/status_bar_notification_dismiss_all.xml b/packages/SystemUI/res/layout/status_bar_notification_footer.xml index 8dc4cb4a4b93..aa0d4a05f6cc 100644 --- a/packages/SystemUI/res/layout/status_bar_notification_dismiss_all.xml +++ b/packages/SystemUI/res/layout/status_bar_notification_footer.xml @@ -15,13 +15,27 @@ --> <!-- Extends Framelayout --> -<com.android.systemui.statusbar.DismissView +<com.android.systemui.statusbar.FooterView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingEnd="8dp" android:visibility="gone"> - <com.android.systemui.statusbar.DismissViewButton + <FrameLayout + android:id="@+id/content" + android:layout_width="match_parent" + android:layout_height="wrap_content" > + <com.android.systemui.statusbar.FooterViewButton + style="@android:style/Widget.Material.Button.Borderless" + android:id="@+id/manage_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="start" + android:focusable="true" + android:text="@string/manage_notifications_text" + android:textColor="?attr/wallpaperTextColor" + android:textAllCaps="false"/> + <com.android.systemui.statusbar.FooterViewButton style="@android:style/Widget.Material.Button.Borderless" android:id="@+id/dismiss_text" android:layout_width="wrap_content" @@ -32,4 +46,5 @@ android:text="@string/clear_all_notifications_text" android:textColor="?attr/wallpaperTextColor" android:textAllCaps="true"/> -</com.android.systemui.statusbar.DismissView> + </FrameLayout> +</com.android.systemui.statusbar.FooterView> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 260289eb4cd3..909c18bb1b16 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1053,6 +1053,9 @@ <!-- The text to clear all notifications. [CHAR LIMIT=60] --> <string name="clear_all_notifications_text">Clear all</string> + <!-- The text for the manage notifications link. [CHAR LIMIT=40] --> + <string name="manage_notifications_text">Manage notifications</string> + <!-- The text to show in the notifications shade when dnd is suppressing notifications. [CHAR LIMIT=100] --> <string name="dnd_suppressing_shade_text">Do Not disturb is hiding notifications</string> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java index 3698c3a0038d..4388b41fe8d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java @@ -48,6 +48,11 @@ public class EmptyShadeView extends StackScrollerDecorView { return findViewById(R.id.no_notifications); } + @Override + protected View findSecondaryView() { + return null; + } + public void setTextColor(@ColorInt int color) { mEmptyText.setTextColor(color); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java b/packages/SystemUI/src/com/android/systemui/statusbar/FooterView.java index d7c6443a6c3b..0f4b6215dced 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/FooterView.java @@ -26,11 +26,12 @@ import com.android.systemui.R; import com.android.systemui.statusbar.stack.ExpandableViewState; import com.android.systemui.statusbar.stack.StackScrollState; -public class DismissView extends StackScrollerDecorView { +public class FooterView extends StackScrollerDecorView { private final int mClearAllTopPadding; - private DismissViewButton mDismissButton; + private FooterViewButton mDismissButton; + private FooterViewButton mManageButton; - public DismissView(Context context, AttributeSet attrs) { + public FooterView(Context context, AttributeSet attrs) { super(context, attrs); mClearAllTopPadding = context.getResources().getDimensionPixelSize( R.dimen.clear_all_padding_top); @@ -38,21 +39,31 @@ public class DismissView extends StackScrollerDecorView { @Override protected View findContentView() { + return findViewById(R.id.content); + } + + protected View findSecondaryView() { return findViewById(R.id.dismiss_text); } @Override protected void onFinishInflate() { super.onFinishInflate(); - mDismissButton = (DismissViewButton) findContentView(); + mDismissButton = (FooterViewButton) findSecondaryView(); + mManageButton = findViewById(R.id.manage_text); } public void setTextColor(@ColorInt int color) { + mManageButton.setTextColor(color); mDismissButton.setTextColor(color); } - public void setOnButtonClickListener(OnClickListener listener) { - mContent.setOnClickListener(listener); + public void setManageButtonClickListener(OnClickListener listener) { + mManageButton.setOnClickListener(listener); + } + + public void setDismissButtonClickListener(OnClickListener listener) { + mDismissButton.setOnClickListener(listener); } public boolean isOnEmptySpace(float touchX, float touchY) { @@ -68,25 +79,26 @@ public class DismissView extends StackScrollerDecorView { mDismissButton.setText(R.string.clear_all_notifications_text); mDismissButton.setContentDescription( mContext.getString(R.string.accessibility_clear_all)); + mManageButton.setText(R.string.manage_notifications_text); } public boolean isButtonVisible() { - return mDismissButton.getAlpha() != 0.0f; + return mManageButton.getAlpha() != 0.0f; } @Override public ExpandableViewState createNewViewState(StackScrollState stackScrollState) { - return new DismissViewState(); + return new FooterViewState(); } - public class DismissViewState extends ExpandableViewState { + public class FooterViewState extends ExpandableViewState { @Override public void applyToView(View view) { super.applyToView(view); - if (view instanceof DismissView) { - DismissView dismissView = (DismissView) view; + if (view instanceof FooterView) { + FooterView footerView = (FooterView) view; boolean visible = this.clipTopAmount < mClearAllTopPadding; - dismissView.performVisibilityAnimation(visible && !dismissView.willBeGone()); + footerView.performVisibilityAnimation(visible && !footerView.willBeGone()); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/FooterViewButton.java index b608d67e8eef..16ca0f2119fe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/FooterViewButton.java @@ -23,21 +23,21 @@ import android.view.ViewGroup; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; -public class DismissViewButton extends AlphaOptimizedButton { +public class FooterViewButton extends AlphaOptimizedButton { - public DismissViewButton(Context context) { + public FooterViewButton(Context context) { this(context, null); } - public DismissViewButton(Context context, AttributeSet attrs) { + public FooterViewButton(Context context, AttributeSet attrs) { this(context, attrs, 0); } - public DismissViewButton(Context context, AttributeSet attrs, int defStyleAttr) { + public FooterViewButton(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } - public DismissViewButton(Context context, AttributeSet attrs, int defStyleAttr, + public FooterViewButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java index badc40d5f01a..14a6c42eb7c4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java @@ -31,8 +31,11 @@ import com.android.systemui.Interpolators; public abstract class StackScrollerDecorView extends ExpandableView { protected View mContent; + protected View mSecondaryView; private boolean mIsVisible; + private boolean mIsSecondaryVisible; private boolean mAnimating; + private int mDuration = 260; public StackScrollerDecorView(Context context, AttributeSet attrs) { super(context, attrs); @@ -42,6 +45,7 @@ public abstract class StackScrollerDecorView extends ExpandableView { protected void onFinishInflate() { super.onFinishInflate(); mContent = findContentView(); + mSecondaryView = findSecondaryView(); setInvisible(); } @@ -57,17 +61,37 @@ public abstract class StackScrollerDecorView extends ExpandableView { } public void performVisibilityAnimation(boolean nowVisible) { - animateText(nowVisible, null /* onFinishedRunnable */); + animateText(mContent, nowVisible, null /* onFinishedRunnable */); + mIsVisible = nowVisible; } public void performVisibilityAnimation(boolean nowVisible, Runnable onFinishedRunnable) { - animateText(nowVisible, onFinishedRunnable); + animateText(mContent, nowVisible, onFinishedRunnable); + mIsVisible = nowVisible; + } + + public void performSecondaryVisibilityAnimation(boolean nowVisible) { + performSecondaryVisibilityAnimation(nowVisible, null /* onFinishedRunnable */); + } + + public void performSecondaryVisibilityAnimation(boolean nowVisible, + Runnable onFinishedRunnable) { + animateText(mSecondaryView, nowVisible, onFinishedRunnable); + mIsSecondaryVisible = nowVisible; + } + + public boolean isSecondaryVisible() { + return mSecondaryView != null && (mIsSecondaryVisible || mAnimating); } public boolean isVisible() { return mIsVisible || mAnimating; } + void setDuration(int duration) { + mDuration = duration; + } + /** * Animate the text to a new visibility. * @@ -75,7 +99,10 @@ public abstract class StackScrollerDecorView extends ExpandableView { * @param onFinishedRunnable A runnable which should be run when the animation is * finished. */ - private void animateText(boolean nowVisible, final Runnable onFinishedRunnable) { + private void animateText(View view, boolean nowVisible, final Runnable onFinishedRunnable) { + if (view == null) { + return; + } if (nowVisible != mIsVisible) { // Animate text float endValue = nowVisible ? 1.0f : 0.0f; @@ -86,10 +113,10 @@ public abstract class StackScrollerDecorView extends ExpandableView { interpolator = Interpolators.ALPHA_OUT; } mAnimating = true; - mContent.animate() + view.animate() .alpha(endValue) .setInterpolator(interpolator) - .setDuration(260) + .setDuration(mDuration) .withEndAction(new Runnable() { @Override public void run() { @@ -99,7 +126,6 @@ public abstract class StackScrollerDecorView extends ExpandableView { } } }); - mIsVisible = nowVisible; } else { if (onFinishedRunnable != null) { onFinishedRunnable.run(); @@ -109,7 +135,11 @@ public abstract class StackScrollerDecorView extends ExpandableView { public void setInvisible() { mContent.setAlpha(0.0f); + if (mSecondaryView != null) { + mSecondaryView.setAlpha(0.0f); + } mIsVisible = false; + mIsSecondaryVisible = false; } @Override @@ -134,7 +164,15 @@ public abstract class StackScrollerDecorView extends ExpandableView { public void cancelAnimation() { mContent.animate().cancel(); + if (mSecondaryView != null) { + mSecondaryView.animate().cancel(); + } } protected abstract View findContentView(); + + /** + * Returns a view that might not always appear while the main content view is still visible. + */ + protected abstract View findSecondaryView(); } 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 64e205d0545b..cccda90c20a8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -2165,18 +2165,18 @@ public class NotificationPanelView extends PanelView implements @Override protected boolean fullyExpandedClearAllVisible() { - return mNotificationStackScroller.isDismissViewNotGone() + return mNotificationStackScroller.isFooterViewNotGone() && mNotificationStackScroller.isScrolledToBottom() && !mQsExpandImmediate; } @Override protected boolean isClearAllVisible() { - return mNotificationStackScroller.isDismissViewVisible(); + return mNotificationStackScroller.isFooterViewVisible(); } @Override protected int getClearAllHeight() { - return mNotificationStackScroller.getDismissViewHeight(); + return mNotificationStackScroller.getFooterViewHeight(); } @Override 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 82469742020e..ddb67dfa8f33 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -130,7 +130,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.colorextraction.ColorExtractor; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.widget.LockPatternUtils; @@ -185,12 +184,11 @@ import com.android.systemui.statusbar.AppOpsListener; import com.android.systemui.statusbar.BackDropView; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CrossFadeHelper; -import com.android.systemui.statusbar.DismissView; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.FooterView; import com.android.systemui.statusbar.GestureRecorder; -import com.android.systemui.statusbar.HeadsUpStatusBarView; import com.android.systemui.statusbar.KeyboardShortcuts; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.NotificationData; @@ -237,7 +235,6 @@ import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; -import com.android.systemui.util.NotificationChannels; import com.android.systemui.volume.VolumeComponent; import java.io.FileDescriptor; @@ -577,7 +574,7 @@ public class StatusBar extends SystemUI implements DemoMode, private final LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger(); protected NotificationIconAreaController mNotificationIconAreaController; private boolean mReinflateNotificationsOnUserSwitched; - private boolean mClearAllEnabled; + protected boolean mClearAllEnabled; @Nullable private View mAmbientIndicationContainer; private SysuiColorExtractor mColorExtractor; private ScreenLifecycle mScreenLifecycle; @@ -868,7 +865,7 @@ public class StatusBar extends SystemUI implements DemoMode, mVisualStabilityManager.setVisibilityLocationProvider(mStackScroller); inflateEmptyShadeView(); - inflateDismissView(); + inflateFooterView(); mBackdrop = mStatusBarWindow.findViewById(R.id.backdrop); mBackdropFront = mBackdrop.findViewById(R.id.backdrop_front); @@ -1133,8 +1130,8 @@ public class StatusBar extends SystemUI implements DemoMode, protected void reevaluateStyles() { inflateSignalClusters(); - inflateDismissView(); - updateClearAll(); + inflateFooterView(); + updateFooter(); inflateEmptyShadeView(); updateEmptyShadeView(); } @@ -1186,18 +1183,21 @@ public class StatusBar extends SystemUI implements DemoMode, mStackScroller.setEmptyShadeView(mEmptyShadeView); } - private void inflateDismissView() { - if (!mClearAllEnabled || mStackScroller == null) { + private void inflateFooterView() { + if (mStackScroller == null) { return; } - mDismissView = (DismissView) LayoutInflater.from(mContext).inflate( - R.layout.status_bar_notification_dismiss_all, mStackScroller, false); - mDismissView.setOnButtonClickListener(v -> { + mFooterView = (FooterView) LayoutInflater.from(mContext).inflate( + R.layout.status_bar_notification_footer, mStackScroller, false); + mFooterView.setDismissButtonClickListener(v -> { mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES); clearAllNotifications(); }); - mStackScroller.setDismissView(mDismissView); + mFooterView.setManageButtonClickListener(v -> { + manageNotifications(); + }); + mStackScroller.setFooterView(mFooterView); } protected void createUserSwitcher() { @@ -1211,6 +1211,12 @@ public class StatusBar extends SystemUI implements DemoMode, R.layout.super_status_bar, null); } + public void manageNotifications() { + Intent intent = new Intent(Settings.ACTION_ALL_APPS_NOTIFICATION_SETTINGS); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent, true, true); + } + public void clearAllNotifications() { // animate-swipe all dismissable notifications, then animate the shade closed @@ -1393,7 +1399,7 @@ public class StatusBar extends SystemUI implements DemoMode, mViewHierarchyManager.updateNotificationViews(); updateSpeedBumpIndex(); - updateClearAll(); + updateFooter(); updateEmptyShadeView(); updateQsExpansionEnabled(); @@ -1457,13 +1463,14 @@ public class StatusBar extends SystemUI implements DemoMode, mQSPanel.clickTile(tile); } - private void updateClearAll() { - if (!mClearAllEnabled) { - return; - } - boolean showDismissView = mState != StatusBarState.KEYGUARD + @VisibleForTesting + protected void updateFooter() { + boolean showFooterView = mState != StatusBarState.KEYGUARD + && mEntryManager.getNotificationData().getActiveNotifications().size() != 0; + boolean showDismissView = mClearAllEnabled && mState != StatusBarState.KEYGUARD && hasActiveClearableNotifications(); - mStackScroller.updateDismissView(showDismissView); + + mStackScroller.updateFooterView(showFooterView, showDismissView); } /** @@ -4938,7 +4945,7 @@ public class StatusBar extends SystemUI implements DemoMode, protected RecentsComponent mRecents; protected NotificationShelf mNotificationShelf; - protected DismissView mDismissView; + protected FooterView mFooterView; protected EmptyShadeView mEmptyShadeView; protected AssistManager mAssistManager; @@ -5411,8 +5418,8 @@ public class StatusBar extends SystemUI implements DemoMode, // incremented in the following "changeViewPosition" calls so that its value is correct for // subsequent calls. int offsetFromEnd = 1; - if (mDismissView != null) { - mStackScroller.changeViewPosition(mDismissView, + if (mFooterView != null) { + mStackScroller.changeViewPosition(mFooterView, mStackScroller.getChildCount() - offsetFromEnd++); } 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 dc9420314371..8f6fa4938d7e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -16,7 +16,8 @@ package com.android.systemui.statusbar.stack; -import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters; +import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator + .ExpandAnimationParameters; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -43,6 +44,7 @@ import android.os.Handler; import android.service.notification.StatusBarNotification; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; +import android.support.v4.graphics.ColorUtils; import android.util.AttributeSet; import android.util.FloatProperty; import android.util.Log; @@ -77,10 +79,10 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.statusbar.ActivatableNotificationView; -import com.android.systemui.statusbar.DismissView; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; +import com.android.systemui.statusbar.FooterView; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.NotificationGuts; import com.android.systemui.statusbar.NotificationListContainer; @@ -99,8 +101,6 @@ import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.HeadsUpUtil; import com.android.systemui.statusbar.policy.ScrollAdapter; -import android.support.v4.graphics.ColorUtils; - import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; @@ -109,7 +109,6 @@ import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.function.BiConsumer; -import java.util.function.Consumer; /** * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack. @@ -226,7 +225,7 @@ public class NotificationStackScrollLayout extends ViewGroup private boolean mExpandingNotification; private boolean mExpandedInThisMotion; protected boolean mScrollingEnabled; - protected DismissView mDismissView; + protected FooterView mFooterView; protected EmptyShadeView mEmptyShadeView; private boolean mDismissAllInProgress; private boolean mFadeNotificationsOnDismiss; @@ -3838,13 +3837,13 @@ public class NotificationStackScrollLayout extends ViewGroup Context context = new ContextThemeWrapper(mContext, lightTheme ? R.style.Theme_SystemUI_Light : R.style.Theme_SystemUI); final int textColor = Utils.getColorAttr(context, R.attr.wallpaperTextColor); - mDismissView.setTextColor(textColor); + mFooterView.setTextColor(textColor); mEmptyShadeView.setTextColor(textColor); } public void goToFullShade(long delay) { - if (mDismissView != null) { - mDismissView.setInvisible(); + if (mFooterView != null) { + mFooterView.setInvisible(); } mEmptyShadeView.setInvisible(); mGoToFullShadeNeedsAnimation = true; @@ -3967,14 +3966,14 @@ public class NotificationStackScrollLayout extends ViewGroup return -1; } - public void setDismissView(@NonNull DismissView dismissView) { + public void setFooterView(@NonNull FooterView footerView) { int index = -1; - if (mDismissView != null) { - index = indexOfChild(mDismissView); - removeView(mDismissView); + if (mFooterView != null) { + index = indexOfChild(mFooterView); + removeView(mFooterView); } - mDismissView = dismissView; - addView(mDismissView, index); + mFooterView = footerView; + addView(mFooterView, index); } public void setEmptyShadeView(EmptyShadeView emptyShadeView) { @@ -3992,77 +3991,64 @@ public class NotificationStackScrollLayout extends ViewGroup int newVisibility = visible ? VISIBLE : GONE; if (oldVisibility != newVisibility) { if (newVisibility != GONE) { - if (mEmptyShadeView.willBeGone()) { - mEmptyShadeView.cancelAnimation(); - } else { - mEmptyShadeView.setInvisible(); - } if (mStatusBar.areNotificationsHidden()) { mEmptyShadeView.setText(R.string.dnd_suppressing_shade_text); } else { mEmptyShadeView.setText(R.string.empty_shade_text); } - mEmptyShadeView.setVisibility(newVisibility); - mEmptyShadeView.setWillBeGone(false); - updateContentHeight(); - notifyHeightChangeListener(mEmptyShadeView); + showFooterView(mEmptyShadeView); } else { - Runnable onFinishedRunnable = new Runnable() { - @Override - public void run() { - mEmptyShadeView.setVisibility(GONE); - mEmptyShadeView.setWillBeGone(false); - updateContentHeight(); - notifyHeightChangeListener(mEmptyShadeView); - } - }; - if (mAnimationsEnabled && mIsExpanded) { - mEmptyShadeView.setWillBeGone(true); - mEmptyShadeView.performVisibilityAnimation(false, onFinishedRunnable); - } else { - mEmptyShadeView.setInvisible(); - onFinishedRunnable.run(); - } + hideFooterView(mEmptyShadeView, true); } } } - public void updateDismissView(boolean visible) { - if (mDismissView == null) { + public void updateFooterView(boolean visible, boolean showDismissView) { + if (mFooterView == null) { return; } - - int oldVisibility = mDismissView.willBeGone() ? GONE : mDismissView.getVisibility(); + int oldVisibility = mFooterView.willBeGone() ? GONE : mFooterView.getVisibility(); int newVisibility = visible ? VISIBLE : GONE; if (oldVisibility != newVisibility) { if (newVisibility != GONE) { - if (mDismissView.willBeGone()) { - mDismissView.cancelAnimation(); - } else { - mDismissView.setInvisible(); - } - mDismissView.setVisibility(newVisibility); - mDismissView.setWillBeGone(false); - updateContentHeight(); - notifyHeightChangeListener(mDismissView); + showFooterView(mFooterView); } else { - Runnable dimissHideFinishRunnable = new Runnable() { - @Override - public void run() { - mDismissView.setVisibility(GONE); - mDismissView.setWillBeGone(false); - updateContentHeight(); - notifyHeightChangeListener(mDismissView); - } - }; - if (mDismissView.isButtonVisible() && mIsExpanded && mAnimationsEnabled) { - mDismissView.setWillBeGone(true); - mDismissView.performVisibilityAnimation(false, dimissHideFinishRunnable); - } else { - dimissHideFinishRunnable.run(); - } + hideFooterView(mFooterView, mFooterView.isButtonVisible()); } } + if (mFooterView.isSecondaryVisible() != showDismissView) { + mFooterView.performSecondaryVisibilityAnimation(showDismissView); + } + } + + private void showFooterView(StackScrollerDecorView footerView) { + if (footerView.willBeGone()) { + footerView.cancelAnimation(); + } else { + footerView.setInvisible(); + } + footerView.setVisibility(VISIBLE); + footerView.setWillBeGone(false); + updateContentHeight(); + notifyHeightChangeListener(footerView); + } + + private void hideFooterView(StackScrollerDecorView footerView, boolean isButtonVisible) { + Runnable onHideFinishRunnable = new Runnable() { + @Override + public void run() { + footerView.setVisibility(GONE); + footerView.setWillBeGone(false); + updateContentHeight(); + notifyHeightChangeListener(footerView); + } + }; + if (isButtonVisible && mIsExpanded && mAnimationsEnabled) { + footerView.setWillBeGone(true); + footerView.performVisibilityAnimation(false, onHideFinishRunnable); + } else { + onHideFinishRunnable.run(); + } } public void setDismissAllInProgress(boolean dismissAllInProgress) { @@ -4088,18 +4074,18 @@ public class NotificationStackScrollLayout extends ViewGroup } } - public boolean isDismissViewNotGone() { - return mDismissView != null - && mDismissView.getVisibility() != View.GONE - && !mDismissView.willBeGone(); + public boolean isFooterViewNotGone() { + return mFooterView != null + && mFooterView.getVisibility() != View.GONE + && !mFooterView.willBeGone(); } - public boolean isDismissViewVisible() { - return mDismissView != null && mDismissView.isVisible(); + public boolean isFooterViewVisible() { + return mFooterView != null && mFooterView.isVisible(); } - public int getDismissViewHeight() { - return mDismissView == null ? 0 : mDismissView.getHeight() + mPaddingBetweenElements; + public int getFooterViewHeight() { + return mFooterView == null ? 0 : mFooterView.getHeight() + mPaddingBetweenElements; } public int getEmptyShadeViewHeight() { @@ -4155,8 +4141,8 @@ public class NotificationStackScrollLayout extends ViewGroup } boolean belowChild = touchY > childTop + child.getActualHeight() - child.getClipBottomAmount(); - if (child == mDismissView) { - if(!belowChild && !mDismissView.isOnEmptySpace(touchX - mDismissView.getX(), + if (child == mFooterView) { + if(!belowChild && !mFooterView.isOnEmptySpace(touchX - mFooterView.getX(), touchY - childTop)) { // We clicked on the dismiss button return false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java index 7c8e0fc4886b..a8d2d98b6f2b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java @@ -22,10 +22,10 @@ import android.util.Log; import android.view.View; import android.view.ViewGroup; import com.android.systemui.R; -import com.android.systemui.statusbar.DismissView; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; +import com.android.systemui.statusbar.FooterView; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.notification.NotificationUtils; @@ -390,7 +390,7 @@ public class StackScrollAlgorithm { int paddingAfterChild = getPaddingAfterChild(algorithmState, child); int childHeight = getMaxAllowedChildHeight(child); childViewState.yTranslation = currentYPosition; - boolean isDismissView = child instanceof DismissView; + boolean isFooterView = child instanceof FooterView; boolean isEmptyShadeView = child instanceof EmptyShadeView; childViewState.location = ExpandableViewState.LOCATION_MAIN_AREA; @@ -404,7 +404,7 @@ public class StackScrollAlgorithm { float end = childViewState.yTranslation + childViewState.height + inset; childViewState.headsUpIsVisible = end < ambientState.getMaxHeadsUpTranslation(); } - if (isDismissView) { + if (isFooterView) { childViewState.yTranslation = Math.min(childViewState.yTranslation, ambientState.getInnerHeight() - childHeight); } else if (isEmptyShadeView) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/FooterViewTest.java new file mode 100644 index 000000000000..96b02550b638 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/FooterViewTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.ContextWrapper; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.Color; +import android.graphics.drawable.Icon; +import android.os.UserHandle; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; + +import com.android.internal.statusbar.StatusBarIcon; +import com.android.internal.util.NotificationColorUtil; +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatcher; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class FooterViewTest extends SysuiTestCase { + + FooterView mView; + + @Before + public void setUp() { + mView = (FooterView) LayoutInflater.from(mContext).inflate( + R.layout.status_bar_notification_footer, null, false); + mView.setDuration(0); + } + + @Test + public void testViewsNotNull() { + assertNotNull(mView.findContentView()); + assertNotNull(mView.findSecondaryView()); + } + + @Test + public void setDismissOnClick() { + mView.setDismissButtonClickListener(mock(View.OnClickListener.class)); + assertTrue(mView.findSecondaryView().hasOnClickListeners()); + } + + @Test + public void setManageOnClick() { + mView.setManageButtonClickListener(mock(View.OnClickListener.class)); + assertTrue(mView.findViewById(R.id.manage_text).hasOnClickListeners()); + } + + @Test + public void testPerformVisibilityAnimation() { + mView.setInvisible(); + assertFalse(mView.isVisible()); + + Runnable test = new Runnable() { + @Override + public void run() { + assertEquals(1.0f, mView.findContentView().getAlpha()); + assertEquals(0.0f, mView.findSecondaryView().getAlpha()); + assertTrue(mView.isVisible()); + } + }; + mView.performVisibilityAnimation(true, test); + } + + @Test + public void testPerformSecondaryVisibilityAnimation() { + mView.setInvisible(); + assertFalse(mView.isSecondaryVisible()); + + Runnable test = new Runnable() { + @Override + public void run() { + assertEquals(0.0f, mView.findContentView().getAlpha()); + assertEquals(1.0f, mView.findSecondaryView().getAlpha()); + assertTrue(mView.isSecondaryVisible()); + } + }; + mView.performSecondaryVisibilityAnimation(true, test); + } +} + diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index f13fa4e51b78..f81ffd825668 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -18,7 +18,9 @@ package com.android.systemui.statusbar.phone; import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; import static junit.framework.TestCase.fail; @@ -55,6 +57,8 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.util.SparseArray; +import android.view.Gravity; +import android.view.View; import android.view.ViewGroup.LayoutParams; import com.android.internal.logging.MetricsLogger; @@ -65,13 +69,15 @@ import com.android.keyguard.KeyguardHostView.OnDismissAction; import com.android.systemui.ForegroundServiceController; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; -import com.android.systemui.UiOffloadThread; import com.android.systemui.assist.AssistManager; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.statusbar.ActivatableNotificationView; import com.android.systemui.statusbar.AppOpsListener; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.FooterView; +import com.android.systemui.statusbar.FooterViewButton; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.NotificationData.Entry; @@ -88,7 +94,6 @@ import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.KeyguardMonitor; import com.android.systemui.statusbar.policy.KeyguardMonitorImpl; @@ -97,12 +102,14 @@ import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.io.ByteArrayOutputStream; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.function.Predicate; @SmallTest @RunWith(AndroidTestingRunner.class) @@ -558,6 +565,68 @@ public class StatusBarTest extends SysuiTestCase { verify(mScrimController).setKeyguardOccluded(eq(false)); } + @Test + public void testInflateFooterView() { + mStatusBar.reevaluateStyles(); + ArgumentCaptor<FooterView> captor = ArgumentCaptor.forClass(FooterView.class); + verify(mStackScroller).setFooterView(captor.capture()); + + assertNotNull(captor.getValue().findViewById(R.id.manage_text).hasOnClickListeners()); + assertNotNull(captor.getValue().findViewById(R.id.dismiss_text).hasOnClickListeners()); + } + + @Test + public void testUpdateFooter_noNotifications() { + mStatusBar.setBarStateForTest(StatusBarState.SHADE); + assertEquals(0, mEntryManager.getNotificationData().getActiveNotifications().size()); + + mStatusBar.updateFooter(); + verify(mStackScroller).updateFooterView(false, false); + } + + @Test + public void testUpdateFooter_oneClearableNotification() { + mStatusBar.setBarStateForTest(StatusBarState.SHADE); + ArrayList<Entry> entries = new ArrayList<>(); + entries.add(mock(Entry.class)); + when(mNotificationData.getActiveNotifications()).thenReturn(entries); + + ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); + when(row.canViewBeDismissed()).thenReturn(true); + when(mStackScroller.getChildCount()).thenReturn(1); + when(mStackScroller.getChildAt(anyInt())).thenReturn(row); + + mStatusBar.updateFooter(); + verify(mStackScroller).updateFooterView(true, true); + } + + @Test + public void testUpdateFooter_oneNonClearableNotification() { + mStatusBar.setBarStateForTest(StatusBarState.SHADE); + ArrayList<Entry> entries = new ArrayList<>(); + entries.add(mock(Entry.class)); + when(mNotificationData.getActiveNotifications()).thenReturn(entries); + + mStatusBar.updateFooter(); + verify(mStackScroller).updateFooterView(true, false); + } + + @Test + public void testUpdateFooter_atEnd() { + // add footer + mStatusBar.reevaluateStyles(); + + // add notification + ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); + when(row.isClearable()).thenReturn(true); + mStackScroller.addContainerView(row); + + mStatusBar.onUpdateRowStates(); + + // move footer to end + verify(mStackScroller).changeViewPosition(any(FooterView.class), eq(-1 /* end */)); + } + static class TestableStatusBar extends StatusBar { public TestableStatusBar(StatusBarKeyguardViewManager man, UnlockMethodCache unlock, KeyguardIndicationController key, @@ -587,6 +656,7 @@ public class StatusBarTest extends SysuiTestCase { mScrimController = scrimController; mFingerprintUnlockController = fingerprintUnlockController; mActivityLaunchAnimator = launchAnimator; + mClearAllEnabled = true; } private WakefulnessLifecycle createAwakeWakefulnessLifecycle() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayoutTest.java index 6fa91ff07850..42069e2f150a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayoutTest.java @@ -23,10 +23,12 @@ import static org.mockito.Mockito.when; import android.support.test.annotation.UiThreadTest; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import android.view.View; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.EmptyShadeView; +import com.android.systemui.statusbar.FooterView; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.StatusBar; @@ -94,4 +96,30 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { verify(view).setText(R.string.empty_shade_text); } + + @Test + public void manageNotifications_visible() { + FooterView view = mock(FooterView.class); + mStackScroller.setFooterView(view); + when(view.willBeGone()).thenReturn(true); + when(view.isSecondaryVisible()).thenReturn(true); + + mStackScroller.updateFooterView(true, false); + + verify(view).setVisibility(View.VISIBLE); + verify(view).performSecondaryVisibilityAnimation(false); + } + + @Test + public void clearAll_visible() { + FooterView view = mock(FooterView.class); + mStackScroller.setFooterView(view); + when(view.willBeGone()).thenReturn(true); + when(view.isSecondaryVisible()).thenReturn(false); + + mStackScroller.updateFooterView(true, true); + + verify(view).setVisibility(View.VISIBLE); + verify(view).performSecondaryVisibilityAnimation(true); + } } |