Merge changes from topic "interruptions-refactor-0" into udc-dev
* changes: Add VisualInterruptionDecisionProvider to SystemUIModule Wrap NotificationInterruptStateProvider Add VisualInterruptionDecisionProvider interface
This commit is contained in:
@@ -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.notifcollection.CommonNotifCollection;
|
||||||
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
|
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
|
||||||
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
|
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.people.PeopleHubModule;
|
||||||
import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
|
import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
|
||||||
import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
|
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.systemui.wmshell.BubblesManager;
|
||||||
import com.android.wm.shell.bubbles.Bubbles;
|
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.Binds;
|
||||||
import dagger.BindsOptionalOf;
|
import dagger.BindsOptionalOf;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
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.
|
* A dagger module for injecting components of System UI that are required by System UI.
|
||||||
*
|
*
|
||||||
@@ -315,4 +317,11 @@ public abstract class SystemUIModule {
|
|||||||
@Binds
|
@Binds
|
||||||
abstract LargeScreenShadeInterpolator largeScreensShadeInterpolator(
|
abstract LargeScreenShadeInterpolator largeScreensShadeInterpolator(
|
||||||
LargeScreenShadeInterpolatorImpl impl);
|
LargeScreenShadeInterpolatorImpl impl);
|
||||||
|
|
||||||
|
@SysUISingleton
|
||||||
|
@Provides
|
||||||
|
static VisualInterruptionDecisionProvider provideVisualInterruptionDecisionProvider(
|
||||||
|
NotificationInterruptStateProvider innerProvider) {
|
||||||
|
return new NotificationInterruptStateProviderWrapper(innerProvider);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) }
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user