Loading packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt +3 −4 Original line number Diff line number Diff line Loading @@ -591,9 +591,8 @@ class NotificationEntryAdapterTest : SysuiTestCase() { underTest = factory.create(entry) as NotificationEntryAdapter underTest.onBundleDisabled() assertThat(underTest.isMarkedForUserTriggeredMovement).isTrue() verify(kosmos.mockVisualStabilityCoordinator) .temporarilyAllowSectionChanges(eq(entry), anyLong()) .temporarilyAllowFreeMovement(eq(entry), anyLong()) } @Test Loading Loading @@ -630,8 +629,8 @@ class NotificationEntryAdapterTest : SysuiTestCase() { underTest = factory.create(summaryEntry) as NotificationEntryAdapter underTest.onBundleDisabled() verify(kosmos.mockVisualStabilityCoordinator) .temporarilyAllowSectionChanges(eq(summaryEntry), anyLong()) .temporarilyAllowFreeMovement(eq(summaryEntry), anyLong()) verify(kosmos.mockVisualStabilityCoordinator) .temporarilyAllowSectionChanges(eq(childEntry), anyLong()) .temporarilyAllowFreeMovement(eq(childEntry), anyLong()) } } packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt +1 −3 Original line number Diff line number Diff line Loading @@ -291,9 +291,7 @@ class NotificationEntryAdapter( } override fun onBundleDisabled() { markForUserTriggeredMovement(true) onImportanceChanged() visualStabilityCoordinator.temporarilyAllowFreeMovement(entry, SystemClock.uptimeMillis()) if (isGroupRoot()) { row.attachedChildren?.forEach { it.entryAdapter.onBundleDisabled() } } Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java +66 −4 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.systemui.Dumpable; Loading Loading @@ -47,6 +48,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider; import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository; import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor; import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import com.android.systemui.statusbar.notification.shared.NotificationMinimalism; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.DelayableExecutor; Loading Loading @@ -109,6 +111,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { // value: runnable that when run removes its associated RemoveOverrideSuppressionRunnable // from the DelayableExecutor's queue private Map<String, Runnable> mEntriesThatCanChangeSection = new HashMap<>(); private Map<String, Runnable> mEntriesThatCanMoveFreely = new HashMap<>(); @VisibleForTesting protected static final long ALLOW_SECTION_CHANGE_TIMEOUT = 500; Loading Loading @@ -324,10 +327,12 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { isGroupChangeAllowedForEntry = isEveryChangeAllowed() || canReorderNotificationEntry(entry) || canMoveForHeadsUp(entry); || canMoveForHeadsUp(entry) || canFreelyMoveEntry(entry); } else { isGroupChangeAllowedForEntry = mReorderingAllowed || canMoveForHeadsUp(entry); || canMoveForHeadsUp(entry) || canFreelyMoveEntry(entry); } mIsSuppressingParentChange |= !isGroupChangeAllowedForEntry; return isGroupChangeAllowedForEntry; Loading @@ -335,7 +340,8 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { @Override public boolean isParentChangeAllowed(@NonNull GroupEntry entry) { final boolean isBundleChangeAllowedForGroup = isEveryChangeAllowed(); final boolean isBundleChangeAllowedForGroup = isEveryChangeAllowed() || canFreelyMoveEntry(entry); mIsSuppressingParentChange |= !isBundleChangeAllowedForGroup; return isBundleChangeAllowedForGroup; } Loading Loading @@ -387,7 +393,9 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { } return canReorderNotificationEntry(notificationEntry) || canMoveForHeadsUp(notificationEntry); || canMoveForHeadsUp(notificationEntry) || (notificationEntry != null && canFreelyMoveEntry(notificationEntry)); } else { return mReorderingAllowed || canMoveForHeadsUp(notificationEntry); } Loading Loading @@ -497,6 +505,41 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { } } /** * Allows this notification entry to be re-ordered and re-parented in the notification list * temporarily until the timeout has passed. * * Typically this is allowed because the user has directly changed something about the * notification and we are reordering based on the user's change. * * @param entry notification entry that can move freely even if it would be otherwise suppressed * @param now current time SystemClock.elapsedRealtime */ public void temporarilyAllowFreeMovement(@NonNull NotificationEntry entry, long now) { if (NotificationBundleUi.isUnexpectedlyInLegacyMode()) { return; } final String entryKey = entry.getKey(); final Runnable existing = mEntriesThatCanMoveFreely.get(entryKey); final boolean wasAllowedToMoveFreely = existing != null; // If it exists, cancel previous timeout if (wasAllowedToMoveFreely) { existing.run(); } // Schedule & store new timeout cancellable mEntriesThatCanMoveFreely.put( entryKey, mDelayableExecutor.executeAtTime( () -> mEntriesThatCanMoveFreely.remove(entryKey), now + ALLOW_SECTION_CHANGE_TIMEOUT)); if (!wasAllowedToMoveFreely) { mNotifStabilityManager.invalidateList("temporarilyAllowFreeMovement"); } } /** * Allows this notification entry to be re-ordered in the notification list temporarily until * the timeout has passed. Loading Loading @@ -529,6 +572,25 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { } } private boolean canFreelyMoveEntry(@NonNull GroupEntry entry) { if (!NotificationBundleUi.isEnabled()) { return false; } @Nullable NotificationEntry representativeEntry = entry.getRepresentativeEntry(); if (representativeEntry == null) { return false; } return canFreelyMoveEntry(representativeEntry); } private boolean canFreelyMoveEntry(@NonNull NotificationEntry entry) { if (NotificationBundleUi.isEnabled()) { return mEntriesThatCanMoveFreely.containsKey(entry.getKey()); } else { return false; } } final StatusBarStateController.StateListener mStatusBarStateControllerListener = new StatusBarStateController.StateListener() { @Override Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BundledNotificationInfo.kt +10 −40 Original line number Diff line number Diff line Loading @@ -15,34 +15,17 @@ */ package com.android.systemui.statusbar.notification.row import android.app.INotificationManager import android.app.NotificationChannel.NEWS_ID import android.app.NotificationChannel.PROMOTIONS_ID import android.app.NotificationChannel.RECS_ID import android.app.NotificationChannel.SOCIAL_MEDIA_ID import android.content.Context import android.content.pm.PackageManager import android.os.RemoteException import android.service.notification.Adjustment import android.service.notification.NotificationListenerService import android.service.notification.StatusBarNotification import android.util.AttributeSet import android.view.View import android.view.ViewGroup import android.widget.TextView import com.android.internal.logging.MetricsLogger import com.android.internal.logging.UiEventLogger import com.android.systemui.res.R import com.android.systemui.statusbar.notification.AssistantFeedbackController import com.android.systemui.statusbar.notification.collection.EntryAdapter import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.promoted.domain.interactor.PackageDemotionInteractor import com.android.systemui.statusbar.notification.row.icon.AppIconProvider import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider import com.android.systemui.statusbar.notification.shared.NotificationBundleUi import com.google.android.material.materialswitch.MaterialSwitch /** * The guts of a notification revealed when performing a long press, specifically for notifications * that are bundled. Contains controls to allow user to disable the feature for the app that posted Loading @@ -63,20 +46,16 @@ class BundledNotificationInfo(context: Context?, attrs: AttributeSet?) : toggle.setOnCheckedChangeListener { buttonView, isChecked -> val isAChange = isChecked != enabled val done = findViewById<TextView>(R.id.done) done.setText( if (isAChange) R.string.inline_ok_button else R.string.inline_done_button ) done.setText(if (isAChange) R.string.inline_ok_button else R.string.inline_done_button) } val done = findViewById<TextView>(R.id.done) done.setOnClickListener { try { if (NotificationBundleUi.isEnabled) { mEntryAdapter.markForUserTriggeredMovement(true) mEntryAdapter.onImportanceChanged() if (enabled && !toggle.isChecked) { mEntryAdapter.onBundleDisabled() } } else { mEntry.markForUserTriggeredMovement(true) mOnUserInteractionCallback.onImportanceChanged(mEntry) Loading @@ -92,27 +71,18 @@ class BundledNotificationInfo(context: Context?, attrs: AttributeSet?) : throw RuntimeException(e) } } done.setText( if (enabled) R.string.inline_done_button else R.string.inline_ok_button ) done.setText(if (enabled) R.string.inline_done_button else R.string.inline_ok_button) done.setAccessibilityDelegate(mGutsContainer.accessibilityDelegate) val toggleWrapper = findViewById<ViewGroup>(R.id.classification_toggle) toggleWrapper.setOnClickListener { toggle.performClick() } toggleWrapper.setOnClickListener { toggle.performClick() } findViewById<TextView>(R.id.feature_summary).setText( resources.getString(R.string.notification_guts_bundle_summary, mAppName)); findViewById<TextView>(R.id.feature_summary) .setText(resources.getString(R.string.notification_guts_bundle_summary, mAppName)) val dismissButton = findViewById<View>(R.id.inline_dismiss) dismissButton.setOnClickListener(mOnCloseClickListener) dismissButton.visibility = if (dismissButton.hasOnClickListeners() && mIsDismissable) VISIBLE else GONE dismissButton.visibility = if (dismissButton.hasOnClickListeners() && mIsDismissable) VISIBLE else GONE } companion object { Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt +3 −4 Original line number Diff line number Diff line Loading @@ -591,9 +591,8 @@ class NotificationEntryAdapterTest : SysuiTestCase() { underTest = factory.create(entry) as NotificationEntryAdapter underTest.onBundleDisabled() assertThat(underTest.isMarkedForUserTriggeredMovement).isTrue() verify(kosmos.mockVisualStabilityCoordinator) .temporarilyAllowSectionChanges(eq(entry), anyLong()) .temporarilyAllowFreeMovement(eq(entry), anyLong()) } @Test Loading Loading @@ -630,8 +629,8 @@ class NotificationEntryAdapterTest : SysuiTestCase() { underTest = factory.create(summaryEntry) as NotificationEntryAdapter underTest.onBundleDisabled() verify(kosmos.mockVisualStabilityCoordinator) .temporarilyAllowSectionChanges(eq(summaryEntry), anyLong()) .temporarilyAllowFreeMovement(eq(summaryEntry), anyLong()) verify(kosmos.mockVisualStabilityCoordinator) .temporarilyAllowSectionChanges(eq(childEntry), anyLong()) .temporarilyAllowFreeMovement(eq(childEntry), anyLong()) } }
packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt +1 −3 Original line number Diff line number Diff line Loading @@ -291,9 +291,7 @@ class NotificationEntryAdapter( } override fun onBundleDisabled() { markForUserTriggeredMovement(true) onImportanceChanged() visualStabilityCoordinator.temporarilyAllowFreeMovement(entry, SystemClock.uptimeMillis()) if (isGroupRoot()) { row.attachedChildren?.forEach { it.entryAdapter.onBundleDisabled() } } Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java +66 −4 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.systemui.Dumpable; Loading Loading @@ -47,6 +48,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider; import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository; import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor; import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import com.android.systemui.statusbar.notification.shared.NotificationMinimalism; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.DelayableExecutor; Loading Loading @@ -109,6 +111,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { // value: runnable that when run removes its associated RemoveOverrideSuppressionRunnable // from the DelayableExecutor's queue private Map<String, Runnable> mEntriesThatCanChangeSection = new HashMap<>(); private Map<String, Runnable> mEntriesThatCanMoveFreely = new HashMap<>(); @VisibleForTesting protected static final long ALLOW_SECTION_CHANGE_TIMEOUT = 500; Loading Loading @@ -324,10 +327,12 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { isGroupChangeAllowedForEntry = isEveryChangeAllowed() || canReorderNotificationEntry(entry) || canMoveForHeadsUp(entry); || canMoveForHeadsUp(entry) || canFreelyMoveEntry(entry); } else { isGroupChangeAllowedForEntry = mReorderingAllowed || canMoveForHeadsUp(entry); || canMoveForHeadsUp(entry) || canFreelyMoveEntry(entry); } mIsSuppressingParentChange |= !isGroupChangeAllowedForEntry; return isGroupChangeAllowedForEntry; Loading @@ -335,7 +340,8 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { @Override public boolean isParentChangeAllowed(@NonNull GroupEntry entry) { final boolean isBundleChangeAllowedForGroup = isEveryChangeAllowed(); final boolean isBundleChangeAllowedForGroup = isEveryChangeAllowed() || canFreelyMoveEntry(entry); mIsSuppressingParentChange |= !isBundleChangeAllowedForGroup; return isBundleChangeAllowedForGroup; } Loading Loading @@ -387,7 +393,9 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { } return canReorderNotificationEntry(notificationEntry) || canMoveForHeadsUp(notificationEntry); || canMoveForHeadsUp(notificationEntry) || (notificationEntry != null && canFreelyMoveEntry(notificationEntry)); } else { return mReorderingAllowed || canMoveForHeadsUp(notificationEntry); } Loading Loading @@ -497,6 +505,41 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { } } /** * Allows this notification entry to be re-ordered and re-parented in the notification list * temporarily until the timeout has passed. * * Typically this is allowed because the user has directly changed something about the * notification and we are reordering based on the user's change. * * @param entry notification entry that can move freely even if it would be otherwise suppressed * @param now current time SystemClock.elapsedRealtime */ public void temporarilyAllowFreeMovement(@NonNull NotificationEntry entry, long now) { if (NotificationBundleUi.isUnexpectedlyInLegacyMode()) { return; } final String entryKey = entry.getKey(); final Runnable existing = mEntriesThatCanMoveFreely.get(entryKey); final boolean wasAllowedToMoveFreely = existing != null; // If it exists, cancel previous timeout if (wasAllowedToMoveFreely) { existing.run(); } // Schedule & store new timeout cancellable mEntriesThatCanMoveFreely.put( entryKey, mDelayableExecutor.executeAtTime( () -> mEntriesThatCanMoveFreely.remove(entryKey), now + ALLOW_SECTION_CHANGE_TIMEOUT)); if (!wasAllowedToMoveFreely) { mNotifStabilityManager.invalidateList("temporarilyAllowFreeMovement"); } } /** * Allows this notification entry to be re-ordered in the notification list temporarily until * the timeout has passed. Loading Loading @@ -529,6 +572,25 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { } } private boolean canFreelyMoveEntry(@NonNull GroupEntry entry) { if (!NotificationBundleUi.isEnabled()) { return false; } @Nullable NotificationEntry representativeEntry = entry.getRepresentativeEntry(); if (representativeEntry == null) { return false; } return canFreelyMoveEntry(representativeEntry); } private boolean canFreelyMoveEntry(@NonNull NotificationEntry entry) { if (NotificationBundleUi.isEnabled()) { return mEntriesThatCanMoveFreely.containsKey(entry.getKey()); } else { return false; } } final StatusBarStateController.StateListener mStatusBarStateControllerListener = new StatusBarStateController.StateListener() { @Override Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BundledNotificationInfo.kt +10 −40 Original line number Diff line number Diff line Loading @@ -15,34 +15,17 @@ */ package com.android.systemui.statusbar.notification.row import android.app.INotificationManager import android.app.NotificationChannel.NEWS_ID import android.app.NotificationChannel.PROMOTIONS_ID import android.app.NotificationChannel.RECS_ID import android.app.NotificationChannel.SOCIAL_MEDIA_ID import android.content.Context import android.content.pm.PackageManager import android.os.RemoteException import android.service.notification.Adjustment import android.service.notification.NotificationListenerService import android.service.notification.StatusBarNotification import android.util.AttributeSet import android.view.View import android.view.ViewGroup import android.widget.TextView import com.android.internal.logging.MetricsLogger import com.android.internal.logging.UiEventLogger import com.android.systemui.res.R import com.android.systemui.statusbar.notification.AssistantFeedbackController import com.android.systemui.statusbar.notification.collection.EntryAdapter import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.promoted.domain.interactor.PackageDemotionInteractor import com.android.systemui.statusbar.notification.row.icon.AppIconProvider import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider import com.android.systemui.statusbar.notification.shared.NotificationBundleUi import com.google.android.material.materialswitch.MaterialSwitch /** * The guts of a notification revealed when performing a long press, specifically for notifications * that are bundled. Contains controls to allow user to disable the feature for the app that posted Loading @@ -63,20 +46,16 @@ class BundledNotificationInfo(context: Context?, attrs: AttributeSet?) : toggle.setOnCheckedChangeListener { buttonView, isChecked -> val isAChange = isChecked != enabled val done = findViewById<TextView>(R.id.done) done.setText( if (isAChange) R.string.inline_ok_button else R.string.inline_done_button ) done.setText(if (isAChange) R.string.inline_ok_button else R.string.inline_done_button) } val done = findViewById<TextView>(R.id.done) done.setOnClickListener { try { if (NotificationBundleUi.isEnabled) { mEntryAdapter.markForUserTriggeredMovement(true) mEntryAdapter.onImportanceChanged() if (enabled && !toggle.isChecked) { mEntryAdapter.onBundleDisabled() } } else { mEntry.markForUserTriggeredMovement(true) mOnUserInteractionCallback.onImportanceChanged(mEntry) Loading @@ -92,27 +71,18 @@ class BundledNotificationInfo(context: Context?, attrs: AttributeSet?) : throw RuntimeException(e) } } done.setText( if (enabled) R.string.inline_done_button else R.string.inline_ok_button ) done.setText(if (enabled) R.string.inline_done_button else R.string.inline_ok_button) done.setAccessibilityDelegate(mGutsContainer.accessibilityDelegate) val toggleWrapper = findViewById<ViewGroup>(R.id.classification_toggle) toggleWrapper.setOnClickListener { toggle.performClick() } toggleWrapper.setOnClickListener { toggle.performClick() } findViewById<TextView>(R.id.feature_summary).setText( resources.getString(R.string.notification_guts_bundle_summary, mAppName)); findViewById<TextView>(R.id.feature_summary) .setText(resources.getString(R.string.notification_guts_bundle_summary, mAppName)) val dismissButton = findViewById<View>(R.id.inline_dismiss) dismissButton.setOnClickListener(mOnCloseClickListener) dismissButton.visibility = if (dismissButton.hasOnClickListeners() && mIsDismissable) VISIBLE else GONE dismissButton.visibility = if (dismissButton.hasOnClickListeners() && mIsDismissable) VISIBLE else GONE } companion object { Loading