[Media TTT] Add the app icon to the view.
Fixes: 209796699 Test: manual (icon shows on the command `adb shell cmd statusbar media-ttt-chip-add Tablet <any state>` Test: MediaTttChipControllerTest Change-Id: I59f243ed28521ce7ce0c07a0e92a5ac4e37c6599
This commit is contained in:
@@ -26,6 +26,13 @@
|
||||
android:gravity="center_vertical"
|
||||
>
|
||||
|
||||
<com.android.internal.widget.CachingIconView
|
||||
android:id="@+id/app_icon"
|
||||
android:layout_width="@dimen/media_ttt_icon_size"
|
||||
android:layout_height="@dimen/media_ttt_icon_size"
|
||||
android:layout_marginEnd="12dp"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="wrap_content"
|
||||
@@ -37,8 +44,8 @@
|
||||
<ProgressBar
|
||||
android:id="@+id/loading"
|
||||
android:indeterminate="true"
|
||||
android:layout_width="@dimen/media_ttt_icon_size"
|
||||
android:layout_height="@dimen/media_ttt_icon_size"
|
||||
android:layout_width="@dimen/media_ttt_loading_size"
|
||||
android:layout_height="@dimen/media_ttt_loading_size"
|
||||
android:layout_marginStart="12dp"
|
||||
android:indeterminateTint="?androidprv:attr/colorAccentPrimaryVariant"
|
||||
style="?android:attr/progressBarStyleSmall"
|
||||
|
||||
@@ -980,7 +980,8 @@
|
||||
<!-- Media tap-to-transfer chip -->
|
||||
<dimen name="media_ttt_chip_outer_padding">16dp</dimen>
|
||||
<dimen name="media_ttt_text_size">16sp</dimen>
|
||||
<dimen name="media_ttt_icon_size">16dp</dimen>
|
||||
<dimen name="media_ttt_icon_size">24dp</dimen>
|
||||
<dimen name="media_ttt_loading_size">20dp</dimen>
|
||||
<dimen name="media_ttt_undo_button_vertical_padding">8dp</dimen>
|
||||
<dimen name="media_ttt_undo_button_vertical_negative_margin">-8dp</dimen>
|
||||
|
||||
|
||||
@@ -99,12 +99,13 @@ public interface MediaModule {
|
||||
static Optional<MediaTttCommandLineHelper> providesMediaTttCommandLineHelper(
|
||||
MediaTttFlags mediaTttFlags,
|
||||
CommandRegistry commandRegistry,
|
||||
Context context,
|
||||
MediaTttChipController mediaTttChipController,
|
||||
@Main DelayableExecutor mainExecutor) {
|
||||
if (!mediaTttFlags.isMediaTttEnabled()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(new MediaTttCommandLineHelper(
|
||||
commandRegistry, mediaTttChipController, mainExecutor));
|
||||
commandRegistry, context, mediaTttChipController, mainExecutor));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.view.View
|
||||
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
|
||||
@@ -74,6 +75,11 @@ class MediaTttChipController @Inject constructor(
|
||||
}
|
||||
val currentChipView = chipView!!
|
||||
|
||||
// App icon
|
||||
currentChipView.findViewById<CachingIconView>(R.id.app_icon).apply {
|
||||
this.setImageDrawable(chipState.appIconDrawable)
|
||||
}
|
||||
|
||||
// Text
|
||||
currentChipView.requireViewById<TextView>(R.id.text).apply {
|
||||
text = context.getString(chipState.chipText, chipState.otherDeviceName)
|
||||
@@ -128,7 +134,11 @@ class MediaTttChipController @Inject constructor(
|
||||
val undoRunnable = chipState.future.get(TRANSFER_TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||
// Make UI changes on the main thread
|
||||
mainExecutor.execute {
|
||||
displayChip(TransferSucceeded(chipState.otherDeviceName, undoRunnable))
|
||||
displayChip(
|
||||
TransferSucceeded(
|
||||
chipState.otherDeviceName, chipState.appIconDrawable, undoRunnable
|
||||
)
|
||||
)
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
// TODO(b/203800327): Maybe show a failure chip here if UX decides we need one.
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.systemui.media.taptotransfer
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.annotation.StringRes
|
||||
import com.android.systemui.R
|
||||
import java.util.concurrent.Future
|
||||
@@ -26,12 +27,15 @@ import java.util.concurrent.Future
|
||||
*
|
||||
* 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(
|
||||
/** A string resource for the text that the chip should display. */
|
||||
@StringRes internal val chipText: Int,
|
||||
/** The name of the other device involved in the transfer. */
|
||||
internal val otherDeviceName: String
|
||||
internal val otherDeviceName: String,
|
||||
internal val appIconDrawable: Drawable,
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -39,8 +43,9 @@ sealed class MediaTttChipState(
|
||||
* The chip will instruct the user to move closer in order to initiate the transfer.
|
||||
*/
|
||||
class MoveCloserToTransfer(
|
||||
otherDeviceName: String
|
||||
) : MediaTttChipState(R.string.media_move_closer_to_transfer, otherDeviceName)
|
||||
otherDeviceName: String,
|
||||
appIconDrawable: Drawable
|
||||
) : MediaTttChipState(R.string.media_move_closer_to_transfer, otherDeviceName, appIconDrawable)
|
||||
|
||||
/**
|
||||
* A state representing that a transfer has been initiated (but not completed).
|
||||
@@ -52,8 +57,9 @@ class MoveCloserToTransfer(
|
||||
*/
|
||||
class TransferInitiated(
|
||||
otherDeviceName: String,
|
||||
appIconDrawable: Drawable,
|
||||
val future: Future<Runnable?>
|
||||
) : MediaTttChipState(R.string.media_transfer_playing, otherDeviceName)
|
||||
) : MediaTttChipState(R.string.media_transfer_playing, otherDeviceName, appIconDrawable)
|
||||
|
||||
/**
|
||||
* A state representing that a transfer has been successfully completed.
|
||||
@@ -63,5 +69,6 @@ class TransferInitiated(
|
||||
*/
|
||||
class TransferSucceeded(
|
||||
otherDeviceName: String,
|
||||
appIconDrawable: Drawable,
|
||||
val undoRunnable: Runnable? = null
|
||||
) : MediaTttChipState(R.string.media_transfer_playing, otherDeviceName)
|
||||
) : MediaTttChipState(R.string.media_transfer_playing, otherDeviceName, appIconDrawable)
|
||||
|
||||
@@ -16,8 +16,11 @@
|
||||
|
||||
package com.android.systemui.media.taptotransfer
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Icon
|
||||
import android.util.Log
|
||||
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.statusbar.commandline.Command
|
||||
@@ -34,9 +37,13 @@ import javax.inject.Inject
|
||||
@SysUISingleton
|
||||
class MediaTttCommandLineHelper @Inject constructor(
|
||||
commandRegistry: CommandRegistry,
|
||||
context: Context,
|
||||
private val mediaTttChipController: MediaTttChipController,
|
||||
@Main private val mainExecutor: DelayableExecutor,
|
||||
) {
|
||||
private val appIconDrawable =
|
||||
Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context)
|
||||
|
||||
init {
|
||||
commandRegistry.registerCommand(ADD_CHIP_COMMAND_TAG) { AddChipCommand() }
|
||||
commandRegistry.registerCommand(REMOVE_CHIP_COMMAND_TAG) { RemoveChipCommand() }
|
||||
@@ -47,19 +54,21 @@ class MediaTttCommandLineHelper @Inject constructor(
|
||||
val otherDeviceName = args[0]
|
||||
when (args[1]) {
|
||||
MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME -> {
|
||||
mediaTttChipController.displayChip(MoveCloserToTransfer(otherDeviceName))
|
||||
mediaTttChipController.displayChip(
|
||||
MoveCloserToTransfer(otherDeviceName, appIconDrawable)
|
||||
)
|
||||
}
|
||||
TRANSFER_INITIATED_COMMAND_NAME -> {
|
||||
val futureTask = FutureTask { fakeUndoRunnable }
|
||||
mediaTttChipController.displayChip(
|
||||
TransferInitiated(otherDeviceName, futureTask)
|
||||
TransferInitiated(otherDeviceName, appIconDrawable, futureTask)
|
||||
)
|
||||
mainExecutor.executeDelayed({ futureTask.run() }, FUTURE_WAIT_TIME)
|
||||
|
||||
}
|
||||
TRANSFER_SUCCEEDED_COMMAND_NAME -> {
|
||||
mediaTttChipController.displayChip(
|
||||
TransferSucceeded(otherDeviceName, fakeUndoRunnable)
|
||||
TransferSucceeded(otherDeviceName, appIconDrawable, fakeUndoRunnable)
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
|
||||
@@ -16,8 +16,11 @@
|
||||
|
||||
package com.android.systemui.media.taptotransfer
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.Icon
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.test.filters.SmallTest
|
||||
@@ -36,10 +39,11 @@ 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() {
|
||||
|
||||
private lateinit var appIconDrawable: Drawable
|
||||
private lateinit var fakeMainClock: FakeSystemClock
|
||||
private lateinit var fakeMainExecutor: FakeExecutor
|
||||
private lateinit var fakeBackgroundClock: FakeSystemClock
|
||||
@@ -53,6 +57,7 @@ class MediaTttChipControllerTest : SysuiTestCase() {
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
appIconDrawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context)
|
||||
fakeMainClock = FakeSystemClock()
|
||||
fakeMainExecutor = FakeExecutor(fakeMainClock)
|
||||
fakeBackgroundClock = FakeSystemClock()
|
||||
@@ -64,24 +69,24 @@ class MediaTttChipControllerTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun displayChip_chipAdded() {
|
||||
mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME))
|
||||
mediaTttChipController.displayChip(moveCloserToTransfer())
|
||||
|
||||
verify(windowManager).addView(any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun displayChip_twice_chipNotAddedTwice() {
|
||||
mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME))
|
||||
mediaTttChipController.displayChip(moveCloserToTransfer())
|
||||
reset(windowManager)
|
||||
|
||||
mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME))
|
||||
mediaTttChipController.displayChip(moveCloserToTransfer())
|
||||
verify(windowManager, never()).addView(any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeChip_chipRemoved() {
|
||||
// First, add the chip
|
||||
mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME))
|
||||
mediaTttChipController.displayChip(moveCloserToTransfer())
|
||||
|
||||
// Then, remove it
|
||||
mediaTttChipController.removeChip()
|
||||
@@ -97,24 +102,26 @@ class MediaTttChipControllerTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveCloserToTransfer_chipTextContainsDeviceName_noLoadingIcon_noUndo() {
|
||||
mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME))
|
||||
fun moveCloserToTransfer_appIcon_chipTextContainsDeviceName_noLoadingIcon_noUndo() {
|
||||
mediaTttChipController.displayChip(moveCloserToTransfer())
|
||||
|
||||
val chipView = getChipView()
|
||||
assertThat(chipView.getAppIconDrawable()).isEqualTo(appIconDrawable)
|
||||
assertThat(chipView.getChipText()).contains(DEVICE_NAME)
|
||||
assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
|
||||
assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun transferInitiated_futureNotResolvedYet_loadingIcon_noUndo() {
|
||||
fun transferInitiated_futureNotResolvedYet_appIcon_loadingIcon_noUndo() {
|
||||
val future: SettableFuture<Runnable?> = SettableFuture.create()
|
||||
mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, future))
|
||||
mediaTttChipController.displayChip(transferInitiated(future))
|
||||
|
||||
// Don't resolve the future in any way and don't run our executors
|
||||
|
||||
// Assert we're still in the loading state
|
||||
val chipView = getChipView()
|
||||
assertThat(chipView.getAppIconDrawable()).isEqualTo(appIconDrawable)
|
||||
assertThat(chipView.getChipText()).contains(DEVICE_NAME)
|
||||
assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
|
||||
assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
|
||||
@@ -125,7 +132,7 @@ class MediaTttChipControllerTest : SysuiTestCase() {
|
||||
val future: SettableFuture<Runnable?> = SettableFuture.create()
|
||||
val undoRunnable = Runnable { }
|
||||
|
||||
mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, future))
|
||||
mediaTttChipController.displayChip(transferInitiated(future))
|
||||
|
||||
future.set(undoRunnable)
|
||||
fakeBackgroundExecutor.advanceClockToLast()
|
||||
@@ -146,7 +153,7 @@ class MediaTttChipControllerTest : SysuiTestCase() {
|
||||
fun transferInitiated_futureCancelled_chipRemoved() {
|
||||
val future: SettableFuture<Runnable?> = SettableFuture.create()
|
||||
|
||||
mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, future))
|
||||
mediaTttChipController.displayChip(transferInitiated(future))
|
||||
|
||||
future.cancel(true)
|
||||
fakeBackgroundExecutor.advanceClockToLast()
|
||||
@@ -163,7 +170,7 @@ class MediaTttChipControllerTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun transferInitiated_futureNotResolvedAfterTimeout_chipRemoved() {
|
||||
val future: SettableFuture<Runnable?> = SettableFuture.create()
|
||||
mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, future))
|
||||
mediaTttChipController.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
|
||||
@@ -180,22 +187,28 @@ class MediaTttChipControllerTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun transferSucceededNullUndoRunnable_chipTextContainsDeviceName_noLoadingIcon_noUndo() {
|
||||
mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME, undoRunnable = null))
|
||||
fun transferSucceeded_appIcon_chipTextContainsDeviceName_noLoadingIcon() {
|
||||
mediaTttChipController.displayChip(transferSucceeded())
|
||||
|
||||
val chipView = getChipView()
|
||||
assertThat(chipView.getAppIconDrawable()).isEqualTo(appIconDrawable)
|
||||
assertThat(chipView.getChipText()).contains(DEVICE_NAME)
|
||||
assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun transferSucceededNullUndoRunnable_noUndo() {
|
||||
mediaTttChipController.displayChip(transferSucceeded(undoRunnable = null))
|
||||
|
||||
val chipView = getChipView()
|
||||
assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun transferSucceededWithUndoRunnable_chipTextContainsDeviceName_noLoadingIcon_undoWithClick() {
|
||||
mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME) { })
|
||||
fun transferSucceededWithUndoRunnable_undoWithClick() {
|
||||
mediaTttChipController.displayChip(transferSucceeded { })
|
||||
|
||||
val chipView = getChipView()
|
||||
assertThat(chipView.getChipText()).contains(DEVICE_NAME)
|
||||
assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
|
||||
assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
|
||||
assertThat(chipView.getUndoButton().hasOnClickListeners()).isTrue()
|
||||
}
|
||||
@@ -205,7 +218,7 @@ class MediaTttChipControllerTest : SysuiTestCase() {
|
||||
var runnableRun = false
|
||||
val runnable = Runnable { runnableRun = true }
|
||||
|
||||
mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME, runnable))
|
||||
mediaTttChipController.displayChip(transferSucceeded(undoRunnable = runnable))
|
||||
getChipView().getUndoButton().performClick()
|
||||
|
||||
assertThat(runnableRun).isTrue()
|
||||
@@ -213,36 +226,39 @@ class MediaTttChipControllerTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun changeFromCloserToTransferToTransferInitiated_loadingIconAppears() {
|
||||
mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME))
|
||||
mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, TEST_FUTURE))
|
||||
mediaTttChipController.displayChip(moveCloserToTransfer())
|
||||
mediaTttChipController.displayChip(transferInitiated())
|
||||
|
||||
assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun changeFromTransferInitiatedToTransferSucceeded_loadingIconDisappears() {
|
||||
mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, TEST_FUTURE))
|
||||
mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME))
|
||||
mediaTttChipController.displayChip(transferInitiated())
|
||||
mediaTttChipController.displayChip(transferSucceeded())
|
||||
|
||||
assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.GONE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun changeFromTransferInitiatedToTransferSucceeded_undoButtonAppears() {
|
||||
mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, TEST_FUTURE))
|
||||
mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME) { })
|
||||
mediaTttChipController.displayChip(transferInitiated())
|
||||
mediaTttChipController.displayChip(transferSucceeded { })
|
||||
|
||||
assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.VISIBLE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun changeFromTransferSucceededToMoveCloser_undoButtonDisappears() {
|
||||
mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME))
|
||||
mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME))
|
||||
mediaTttChipController.displayChip(transferSucceeded())
|
||||
mediaTttChipController.displayChip(moveCloserToTransfer())
|
||||
|
||||
assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.GONE)
|
||||
}
|
||||
|
||||
private fun LinearLayout.getAppIconDrawable(): Drawable =
|
||||
(this.requireViewById<ImageView>(R.id.app_icon)).drawable
|
||||
|
||||
private fun LinearLayout.getChipText(): String =
|
||||
(this.requireViewById<TextView>(R.id.text)).text as String
|
||||
|
||||
@@ -256,6 +272,19 @@ class MediaTttChipControllerTest : SysuiTestCase() {
|
||||
verify(windowManager).addView(viewCaptor.capture(), any())
|
||||
return viewCaptor.value as LinearLayout
|
||||
}
|
||||
|
||||
/** Helper method providing default parameters to not clutter up the tests. */
|
||||
private fun moveCloserToTransfer() = MoveCloserToTransfer(DEVICE_NAME, appIconDrawable)
|
||||
|
||||
/** Helper method providing default parameters to not clutter up the tests. */
|
||||
private fun transferInitiated(
|
||||
future: Future<Runnable?> = TEST_FUTURE
|
||||
) = TransferInitiated(DEVICE_NAME, appIconDrawable, future)
|
||||
|
||||
/** Helper method providing default parameters to not clutter up the tests. */
|
||||
private fun transferSucceeded(
|
||||
undoRunnable: Runnable? = null
|
||||
) = TransferSucceeded(DEVICE_NAME, appIconDrawable, undoRunnable)
|
||||
}
|
||||
|
||||
private const val DEVICE_NAME = "My Tablet"
|
||||
|
||||
@@ -49,7 +49,7 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
mediaTttCommandLineHelper =
|
||||
MediaTttCommandLineHelper(
|
||||
commandRegistry, mediaTttChipController, FakeExecutor(FakeSystemClock())
|
||||
commandRegistry, context, mediaTttChipController, FakeExecutor(FakeSystemClock())
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user