summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Julia Tuttle <juliatuttle@google.com> 2023-04-06 20:48:29 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2023-04-06 20:48:29 +0000
commitfb67b8afafb40a3ed1493ebfb115efb315435e15 (patch)
treed5a1c334a341362d063ee37a92917a709b2715ec
parent0b097466e5ee4b2cdd4c084ade3651e517f7a9bb (diff)
parent04477663e60fd57b61851fd8126439256649e2c1 (diff)
Merge changes from topic "interruptions-refactor-0" into udc-dev
* changes: Add VisualInterruptionDecisionProvider to SystemUIModule Wrap NotificationInterruptStateProvider Add VisualInterruptionDecisionProvider interface
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt75
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt112
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt78
4 files changed, 279 insertions, 5 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 63a4fd2189d8..7945470b424a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -85,6 +85,8 @@ import com.android.systemui.statusbar.notification.collection.inflation.Notifica
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
import com.android.systemui.statusbar.notification.people.PeopleHubModule;
import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
@@ -116,16 +118,16 @@ import com.android.systemui.wallet.dagger.WalletModule;
import com.android.systemui.wmshell.BubblesManager;
import com.android.wm.shell.bubbles.Bubbles;
-import java.util.Optional;
-import java.util.concurrent.Executor;
-
-import javax.inject.Named;
-
import dagger.Binds;
import dagger.BindsOptionalOf;
import dagger.Module;
import dagger.Provides;
+import java.util.Optional;
+import java.util.concurrent.Executor;
+
+import javax.inject.Named;
+
/**
* A dagger module for injecting components of System UI that are required by System UI.
*
@@ -315,4 +317,11 @@ public abstract class SystemUIModule {
@Binds
abstract LargeScreenShadeInterpolator largeScreensShadeInterpolator(
LargeScreenShadeInterpolatorImpl impl);
+
+ @SysUISingleton
+ @Provides
+ static VisualInterruptionDecisionProvider provideVisualInterruptionDecisionProvider(
+ NotificationInterruptStateProvider innerProvider) {
+ return new NotificationInterruptStateProviderWrapper(innerProvider);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt
new file mode 100644
index 000000000000..f2216fce6fef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 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.notification.interruption
+
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.Decision
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.FullScreenIntentDecision
+
+/**
+ * Wraps a [NotificationInterruptStateProvider] to convert it to the new
+ * [VisualInterruptionDecisionProvider] interface.
+ */
+@SysUISingleton
+class NotificationInterruptStateProviderWrapper(
+ private val wrapped: NotificationInterruptStateProvider
+) : VisualInterruptionDecisionProvider {
+
+ @VisibleForTesting
+ enum class DecisionImpl(override val shouldInterrupt: Boolean) : Decision {
+ SHOULD_INTERRUPT(shouldInterrupt = true),
+ SHOULD_NOT_INTERRUPT(shouldInterrupt = false);
+
+ companion object {
+ fun of(booleanDecision: Boolean) =
+ if (booleanDecision) SHOULD_INTERRUPT else SHOULD_NOT_INTERRUPT
+ }
+ }
+
+ @VisibleForTesting
+ class FullScreenIntentDecisionImpl(
+ val originalEntry: NotificationEntry,
+ val originalDecision: NotificationInterruptStateProvider.FullScreenIntentDecision
+ ) : FullScreenIntentDecision {
+ override val shouldInterrupt = originalDecision.shouldLaunch
+ override val wouldInterruptWithoutDnd = originalDecision == NO_FSI_SUPPRESSED_ONLY_BY_DND
+ }
+
+ override fun addSuppressor(suppressor: NotificationInterruptSuppressor) {
+ wrapped.addSuppressor(suppressor)
+ }
+
+ override fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision =
+ wrapped.checkHeadsUp(entry, /* log= */ false).let { DecisionImpl.of(it) }
+
+ override fun makeAndLogHeadsUpDecision(entry: NotificationEntry): Decision =
+ wrapped.checkHeadsUp(entry, /* log= */ true).let { DecisionImpl.of(it) }
+
+ override fun makeUnloggedFullScreenIntentDecision(entry: NotificationEntry) =
+ wrapped.getFullScreenIntentDecision(entry).let { FullScreenIntentDecisionImpl(entry, it) }
+
+ override fun logFullScreenIntentDecision(decision: FullScreenIntentDecision) {
+ (decision as FullScreenIntentDecisionImpl).let {
+ wrapped.logFullScreenIntentDecision(it.originalEntry, it.originalDecision)
+ }
+ }
+
+ override fun makeAndLogBubbleDecision(entry: NotificationEntry): Decision =
+ wrapped.shouldBubbleUp(entry).let { DecisionImpl.of(it) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
new file mode 100644
index 000000000000..c0f4fcda56bb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2023 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.notification.interruption
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+
+/**
+ * Decides whether a notification should visually interrupt the user in various ways.
+ *
+ * These include displaying the notification as heads-up (peeking while the device is awake or
+ * pulsing while the device is dozing), displaying the notification as a bubble, and launching a
+ * full-screen intent for the notification.
+ */
+interface VisualInterruptionDecisionProvider {
+ /**
+ * Represents the decision to visually interrupt or not.
+ *
+ * Used for heads-up and bubble decisions; subclassed by [FullScreenIntentDecision] for
+ * full-screen intent decisions.
+ *
+ * @property[shouldInterrupt] whether a visual interruption should be triggered
+ */
+ interface Decision {
+ val shouldInterrupt: Boolean
+ }
+
+ /**
+ * Represents the decision to launch a full-screen intent for a notification or not.
+ *
+ * @property[wouldInterruptWithoutDnd] whether a full-screen intent should not be launched only
+ * because Do Not Disturb has suppressed it
+ */
+ interface FullScreenIntentDecision : Decision {
+ val wouldInterruptWithoutDnd: Boolean
+ }
+
+ /**
+ * Adds a [component][suppressor] that can suppress visual interruptions.
+ *
+ * This class may call suppressors in any order.
+ *
+ * @param[suppressor] the suppressor to add
+ */
+ fun addSuppressor(suppressor: NotificationInterruptSuppressor)
+
+ /**
+ * Decides whether a [notification][entry] should display as heads-up or not, but does not log
+ * that decision.
+ *
+ * @param[entry] the notification that this decision is about
+ * @return the decision to display that notification as heads-up or not
+ */
+ fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision
+
+ /**
+ * Decides whether a [notification][entry] should display as heads-up or not, and logs that
+ * decision.
+ *
+ * If the device is awake, the decision will consider whether the notification should "peek"
+ * (slide in from the top of the screen over the current activity).
+ *
+ * If the device is dozing, the decision will consider whether the notification should "pulse"
+ * (wake the screen up and display the ambient view of the notification).
+ *
+ * @see[makeUnloggedHeadsUpDecision]
+ *
+ * @param[entry] the notification that this decision is about
+ * @return the decision to display that notification as heads-up or not
+ */
+ fun makeAndLogHeadsUpDecision(entry: NotificationEntry): Decision
+
+ /**
+ * Decides whether a [notification][entry] should launch a full-screen intent or not, but does
+ * not log that decision.
+ *
+ * The returned decision can be logged by passing it to [logFullScreenIntentDecision].
+ *
+ * @see[makeAndLogHeadsUpDecision]
+ *
+ * @param[entry] the notification that this decision is about
+ * @return the decision to launch a full-screen intent for that notification or not
+ */
+ fun makeUnloggedFullScreenIntentDecision(entry: NotificationEntry): FullScreenIntentDecision
+
+ /**
+ * Logs a previous [decision] to launch a full-screen intent or not.
+ *
+ * @param[decision] the decision to log
+ */
+ fun logFullScreenIntentDecision(decision: FullScreenIntentDecision)
+
+ /**
+ * Decides whether a [notification][entry] should display as a bubble or not.
+ *
+ * @param[entry] the notification that this decision is about
+ * @return the decision to display that notification as a bubble or not
+ */
+ fun makeAndLogBubbleDecision(entry: NotificationEntry): Decision
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
new file mode 100644
index 000000000000..cbb08946a1b0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
@@ -0,0 +1,78 @@
+package com.android.systemui.statusbar.notification.interruption
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.NO_FSI_NOT_IMPORTANT_ENOUGH
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.NO_FSI_SUPPRESSED_BY_DND
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.DecisionImpl
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.FullScreenIntentDecisionImpl
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NotificationInterruptStateProviderWrapperTest : SysuiTestCase() {
+
+ @Test
+ fun decisionOfTrue() {
+ assertTrue(DecisionImpl.of(true).shouldInterrupt)
+ }
+
+ @Test
+ fun decisionOfFalse() {
+ assertFalse(DecisionImpl.of(false).shouldInterrupt)
+ }
+
+ @Test
+ fun decisionOfTrueInterned() {
+ assertEquals(DecisionImpl.of(true), DecisionImpl.of(true))
+ }
+
+ @Test
+ fun decisionOfFalseInterned() {
+ assertEquals(DecisionImpl.of(false), DecisionImpl.of(false))
+ }
+
+ @Test
+ fun fullScreenIntentDecisionShouldInterrupt() {
+ makeFsiDecision(FSI_DEVICE_NOT_INTERACTIVE).let {
+ assertTrue(it.shouldInterrupt)
+ assertFalse(it.wouldInterruptWithoutDnd)
+ }
+ }
+
+ @Test
+ fun fullScreenIntentDecisionShouldNotInterrupt() {
+ makeFsiDecision(NO_FSI_NOT_IMPORTANT_ENOUGH).let {
+ assertFalse(it.shouldInterrupt)
+ assertFalse(it.wouldInterruptWithoutDnd)
+ }
+ }
+
+ @Test
+ fun fullScreenIntentDecisionWouldInterruptWithoutDnd() {
+ makeFsiDecision(NO_FSI_SUPPRESSED_ONLY_BY_DND).let {
+ assertFalse(it.shouldInterrupt)
+ assertTrue(it.wouldInterruptWithoutDnd)
+ }
+ }
+
+ @Test
+ fun fullScreenIntentDecisionWouldNotInterruptEvenWithoutDnd() {
+ makeFsiDecision(NO_FSI_SUPPRESSED_BY_DND).let {
+ assertFalse(it.shouldInterrupt)
+ assertFalse(it.wouldInterruptWithoutDnd)
+ }
+ }
+
+ private fun makeFsiDecision(originalDecision: FullScreenIntentDecision) =
+ FullScreenIntentDecisionImpl(NotificationEntryBuilder().build(), originalDecision)
+}