Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2f9fbcfb authored by Evan Laird's avatar Evan Laird Committed by Automerger Merge Worker
Browse files

Merge changes I7bffeb56,I945e5086,Ic801c654,I50c54322 into udc-qpr-dev am:...

Merge changes I7bffeb56,I945e5086,Ic801c654,I50c54322 into udc-qpr-dev am: b9309781 am: d6ca8b2c

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23814723



Change-Id: I5911fd6ce49a3687af2d21aa072cc8756f02d43c
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents 4b766168 d6ca8b2c
Loading
Loading
Loading
Loading
+29 −3
Original line number Diff line number Diff line
@@ -56,9 +56,14 @@ class ScreenDecorHwcLayer(
) : DisplayCutoutBaseView(context) {
    val colorMode: Int
    private val useInvertedAlphaColor: Boolean
    private val color: Int
    private var color: Int = Color.BLACK
        set(value) {
            field = value
            paint.color = value
        }

    private val bgColor: Int
    private val cornerFilter: ColorFilter
    private var cornerFilter: ColorFilter
    private val cornerBgFilter: ColorFilter
    private val clearPaint: Paint
    @JvmField val transparentRect: Rect = Rect()
@@ -109,10 +114,16 @@ class ScreenDecorHwcLayer(
    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        parent.requestTransparentRegion(this)
        updateColors()
    }

    private fun updateColors() {
        if (!debug) {
            viewRootImpl.setDisplayDecoration(true)
        }

        cornerFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)

        if (useInvertedAlphaColor) {
            paint.set(clearPaint)
        } else {
@@ -121,6 +132,21 @@ class ScreenDecorHwcLayer(
        }
    }

    fun setDebugColor(color: Int) {
        if (!debug) {
            return
        }

        if (this.color == color) {
            return
        }

        this.color = color

        updateColors()
        invalidate()
    }

    override fun onUpdate() {
        parent.requestTransparentRegion(this)
    }
@@ -367,7 +393,7 @@ class ScreenDecorHwcLayer(
    /**
     * Update the rounded corner drawables.
     */
    fun updateRoundedCornerDrawable(top: Drawable, bottom: Drawable) {
    fun updateRoundedCornerDrawable(top: Drawable?, bottom: Drawable?) {
        roundedCornerDrawableTop = top
        roundedCornerDrawableBottom = bottom
        updateRoundedCornerDrawableBounds()
+48 −6
Original line number Diff line number Diff line
@@ -69,9 +69,9 @@ import com.android.internal.util.Preconditions;
import com.android.settingslib.Utils;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.decor.CutoutDecorProviderFactory;
import com.android.systemui.decor.DebugRoundedCornerDelegate;
import com.android.systemui.decor.DebugRoundedCornerModel;
import com.android.systemui.decor.DecorProvider;
import com.android.systemui.decor.DecorProviderFactory;
import com.android.systemui.decor.DecorProviderKt;
@@ -80,10 +80,12 @@ import com.android.systemui.decor.OverlayWindow;
import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
import com.android.systemui.decor.RoundedCornerDecorProviderFactory;
import com.android.systemui.decor.RoundedCornerResDelegateImpl;
import com.android.systemui.decor.ScreenDecorCommand;
import com.android.systemui.log.ScreenDecorationsLogger;
import com.android.systemui.qs.SettingObserver;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.statusbar.events.PrivacyDotViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.ThreadFactory;
@@ -95,7 +97,6 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;

import javax.inject.Inject;

@@ -130,7 +131,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable {
    @VisibleForTesting
    protected boolean mIsRegistered;
    private final Context mContext;
    private final Executor mMainExecutor;
    private final CommandRegistry mCommandRegistry;
    private final SecureSettings mSecureSettings;
    @VisibleForTesting
    DisplayTracker.Callback mDisplayListener;
@@ -313,8 +314,8 @@ public class ScreenDecorations implements CoreStartable, Dumpable {

    @Inject
    public ScreenDecorations(Context context,
            @Main Executor mainExecutor,
            SecureSettings secureSettings,
            CommandRegistry commandRegistry,
            UserTracker userTracker,
            DisplayTracker displayTracker,
            PrivacyDotViewController dotViewController,
@@ -324,8 +325,8 @@ public class ScreenDecorations implements CoreStartable, Dumpable {
            ScreenDecorationsLogger logger,
            AuthController authController) {
        mContext = context;
        mMainExecutor = mainExecutor;
        mSecureSettings = secureSettings;
        mCommandRegistry = commandRegistry;
        mUserTracker = userTracker;
        mDisplayTracker = displayTracker;
        mDotViewController = dotViewController;
@@ -350,6 +351,45 @@ public class ScreenDecorations implements CoreStartable, Dumpable {
        }
    };

    private final ScreenDecorCommand.Callback mScreenDecorCommandCallback = (cmd, pw) -> {
        // If we are exiting debug mode, we can set it (false) and bail, otherwise we will
        // ensure that debug mode is set
        if (cmd.getDebug() != null && !cmd.getDebug()) {
            setDebug(false);
            return;
        } else {
            // setDebug is idempotent
            setDebug(true);
        }

        if (cmd.getColor() != null) {
            mDebugColor = cmd.getColor();
            mExecutor.execute(() -> {
                if (mScreenDecorHwcLayer != null) {
                    mScreenDecorHwcLayer.setDebugColor(cmd.getColor());
                }
                updateColorInversionDefault();
            });
        }

        DebugRoundedCornerModel roundedTop = null;
        DebugRoundedCornerModel roundedBottom = null;
        if (cmd.getRoundedTop() != null) {
            roundedTop = cmd.getRoundedTop().toRoundedCornerDebugModel();
        }
        if (cmd.getRoundedBottom() != null) {
            roundedBottom = cmd.getRoundedBottom().toRoundedCornerDebugModel();
        }
        if (roundedTop != null || roundedBottom != null) {
            mDebugRoundedCornerDelegate.applyNewDebugCorners(roundedTop, roundedBottom);
            mExecutor.execute(() -> {
                removeAllOverlays();
                removeHwcOverlay();
                setupDecorations();
            });
        }
    };

    @Override
    public void start() {
        if (DEBUG_DISABLE_SCREEN_DECORATIONS) {
@@ -361,6 +401,8 @@ public class ScreenDecorations implements CoreStartable, Dumpable {
        mExecutor.execute(this::startOnScreenDecorationsThread);
        mDotViewController.setUiExecutor(mExecutor);
        mAuthController.addCallback(mAuthControllerCallback);
        mCommandRegistry.registerCommand(ScreenDecorCommand.SCREEN_DECOR_CMD_NAME,
                () -> new ScreenDecorCommand(mScreenDecorCommandCallback));
    }

    /**
@@ -1228,7 +1270,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable {
            bottomDrawable = mDebugRoundedCornerDelegate.getBottomRoundedDrawable();
        }

        if (topDrawable == null || bottomDrawable == null) {
        if (topDrawable == null && bottomDrawable == null) {
            return;
        }
        mScreenDecorHwcLayer.updateRoundedCornerDrawable(topDrawable, bottomDrawable);
+22 −8
Original line number Diff line number Diff line
@@ -77,16 +77,30 @@ class DebugRoundedCornerDelegate : RoundedCornerResDelegate {
    }

    fun applyNewDebugCorners(
        topCorner: DebugRoundedCornerModel,
        bottomCorner: DebugRoundedCornerModel,
        topCorner: DebugRoundedCornerModel?,
        bottomCorner: DebugRoundedCornerModel?,
    ) {
        topCorner?.let {
            hasTop = true
        topRoundedDrawable = topCorner.toPathDrawable(paint)
        topRoundedSize = topCorner.size()
            topRoundedDrawable = it.toPathDrawable(paint)
            topRoundedSize = it.size()
        }
            ?: {
                hasTop = false
                topRoundedDrawable = null
                topRoundedSize = Size(0, 0)
            }

        bottomCorner?.let {
            hasBottom = true
        bottomRoundedDrawable = bottomCorner.toPathDrawable(paint)
        bottomRoundedSize = bottomCorner.size()
            bottomRoundedDrawable = it.toPathDrawable(paint)
            bottomRoundedSize = it.size()
        }
            ?: {
                hasBottom = false
                bottomRoundedDrawable = null
                bottomRoundedSize = Size(0, 0)
            }
    }

    /**
+171 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.decor

import android.graphics.Color
import android.graphics.Path
import android.util.PathParser
import com.android.systemui.statusbar.commandline.ParseableCommand
import com.android.systemui.statusbar.commandline.Type
import com.android.systemui.statusbar.commandline.map
import java.io.PrintWriter

/** Debug screen-decor command to be handled by the SystemUI command line interface */
class ScreenDecorCommand(
    private val callback: Callback,
) : ParseableCommand(SCREEN_DECOR_CMD_NAME) {
    val debug: Boolean? by
        param(
            longName = "debug",
            description =
                "Enter or exits debug mode. Effectively makes the corners visible and allows " +
                    "for overriding the path data for the anti-aliasing corner paths and display " +
                    "cutout.",
            valueParser = Type.Boolean,
        )

    val color: Int? by
        param(
            longName = "color",
            shortName = "c",
            description =
                "Set a specific color for the debug assets. See Color#parseString() for " +
                    "accepted inputs.",
            valueParser = Type.String.map { it.toColorIntOrNull() }
        )

    val roundedTop: RoundedCornerSubCommand? by subCommand(RoundedCornerSubCommand("rounded-top"))

    val roundedBottom: RoundedCornerSubCommand? by
        subCommand(RoundedCornerSubCommand("rounded-bottom"))

    override fun execute(pw: PrintWriter) {
        callback.onExecute(this, pw)
    }

    override fun toString(): String {
        return "ScreenDecorCommand(" +
            "debug=$debug, " +
            "color=$color, " +
            "roundedTop=$roundedTop, " +
            "roundedBottom=$roundedBottom)"
    }

    /** For use in ScreenDecorations.java, define a Callback */
    interface Callback {
        fun onExecute(cmd: ScreenDecorCommand, pw: PrintWriter)
    }

    companion object {
        const val SCREEN_DECOR_CMD_NAME = "screen-decor"
    }
}

/**
 * Defines a subcommand suitable for `rounded-top` and `rounded-bottom`. They both have the same
 * API.
 */
class RoundedCornerSubCommand(name: String) : ParseableCommand(name) {
    val height by
        param(
                longName = "height",
                description = "The height of a corner, in pixels.",
                valueParser = Type.Int,
            )
            .required()

    val width by
        param(
                longName = "width",
                description =
                    "The width of the corner, in pixels. Likely should be equal to the height.",
                valueParser = Type.Int,
            )
            .required()

    val pathData by
        param(
                longName = "path-data",
                shortName = "d",
                description =
                    "PathParser-compatible path string to be rendered as the corner drawable. " +
                        "This path should be a closed arc oriented as the top-left corner " +
                        "of the device",
                valueParser = Type.String.map { it.toPathOrNull() }
            )
            .required()

    val viewportHeight: Float? by
        param(
            longName = "viewport-height",
            description =
                "The height of the viewport for the given path string. " +
                    "If null, the corner height will be used.",
            valueParser = Type.Float,
        )

    val scaleY: Float
        get() = viewportHeight?.let { height.toFloat() / it } ?: 1.0f

    val viewportWidth: Float? by
        param(
            longName = "viewport-width",
            description =
                "The width of the viewport for the given path string. " +
                    "If null, the corner width will be used.",
            valueParser = Type.Float,
        )

    val scaleX: Float
        get() = viewportWidth?.let { width.toFloat() / it } ?: 1.0f

    override fun execute(pw: PrintWriter) {
        // Not needed for a subcommand
    }

    override fun toString(): String {
        return "RoundedCornerSubCommand(" +
            "height=$height," +
            " width=$width," +
            " pathData='$pathData'," +
            " viewportHeight=$viewportHeight," +
            " viewportWidth=$viewportWidth)"
    }

    fun toRoundedCornerDebugModel(): DebugRoundedCornerModel =
        DebugRoundedCornerModel(
            path = pathData,
            width = width,
            height = height,
            scaleX = scaleX,
            scaleY = scaleY,
        )
}

fun String.toPathOrNull(): Path? =
    try {
        PathParser.createPathFromPathData(this)
    } catch (e: Exception) {
        null
    }

fun String.toColorIntOrNull(): Int? =
    try {
        Color.parseColor(this)
    } catch (e: Exception) {
        null
    }
+327 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.statusbar.commandline

/**
 * [CommandParser] defines the collection of tokens which can be parsed from an incoming command
 * list, and parses them into their respective containers. Supported tokens are of the following
 * forms:
 * ```
 * Flag: boolean value, false by default. always optional.
 * Param: named parameter, taking N args all of a given type. Currently only single arg parameters
 *        are supported.
 * SubCommand: named command created by adding a command to a parent. Supports all fields above, but
 *             not other subcommands.
 * ```
 *
 * Tokens are added via the factory methods for each token type. They can be made `required` by
 * calling the [require] method for the appropriate type, as follows:
 * ```
 * val requiredParam = parser.require(parser.param(...))
 * ```
 *
 * The reason for having an explicit require is so that generic type arguments can be handled
 * properly. See [SingleArgParam] and [SingleArgParamOptional] for the difference between an
 * optional parameter and a required one.
 *
 * Typical usage of a required parameter, however, will occur within the context of a
 * [ParseableCommand], which defines a convenience `require()` method:
 * ```
 * class MyCommand : ParseableCommand {
 *   val requiredParam = param(...).require()
 * }
 * ```
 *
 * This parser defines two modes of parsing, both of which validate for required parameters.
 * 1. [parse] is a top-level parsing method. This parser will walk the given arg list and populate
 *    all of the delegate classes based on their type. It will handle SubCommands, and after parsing
 *    will check for any required-but-missing SubCommands or Params.
 *
 *    **This method requires that every received token is represented in its grammar.**
 * 2. [parseAsSubCommand] is a second-level parsing method suitable for any [SubCommand]. This
 *    method will handle _only_ flags and params. It will return parsing control to its parent
 *    parser on the first unknown token rather than throwing.
 */
class CommandParser {
    private val _flags = mutableListOf<Flag>()
    val flags: List<Flag> = _flags
    private val _params = mutableListOf<Param>()
    val params: List<Param> = _params
    private val _subCommands = mutableListOf<SubCommand>()
    val subCommands: List<SubCommand> = _subCommands

    private val tokenSet = mutableSetOf<String>()

    /**
     * Parse the arg list into the fields defined in the containing class.
     *
     * @return true if all required fields are present after parsing
     * @throws ArgParseError on any failure to process args
     */
    fun parse(args: List<String>): Boolean {
        if (args.isEmpty()) {
            return false
        }

        val iterator = args.listIterator()
        var tokenHandled: Boolean
        while (iterator.hasNext()) {
            val token = iterator.next()
            tokenHandled = false

            flags
                .find { it.matches(token) }
                ?.let {
                    it.inner = true
                    tokenHandled = true
                }

            if (tokenHandled) continue

            params
                .find { it.matches(token) }
                ?.let {
                    it.parseArgsFromIter(iterator)
                    tokenHandled = true
                }

            if (tokenHandled) continue

            subCommands
                .find { it.matches(token) }
                ?.let {
                    it.parseSubCommandArgs(iterator)
                    tokenHandled = true
                }

            if (!tokenHandled) {
                throw ArgParseError("Unknown token: $token")
            }
        }

        return validateRequiredParams()
    }

    /**
     * Parse a subset of the commands that came in from the top-level [parse] method, for the
     * subcommand that this parser represents. Note that subcommands may not contain other
     * subcommands. But they may contain flags and params.
     *
     * @return true if all required fields are present after parsing
     * @throws ArgParseError on any failure to process args
     */
    fun parseAsSubCommand(iter: ListIterator<String>): Boolean {
        // arg[-1] is our subcommand name, so the rest of the args are either for this
        // subcommand, OR for the top-level command to handle. Therefore, we bail on the first
        // failure, but still check our own required params

        // The mere presence of a subcommand (similar to a flag) is a valid subcommand
        if (flags.isEmpty() && params.isEmpty()) {
            return validateRequiredParams()
        }

        var tokenHandled: Boolean
        while (iter.hasNext()) {
            val token = iter.next()
            tokenHandled = false

            flags
                .find { it.matches(token) }
                ?.let {
                    it.inner = true
                    tokenHandled = true
                }

            if (tokenHandled) continue

            params
                .find { it.matches(token) }
                ?.let {
                    it.parseArgsFromIter(iter)
                    tokenHandled = true
                }

            if (!tokenHandled) {
                // Move the cursor position backwards since we've arrived at a token
                // that we don't own
                iter.previous()
                break
            }
        }

        return validateRequiredParams()
    }

    /**
     * If [parse] or [parseAsSubCommand] does not produce a valid result, generate a list of errors
     * based on missing elements
     */
    fun generateValidationErrorMessages(): List<String> {
        val missingElements = mutableListOf<String>()

        if (unhandledParams.isNotEmpty()) {
            val names = unhandledParams.map { it.longName }
            missingElements.add("No values passed for required params: $names")
        }

        if (unhandledSubCmds.isNotEmpty()) {
            missingElements.addAll(unhandledSubCmds.map { it.longName })
            val names = unhandledSubCmds.map { it.shortName }
            missingElements.add("No values passed for required sub-commands: $names")
        }

        return missingElements
    }

    /** Check for any missing, required params, or any invalid subcommands */
    private fun validateRequiredParams(): Boolean =
        unhandledParams.isEmpty() && unhandledSubCmds.isEmpty() && unvalidatedSubCmds.isEmpty()

    // If any required param (aka non-optional) hasn't handled a field, then return false
    private val unhandledParams: List<Param>
        get() = params.filter { (it is SingleArgParam<*>) && !it.handled }

    private val unhandledSubCmds: List<SubCommand>
        get() = subCommands.filter { (it is RequiredSubCommand<*> && !it.handled) }

    private val unvalidatedSubCmds: List<SubCommand>
        get() = subCommands.filter { !it.validationStatus }

    private fun checkCliNames(short: String?, long: String): String? {
        if (short != null && tokenSet.contains(short)) {
            return short
        }

        if (tokenSet.contains(long)) {
            return long
        }

        return null
    }

    private fun subCommandContainsSubCommands(cmd: ParseableCommand): Boolean =
        cmd.parser.subCommands.isNotEmpty()

    private fun registerNames(short: String?, long: String) {
        if (short != null) {
            tokenSet.add(short)
        }
        tokenSet.add(long)
    }

    /**
     * Turns a [SingleArgParamOptional]<T> into a [SingleArgParam] by converting the [T?] into [T]
     *
     * @return a [SingleArgParam] property delegate
     */
    fun <T : Any> require(old: SingleArgParamOptional<T>): SingleArgParam<T> {
        val newParam =
            SingleArgParam(
                longName = old.longName,
                shortName = old.shortName,
                description = old.description,
                valueParser = old.valueParser,
            )

        replaceWithRequired(old, newParam)
        return newParam
    }

    private fun <T : Any> replaceWithRequired(
        old: SingleArgParamOptional<T>,
        new: SingleArgParam<T>,
    ) {
        _params.remove(old)
        _params.add(new)
    }

    /**
     * Turns an [OptionalSubCommand] into a [RequiredSubCommand] by converting the [T?] in to [T]
     *
     * @return a [RequiredSubCommand] property delegate
     */
    fun <T : ParseableCommand> require(optional: OptionalSubCommand<T>): RequiredSubCommand<T> {
        val newCmd = RequiredSubCommand(optional.cmd)
        replaceWithRequired(optional, newCmd)
        return newCmd
    }

    private fun <T : ParseableCommand> replaceWithRequired(
        old: OptionalSubCommand<T>,
        new: RequiredSubCommand<T>,
    ) {
        _subCommands.remove(old)
        _subCommands.add(new)
    }

    internal fun flag(
        longName: String,
        shortName: String? = null,
        description: String = "",
    ): Flag {
        checkCliNames(shortName, longName)?.let {
            throw IllegalArgumentException("Detected reused flag name ($it)")
        }
        registerNames(shortName, longName)

        val flag = Flag(shortName, longName, description)
        _flags.add(flag)
        return flag
    }

    internal fun <T : Any> param(
        longName: String,
        shortName: String? = null,
        description: String = "",
        valueParser: ValueParser<T>,
    ): SingleArgParamOptional<T> {
        checkCliNames(shortName, longName)?.let {
            throw IllegalArgumentException("Detected reused param name ($it)")
        }
        registerNames(shortName, longName)

        val param =
            SingleArgParamOptional(
                shortName = shortName,
                longName = longName,
                description = description,
                valueParser = valueParser,
            )
        _params.add(param)
        return param
    }

    internal fun <T : ParseableCommand> subCommand(
        command: T,
    ): OptionalSubCommand<T> {
        checkCliNames(null, command.name)?.let {
            throw IllegalArgumentException("Cannot re-use name for subcommand ($it)")
        }

        if (subCommandContainsSubCommands(command)) {
            throw IllegalArgumentException(
                "SubCommands may not contain other SubCommands. $command"
            )
        }

        registerNames(null, command.name)

        val subCmd = OptionalSubCommand(command)
        _subCommands.add(subCmd)
        return subCmd
    }
}
Loading