diff --git a/packages/SystemUI/res/drawable/media_ttt_chip_background_receiver.xml b/packages/SystemUI/res/drawable/media_ttt_chip_background_receiver.xml new file mode 100644 index 0000000000000..708bc1ac7e8a7 --- /dev/null +++ b/packages/SystemUI/res/drawable/media_ttt_chip_background_receiver.xml @@ -0,0 +1,26 @@ + + + + + + diff --git a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml new file mode 100644 index 0000000000000..88feacd9bbd99 --- /dev/null +++ b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml @@ -0,0 +1,32 @@ + + + + + + + + diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index bb9d8a2bcb7c5..44c38ffa3b805 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -981,7 +981,7 @@ 4dp 16sp - + 16dp 16sp 24dp @@ -989,6 +989,10 @@ 8dp -8dp + + 100dp + 95dp + 35dp 15dp diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index 1e96809b413ba..cffb2f79ebfb1 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -24,6 +24,7 @@ import com.android.systemui.SystemUIAppComponentFactory; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardSliceProvider; import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper; +import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver; import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender; import com.android.systemui.people.PeopleProvider; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -135,6 +136,7 @@ public interface SysUIComponent { getNaturalRotationUnfoldProgressProvider().ifPresent(o -> o.init()); // No init method needed, just needs to be gotten so that it's created. getMediaTttChipControllerSender(); + getMediaTttChipControllerReceiver(); getMediaTttCommandLineHelper(); getUnfoldLatencyTracker().init(); } @@ -192,6 +194,9 @@ public interface SysUIComponent { /** */ Optional getMediaTttChipControllerSender(); + /** */ + Optional getMediaTttChipControllerReceiver(); + /** */ Optional getMediaTttCommandLineHelper(); 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 d76d17910dc27..558f0e6342555 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java +++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java @@ -28,6 +28,7 @@ import com.android.systemui.media.MediaHost; import com.android.systemui.media.MediaHostStatesManager; 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.commandline.CommandRegistry; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -93,6 +94,19 @@ public interface MediaModule { context, windowManager, mainExecutor, backgroundExecutor)); } + /** */ + @Provides + @SysUISingleton + static Optional providesMediaTttChipControllerReceiver( + MediaTttFlags mediaTttFlags, + Context context, + WindowManager windowManager) { + if (!mediaTttFlags.isMediaTttEnabled()) { + return Optional.empty(); + } + return Optional.of(new MediaTttChipControllerReceiver(context, windowManager)); + } + /** */ @Provides @SysUISingleton @@ -101,11 +115,17 @@ public interface MediaModule { CommandRegistry commandRegistry, Context context, MediaTttChipControllerSender mediaTttChipControllerSender, + MediaTttChipControllerReceiver mediaTttChipControllerReceiver, @Main DelayableExecutor mainExecutor) { if (!mediaTttFlags.isMediaTttEnabled()) { return Optional.empty(); } - return Optional.of(new MediaTttCommandLineHelper( - commandRegistry, context, mediaTttChipControllerSender, mainExecutor)); + return Optional.of( + new MediaTttCommandLineHelper( + commandRegistry, + context, + mediaTttChipControllerSender, + mediaTttChipControllerReceiver, + mainExecutor)); } } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt index 93f7b7dff96ca..99b283e8ec028 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt @@ -17,12 +17,15 @@ package com.android.systemui.media.taptotransfer import android.content.Context +import android.graphics.Color 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.media.taptotransfer.receiver.MediaTttChipControllerReceiver +import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender import com.android.systemui.media.taptotransfer.sender.MoveCloserToTransfer import com.android.systemui.media.taptotransfer.sender.TransferInitiated @@ -43,17 +46,26 @@ class MediaTttCommandLineHelper @Inject constructor( commandRegistry: CommandRegistry, context: Context, private val mediaTttChipControllerSender: MediaTttChipControllerSender, + private val mediaTttChipControllerReceiver: MediaTttChipControllerReceiver, @Main private val mainExecutor: DelayableExecutor, ) { private val appIconDrawable = - Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context) + Icon.createWithResource(context, R.drawable.ic_avatar_user).loadDrawable(context).also { + it.setTint(Color.YELLOW) + } init { - commandRegistry.registerCommand(ADD_CHIP_COMMAND_TAG) { AddChipCommand() } - commandRegistry.registerCommand(REMOVE_CHIP_COMMAND_TAG) { RemoveChipCommand() } + commandRegistry.registerCommand( + ADD_CHIP_COMMAND_SENDER_TAG) { AddChipCommandSender() } + commandRegistry.registerCommand( + REMOVE_CHIP_COMMAND_SENDER_TAG) { RemoveChipCommandSender() } + commandRegistry.registerCommand( + ADD_CHIP_COMMAND_RECEIVER_TAG) { AddChipCommandReceiver() } + commandRegistry.registerCommand( + REMOVE_CHIP_COMMAND_RECEIVER_TAG) { RemoveChipCommandReceiver() } } - inner class AddChipCommand : Command { + inner class AddChipCommandSender : Command { override fun execute(pw: PrintWriter, args: List) { val otherDeviceName = args[0] when (args[1]) { @@ -86,18 +98,40 @@ class MediaTttCommandLineHelper @Inject constructor( } override fun help(pw: PrintWriter) { - pw.println( - "Usage: adb shell cmd statusbar $ADD_CHIP_COMMAND_TAG " + pw.println("Usage: adb shell cmd statusbar " + + "$ADD_CHIP_COMMAND_SENDER_TAG " ) } } - inner class RemoveChipCommand : Command { + /** A command to REMOVE the media ttt chip on the SENDER device. */ + inner class RemoveChipCommandSender : Command { override fun execute(pw: PrintWriter, args: List) { mediaTttChipControllerSender.removeChip() } override fun help(pw: PrintWriter) { - pw.println("Usage: adb shell cmd statusbar $REMOVE_CHIP_COMMAND_TAG") + pw.println("Usage: adb shell cmd statusbar $REMOVE_CHIP_COMMAND_SENDER_TAG") + } + } + + + /** A command to DISPLAY the media ttt chip on the RECEIVER device. */ + inner class AddChipCommandReceiver : Command { + override fun execute(pw: PrintWriter, args: List) { + mediaTttChipControllerReceiver.displayChip(ChipStateReceiver(appIconDrawable)) + } + override fun help(pw: PrintWriter) { + pw.println("Usage: adb shell cmd statusbar $ADD_CHIP_COMMAND_RECEIVER_TAG") + } + } + + /** A command to REMOVE the media ttt chip on the RECEIVER device. */ + inner class RemoveChipCommandReceiver : Command { + override fun execute(pw: PrintWriter, args: List) { + mediaTttChipControllerReceiver.removeChip() + } + override fun help(pw: PrintWriter) { + pw.println("Usage: adb shell cmd statusbar $REMOVE_CHIP_COMMAND_RECEIVER_TAG") } } @@ -107,9 +141,13 @@ class MediaTttCommandLineHelper @Inject constructor( } @VisibleForTesting -const val ADD_CHIP_COMMAND_TAG = "media-ttt-chip-add" +const val ADD_CHIP_COMMAND_SENDER_TAG = "media-ttt-chip-add-sender" @VisibleForTesting -const val REMOVE_CHIP_COMMAND_TAG = "media-ttt-chip-remove" +const val REMOVE_CHIP_COMMAND_SENDER_TAG = "media-ttt-chip-remove-sender" +@VisibleForTesting +const val ADD_CHIP_COMMAND_RECEIVER_TAG = "media-ttt-chip-add-receiver" +@VisibleForTesting +const val REMOVE_CHIP_COMMAND_RECEIVER_TAG = "media-ttt-chip-remove-receiver" @VisibleForTesting val MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME = MoveCloserToTransfer::class.simpleName!! @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt new file mode 100644 index 0000000000000..5397235e01375 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt @@ -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.receiver + +import android.graphics.drawable.Drawable +import com.android.systemui.media.taptotransfer.common.MediaTttChipState + +/** + * A class that stores all the information necessary to display the media tap-to-transfer chip on + * the receiver device. + */ +class ChipStateReceiver( + appIconDrawable: Drawable +) : MediaTttChipState(appIconDrawable) 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 new file mode 100644 index 0000000000000..17809548d1fdc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt @@ -0,0 +1,43 @@ +/* + * 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.receiver + +import android.content.Context +import android.view.ViewGroup +import android.view.WindowManager +import com.android.systemui.R +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon +import javax.inject.Inject + +/** + * A controller to display and hide the Media Tap-To-Transfer chip on the **receiving** device. + * + * This chip is shown when a user is transferring media to/from a sending device and this device. + */ +@SysUISingleton +class MediaTttChipControllerReceiver @Inject constructor( + context: Context, + windowManager: WindowManager, +) : MediaTttChipControllerCommon( + context, windowManager, R.layout.media_ttt_chip_receiver +) { + + override fun updateChipView(chipState: ChipStateReceiver, currentChipView: ViewGroup) { + setIcon(chipState, currentChipView) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt index b29e4b85f81c5..dec5a100e20d2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt @@ -18,6 +18,8 @@ package com.android.systemui.media.taptotransfer import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver +import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender import com.android.systemui.media.taptotransfer.sender.MoveCloserToTransfer import com.android.systemui.media.taptotransfer.sender.TransferInitiated @@ -47,6 +49,8 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { @Mock private lateinit var mediaTttChipControllerSender: MediaTttChipControllerSender + @Mock + private lateinit var mediaTttChipControllerReceiver: MediaTttChipControllerReceiver @Before fun setUp() { @@ -56,73 +60,106 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { commandRegistry, context, mediaTttChipControllerSender, + mediaTttChipControllerReceiver, FakeExecutor(FakeSystemClock()) ) } @Test(expected = IllegalStateException::class) - fun constructor_addCommandAlreadyRegistered() { + fun constructor_addSenderCommandAlreadyRegistered() { // Since creating the chip controller should automatically register the add command, it // should throw when registering it again. commandRegistry.registerCommand( - ADD_CHIP_COMMAND_TAG + ADD_CHIP_COMMAND_SENDER_TAG ) { EmptyCommand() } } @Test(expected = IllegalStateException::class) - fun constructor_removeCommandAlreadyRegistered() { + fun constructor_removeSenderCommandAlreadyRegistered() { // Since creating the chip controller should automatically register the remove command, it // should throw when registering it again. commandRegistry.registerCommand( - REMOVE_CHIP_COMMAND_TAG + REMOVE_CHIP_COMMAND_SENDER_TAG + ) { EmptyCommand() } + } + + @Test(expected = IllegalStateException::class) + fun constructor_addReceiverCommandAlreadyRegistered() { + // Since creating the chip controller should automatically register the add command, it + // should throw when registering it again. + commandRegistry.registerCommand( + ADD_CHIP_COMMAND_RECEIVER_TAG + ) { EmptyCommand() } + } + + @Test(expected = IllegalStateException::class) + fun constructor_removeReceiverCommandAlreadyRegistered() { + // Since creating the chip controller should automatically register the remove command, it + // should throw when registering it again. + commandRegistry.registerCommand( + REMOVE_CHIP_COMMAND_RECEIVER_TAG ) { EmptyCommand() } } @Test - fun moveCloserToTransfer_chipDisplayWithCorrectState() { + fun sender_moveCloserToTransfer_chipDisplayWithCorrectState() { commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand()) verify(mediaTttChipControllerSender).displayChip(any(MoveCloserToTransfer::class.java)) } @Test - fun transferInitiated_chipDisplayWithCorrectState() { + fun sender_transferInitiated_chipDisplayWithCorrectState() { commandRegistry.onShellCommand(pw, getTransferInitiatedCommand()) verify(mediaTttChipControllerSender).displayChip(any(TransferInitiated::class.java)) } @Test - fun transferSucceeded_chipDisplayWithCorrectState() { + fun sender_transferSucceeded_chipDisplayWithCorrectState() { commandRegistry.onShellCommand(pw, getTransferSucceededCommand()) verify(mediaTttChipControllerSender).displayChip(any(TransferSucceeded::class.java)) } @Test - fun removeCommand_chipRemoved() { - commandRegistry.onShellCommand(pw, arrayOf(REMOVE_CHIP_COMMAND_TAG)) + fun sender_removeCommand_chipRemoved() { + commandRegistry.onShellCommand(pw, arrayOf(REMOVE_CHIP_COMMAND_SENDER_TAG)) verify(mediaTttChipControllerSender).removeChip() } + @Test + fun receiver_addCommand_chipAdded() { + commandRegistry.onShellCommand(pw, arrayOf(ADD_CHIP_COMMAND_RECEIVER_TAG)) + + verify(mediaTttChipControllerReceiver).displayChip(any(ChipStateReceiver::class.java)) + } + + @Test + fun receiver_removeCommand_chipRemoved() { + commandRegistry.onShellCommand(pw, arrayOf(REMOVE_CHIP_COMMAND_RECEIVER_TAG)) + + verify(mediaTttChipControllerReceiver).removeChip() + } + private fun getMoveCloserToTransferCommand(): Array = arrayOf( - ADD_CHIP_COMMAND_TAG, + ADD_CHIP_COMMAND_SENDER_TAG, DEVICE_NAME, MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME ) private fun getTransferInitiatedCommand(): Array = arrayOf( - ADD_CHIP_COMMAND_TAG, + ADD_CHIP_COMMAND_SENDER_TAG, DEVICE_NAME, TRANSFER_INITIATED_COMMAND_NAME ) private fun getTransferSucceededCommand(): Array = arrayOf( - ADD_CHIP_COMMAND_TAG, + ADD_CHIP_COMMAND_SENDER_TAG, DEVICE_NAME, TRANSFER_SUCCEEDED_COMMAND_NAME ) 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 new file mode 100644 index 0000000000000..2ff472fc7abeb --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt @@ -0,0 +1,67 @@ +/* + * 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.receiver + +import android.graphics.drawable.Drawable +import android.graphics.drawable.Icon +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.ImageView +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.any +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations + +@SmallTest +class MediaTttChipControllerReceiverTest : SysuiTestCase() { + private lateinit var controllerReceiver: MediaTttChipControllerReceiver + + @Mock + private lateinit var windowManager: WindowManager + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + controllerReceiver = MediaTttChipControllerReceiver(context, windowManager) + } + + @Test + fun displayChip_chipContainsIcon() { + val drawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context) + + controllerReceiver.displayChip(ChipStateReceiver(drawable)) + + assertThat(getChipView().getAppIconDrawable()).isEqualTo(drawable) + } + + private fun getChipView(): ViewGroup { + val viewCaptor = ArgumentCaptor.forClass(View::class.java) + Mockito.verify(windowManager).addView(viewCaptor.capture(), any()) + return viewCaptor.value as ViewGroup + } + + private fun ViewGroup.getAppIconDrawable(): Drawable = + (this.requireViewById(R.id.app_icon)).drawable +}