From 634d0eee2fa3b12caf74e546a19a45306f360e89 Mon Sep 17 00:00:00 2001 From: Caitlin Cassidy Date: Thu, 10 Feb 2022 21:25:42 +0000 Subject: [PATCH] [Media TTT] Timeout the chip after 3 seconds. (Also updates the media dagger module to use Lazys so we don't have to update the module each time a constructor changes.) Bug: 214274529 Bug: 211487971 Test: manual: verified chip disappears after 3 seconds Test: atest MediaTttChipControllerCommonTest Change-Id: Ib9425d93e28aa5f6b9071ac10e864114363283f4 --- .../systemui/media/dagger/MediaModule.java | 35 +++------ .../common/MediaTttChipControllerCommon.kt | 14 +++- .../MediaTttChipControllerReceiver.kt | 6 +- .../sender/MediaTttChipControllerSender.kt | 5 +- .../MediaTttChipControllerCommonTest.kt | 71 +++++++++++++++++-- .../MediaTttChipControllerReceiverTest.kt | 9 ++- .../MediaTttChipControllerSenderTest.kt | 6 +- 7 files changed, 108 insertions(+), 38 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java index 2dff9472be688..c3b4354ebabe3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java +++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java @@ -16,12 +16,7 @@ package com.android.systemui.media.dagger; -import android.content.Context; -import android.os.Handler; -import android.view.WindowManager; - import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.media.MediaDataManager; import com.android.systemui.media.MediaFlags; import com.android.systemui.media.MediaHierarchyManager; @@ -34,11 +29,8 @@ import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper; import com.android.systemui.media.taptotransfer.MediaTttFlags; import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver; import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender; -import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.commandline.CommandRegistry; import java.util.Optional; -import java.util.concurrent.Executor; import javax.inject.Named; @@ -101,13 +93,11 @@ public interface MediaModule { @SysUISingleton static Optional providesMediaTttChipControllerSender( MediaTttFlags mediaTttFlags, - CommandQueue commandQueue, - Context context, - WindowManager windowManager) { + Lazy controllerSenderLazy) { if (!mediaTttFlags.isMediaTttEnabled()) { return Optional.empty(); } - return Optional.of(new MediaTttChipControllerSender(commandQueue, context, windowManager)); + return Optional.of(controllerSenderLazy.get()); } /** */ @@ -115,16 +105,11 @@ public interface MediaModule { @SysUISingleton static Optional providesMediaTttChipControllerReceiver( MediaTttFlags mediaTttFlags, - CommandQueue commandQueue, - Context context, - WindowManager windowManager, - @Main Handler mainHandler) { + Lazy controllerReceiverLazy) { if (!mediaTttFlags.isMediaTttEnabled()) { return Optional.empty(); } - return Optional.of( - new MediaTttChipControllerReceiver( - commandQueue, context, windowManager, mainHandler)); + return Optional.of(controllerReceiverLazy.get()); } /** */ @@ -132,14 +117,11 @@ public interface MediaModule { @SysUISingleton static Optional providesMediaTttCommandLineHelper( MediaTttFlags mediaTttFlags, - CommandRegistry commandRegistry, - Context context, - @Main Executor mainExecutor) { + Lazy helperLazy) { if (!mediaTttFlags.isMediaTttEnabled()) { return Optional.empty(); } - return Optional.of( - new MediaTttCommandLineHelper(commandRegistry, context, mainExecutor)); + return Optional.of(helperLazy.get()); } /** */ @@ -147,13 +129,12 @@ public interface MediaModule { @SysUISingleton static Optional providesMediaMuteAwaitConnectionCli( MediaFlags mediaFlags, - CommandRegistry commandRegistry, - Context context + Lazy muteAwaitConnectionCliLazy ) { if (!mediaFlags.areMuteAwaitConnectionsEnabled()) { return Optional.empty(); } - return Optional.of(new MediaMuteAwaitConnectionCli(commandRegistry, context)); + return Optional.of(muteAwaitConnectionCliLazy.get()); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt index 4993105fa681d..ee2fba09358b8 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt @@ -25,8 +25,11 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.WindowManager +import androidx.annotation.VisibleForTesting import com.android.internal.widget.CachingIconView import com.android.systemui.R +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.util.concurrency.DelayableExecutor /** * A superclass controller that provides common functionality for showing chips on the sender device @@ -38,6 +41,7 @@ import com.android.systemui.R abstract class MediaTttChipControllerCommon( internal val context: Context, private val windowManager: WindowManager, + @Main private val mainExecutor: DelayableExecutor, @LayoutRes private val chipLayoutRes: Int ) { /** The window layout parameters we'll use when attaching the view to a window. */ @@ -56,6 +60,9 @@ abstract class MediaTttChipControllerCommon( /** The chip view currently being displayed. Null if the chip is not being displayed. */ var chipView: ViewGroup? = null + /** A [Runnable] that, when run, will cancel the pending timeout of the chip. */ + var cancelChipViewTimeout: Runnable? = null + /** * Displays the chip with the current state. * @@ -77,8 +84,11 @@ abstract class MediaTttChipControllerCommon( if (oldChipView == null) { windowManager.addView(chipView, windowLayoutParams) } - } + // Cancel and re-set the chip timeout each time we get a new state. + cancelChipViewTimeout?.run() + cancelChipViewTimeout = mainExecutor.executeDelayed(this::removeChip, TIMEOUT_MILLIS) + } /** Hides the chip. */ fun removeChip() { @@ -118,3 +128,5 @@ abstract class MediaTttChipControllerCommon( // Used in CTS tests UpdateMediaTapToTransferSenderDisplayTest and // UpdateMediaTapToTransferReceiverDisplayTest private const val WINDOW_TITLE = "Media Transfer Chip View" +@VisibleForTesting +const val TIMEOUT_MILLIS = 3000L diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt index 18623c0686fde..214a8888e0b5b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt @@ -18,7 +18,6 @@ package com.android.systemui.media.taptotransfer.receiver import android.app.StatusBarManager import android.content.Context -import android.graphics.drawable.Drawable import android.graphics.drawable.Icon import android.media.MediaRoute2Info import android.os.Handler @@ -27,10 +26,10 @@ import android.view.ViewGroup import android.view.WindowManager import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.util.concurrency.DelayableExecutor import javax.inject.Inject /** @@ -43,9 +42,10 @@ class MediaTttChipControllerReceiver @Inject constructor( commandQueue: CommandQueue, context: Context, windowManager: WindowManager, + mainExecutor: DelayableExecutor, @Main private val mainHandler: Handler, ) : MediaTttChipControllerCommon( - context, windowManager, R.layout.media_ttt_chip_receiver + context, windowManager, mainExecutor, R.layout.media_ttt_chip_receiver ) { private val commandQueueCallbacks = object : CommandQueue.Callbacks { override fun updateMediaTapToTransferReceiverDisplay( diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt index da767ea900557..482e604a06351 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt @@ -27,8 +27,10 @@ import android.widget.TextView import com.android.internal.statusbar.IUndoMediaTransferCallback import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.util.concurrency.DelayableExecutor import javax.inject.Inject /** @@ -40,8 +42,9 @@ class MediaTttChipControllerSender @Inject constructor( commandQueue: CommandQueue, context: Context, windowManager: WindowManager, + @Main private val mainExecutor: DelayableExecutor, ) : MediaTttChipControllerCommon( - context, windowManager, R.layout.media_ttt_chip + context, windowManager, mainExecutor, R.layout.media_ttt_chip ) { private val commandQueueCallbacks = object : CommandQueue.Callbacks { override fun updateMediaTapToTransferSenderDisplay( diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt index 9da405c56e2dc..ea0a5a42ad211 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt @@ -25,7 +25,11 @@ import android.widget.ImageView import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -40,6 +44,9 @@ import org.mockito.MockitoAnnotations class MediaTttChipControllerCommonTest : SysuiTestCase() { private lateinit var controllerCommon: MediaTttChipControllerCommon + private lateinit var fakeClock: FakeSystemClock + private lateinit var fakeExecutor: FakeExecutor + private lateinit var appIconDrawable: Drawable @Mock private lateinit var windowManager: WindowManager @@ -48,7 +55,10 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) appIconDrawable = context.getDrawable(R.drawable.ic_cake)!! - controllerCommon = TestControllerCommon(context, windowManager) + fakeClock = FakeSystemClock() + fakeExecutor = FakeExecutor(fakeClock) + + controllerCommon = TestControllerCommon(context, windowManager, fakeExecutor) } @Test @@ -67,6 +77,58 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { verify(windowManager, never()).addView(any(), any()) } + @Test + fun displayChip_chipDoesNotDisappearsBeforeTimeout() { + controllerCommon.displayChip(getState()) + reset(windowManager) + + fakeClock.advanceTime(TIMEOUT_MILLIS - 1) + + verify(windowManager, never()).removeView(any()) + } + + @Test + fun displayChip_chipDisappearsAfterTimeout() { + controllerCommon.displayChip(getState()) + reset(windowManager) + + fakeClock.advanceTime(TIMEOUT_MILLIS + 1) + + verify(windowManager).removeView(any()) + } + + @Test + fun displayChip_calledAgainBeforeTimeout_timeoutReset() { + // First, display the chip + controllerCommon.displayChip(getState()) + + // After some time, re-display the chip + val waitTime = 1000L + fakeClock.advanceTime(waitTime) + controllerCommon.displayChip(getState()) + + // Wait until the timeout for the first display would've happened + fakeClock.advanceTime(TIMEOUT_MILLIS - waitTime + 1) + + // Verify we didn't hide the chip + verify(windowManager, never()).removeView(any()) + } + + @Test + fun displayChip_calledAgainBeforeTimeout_eventuallyTimesOut() { + // First, display the chip + controllerCommon.displayChip(getState()) + + // After some time, re-display the chip + fakeClock.advanceTime(1000L) + controllerCommon.displayChip(getState()) + + // Ensure we still hide the chip eventually + fakeClock.advanceTime(TIMEOUT_MILLIS + 1) + + verify(windowManager).removeView(any()) + } + @Test fun removeChip_chipRemoved() { // First, add the chip @@ -110,9 +172,10 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { inner class TestControllerCommon( context: Context, - windowManager: WindowManager - ) : MediaTttChipControllerCommon( - context, windowManager, R.layout.media_ttt_chip + windowManager: WindowManager, + @Main mainExecutor: DelayableExecutor, + ) : MediaTttChipControllerCommon( + context, windowManager, mainExecutor, R.layout.media_ttt_chip ) { override fun updateChipView(chipState: MediaTttChipState, currentChipView: ViewGroup) { } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt index 4cbf749d20320..117a6c8985108 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt @@ -30,8 +30,10 @@ import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -70,7 +72,12 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { context.setMockPackageManager(packageManager) controllerReceiver = MediaTttChipControllerReceiver( - commandQueue, context, windowManager, Handler.getMain()) + commandQueue, + context, + windowManager, + FakeExecutor(FakeSystemClock()), + Handler.getMain() + ) val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) verify(commandQueue).addCallback(callbackCaptor.capture()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt index 7cfea2455d24a..b440064298281 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt @@ -31,8 +31,10 @@ import com.android.internal.statusbar.IUndoMediaTransferCallback import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -70,7 +72,9 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { )).thenReturn(applicationInfo) context.setMockPackageManager(packageManager) - controllerSender = MediaTttChipControllerSender(commandQueue, context, windowManager) + controllerSender = MediaTttChipControllerSender( + commandQueue, context, windowManager, FakeExecutor(FakeSystemClock()) + ) val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) verify(commandQueue).addCallback(callbackCaptor.capture())