Merge "[Media TTT] Define a chip controller and a way to issue commands via adb to control the chip."

This commit is contained in:
Caitlin Cassidy
2021-11-25 02:49:22 +00:00
committed by Android (Google) Code Review
6 changed files with 273 additions and 0 deletions

View File

@@ -23,6 +23,7 @@ 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.people.PeopleProvider;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
@@ -126,6 +127,8 @@ public interface SysUIComponent {
c.getUnfoldTransitionWallpaperController().init();
});
getNaturalRotationUnfoldProgressProvider().ifPresent(o -> o.init());
// No init method needed, just needs to be gotten so that it's created.
getMediaTttChipController();
}
/**
@@ -172,6 +175,9 @@ public interface SysUIComponent {
*/
Optional<NaturalRotationUnfoldProgressProvider> getNaturalRotationUnfoldProgressProvider();
/** */
Optional<MediaTttChipController> getMediaTttChipController();
/**
* Member injection into the supplied argument.
*/

View File

@@ -114,6 +114,10 @@ public class Flags {
public static final BooleanFlag MONET =
new BooleanFlag(800, true, R.bool.flag_monet);
/***************************************/
// 900 - media
public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, false);
// Pay no attention to the reflection behind the curtain.
// ========================== Curtain ==========================
// | |

View File

@@ -16,11 +16,19 @@
package com.android.systemui.media.dagger;
import android.content.Context;
import android.view.WindowManager;
import com.android.systemui.dagger.SysUISingleton;
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.MediaTttFlags;
import com.android.systemui.statusbar.commandline.CommandRegistry;
import java.util.Optional;
import javax.inject.Named;
@@ -63,4 +71,18 @@ public interface MediaModule {
MediaHostStatesManager statesManager) {
return new MediaHost(stateHolder, hierarchyManager, dataManager, statesManager);
}
/** */
@Provides
@SysUISingleton
static Optional<MediaTttChipController> providesMediaTttChipController(
MediaTttFlags mediaTttFlags,
Context context,
CommandRegistry commandRegistry,
WindowManager windowManager) {
if (!mediaTttFlags.isMediaTttEnabled()) {
return Optional.empty();
}
return Optional.of(new MediaTttChipController(context, commandRegistry, windowManager));
}
}

View File

@@ -0,0 +1,99 @@
/*
* 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
import android.content.Context
import android.graphics.PixelFormat
import android.view.Gravity
import android.view.WindowManager
import android.widget.TextView
import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import java.io.PrintWriter
import javax.inject.Inject
/**
* 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.
*/
@SysUISingleton
class MediaTttChipController @Inject constructor(
context: Context,
commandRegistry: CommandRegistry,
private val windowManager: WindowManager,
) {
init {
commandRegistry.registerCommand(ADD_CHIP_COMMAND_TAG) { AddChipCommand() }
commandRegistry.registerCommand(REMOVE_CHIP_COMMAND_TAG) { RemoveChipCommand() }
}
private val windowLayoutParams = WindowManager.LayoutParams().apply {
width = WindowManager.LayoutParams.MATCH_PARENT
height = WindowManager.LayoutParams.MATCH_PARENT
gravity = Gravity.CENTER_HORIZONTAL
type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY
title = "Media Tap-To-Transfer Chip View"
flags = (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
format = PixelFormat.TRANSLUCENT
setTrustedOverlay()
}
// TODO(b/203800327): Create a layout that matches UX.
private val chipView: TextView = TextView(context).apply {
text = "Media Tap-To-Transfer Chip"
}
private var chipDisplaying: Boolean = false
private fun addChip() {
if (chipDisplaying) { return }
windowManager.addView(chipView, windowLayoutParams)
chipDisplaying = true
}
private fun removeChip() {
if (!chipDisplaying) { return }
windowManager.removeView(chipView)
chipDisplaying = false
}
inner class AddChipCommand : Command {
override fun execute(pw: PrintWriter, args: List<String>) = addChip()
override fun help(pw: PrintWriter) {
pw.println("Usage: adb shell cmd statusbar $ADD_CHIP_COMMAND_TAG")
}
}
inner class RemoveChipCommand : Command {
override fun execute(pw: PrintWriter, args: List<String>) = removeChip()
override fun help(pw: PrintWriter) {
pw.println("Usage: adb shell cmd statusbar $REMOVE_CHIP_COMMAND_TAG")
}
}
companion object {
@VisibleForTesting
const val ADD_CHIP_COMMAND_TAG = "media-ttt-chip-add"
@VisibleForTesting
const val REMOVE_CHIP_COMMAND_TAG = "media-ttt-chip-remove"
}
}

View File

@@ -0,0 +1,29 @@
/*
* 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
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import javax.inject.Inject
/** Flags related to media tap-to-transfer. */
@SysUISingleton
class MediaTttFlags @Inject constructor(private val featureFlags: FeatureFlags) {
/** */
fun isMediaTttEnabled(): Boolean = featureFlags.isEnabled(Flags.MEDIA_TAP_TO_TRANSFER)
}

View File

@@ -0,0 +1,113 @@
/*
* 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
import android.view.WindowManager
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
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
import java.io.PrintWriter
import java.io.StringWriter
import java.util.concurrent.Executor
@SmallTest
class MediaTttChipControllerTest : SysuiTestCase() {
private lateinit var mediaTttChipController: MediaTttChipController
private val inlineExecutor = Executor { command -> command.run() }
private val commandRegistry = CommandRegistry(context, inlineExecutor)
private val pw = PrintWriter(StringWriter())
@Mock
private lateinit var windowManager: WindowManager
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
mediaTttChipController = MediaTttChipController(context, commandRegistry, windowManager)
}
@Test(expected = IllegalStateException::class)
fun constructor_addCommmandAlreadyRegistered() {
// Since creating the chip controller should automatically register the add command, it
// should throw when registering it again.
commandRegistry.registerCommand(
MediaTttChipController.ADD_CHIP_COMMAND_TAG
) { EmptyCommand() }
}
@Test(expected = IllegalStateException::class)
fun constructor_removeCommmandAlreadyRegistered() {
// Since creating the chip controller should automatically register the remove command, it
// should throw when registering it again.
commandRegistry.registerCommand(
MediaTttChipController.REMOVE_CHIP_COMMAND_TAG
) { EmptyCommand() }
}
@Test
fun addChipCommand_chipAdded() {
commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.ADD_CHIP_COMMAND_TAG))
verify(windowManager).addView(any(), any())
}
@Test
fun addChipCommand_twice_chipNotAddedTwice() {
commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.ADD_CHIP_COMMAND_TAG))
reset(windowManager)
commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.ADD_CHIP_COMMAND_TAG))
verify(windowManager, never()).addView(any(), any())
}
@Test
fun removeChipCommand_chipRemoved() {
// First, add the chip
commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.ADD_CHIP_COMMAND_TAG))
// Then, remove it
commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.REMOVE_CHIP_COMMAND_TAG))
verify(windowManager).removeView(any())
}
@Test
fun removeChipCommand_noAdd_viewNotRemoved() {
commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.REMOVE_CHIP_COMMAND_TAG))
verify(windowManager, never()).removeView(any())
}
class EmptyCommand : Command {
override fun execute(pw: PrintWriter, args: List<String>) {
}
override fun help(pw: PrintWriter) {
}
}
}