diff options
6 files changed, 174 insertions, 99 deletions
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 c9fce794f57f..79e1fb9a60f6 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 @@ -33,11 +33,13 @@ import android.view.accessibility.AccessibilityManager import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT +import androidx.annotation.CallSuper import com.android.internal.widget.CachingIconView import com.android.settingslib.Utils import com.android.systemui.R import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.statusbar.gesture.TapGestureDetector +import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.view.ViewUtil @@ -52,17 +54,17 @@ import com.android.systemui.util.view.ViewUtil * display the chip in a certain state, since they receive <T> in [updateChipView]. */ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>( - internal val context: Context, - internal val logger: MediaTttLogger, - internal val windowManager: WindowManager, - private val viewUtil: ViewUtil, - @Main private val mainExecutor: DelayableExecutor, - private val accessibilityManager: AccessibilityManager, - private val tapGestureDetector: TapGestureDetector, - private val powerManager: PowerManager, - @LayoutRes private val chipLayoutRes: Int + internal val context: Context, + internal val logger: MediaTttLogger, + internal val windowManager: WindowManager, + private val viewUtil: ViewUtil, + @Main private val mainExecutor: DelayableExecutor, + private val accessibilityManager: AccessibilityManager, + private val configurationController: ConfigurationController, + private val tapGestureDetector: TapGestureDetector, + private val powerManager: PowerManager, + @LayoutRes private val chipLayoutRes: Int, ) { - /** * Window layout params that will be used as a starting point for the [windowLayoutParams] of * all subclasses. @@ -89,42 +91,40 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>( /** The chip view currently being displayed. Null if the chip is not being displayed. */ private var chipView: ViewGroup? = null + /** The chip info currently being displayed. Null if the chip is not being displayed. */ + internal var chipInfo: T? = null + /** A [Runnable] that, when run, will cancel the pending timeout of the chip. */ private var cancelChipViewTimeout: Runnable? = null /** - * Displays the chip with the current state. + * Displays the chip with the provided [newChipInfo]. * * This method handles inflating and attaching the view, then delegates to [updateChipView] to * display the correct information in the chip. */ - fun displayChip(chipInfo: T) { - val oldChipView = chipView - if (chipView == null) { - chipView = LayoutInflater - .from(context) - .inflate(chipLayoutRes, null) as ViewGroup - } - val currentChipView = chipView!! + fun displayChip(newChipInfo: T) { + val currentChipView = chipView - updateChipView(chipInfo, currentChipView) - - // Add view if necessary - if (oldChipView == null) { + if (currentChipView != null) { + updateChipView(newChipInfo, currentChipView) + } else { + // The chip is new, so set up all our callbacks and inflate the view + configurationController.addCallback(displayScaleListener) tapGestureDetector.addOnGestureDetectedCallback(TAG, this::onScreenTapped) - windowManager.addView(chipView, windowLayoutParams) // Wake the screen so the user will see the chip powerManager.wakeUp( SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_APPLICATION, "com.android.systemui:media_tap_to_transfer_activated" ) - animateChipIn(currentChipView) + + inflateAndUpdateChip(newChipInfo) } // Cancel and re-set the chip timeout each time we get a new state. val timeout = accessibilityManager.getRecommendedTimeoutMillis( - chipInfo.getTimeoutMs().toInt(), + newChipInfo.getTimeoutMs().toInt(), // Not all chips have controls so FLAG_CONTENT_CONTROLS might be superfluous, but // include it just to be safe. FLAG_CONTENT_ICONS or FLAG_CONTENT_TEXT or FLAG_CONTENT_CONTROLS @@ -136,6 +136,32 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>( ) } + /** Inflates a new chip view, updates it with [newChipInfo], and adds the view to the window. */ + private fun inflateAndUpdateChip(newChipInfo: T) { + val newChipView = LayoutInflater + .from(context) + .inflate(chipLayoutRes, null) as ViewGroup + chipView = newChipView + updateChipView(newChipInfo, newChipView) + windowManager.addView(newChipView, windowLayoutParams) + animateChipIn(newChipView) + } + + /** Removes then re-inflates the chip. */ + private fun reinflateChip() { + val currentChipInfo = chipInfo + if (chipView == null || currentChipInfo == null) { return } + + windowManager.removeView(chipView) + inflateAndUpdateChip(currentChipInfo) + } + + private val displayScaleListener = object : ConfigurationController.ConfigurationListener { + override fun onDensityOrFontScaleChanged() { + reinflateChip() + } + } + /** * Hides the chip. * @@ -145,17 +171,22 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>( open fun removeChip(removalReason: String) { if (chipView == null) { return } logger.logChipRemoval(removalReason) + configurationController.removeCallback(displayScaleListener) tapGestureDetector.removeOnGestureDetectedCallback(TAG) windowManager.removeView(chipView) chipView = null + chipInfo = null // No need to time the chip out since it's already gone cancelChipViewTimeout?.run() } /** - * A method implemented by subclasses to update [currentChipView] based on [chipInfo]. + * A method implemented by subclasses to update [currentChipView] based on [newChipInfo]. */ - abstract fun updateChipView(chipInfo: T, currentChipView: ViewGroup) + @CallSuper + open fun updateChipView(newChipInfo: T, currentChipView: ViewGroup) { + chipInfo = newChipInfo + } /** * A method that can be implemented by subclcasses to do custom animations for when the chip 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 99a5b8b2d450..f0e5a3a63405 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 @@ -42,6 +42,7 @@ import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCom import com.android.systemui.media.taptotransfer.common.MediaTttLogger import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.gesture.TapGestureDetector +import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.animation.AnimationUtil.Companion.frames import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.view.ViewUtil @@ -54,27 +55,29 @@ import javax.inject.Inject */ @SysUISingleton class MediaTttChipControllerReceiver @Inject constructor( - commandQueue: CommandQueue, - context: Context, - @MediaTttReceiverLogger logger: MediaTttLogger, - windowManager: WindowManager, - viewUtil: ViewUtil, - mainExecutor: DelayableExecutor, - accessibilityManager: AccessibilityManager, - tapGestureDetector: TapGestureDetector, - powerManager: PowerManager, - @Main private val mainHandler: Handler, - private val uiEventLogger: MediaTttReceiverUiEventLogger, + commandQueue: CommandQueue, + context: Context, + @MediaTttReceiverLogger logger: MediaTttLogger, + windowManager: WindowManager, + viewUtil: ViewUtil, + mainExecutor: DelayableExecutor, + accessibilityManager: AccessibilityManager, + configurationController: ConfigurationController, + tapGestureDetector: TapGestureDetector, + powerManager: PowerManager, + @Main private val mainHandler: Handler, + private val uiEventLogger: MediaTttReceiverUiEventLogger, ) : MediaTttChipControllerCommon<ChipReceiverInfo>( - context, - logger, - windowManager, - viewUtil, - mainExecutor, - accessibilityManager, - tapGestureDetector, - powerManager, - R.layout.media_ttt_chip_receiver + context, + logger, + windowManager, + viewUtil, + mainExecutor, + accessibilityManager, + configurationController, + tapGestureDetector, + powerManager, + R.layout.media_ttt_chip_receiver, ) { @SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS override val windowLayoutParams = commonWindowLayoutParams.apply { @@ -140,12 +143,13 @@ class MediaTttChipControllerReceiver @Inject constructor( ) } - override fun updateChipView(chipInfo: ChipReceiverInfo, currentChipView: ViewGroup) { + override fun updateChipView(newChipInfo: ChipReceiverInfo, currentChipView: ViewGroup) { + super.updateChipView(newChipInfo, currentChipView) setIcon( currentChipView, - chipInfo.routeInfo.packageName, - chipInfo.appIconDrawableOverride, - chipInfo.appNameOverride + newChipInfo.routeInfo.packageName, + newChipInfo.appIconDrawableOverride, + newChipInfo.appNameOverride ) } 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 797a7701413b..540798af9a77 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 @@ -39,6 +39,7 @@ import com.android.systemui.media.taptotransfer.common.MediaTttLogger import com.android.systemui.media.taptotransfer.common.MediaTttRemovalReason import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.gesture.TapGestureDetector +import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.view.ViewUtil import javax.inject.Inject @@ -49,33 +50,33 @@ import javax.inject.Inject */ @SysUISingleton class MediaTttChipControllerSender @Inject constructor( - commandQueue: CommandQueue, - context: Context, - @MediaTttSenderLogger logger: MediaTttLogger, - windowManager: WindowManager, - viewUtil: ViewUtil, - @Main mainExecutor: DelayableExecutor, - accessibilityManager: AccessibilityManager, - tapGestureDetector: TapGestureDetector, - powerManager: PowerManager, - private val uiEventLogger: MediaTttSenderUiEventLogger + commandQueue: CommandQueue, + context: Context, + @MediaTttSenderLogger logger: MediaTttLogger, + windowManager: WindowManager, + viewUtil: ViewUtil, + @Main mainExecutor: DelayableExecutor, + accessibilityManager: AccessibilityManager, + configurationController: ConfigurationController, + tapGestureDetector: TapGestureDetector, + powerManager: PowerManager, + private val uiEventLogger: MediaTttSenderUiEventLogger ) : MediaTttChipControllerCommon<ChipSenderInfo>( - context, - logger, - windowManager, - viewUtil, - mainExecutor, - accessibilityManager, - tapGestureDetector, - powerManager, - R.layout.media_ttt_chip + context, + logger, + windowManager, + viewUtil, + mainExecutor, + accessibilityManager, + configurationController, + tapGestureDetector, + powerManager, + R.layout.media_ttt_chip, ) { override val windowLayoutParams = commonWindowLayoutParams.apply { gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL) } - private var currentlyDisplayedChipState: ChipStateSender? = null - private val commandQueueCallbacks = object : CommandQueue.Callbacks { override fun updateMediaTapToTransferSenderDisplay( @StatusBarManager.MediaTransferSenderState displayState: Int, @@ -116,16 +117,18 @@ class MediaTttChipControllerSender @Inject constructor( /** Displays the chip view for the given state. */ override fun updateChipView( - chipInfo: ChipSenderInfo, - currentChipView: ViewGroup) { - val chipState = chipInfo.state - currentlyDisplayedChipState = chipState + newChipInfo: ChipSenderInfo, + currentChipView: ViewGroup + ) { + super.updateChipView(newChipInfo, currentChipView) + + val chipState = newChipInfo.state // App icon - setIcon(currentChipView, chipInfo.routeInfo.packageName) + setIcon(currentChipView, newChipInfo.routeInfo.packageName) // Text - val otherDeviceName = chipInfo.routeInfo.name.toString() + val otherDeviceName = newChipInfo.routeInfo.name.toString() currentChipView.requireViewById<TextView>(R.id.text).apply { text = chipState.getChipTextString(context, otherDeviceName) } @@ -137,7 +140,7 @@ class MediaTttChipControllerSender @Inject constructor( // Undo val undoView = currentChipView.requireViewById<View>(R.id.undo) val undoClickListener = chipState.undoClickListener( - this, chipInfo.routeInfo, chipInfo.undoCallback, uiEventLogger + this, newChipInfo.routeInfo, newChipInfo.undoCallback, uiEventLogger ) undoView.setOnClickListener(undoClickListener) undoView.visibility = (undoClickListener != null).visibleIfTrue() @@ -161,12 +164,11 @@ class MediaTttChipControllerSender @Inject constructor( override fun removeChip(removalReason: String) { // Don't remove the chip if we're mid-transfer since the user should still be able to // see the status of the transfer. (But do remove it if it's finally timed out.) - if (currentlyDisplayedChipState?.isMidTransfer == true - && removalReason != MediaTttRemovalReason.REASON_TIMEOUT) { + if (chipInfo?.state?.isMidTransfer == true && + removalReason != MediaTttRemovalReason.REASON_TIMEOUT) { return } super.removeChip(removalReason) - currentlyDisplayedChipState = null } private fun Boolean.visibleIfTrue(): Int { 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 2eb478303cb2..55397868125b 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 @@ -32,6 +32,8 @@ import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.statusbar.gesture.TapGestureDetector +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any @@ -53,7 +55,7 @@ import org.mockito.MockitoAnnotations @SmallTest class MediaTttChipControllerCommonTest : SysuiTestCase() { - private lateinit var controllerCommon: MediaTttChipControllerCommon<ChipInfo> + private lateinit var controllerCommon: TestControllerCommon private lateinit var fakeClock: FakeSystemClock private lateinit var fakeExecutor: FakeExecutor @@ -68,6 +70,8 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { @Mock private lateinit var accessibilityManager: AccessibilityManager @Mock + private lateinit var configurationController: ConfigurationController + @Mock private lateinit var windowManager: WindowManager @Mock private lateinit var viewUtil: ViewUtil @@ -98,14 +102,15 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { fakeExecutor = FakeExecutor(fakeClock) controllerCommon = TestControllerCommon( - context, - logger, - windowManager, - viewUtil, - fakeExecutor, - accessibilityManager, - tapGestureDetector, - powerManager + context, + logger, + windowManager, + viewUtil, + fakeExecutor, + accessibilityManager, + configurationController, + tapGestureDetector, + powerManager, ) } @@ -186,6 +191,19 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { } @Test + fun displayScaleChange_chipReinflatedWithMostRecentState() { + controllerCommon.displayChip(getState(name = "First name")) + controllerCommon.displayChip(getState(name = "Second name")) + reset(windowManager) + + getConfigurationListener().onDensityOrFontScaleChanged() + + verify(windowManager).removeView(any()) + verify(windowManager).addView(any(), any()) + assertThat(controllerCommon.mostRecentChipInfo?.name).isEqualTo("Second name") + } + + @Test fun removeChip_chipRemovedAndGestureDetectionStoppedAndRemovalLogged() { // First, add the chip controllerCommon.displayChip(getState()) @@ -341,7 +359,7 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { verify(windowManager, never()).removeView(any()) } - private fun getState() = ChipInfo() + private fun getState(name: String = "name") = ChipInfo(name) private fun getChipView(): ViewGroup { val viewCaptor = ArgumentCaptor.forClass(View::class.java) @@ -351,6 +369,12 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon) + private fun getConfigurationListener(): ConfigurationListener { + val callbackCaptor = argumentCaptor<ConfigurationListener>() + verify(configurationController).addCallback(capture(callbackCaptor)) + return callbackCaptor.value + } + inner class TestControllerCommon( context: Context, logger: MediaTttLogger, @@ -358,8 +382,9 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { viewUtil: ViewUtil, @Main mainExecutor: DelayableExecutor, accessibilityManager: AccessibilityManager, + configurationController: ConfigurationController, tapGestureDetector: TapGestureDetector, - powerManager: PowerManager + powerManager: PowerManager, ) : MediaTttChipControllerCommon<ChipInfo>( context, logger, @@ -367,16 +392,22 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { viewUtil, mainExecutor, accessibilityManager, + configurationController, tapGestureDetector, powerManager, - R.layout.media_ttt_chip + R.layout.media_ttt_chip, ) { + var mostRecentChipInfo: ChipInfo? = null + override val windowLayoutParams = commonWindowLayoutParams - override fun updateChipView(chipInfo: ChipInfo, currentChipView: ViewGroup) {} + override fun updateChipView(newChipInfo: ChipInfo, currentChipView: ViewGroup) { + super.updateChipView(newChipInfo, currentChipView) + mostRecentChipInfo = newChipInfo + } override fun getIconSize(isAppIcon: Boolean): Int = ICON_SIZE } - inner class ChipInfo : ChipInfoCommon { + inner class ChipInfo(val name: String) : ChipInfoCommon { override fun getTimeoutMs() = 1L } } 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 bbc564193080..7c5d07737313 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 @@ -37,6 +37,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.media.taptotransfer.common.MediaTttLogger import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.gesture.TapGestureDetector +import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq @@ -68,6 +69,8 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { @Mock private lateinit var accessibilityManager: AccessibilityManager @Mock + private lateinit var configurationController: ConfigurationController + @Mock private lateinit var powerManager: PowerManager @Mock private lateinit var windowManager: WindowManager @@ -103,6 +106,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { viewUtil, FakeExecutor(FakeSystemClock()), accessibilityManager, + configurationController, TapGestureDetector(context), powerManager, Handler.getMain(), 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 7ca0cd34ab26..e06a27d6a272 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 @@ -38,12 +38,12 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.media.taptotransfer.common.MediaTttLogger import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.gesture.TapGestureDetector +import com.android.systemui.statusbar.policy.ConfigurationController 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.android.systemui.util.view.ViewUtil - import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -52,8 +52,8 @@ import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.never import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidTestingRunner::class) @@ -70,6 +70,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { @Mock private lateinit var accessibilityManager: AccessibilityManager @Mock + private lateinit var configurationController: ConfigurationController + @Mock private lateinit var powerManager: PowerManager @Mock private lateinit var windowManager: WindowManager @@ -112,6 +114,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { viewUtil, fakeExecutor, accessibilityManager, + configurationController, TapGestureDetector(context), powerManager, senderUiEventLogger |