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

Commit 2c001b4a authored by Hawkwood Glazier's avatar Hawkwood Glazier Committed by Android (Google) Code Review
Browse files

Merge "Persist Plugin failures for 24 hours" into main

parents 1bd78efd ad8b3d36
Loading
Loading
Loading
Loading
+169 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.plugins.processor

import java.io.BufferedWriter
import java.io.Writer

/** [JavaFileWriter] has a collection of utility functions for generating java files. */
class JavaFileWriter(val writer: TabbedWriter) : TabbedWriter by writer {
    fun pkg(pkg: String) {
        line("package $pkg;")
        line()
    }

    fun imports(vararg importGroups: List<String>) {
        var written = mutableSetOf<String>()
        for (importGroup in importGroups) {
            if (importGroup.size > 0) {
                for (importTarget in importGroup) {
                    if (written.add(importTarget)) {
                        line("import $importTarget;")
                    }
                }
                line()
            }
        }
    }

    fun cls(
        className: String,
        isFinal: Boolean = false,
        visibility: String = "public",
        baseType: String? = null,
        interfaces: List<String> = listOf(),
        contents: JavaClassWriter.() -> Unit = {},
    ) {
        val writer = JavaClassWriter(this.writer, className)
        braceBlock(
            buildString {
                append(visibility)
                if (isFinal) {
                    append(" final")
                }

                append(" class ")
                append(className)

                if (baseType != null) {
                    append(" extends ")
                    append(baseType)
                }

                if (interfaces.size > 0) {
                    append(" implements ")
                    var isFirst = true
                    for (interfaceType in interfaces) {
                        if (!isFirst) append(", ")
                        append(interfaceType)
                        isFirst = false
                    }
                }
            }
        ) {
            writer.contents()
        }
    }

    companion object {
        fun writeTo(writer: Writer, write: JavaFileWriter.() -> Unit) {
            BufferedWriter(writer).use { bufferedWriter ->
                JavaFileWriter(TabbedWriterImpl(bufferedWriter)).write()
            }
        }
    }
}

/** [JavaClassWriter] has a collection of utility functions for generating java classes. */
class JavaClassWriter(val writer: TabbedWriter, val className: String) : TabbedWriter by writer {
    fun constructor(
        visibility: String = "public",
        args: JavaMethodWriter.() -> Unit = {},
        contents: JavaMethodWriter.() -> Unit = {},
    ) {
        method(
            methodName = "$className",
            returnType = "",
            visibility = visibility,
            args = args,
            contents = contents,
        )
    }

    fun method(
        methodName: String,
        visibility: String = "public",
        returnType: String = "void",
        isStatic: Boolean = false,
        args: JavaMethodWriter.() -> Unit = {},
        contents: JavaMethodWriter.() -> Unit = {},
    ) {
        val writer =
            JavaMethodWriter(
                this.writer,
                className = className,
                methodName = methodName,
                returnType = returnType,
                isStatic = isStatic,
            )

        parenBlock(
            buildString {
                append(visibility)
                append(" ")

                if (isStatic) {
                    append(" static ")
                }

                if (!returnType.isNullOrEmpty()) {
                    append(returnType)
                    append(" ")
                }

                append(methodName)
            }
        ) {
            writer.args()
        }

        braceBlock { writer.contents() }
        line()
    }
}

/** [JavaMethodWriter] has a collection of utility functions for generating java functions. */
class JavaMethodWriter(
    val writer: TabbedWriter,
    val className: String,
    val methodName: String,
    val returnType: String,
    val isStatic: Boolean,
) : TabbedWriter by writer {
    val isVoid: Boolean = returnType == "void"

    var isFirstArg = true
    var callArgs = StringBuilder()

    fun arg(name: String, type: String) {
        if (!isFirstArg) {
            completeLine(",")
            callArgs.append(", ")
        }

        startLine("$type $name")
        callArgs.append(name)
        isFirstArg = false
    }
}
+209 −229

File changed.

Preview size limit exceeded, changes collapsed.

+30 −22
Original line number Diff line number Diff line
@@ -14,27 +14,41 @@
package com.android.systemui.plugins.processor

import java.io.BufferedWriter
import java.io.Writer

interface TabbedWriter {
    val tabCount: Int

    fun line()

    fun line(str: String)

    fun completeLine(str: String)

    fun startLine(str: String)

    fun appendLine(str: String)

    fun braceBlock(str: String = "", write: TabbedWriter.() -> Unit)

    fun parenBlock(str: String = "", write: TabbedWriter.() -> Unit)
}

/**
 * [TabbedWriter] is a convience class which tracks and writes correctly tabbed lines for generating
 * source files. These files don't need to be correctly tabbed as they're ephemeral and not part of
 * the source tree, but correct tabbing makes debugging much easier when the build fails.
 * [TabbedWriter] is a convenience class which tracks and writes correctly tabbed lines of text for
 * generating source files. These files don't need to be correctly tabbed as they're ephemeral and
 * not part of the source tree, but correct tabbing makes debugging much easier.
 */
class TabbedWriter(writer: Writer) : AutoCloseable {
    private val target = BufferedWriter(writer)
class TabbedWriterImpl(private val target: BufferedWriter) : TabbedWriter {
    private var isInProgress = false
    var tabCount: Int = 0
    override var tabCount: Int = 0
        private set

    override fun close() = target.close()

    fun line() {
    override fun line() {
        target.newLine()
        isInProgress = false
    }

    fun line(str: String) {
    override fun line(str: String) {
        if (isInProgress) {
            target.newLine()
        }
@@ -45,7 +59,7 @@ class TabbedWriter(writer: Writer) : AutoCloseable {
        isInProgress = false
    }

    fun completeLine(str: String) {
    override fun completeLine(str: String) {
        if (!isInProgress) {
            target.newLine()
            target.append("    ".repeat(tabCount))
@@ -56,7 +70,7 @@ class TabbedWriter(writer: Writer) : AutoCloseable {
        isInProgress = false
    }

    fun startLine(str: String) {
    override fun startLine(str: String) {
        if (isInProgress) {
            target.newLine()
        }
@@ -66,7 +80,7 @@ class TabbedWriter(writer: Writer) : AutoCloseable {
        isInProgress = true
    }

    fun appendLine(str: String) {
    override fun appendLine(str: String) {
        if (!isInProgress) {
            target.append("    ".repeat(tabCount))
        }
@@ -75,11 +89,11 @@ class TabbedWriter(writer: Writer) : AutoCloseable {
        isInProgress = true
    }

    fun braceBlock(str: String = "", write: TabbedWriter.() -> Unit) {
    override fun braceBlock(str: String, write: TabbedWriter.() -> Unit) {
        block(str, " {", "}", true, write)
    }

    fun parenBlock(str: String = "", write: TabbedWriter.() -> Unit) {
    override fun parenBlock(str: String, write: TabbedWriter.() -> Unit) {
        block(str, "(", ")", false, write)
    }

@@ -103,10 +117,4 @@ class TabbedWriter(writer: Writer) : AutoCloseable {
            startLine(end)
        }
    }

    companion object {
        fun writeTo(writer: Writer, write: TabbedWriter.() -> Unit) {
            TabbedWriter(writer).use { it.write() }
        }
    }
}
+50 −8
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.systemui.shared.plugins;
import android.app.LoadedApk;
import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -58,6 +59,14 @@ public class PluginInstance<T extends Plugin>
        implements PluginLifecycleManager, ProtectedPluginListener {
    private static final String TAG = "PluginInstance";

    private static final String FAIL_FILE_FMT = "PluginFailure_%s";
    private static final String FAIL_TIME = "FailureTime";
    private static final String FAIL_MESSAGE = "ErrorMessage";
    private static final String FAIL_STACK_FMT = "Stack[%d]";

    private static final int FAIL_MAX_STACK = 20;
    private static final long FAIL_TIMEOUT_MILLIS = 24 * 60 * 60 * 1000;

    private final Context mHostContext;
    private final PluginListener<T> mListener;
    private final ComponentName mComponentName;
@@ -75,20 +84,16 @@ public class PluginInstance<T extends Plugin>
            Context hostContext,
            PluginListener<T> listener,
            ComponentName componentName,
            PluginFactory<T> pluginFactory,
            @Nullable T plugin) {
            PluginFactory<T> pluginFactory) {
        mHostContext = hostContext;
        mListener = listener;
        mComponentName = componentName;
        mPluginFactory = pluginFactory;
        mPlugin = plugin;
        mTag = String.format("%s[%s]@%h", TAG, mComponentName.getShortClassName(), hashCode());
        mLogger = new Logger(mListener.getLogBuffer() != null ? mListener.getLogBuffer() :
                new LogcatOnlyMessageBuffer(LogLevel.WARNING), mTag);

        if (mPlugin != null) {
            mPluginContext = mPluginFactory.createPluginContext();
        }
        loadFailure();
    }

    @Override
@@ -105,13 +110,50 @@ public class PluginInstance<T extends Plugin>
    public synchronized boolean onFail(String className, String methodName, Throwable failure) {
        PluginActionManager.logFmt(mLogger, LogLevel.ERROR,
                "Failure from %s. Disabling Plugin.", mPlugin.toString(), null);

        storeFailure(failure);
        mHasError = true;
        unloadPlugin();
        mListener.onPluginDetached(this);
        return true;
    }

    /** Persists failure to avoid boot looping if process recovery fails */
    private synchronized void storeFailure(Throwable failure) {
        SharedPreferences.Editor sharedPrefs = mHostContext.getSharedPreferences(
                String.format(FAIL_FILE_FMT, mComponentName.getShortClassName()),
                Context.MODE_PRIVATE).edit();

        sharedPrefs.clear();
        sharedPrefs.putLong(FAIL_TIME, System.currentTimeMillis());
        sharedPrefs.putString(FAIL_MESSAGE, failure.getMessage());
        for (int i = 0; i < failure.getStackTrace().length && i < FAIL_MAX_STACK; i++) {
            String methodSignature = failure.getStackTrace()[i].toString();
            sharedPrefs.putString(String.format(FAIL_STACK_FMT, i), methodSignature);
        }
        sharedPrefs.commit();
    }

    /** Loads a persisted failure if it's still within the timeout. */
    private synchronized void loadFailure() {
        SharedPreferences sharedPrefs = mHostContext.getSharedPreferences(
                String.format(FAIL_FILE_FMT, mComponentName.getShortClassName()),
                Context.MODE_PRIVATE);

        // TODO(b/438515243): Disable in eng builds
        // TODO(b/438515243): Check apk checksums for differences (systemui & plugin)
        // If the failure occurred too long ago, we ignore it to check if it's still happening.
        if (sharedPrefs.getLong(FAIL_TIME, 0) < System.currentTimeMillis() - FAIL_TIMEOUT_MILLIS) {
            return;
        }

        // Log previous the failure so that it appears in debugging data
        PluginActionManager.logFmt(mLogger, LogLevel.ERROR,
                "Disabling Plugin: %s", mPlugin.toString(), null);
        PluginActionManager.logFmt(mLogger, LogLevel.ERROR,
                "Persisted Failure: %s", sharedPrefs.getString(FAIL_MESSAGE, "Unknown"), null);
        mHasError = true;
    }

    /** Alerts listener and plugin that the plugin has been created. */
    public synchronized void onCreate() {
        if (mHasError) {
@@ -307,7 +349,7 @@ public class PluginInstance<T extends Plugin>
            PluginFactory<T> pluginFactory = new PluginFactory<T>(
                    hostContext, mInstanceFactory, pluginAppInfo, componentName,
                    mVersionChecker, pluginClass, mBaseClassLoader);
            return new PluginInstance<T>(hostContext, listener, componentName, pluginFactory, null);
            return new PluginInstance<T>(hostContext, listener, componentName, pluginFactory);
        }

        private boolean isPluginPackagePrivileged(String packageName) {