Add unlock to see more messaging to notif footer

If the device is locked, and there are no notifications on the keyguard,
and then one or more new notifications are received, and the user
expands the shade, if there would be additional notifications present if
the device is unlocked, then display a message in the footer.

Bug: 254647461
Test: manual
      1: Enable "Only Show Unseen Notifs On Keyguard" flag
      2: Have some notifications
      3: Lock device
      4: On keyguard, observe no notifications
      5. Receive a notification.
      6: Expand shade (without unlocking)
      Observe: New UI treatment is visible
Change-Id: Ibe4c3f955229ab78845a6f51adffc30ab355f52f
This commit is contained in:
Steve Elliott
2022-12-09 15:37:10 -05:00
parent 9ed2b6e46e
commit d8478aeb3b
7 changed files with 145 additions and 26 deletions

View File

@@ -26,6 +26,17 @@
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/unlock_prompt_footer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_gravity="center_horizontal"
android:gravity="center"
android:drawablePadding="8dp"
android:visibility="gone"
android:textAppearance="?android:attr/textAppearanceButton"
android:text="@string/unlock_to_see_notif_text"/>
<com.android.systemui.statusbar.notification.row.FooterViewButton
style="@style/TextAppearance.NotificationSectionHeaderButton"
android:id="@+id/manage_text"

View File

@@ -16,15 +16,22 @@
package com.android.systemui.statusbar.notification.row;
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.StringRes;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.IndentingPrintWriter;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -41,6 +48,11 @@ public class FooterView extends StackScrollerDecorView {
private String mManageNotificationText;
private String mManageNotificationHistoryText;
// Footer label
private TextView mSeenNotifsFooterTextView;
private @StringRes int mSeenNotifsFilteredText;
private int mUnlockIconSize;
public FooterView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -73,10 +85,41 @@ public class FooterView extends StackScrollerDecorView {
super.onFinishInflate();
mClearAllButton = (FooterViewButton) findSecondaryView();
mManageButton = findViewById(R.id.manage_text);
mSeenNotifsFooterTextView = findViewById(R.id.unlock_prompt_footer);
updateResources();
updateText();
}
public void setFooterLabelTextAndIcon(@StringRes int text, @DrawableRes int icon) {
mSeenNotifsFilteredText = text;
if (mSeenNotifsFilteredText != 0) {
mSeenNotifsFooterTextView.setText(mSeenNotifsFilteredText);
} else {
mSeenNotifsFooterTextView.setText(null);
}
Drawable drawable;
if (icon == 0) {
drawable = null;
} else {
drawable = getResources().getDrawable(icon);
drawable.setBounds(0, 0, mUnlockIconSize, mUnlockIconSize);
}
mSeenNotifsFooterTextView.setCompoundDrawablesRelative(drawable, null, null, null);
updateFooterVisibilityMode();
}
private void updateFooterVisibilityMode() {
if (mSeenNotifsFilteredText != 0) {
mManageButton.setVisibility(View.GONE);
mClearAllButton.setVisibility(View.GONE);
mSeenNotifsFooterTextView.setVisibility(View.VISIBLE);
} else {
mManageButton.setVisibility(View.VISIBLE);
mClearAllButton.setVisibility(View.VISIBLE);
mSeenNotifsFooterTextView.setVisibility(View.GONE);
}
}
public void setManageButtonClickListener(OnClickListener listener) {
mManageButton.setOnClickListener(listener);
}
@@ -135,12 +178,19 @@ public class FooterView extends StackScrollerDecorView {
mClearAllButton.setTextColor(textColor);
mManageButton.setBackground(theme.getDrawable(R.drawable.notif_footer_btn_background));
mManageButton.setTextColor(textColor);
final @ColorInt int labelTextColor =
Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary);
mSeenNotifsFooterTextView.setTextColor(labelTextColor);
mSeenNotifsFooterTextView.setCompoundDrawableTintList(
ColorStateList.valueOf(labelTextColor));
}
private void updateResources() {
mManageNotificationText = getContext().getString(R.string.manage_notifications_text);
mManageNotificationHistoryText = getContext()
.getString(R.string.manage_notifications_history_text);
mUnlockIconSize = getResources()
.getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
}
@Override

View File

@@ -538,6 +538,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private NotificationStackScrollLayoutController.TouchHandler mTouchHandler;
private final ScreenOffAnimationController mScreenOffAnimationController;
private boolean mShouldUseSplitNotificationShade;
private boolean mHasFilteredOutSeenNotifications;
private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener =
new ExpandableView.OnHeightChangedListener() {
@@ -684,6 +685,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
updateFooter();
}
void setHasFilteredOutSeenNotifications(boolean hasFilteredOutSeenNotifications) {
mHasFilteredOutSeenNotifications = hasFilteredOutSeenNotifications;
}
@VisibleForTesting
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void updateFooter() {
@@ -4612,13 +4617,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
void updateEmptyShadeView(
boolean visible, boolean areNotificationsHiddenInShade, boolean areSeenNotifsFiltered) {
void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade) {
mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled);
if (areNotificationsHiddenInShade) {
updateEmptyShadeView(R.string.dnd_suppressing_shade_text, 0, 0);
} else if (areSeenNotifsFiltered) {
} else if (mHasFilteredOutSeenNotifications) {
updateEmptyShadeView(
R.string.no_unseen_notif_text,
R.string.unlock_to_see_notif_text,
@@ -4657,13 +4661,20 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void updateFooterView(boolean visible, boolean showDismissView, boolean showHistory) {
if (mFooterView == null) {
if (mFooterView == null || mNotificationStackSizeCalculator == null) {
return;
}
boolean animate = mIsExpanded && mAnimationsEnabled;
mFooterView.setVisible(visible, animate);
mFooterView.setSecondaryVisible(showDismissView, animate);
mFooterView.showHistory(showHistory);
if (mHasFilteredOutSeenNotifications) {
mFooterView.setFooterLabelTextAndIcon(
R.string.unlock_to_see_notif_text,
R.drawable.ic_friction_lock_closed);
} else {
mFooterView.setFooterLabelTextAndIcon(0, 0);
}
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)

View File

@@ -1242,11 +1242,7 @@ public class NotificationStackScrollLayoutController {
// For more details, see: b/228790482
&& !isInTransitionToKeyguard();
mView.updateEmptyShadeView(
shouldShow,
mZenModeController.areNotificationsHiddenInShade(),
mNotifPipelineFlags.getShouldFilterUnseenNotifsOnKeyguard()
&& mSeenNotificationsProvider.getHasFilteredOutSeenNotifications());
mView.updateEmptyShadeView(shouldShow, mZenModeController.areNotificationsHiddenInShade());
Trace.endSection();
}
@@ -1942,6 +1938,9 @@ public class NotificationStackScrollLayoutController {
@Override
public void setNotifStats(@NonNull NotifStats notifStats) {
mNotifStats = notifStats;
mView.setHasFilteredOutSeenNotifications(
mNotifPipelineFlags.getShouldFilterUnseenNotifsOnKeyguard()
&& mSeenNotificationsProvider.getHasFilteredOutSeenNotifications());
updateFooter();
updateShowEmptyShadeView();
}

View File

@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.row;
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
@@ -98,5 +100,16 @@ public class FooterViewTest extends SysuiTestCase {
mView.setSecondaryVisible(true /* visible */, true /* animate */);
}
@Test
public void testSetFooterLabelTextAndIcon() {
mView.setFooterLabelTextAndIcon(
R.string.unlock_to_see_notif_text,
R.drawable.ic_friction_lock_closed);
assertThat(mView.findViewById(R.id.manage_text).getVisibility()).isEqualTo(View.GONE);
assertThat(mView.findSecondaryView().getVisibility()).isEqualTo(View.GONE);
assertThat(mView.findViewById(R.id.unlock_prompt_footer).getVisibility())
.isEqualTo(View.VISIBLE);
}
}

View File

@@ -21,6 +21,7 @@ import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
@@ -65,6 +66,7 @@ import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProviderImpl;
import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.NotifStats;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -139,6 +141,9 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
private final SeenNotificationsProviderImpl mSeenNotificationsProvider =
new SeenNotificationsProviderImpl();
private NotificationStackScrollLayoutController mController;
@Before
@@ -180,7 +185,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mUiEventLogger,
mRemoteInputManager,
mVisibilityLocationProviderDelegator,
new SeenNotificationsProviderImpl(),
mSeenNotificationsProvider,
mShadeController,
mJankMonitor,
mStackLogger,
@@ -233,16 +238,14 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
/* visible= */ true,
/* notifVisibleInShade= */ true,
/* areSeenNotifsFiltered= */false);
/* notifVisibleInShade= */ true);
setupShowEmptyShadeViewState(false);
reset(mNotificationStackScrollLayout);
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
/* visible= */ false,
/* notifVisibleInShade= */ true,
/* areSeenNotifsFiltered= */false);
/* notifVisibleInShade= */ true);
}
@Test
@@ -255,16 +258,14 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
/* visible= */ true,
/* notifVisibleInShade= */ false,
/* areSeenNotifsFiltered= */false);
/* notifVisibleInShade= */ false);
setupShowEmptyShadeViewState(false);
reset(mNotificationStackScrollLayout);
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
/* visible= */ false,
/* notifVisibleInShade= */ false,
/* areSeenNotifsFiltered= */false);
/* notifVisibleInShade= */ false);
}
@Test
@@ -283,16 +284,14 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
/* visible= */ true,
/* notifVisibleInShade= */ false,
/* areSeenNotifsFiltered= */false);
/* notifVisibleInShade= */ false);
mController.setQsFullScreen(true);
reset(mNotificationStackScrollLayout);
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
/* visible= */ true,
/* notifVisibleInShade= */ false,
/* areSeenNotifsFiltered= */false);
/* notifVisibleInShade= */ false);
}
@Test
@@ -400,6 +399,17 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
verify(mNotificationStackScrollLayout).setIsRemoteInputActive(true);
}
@Test
public void testSetNotifStats_updatesHasFilteredOutSeenNotifications() {
when(mNotifPipelineFlags.getShouldFilterUnseenNotifsOnKeyguard()).thenReturn(true);
mSeenNotificationsProvider.setHasFilteredOutSeenNotifications(true);
mController.attach(mNotificationStackScrollLayout);
mController.getNotifStackController().setNotifStats(NotifStats.getEmpty());
verify(mNotificationStackScrollLayout).setHasFilteredOutSeenNotifications(true);
verify(mNotificationStackScrollLayout).updateFooter();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(anyBoolean(), anyBoolean());
}
private LogMaker logMatcher(int category, int type) {
return argThat(new LogMatcher(category, type));
}

View File

@@ -30,6 +30,7 @@ import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.mockito.AdditionalMatchers.not;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
@@ -53,6 +54,7 @@ import android.util.MathUtils;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;
@@ -328,7 +330,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
public void updateEmptyView_dndSuppressing() {
when(mEmptyShadeView.willBeGone()).thenReturn(true);
mStackScroller.updateEmptyShadeView(true, true, false);
mStackScroller.updateEmptyShadeView(true, true);
verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
}
@@ -338,7 +340,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
mStackScroller.setEmptyShadeView(mEmptyShadeView);
when(mEmptyShadeView.willBeGone()).thenReturn(true);
mStackScroller.updateEmptyShadeView(true, false, false);
mStackScroller.updateEmptyShadeView(true, false);
verify(mEmptyShadeView).setText(R.string.empty_shade_text);
}
@@ -347,10 +349,10 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
public void updateEmptyView_noNotificationsToDndSuppressing() {
mStackScroller.setEmptyShadeView(mEmptyShadeView);
when(mEmptyShadeView.willBeGone()).thenReturn(true);
mStackScroller.updateEmptyShadeView(true, false, false);
mStackScroller.updateEmptyShadeView(true, false);
verify(mEmptyShadeView).setText(R.string.empty_shade_text);
mStackScroller.updateEmptyShadeView(true, true, false);
mStackScroller.updateEmptyShadeView(true, true);
verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
}
@@ -818,6 +820,29 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
assertEquals(0f, mAmbientState.getStackY());
}
@Test
public void hasFilteredOutSeenNotifs_updateFooter() {
mStackScroller.setCurrentUserSetup(true);
// add footer
mStackScroller.inflateFooterView();
TextView footerLabel =
mStackScroller.mFooterView.requireViewById(R.id.unlock_prompt_footer);
mStackScroller.setHasFilteredOutSeenNotifications(true);
mStackScroller.updateFooter();
assertThat(footerLabel.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
public void hasFilteredOutSeenNotifs_updateEmptyShadeView() {
mStackScroller.setHasFilteredOutSeenNotifications(true);
mStackScroller.updateEmptyShadeView(true, false);
verify(mEmptyShadeView).setFooterText(not(0));
}
private void setBarStateForTest(int state) {
// Can't inject this through the listener or we end up on the actual implementation
// rather than the mock because the spy just coppied the anonymous inner /shruggie.