Merge "[Media TTT] Define a common controller that will be used for both the sender device chip and the receiver device chip."

This commit is contained in:
Caitlin Cassidy
2021-12-23 16:16:19 +00:00
committed by Android (Google) Code Review
11 changed files with 322 additions and 152 deletions

View File

@@ -23,8 +23,8 @@ import com.android.systemui.InitController;
import com.android.systemui.SystemUIAppComponentFactory;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardSliceProvider;
import com.android.systemui.media.taptotransfer.MediaTttChipController;
import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
import com.android.systemui.people.PeopleProvider;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
@@ -134,7 +134,7 @@ public interface SysUIComponent {
});
getNaturalRotationUnfoldProgressProvider().ifPresent(o -> o.init());
// No init method needed, just needs to be gotten so that it's created.
getMediaTttChipController();
getMediaTttChipControllerSender();
getMediaTttCommandLineHelper();
getUnfoldLatencyTracker().init();
}
@@ -190,7 +190,7 @@ public interface SysUIComponent {
Optional<NaturalRotationUnfoldProgressProvider> getNaturalRotationUnfoldProgressProvider();
/** */
Optional<MediaTttChipController> getMediaTttChipController();
Optional<MediaTttChipControllerSender> getMediaTttChipControllerSender();
/** */
Optional<MediaTttCommandLineHelper> getMediaTttCommandLineHelper();

View File

@@ -26,9 +26,9 @@ import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.media.MediaHostStatesManager;
import com.android.systemui.media.taptotransfer.MediaTttChipController;
import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
import com.android.systemui.media.taptotransfer.MediaTttFlags;
import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -80,7 +80,7 @@ public interface MediaModule {
/** */
@Provides
@SysUISingleton
static Optional<MediaTttChipController> providesMediaTttChipController(
static Optional<MediaTttChipControllerSender> providesMediaTttChipControllerSender(
MediaTttFlags mediaTttFlags,
Context context,
WindowManager windowManager,
@@ -89,7 +89,7 @@ public interface MediaModule {
if (!mediaTttFlags.isMediaTttEnabled()) {
return Optional.empty();
}
return Optional.of(new MediaTttChipController(
return Optional.of(new MediaTttChipControllerSender(
context, windowManager, mainExecutor, backgroundExecutor));
}
@@ -100,12 +100,12 @@ public interface MediaModule {
MediaTttFlags mediaTttFlags,
CommandRegistry commandRegistry,
Context context,
MediaTttChipController mediaTttChipController,
MediaTttChipControllerSender mediaTttChipControllerSender,
@Main DelayableExecutor mainExecutor) {
if (!mediaTttFlags.isMediaTttEnabled()) {
return Optional.empty();
}
return Optional.of(new MediaTttCommandLineHelper(
commandRegistry, context, mediaTttChipController, mainExecutor));
commandRegistry, context, mediaTttChipControllerSender, mainExecutor));
}
}

View File

@@ -23,6 +23,10 @@ import androidx.annotation.VisibleForTesting
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender
import com.android.systemui.media.taptotransfer.sender.MoveCloserToTransfer
import com.android.systemui.media.taptotransfer.sender.TransferInitiated
import com.android.systemui.media.taptotransfer.sender.TransferSucceeded
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -38,7 +42,7 @@ import javax.inject.Inject
class MediaTttCommandLineHelper @Inject constructor(
commandRegistry: CommandRegistry,
context: Context,
private val mediaTttChipController: MediaTttChipController,
private val mediaTttChipControllerSender: MediaTttChipControllerSender,
@Main private val mainExecutor: DelayableExecutor,
) {
private val appIconDrawable =
@@ -54,21 +58,21 @@ class MediaTttCommandLineHelper @Inject constructor(
val otherDeviceName = args[0]
when (args[1]) {
MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME -> {
mediaTttChipController.displayChip(
MoveCloserToTransfer(otherDeviceName, appIconDrawable)
mediaTttChipControllerSender.displayChip(
MoveCloserToTransfer(appIconDrawable, otherDeviceName)
)
}
TRANSFER_INITIATED_COMMAND_NAME -> {
val futureTask = FutureTask { fakeUndoRunnable }
mediaTttChipController.displayChip(
TransferInitiated(otherDeviceName, appIconDrawable, futureTask)
mediaTttChipControllerSender.displayChip(
TransferInitiated(appIconDrawable, otherDeviceName, futureTask)
)
mainExecutor.executeDelayed({ futureTask.run() }, FUTURE_WAIT_TIME)
}
TRANSFER_SUCCEEDED_COMMAND_NAME -> {
mediaTttChipController.displayChip(
TransferSucceeded(otherDeviceName, appIconDrawable, fakeUndoRunnable)
mediaTttChipControllerSender.displayChip(
TransferSucceeded(appIconDrawable, otherDeviceName, fakeUndoRunnable)
)
}
else -> {
@@ -90,7 +94,7 @@ class MediaTttCommandLineHelper @Inject constructor(
inner class RemoveChipCommand : Command {
override fun execute(pw: PrintWriter, args: List<String>) {
mediaTttChipController.removeChip()
mediaTttChipControllerSender.removeChip()
}
override fun help(pw: PrintWriter) {
pw.println("Usage: adb shell cmd statusbar $REMOVE_CHIP_COMMAND_TAG")
@@ -114,3 +118,4 @@ val TRANSFER_INITIATED_COMMAND_NAME = TransferInitiated::class.simpleName!!
val TRANSFER_SUCCEEDED_COMMAND_NAME = TransferSucceeded::class.simpleName!!
private const val FUTURE_WAIT_TIME = 2000L
private const val TAG = "MediaTapToTransferCli"

View File

@@ -0,0 +1,11 @@
# Media Tap-To-Transfer
This package (and child packages) include code for the media tap-to-transfer feature, which
allows users to easily transfer playing media between devices.
In media transfer, there are two devices: the *sender* and the *receiver*. The sender device will
start and stop media casts to the receiver device. On both devices, System UI will display a chip
informing the user about the media cast occurring.
This package is structured so that the sender code is in the sender package, the receiver code is
in the receiver package, and code that's shared between them is in the common package.

View File

@@ -0,0 +1,104 @@
/*
* Copyright (C) 2021 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.media.taptotransfer.common
import android.annotation.LayoutRes
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.PixelFormat
import android.view.Gravity
import android.view.LayoutInflater
import android.view.ViewGroup
import android.view.WindowManager
import com.android.internal.widget.CachingIconView
import com.android.systemui.R
/**
* A superclass controller that provides common functionality for showing chips on the sender device
* and the receiver device.
*
* Subclasses need to override and implement [updateChipView], which is where they can control what
* gets displayed to the user.
*/
abstract class MediaTttChipControllerCommon<T : MediaTttChipState>(
private val context: Context,
private val windowManager: WindowManager,
@LayoutRes private val chipLayoutRes: Int
) {
/** The window layout parameters we'll use when attaching the view to a window. */
@SuppressLint("WrongConstant") // We're allowed to use TYPE_VOLUME_OVERLAY
private val windowLayoutParams = WindowManager.LayoutParams().apply {
width = WindowManager.LayoutParams.WRAP_CONTENT
height = WindowManager.LayoutParams.WRAP_CONTENT
gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY
flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
title = "Media Tap-To-Transfer Chip View"
format = PixelFormat.TRANSLUCENT
setTrustedOverlay()
}
/** The chip view currently being displayed. Null if the chip is not being displayed. */
var chipView: ViewGroup? = null
/**
* Displays the chip with the current state.
*
* This method handles inflating and attaching the view, then delegates to [updateChipView] to
* display the correct information in the chip.
*/
fun displayChip(chipState: T) {
val oldChipView = chipView
if (chipView == null) {
chipView = LayoutInflater
.from(context)
.inflate(chipLayoutRes, null) as ViewGroup
}
val currentChipView = chipView!!
updateChipView(chipState, currentChipView)
// Add view if necessary
if (oldChipView == null) {
windowManager.addView(chipView, windowLayoutParams)
}
}
/** Hides the chip. */
fun removeChip() {
if (chipView == null) { return }
windowManager.removeView(chipView)
chipView = null
}
/**
* A method implemented by subclasses to update [currentChipView] based on [chipState].
*/
abstract fun updateChipView(chipState: T, currentChipView: ViewGroup)
/**
* An internal method to set the icon on the view.
*
* This is in the common superclass since both the sender and the receiver show an icon.
*/
internal fun setIcon(chipState: T, currentChipView: ViewGroup) {
currentChipView.findViewById<CachingIconView>(R.id.app_icon).apply {
this.setImageDrawable(chipState.appIconDrawable)
}
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2021 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.media.taptotransfer.common
import android.graphics.drawable.Drawable
/**
* A superclass chip state that will be subclassed by the sender chip and receiver chip.
*
* @property appIconDrawable a drawable representing the icon of the app playing the media.
*/
open class MediaTttChipState(
internal val appIconDrawable: Drawable
)

View File

@@ -14,52 +14,52 @@
* limitations under the License.
*/
package com.android.systemui.media.taptotransfer
package com.android.systemui.media.taptotransfer.sender
import android.graphics.drawable.Drawable
import androidx.annotation.StringRes
import com.android.systemui.R
import com.android.systemui.media.taptotransfer.common.MediaTttChipState
import java.util.concurrent.Future
/**
* A class that stores all the information necessary to display the media tap-to-transfer chip in
* certain states.
* A class that stores all the information necessary to display the media tap-to-transfer chip on
* the sender device.
*
* This is a sealed class where each subclass represents a specific chip state. Each subclass can
* contain additional information that is necessary for only that state.
*
* @property chipText a string resource for the text that the chip should display.
* @property otherDeviceName the name of the other device involved in the transfer.
* @property appIconDrawable a drawable representing the icon of the app playing the media.
*/
sealed class MediaTttChipState(
sealed class ChipStateSender(
appIconDrawable: Drawable,
@StringRes internal val chipText: Int,
internal val otherDeviceName: String,
internal val appIconDrawable: Drawable,
)
) : MediaTttChipState(appIconDrawable)
/**
* A state representing that the two devices are close but not close enough to initiate a transfer.
* The chip will instruct the user to move closer in order to initiate the transfer.
*/
class MoveCloserToTransfer(
appIconDrawable: Drawable,
otherDeviceName: String,
appIconDrawable: Drawable
) : MediaTttChipState(R.string.media_move_closer_to_transfer, otherDeviceName, appIconDrawable)
) : ChipStateSender(appIconDrawable, R.string.media_move_closer_to_transfer, otherDeviceName)
/**
* A state representing that a transfer has been initiated (but not completed).
*
* @property future a future that will be resolved when the transfer has either succeeded or failed.
* If the transfer succeeded, the future can optionally return an undo runnable (see
* [TransferSucceeded.undoRunnable]). [MediaTttChipController] is responsible for transitioning
* [TransferSucceeded.undoRunnable]). [MediaTttChipControllerSender] is responsible for transitioning
* the chip to the [TransferSucceeded] state if the future resolves successfully.
*/
class TransferInitiated(
otherDeviceName: String,
appIconDrawable: Drawable,
otherDeviceName: String,
val future: Future<Runnable?>
) : MediaTttChipState(R.string.media_transfer_playing, otherDeviceName, appIconDrawable)
) : ChipStateSender(appIconDrawable, R.string.media_transfer_playing, otherDeviceName)
/**
* A state representing that a transfer has been successfully completed.
@@ -68,7 +68,7 @@ class TransferInitiated(
* show an Undo button on the chip if this runnable is present.
*/
class TransferSucceeded(
otherDeviceName: String,
appIconDrawable: Drawable,
otherDeviceName: String,
val undoRunnable: Runnable? = null
) : MediaTttChipState(R.string.media_transfer_playing, otherDeviceName, appIconDrawable)
) : ChipStateSender(appIconDrawable, R.string.media_transfer_playing, otherDeviceName)

View File

@@ -14,71 +14,40 @@
* limitations under the License.
*/
package com.android.systemui.media.taptotransfer
package com.android.systemui.media.taptotransfer.sender
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.PixelFormat
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.LinearLayout
import android.widget.TextView
import com.android.internal.widget.CachingIconView
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 java.util.concurrent.Executor
import java.util.concurrent.TimeUnit
import javax.inject.Inject
const val TAG = "MediaTapToTransfer"
/**
* A controller to display and hide the Media Tap-To-Transfer chip. This chip is shown when a user
* is currently playing media on a local "media cast sender" device (e.g. a phone) and gets close
* enough to a "media cast receiver" device (e.g. a tablet). This chip encourages the user to
* transfer the media from the sender device to the receiver device.
* A controller to display and hide the Media Tap-To-Transfer chip on the **sending** device. This
* chip is shown when a user is transferring media to/from this device and a receiver device.
*/
@SysUISingleton
class MediaTttChipController @Inject constructor(
private val context: Context,
private val windowManager: WindowManager,
class MediaTttChipControllerSender @Inject constructor(
context: Context,
windowManager: WindowManager,
@Main private val mainExecutor: Executor,
@Background private val backgroundExecutor: Executor,
) : MediaTttChipControllerCommon<ChipStateSender>(
context, windowManager, R.layout.media_ttt_chip
) {
@SuppressLint("WrongConstant") // We're allowed to use TYPE_VOLUME_OVERLAY
private val windowLayoutParams = WindowManager.LayoutParams().apply {
width = WindowManager.LayoutParams.WRAP_CONTENT
height = WindowManager.LayoutParams.WRAP_CONTENT
gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY
flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
title = "Media Tap-To-Transfer Chip View"
format = PixelFormat.TRANSLUCENT
setTrustedOverlay()
}
/** The chip view currently being displayed. Null if the chip is not being displayed. */
private var chipView: LinearLayout? = null
/** Displays the chip view for the given state. */
fun displayChip(chipState: MediaTttChipState) {
val oldChipView = chipView
if (chipView == null) {
chipView = LayoutInflater
.from(context)
.inflate(R.layout.media_ttt_chip, null) as LinearLayout
}
val currentChipView = chipView!!
override fun updateChipView(chipState: ChipStateSender, currentChipView: ViewGroup) {
// App icon
currentChipView.findViewById<CachingIconView>(R.id.app_icon).apply {
this.setImageDrawable(chipState.appIconDrawable)
}
setIcon(chipState, currentChipView)
// Text
currentChipView.requireViewById<TextView>(R.id.text).apply {
@@ -108,18 +77,6 @@ class MediaTttChipController @Inject constructor(
if (chipState is TransferInitiated) {
addFutureCallback(chipState)
}
// Add view if necessary
if (oldChipView == null) {
windowManager.addView(chipView, windowLayoutParams)
}
}
/** Hides the chip. */
fun removeChip() {
if (chipView == null) { return }
windowManager.removeView(chipView)
chipView = null
}
/**
@@ -136,7 +93,7 @@ class MediaTttChipController @Inject constructor(
mainExecutor.execute {
displayChip(
TransferSucceeded(
chipState.otherDeviceName, chipState.appIconDrawable, undoRunnable
chipState.appIconDrawable, chipState.otherDeviceName, undoRunnable
)
)
}

View File

@@ -18,6 +18,10 @@ package com.android.systemui.media.taptotransfer
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender
import com.android.systemui.media.taptotransfer.sender.MoveCloserToTransfer
import com.android.systemui.media.taptotransfer.sender.TransferInitiated
import com.android.systemui.media.taptotransfer.sender.TransferSucceeded
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.util.concurrency.FakeExecutor
@@ -42,14 +46,17 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() {
private lateinit var mediaTttCommandLineHelper: MediaTttCommandLineHelper
@Mock
private lateinit var mediaTttChipController: MediaTttChipController
private lateinit var mediaTttChipControllerSender: MediaTttChipControllerSender
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
mediaTttCommandLineHelper =
MediaTttCommandLineHelper(
commandRegistry, context, mediaTttChipController, FakeExecutor(FakeSystemClock())
commandRegistry,
context,
mediaTttChipControllerSender,
FakeExecutor(FakeSystemClock())
)
}
@@ -75,28 +82,28 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() {
fun moveCloserToTransfer_chipDisplayWithCorrectState() {
commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand())
verify(mediaTttChipController).displayChip(any(MoveCloserToTransfer::class.java))
verify(mediaTttChipControllerSender).displayChip(any(MoveCloserToTransfer::class.java))
}
@Test
fun transferInitiated_chipDisplayWithCorrectState() {
commandRegistry.onShellCommand(pw, getTransferInitiatedCommand())
verify(mediaTttChipController).displayChip(any(TransferInitiated::class.java))
verify(mediaTttChipControllerSender).displayChip(any(TransferInitiated::class.java))
}
@Test
fun transferSucceeded_chipDisplayWithCorrectState() {
commandRegistry.onShellCommand(pw, getTransferSucceededCommand())
verify(mediaTttChipController).displayChip(any(TransferSucceeded::class.java))
verify(mediaTttChipControllerSender).displayChip(any(TransferSucceeded::class.java))
}
@Test
fun removeCommand_chipRemoved() {
commandRegistry.onShellCommand(pw, arrayOf(REMOVE_CHIP_COMMAND_TAG))
verify(mediaTttChipController).removeChip()
verify(mediaTttChipControllerSender).removeChip()
}
private fun getMoveCloserToTransferCommand(): Array<String> =

View File

@@ -0,0 +1,94 @@
/*
* Copyright (C) 2021 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.media.taptotransfer.common
import android.content.Context
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.view.ViewGroup
import android.view.WindowManager
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
class MediaTttChipControllerCommonTest : SysuiTestCase() {
private lateinit var controllerCommon: MediaTttChipControllerCommon<MediaTttChipState>
private lateinit var appIconDrawable: Drawable
@Mock
private lateinit var windowManager: WindowManager
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
appIconDrawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context)
controllerCommon = TestControllerCommon(context, windowManager)
}
@Test
fun displayChip_chipAdded() {
controllerCommon.displayChip(MediaTttChipState(appIconDrawable))
verify(windowManager).addView(any(), any())
}
@Test
fun displayChip_twice_chipNotAddedTwice() {
controllerCommon.displayChip(MediaTttChipState(appIconDrawable))
reset(windowManager)
controllerCommon.displayChip(MediaTttChipState(appIconDrawable))
verify(windowManager, never()).addView(any(), any())
}
@Test
fun removeChip_chipRemoved() {
// First, add the chip
controllerCommon.displayChip(MediaTttChipState(appIconDrawable))
// Then, remove it
controllerCommon.removeChip()
verify(windowManager).removeView(any())
}
@Test
fun removeChip_noAdd_viewNotRemoved() {
controllerCommon.removeChip()
verify(windowManager, never()).removeView(any())
}
inner class TestControllerCommon(
context: Context,
windowManager: WindowManager
) : MediaTttChipControllerCommon<MediaTttChipState>(
context, windowManager, R.layout.media_ttt_chip
) {
override fun updateChipView(chipState: MediaTttChipState, currentChipView: ViewGroup) {
}
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.systemui.media.taptotransfer
package com.android.systemui.media.taptotransfer.sender
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
@@ -35,21 +35,19 @@ import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import java.util.concurrent.Future
@SmallTest
class MediaTttChipControllerTest : SysuiTestCase() {
class MediaTttChipControllerSenderTest : SysuiTestCase() {
private lateinit var appIconDrawable: Drawable
private lateinit var fakeMainClock: FakeSystemClock
private lateinit var fakeMainExecutor: FakeExecutor
private lateinit var fakeBackgroundClock: FakeSystemClock
private lateinit var fakeBackgroundExecutor: FakeExecutor
private lateinit var mediaTttChipController: MediaTttChipController
private lateinit var controllerSender: MediaTttChipControllerSender
@Mock
private lateinit var windowManager: WindowManager
@@ -62,48 +60,14 @@ class MediaTttChipControllerTest : SysuiTestCase() {
fakeMainExecutor = FakeExecutor(fakeMainClock)
fakeBackgroundClock = FakeSystemClock()
fakeBackgroundExecutor = FakeExecutor(fakeBackgroundClock)
mediaTttChipController = MediaTttChipController(
controllerSender = MediaTttChipControllerSender(
context, windowManager, fakeMainExecutor, fakeBackgroundExecutor
)
}
@Test
fun displayChip_chipAdded() {
mediaTttChipController.displayChip(moveCloserToTransfer())
verify(windowManager).addView(any(), any())
}
@Test
fun displayChip_twice_chipNotAddedTwice() {
mediaTttChipController.displayChip(moveCloserToTransfer())
reset(windowManager)
mediaTttChipController.displayChip(moveCloserToTransfer())
verify(windowManager, never()).addView(any(), any())
}
@Test
fun removeChip_chipRemoved() {
// First, add the chip
mediaTttChipController.displayChip(moveCloserToTransfer())
// Then, remove it
mediaTttChipController.removeChip()
verify(windowManager).removeView(any())
}
@Test
fun removeChip_noAdd_viewNotRemoved() {
mediaTttChipController.removeChip()
verify(windowManager, never()).removeView(any())
}
@Test
fun moveCloserToTransfer_appIcon_chipTextContainsDeviceName_noLoadingIcon_noUndo() {
mediaTttChipController.displayChip(moveCloserToTransfer())
controllerSender.displayChip(moveCloserToTransfer())
val chipView = getChipView()
assertThat(chipView.getAppIconDrawable()).isEqualTo(appIconDrawable)
@@ -115,7 +79,7 @@ class MediaTttChipControllerTest : SysuiTestCase() {
@Test
fun transferInitiated_futureNotResolvedYet_appIcon_loadingIcon_noUndo() {
val future: SettableFuture<Runnable?> = SettableFuture.create()
mediaTttChipController.displayChip(transferInitiated(future))
controllerSender.displayChip(transferInitiated(future))
// Don't resolve the future in any way and don't run our executors
@@ -132,7 +96,7 @@ class MediaTttChipControllerTest : SysuiTestCase() {
val future: SettableFuture<Runnable?> = SettableFuture.create()
val undoRunnable = Runnable { }
mediaTttChipController.displayChip(transferInitiated(future))
controllerSender.displayChip(transferInitiated(future))
future.set(undoRunnable)
fakeBackgroundExecutor.advanceClockToLast()
@@ -153,7 +117,7 @@ class MediaTttChipControllerTest : SysuiTestCase() {
fun transferInitiated_futureCancelled_chipRemoved() {
val future: SettableFuture<Runnable?> = SettableFuture.create()
mediaTttChipController.displayChip(transferInitiated(future))
controllerSender.displayChip(transferInitiated(future))
future.cancel(true)
fakeBackgroundExecutor.advanceClockToLast()
@@ -170,7 +134,7 @@ class MediaTttChipControllerTest : SysuiTestCase() {
@Test
fun transferInitiated_futureNotResolvedAfterTimeout_chipRemoved() {
val future: SettableFuture<Runnable?> = SettableFuture.create()
mediaTttChipController.displayChip(transferInitiated(future))
controllerSender.displayChip(transferInitiated(future))
// We won't set anything on the future, but we will still run the executors so that we're
// waiting on the future resolving. If we have a bug in our code, then this test will time
@@ -188,7 +152,7 @@ class MediaTttChipControllerTest : SysuiTestCase() {
@Test
fun transferSucceeded_appIcon_chipTextContainsDeviceName_noLoadingIcon() {
mediaTttChipController.displayChip(transferSucceeded())
controllerSender.displayChip(transferSucceeded())
val chipView = getChipView()
assertThat(chipView.getAppIconDrawable()).isEqualTo(appIconDrawable)
@@ -198,7 +162,7 @@ class MediaTttChipControllerTest : SysuiTestCase() {
@Test
fun transferSucceededNullUndoRunnable_noUndo() {
mediaTttChipController.displayChip(transferSucceeded(undoRunnable = null))
controllerSender.displayChip(transferSucceeded(undoRunnable = null))
val chipView = getChipView()
assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
@@ -206,7 +170,7 @@ class MediaTttChipControllerTest : SysuiTestCase() {
@Test
fun transferSucceededWithUndoRunnable_undoWithClick() {
mediaTttChipController.displayChip(transferSucceeded { })
controllerSender.displayChip(transferSucceeded { })
val chipView = getChipView()
assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
@@ -218,7 +182,7 @@ class MediaTttChipControllerTest : SysuiTestCase() {
var runnableRun = false
val runnable = Runnable { runnableRun = true }
mediaTttChipController.displayChip(transferSucceeded(undoRunnable = runnable))
controllerSender.displayChip(transferSucceeded(undoRunnable = runnable))
getChipView().getUndoButton().performClick()
assertThat(runnableRun).isTrue()
@@ -226,32 +190,32 @@ class MediaTttChipControllerTest : SysuiTestCase() {
@Test
fun changeFromCloserToTransferToTransferInitiated_loadingIconAppears() {
mediaTttChipController.displayChip(moveCloserToTransfer())
mediaTttChipController.displayChip(transferInitiated())
controllerSender.displayChip(moveCloserToTransfer())
controllerSender.displayChip(transferInitiated())
assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
}
@Test
fun changeFromTransferInitiatedToTransferSucceeded_loadingIconDisappears() {
mediaTttChipController.displayChip(transferInitiated())
mediaTttChipController.displayChip(transferSucceeded())
controllerSender.displayChip(transferInitiated())
controllerSender.displayChip(transferSucceeded())
assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.GONE)
}
@Test
fun changeFromTransferInitiatedToTransferSucceeded_undoButtonAppears() {
mediaTttChipController.displayChip(transferInitiated())
mediaTttChipController.displayChip(transferSucceeded { })
controllerSender.displayChip(transferInitiated())
controllerSender.displayChip(transferSucceeded { })
assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.VISIBLE)
}
@Test
fun changeFromTransferSucceededToMoveCloser_undoButtonDisappears() {
mediaTttChipController.displayChip(transferSucceeded())
mediaTttChipController.displayChip(moveCloserToTransfer())
controllerSender.displayChip(transferSucceeded())
controllerSender.displayChip(moveCloserToTransfer())
assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.GONE)
}
@@ -274,17 +238,17 @@ class MediaTttChipControllerTest : SysuiTestCase() {
}
/** Helper method providing default parameters to not clutter up the tests. */
private fun moveCloserToTransfer() = MoveCloserToTransfer(DEVICE_NAME, appIconDrawable)
private fun moveCloserToTransfer() = MoveCloserToTransfer(appIconDrawable, DEVICE_NAME)
/** Helper method providing default parameters to not clutter up the tests. */
private fun transferInitiated(
future: Future<Runnable?> = TEST_FUTURE
) = TransferInitiated(DEVICE_NAME, appIconDrawable, future)
) = TransferInitiated(appIconDrawable, DEVICE_NAME, future)
/** Helper method providing default parameters to not clutter up the tests. */
private fun transferSucceeded(
undoRunnable: Runnable? = null
) = TransferSucceeded(DEVICE_NAME, appIconDrawable, undoRunnable)
) = TransferSucceeded(appIconDrawable, DEVICE_NAME, undoRunnable)
}
private const val DEVICE_NAME = "My Tablet"