Merge "[Media TTT] Define a chip controller and a way to issue commands via adb to control the chip."
This commit is contained in:
committed by
Android (Google) Code Review
commit
2f1acdde61
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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 ==========================
|
||||
// | |
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user