1 /* <lambda>null2 * Copyright 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.wm.shell.compatui.letterbox 18 19 import android.content.Context 20 import android.graphics.Color 21 import com.android.internal.protolog.ProtoLog 22 import com.android.window.flags.Flags 23 import com.android.wm.shell.dagger.WMSingleton 24 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_APP_COMPAT 25 import com.android.wm.shell.sysui.ShellCommandHandler 26 import com.android.wm.shell.sysui.ShellCommandHandler.ShellCommandActionHandler 27 import com.android.wm.shell.sysui.ShellInit 28 import java.io.PrintWriter 29 import javax.inject.Inject 30 31 /** 32 * Handles the shell commands for the CompatUI. 33 * 34 * <p> Use with [adb shell wm shell letterbox <command>]. 35 */ 36 @WMSingleton 37 class LetterboxCommandHandler @Inject constructor( 38 private val context: Context, 39 shellInit: ShellInit, 40 shellCommandHandler: ShellCommandHandler, 41 private val letterboxConfiguration: LetterboxConfiguration 42 ) : ShellCommandActionHandler { 43 44 companion object { 45 @JvmStatic 46 private val TAG = "LetterboxCommandHandler" 47 } 48 49 init { 50 if (Flags.appCompatRefactoring()) { 51 ProtoLog.v( 52 WM_SHELL_APP_COMPAT, 53 "%s: %s", 54 TAG, 55 "Initializing LetterboxCommandHandler" 56 ) 57 shellInit.addInitCallback({ 58 shellCommandHandler.addCommandCallback("letterbox", this, this) 59 }, this) 60 } 61 } 62 63 override fun onShellCommand(args: Array<out String>?, pw: PrintWriter?): Boolean { 64 if (args == null || pw == null) { 65 pw!!.println("Missing arguments.") 66 return false 67 } 68 return when (args.size) { 69 1 -> onNoParamsCommand(args[0], pw) 70 2 -> onSingleParamCommand(args[0], args[1], pw) 71 else -> { 72 pw.println("Invalid command: " + args[0]) 73 return false 74 } 75 } 76 } 77 78 override fun printShellCommandHelp(pw: PrintWriter?, prefix: String?) { 79 pw?.println( 80 """ 81 $prefix backgroundColor color" 82 $prefix Color of letterbox which is to be used when letterbox background 83 $prefix type is 'solid-color'. See Color#parseColor for allowed color 84 $prefix formats (#RRGGBB and some colors by name, e.g. magenta or olive). 85 $prefix backgroundColorResource resource_name" 86 $prefix Color resource name of letterbox background which is used when 87 $prefix background type is 'solid-color'. Parameter is a color resource 88 $prefix name, for example, @android:color/system_accent2_50. 89 $prefix backgroundColorReset" 90 $prefix Resets the background color to the default value." 91 $prefix cornerRadius" 92 $prefix Corners radius (in pixels) for activities in the letterbox mode." 93 $prefix If cornerRadius < 0, it will be ignored and corners of the" 94 $prefix activity won't be rounded." 95 $prefix cornerRadiusReset" 96 $prefix Resets the rounded corners radius to the default value." 97 """.trimIndent() 98 ) 99 } 100 101 private fun onSingleParamCommand(command: String, value: String, pw: PrintWriter): Boolean { 102 when (command) { 103 "backgroundColor" -> { 104 return invokeWhenValid( 105 pw, 106 value, 107 ::strToColor, 108 { color -> 109 letterboxConfiguration.setLetterboxBackgroundColor(color) 110 }, 111 { c -> "$c is not a valid color." } 112 ) 113 } 114 115 "backgroundColorResource" -> return invokeWhenValid( 116 pw, 117 value, 118 ::nameToColorId, 119 { color -> 120 letterboxConfiguration.setLetterboxBackgroundColorResourceId(color) 121 }, 122 { c -> 123 "$c is not a valid resource. Color in '@android:color/resource_name'" + 124 " format should be provided as an argument." 125 } 126 ) 127 128 "cornerRadius" -> return invokeWhenValid( 129 pw, 130 value, 131 ::strToInt{ it >= 0 }, 132 { radius -> 133 letterboxConfiguration.setLetterboxActivityCornersRadius(radius) 134 }, 135 { r -> 136 "$r is not a valid radius. It must be an integer >= 0." 137 } 138 ) 139 140 else -> { 141 pw.println("Invalid command: $value") 142 return false 143 } 144 } 145 } 146 147 private fun onNoParamsCommand(command: String, pw: PrintWriter): Boolean { 148 when (command) { 149 "backgroundColor" -> { 150 pw.println( 151 " Background color: " + Integer.toHexString( 152 letterboxConfiguration.getLetterboxBackgroundColor() 153 .toArgb() 154 ) 155 ) 156 return true 157 } 158 159 "backgroundColorReset" -> { 160 letterboxConfiguration.resetLetterboxBackgroundColor() 161 return true 162 } 163 164 "cornerRadius" -> { 165 pw.println( 166 " Rounded corners radius: " + 167 "${letterboxConfiguration.getLetterboxActivityCornersRadius()} px." 168 ) 169 return true 170 } 171 172 "cornerRadiusReset" -> { 173 letterboxConfiguration.resetLetterboxActivityCornersRadius() 174 return true 175 } 176 177 else -> { 178 pw.println("Invalid command: $command") 179 return false 180 } 181 } 182 } 183 184 private fun <T> invokeWhenValid( 185 pw: PrintWriter, 186 input: String, 187 converter: (String) -> T?, 188 consumer: (T) -> Unit, 189 errorMessage: (String) -> String = { value -> " Wrong input value: $value." } 190 ): Boolean { 191 converter(input)?.let { 192 consumer(it) 193 return true 194 } 195 pw.println(errorMessage(input)) 196 return false 197 } 198 199 // Converts a String to Color if possible or it returns null otherwise. 200 private fun strToColor(str: String): Color? = 201 try { 202 Color.valueOf(Color.parseColor(str)) 203 } catch (e: IllegalArgumentException) { 204 null 205 } 206 207 // Converts a resource id to Color if possible or it returns null otherwise. 208 private fun nameToColorId(str: String): Int? = 209 try { 210 context.resources.getIdentifier(str, "color", "com.android.internal") 211 } catch (e: IllegalArgumentException) { 212 null 213 } 214 215 // Converts a String to Int which if possible or it returns null otherwise. 216 // If a predicate is set, it also returns [null] if the predicate evaluate to [false]. 217 private fun strToInt(predicate: (Int) -> Boolean = { _ -> true }): (String) -> Int? = { str -> 218 try { 219 val value = str.toInt() 220 if (predicate(value)) value else null 221 } catch (e: IllegalArgumentException) { 222 null 223 } 224 } 225 } 226