/*
* Copyright 2024 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.wm.shell.compatui.letterbox
import android.content.Context
import android.graphics.Color
import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags
import com.android.wm.shell.dagger.WMSingleton
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_APP_COMPAT
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellCommandHandler.ShellCommandActionHandler
import com.android.wm.shell.sysui.ShellInit
import java.io.PrintWriter
import javax.inject.Inject
/**
* Handles the shell commands for the CompatUI.
*
*
Use with [adb shell wm shell letterbox <command>].
*/
@WMSingleton
class LetterboxCommandHandler
@Inject
constructor(
private val context: Context,
shellInit: ShellInit,
shellCommandHandler: ShellCommandHandler,
private val letterboxConfiguration: LetterboxConfiguration,
) : ShellCommandActionHandler {
companion object {
@JvmStatic private val TAG = "LetterboxCommandHandler"
}
init {
if (Flags.appCompatRefactoring()) {
ProtoLog.v(WM_SHELL_APP_COMPAT, "%s: %s", TAG, "Initializing LetterboxCommandHandler")
shellInit.addInitCallback(
{ shellCommandHandler.addCommandCallback("letterbox", this, this) },
this,
)
}
}
override fun onShellCommand(args: Array?, pw: PrintWriter?): Boolean {
if (args == null || pw == null) {
pw!!.println("Missing arguments.")
return false
}
return when (args.size) {
1 -> onNoParamsCommand(args[0], pw)
2 -> onSingleParamCommand(args[0], args[1], pw)
else -> {
pw.println("Invalid command: " + args[0])
return false
}
}
}
override fun printShellCommandHelp(pw: PrintWriter?, prefix: String?) {
pw?.println(
"""
$prefix backgroundColor color"
$prefix Color of letterbox which is to be used when letterbox background
$prefix type is 'solid-color'. See Color#parseColor for allowed color
$prefix formats (#RRGGBB and some colors by name, e.g. magenta or olive).
$prefix backgroundColorResource resource_name"
$prefix Color resource name of letterbox background which is used when
$prefix background type is 'solid-color'. Parameter is a color resource
$prefix name, for example, @android:color/system_accent2_50.
$prefix backgroundColorReset"
$prefix Resets the background color to the default value."
$prefix cornerRadius"
$prefix Corners radius (in pixels) for activities in the letterbox mode."
$prefix If cornerRadius < 0, it will be ignored and corners of the"
$prefix activity won't be rounded."
$prefix cornerRadiusReset"
$prefix Resets the rounded corners radius to the default value."
"""
.trimIndent()
)
}
private fun onSingleParamCommand(command: String, value: String, pw: PrintWriter): Boolean {
when (command) {
"backgroundColor" -> {
return invokeWhenValid(
pw,
value,
::strToColor,
{ color -> letterboxConfiguration.setLetterboxBackgroundColor(color) },
{ c -> "$c is not a valid color." },
)
}
"backgroundColorResource" ->
return invokeWhenValid(
pw,
value,
::nameToColorId,
{ color ->
letterboxConfiguration.setLetterboxBackgroundColorResourceId(color)
},
{ c ->
"$c is not a valid resource. Color in '@android:color/resource_name'" +
" format should be provided as an argument."
},
)
"cornerRadius" ->
return invokeWhenValid(
pw,
value,
::strToInt { it >= 0 },
{ radius -> letterboxConfiguration.setLetterboxActivityCornersRadius(radius) },
{ r -> "$r is not a valid radius. It must be an integer >= 0." },
)
else -> {
pw.println("Invalid command: $value")
return false
}
}
}
private fun onNoParamsCommand(command: String, pw: PrintWriter): Boolean {
when (command) {
"backgroundColor" -> {
pw.println(
" Background color: " +
Integer.toHexString(
letterboxConfiguration.getLetterboxBackgroundColor().toArgb()
)
)
return true
}
"backgroundColorReset" -> {
letterboxConfiguration.resetLetterboxBackgroundColor()
return true
}
"cornerRadius" -> {
pw.println(
" Rounded corners radius: " +
"${letterboxConfiguration.getLetterboxActivityCornersRadius()} px."
)
return true
}
"cornerRadiusReset" -> {
letterboxConfiguration.resetLetterboxActivityCornersRadius()
return true
}
else -> {
pw.println("Invalid command: $command")
return false
}
}
}
private fun invokeWhenValid(
pw: PrintWriter,
input: String,
converter: (String) -> T?,
consumer: (T) -> Unit,
errorMessage: (String) -> String = { value -> " Wrong input value: $value." },
): Boolean {
converter(input)?.let {
consumer(it)
return true
}
pw.println(errorMessage(input))
return false
}
// Converts a String to Color if possible or it returns null otherwise.
private fun strToColor(str: String): Color? =
try {
Color.valueOf(Color.parseColor(str))
} catch (e: IllegalArgumentException) {
null
}
// Converts a resource id to Color if possible or it returns null otherwise.
private fun nameToColorId(str: String): Int? =
try {
context.resources.getIdentifier(str, "color", "com.android.internal")
} catch (e: IllegalArgumentException) {
null
}
// Converts a String to Int which if possible or it returns null otherwise.
// If a predicate is set, it also returns [null] if the predicate evaluate to [false].
private fun strToInt(predicate: (Int) -> Boolean = { _ -> true }): (String) -> Int? = { str ->
try {
val value = str.toInt()
if (predicate(value)) value else null
} catch (e: IllegalArgumentException) {
null
}
}
}