Use codegen to catch LinkageErrors from plugins

Plugins that were built against old apis have the potential to crash
SystemUI when unsupported calls are made into them, or they make calls
to methods that no longer exist. These cases cause a LinkageError to be
thrown at the callsite. Incrememting the plugin interface version can
mitigate some of these issues, but is an incomplete solution and
requires manually maintence.

This cl attempts to solve this problem by using codegen to write a proxy
implementation of the interface. This proxy catches those errors, logs
them, and provides a recovery path where the plugin can be disabled but
the application can continue. Recovery will not be possible at every
callsite, but this will at least provide standardized wtf logging for
all locations and a straightforward mechanism for correcting issues
when they occur.

I've also changed VersionCheckerImpl to log a wtf and disable the plugin
in the event that versions don't match. This is so that SystemUI can
continue as normal in the event that plugin interface has changed.

Bug: 359432141
Test: Manually checked mismatched clock apk
Flag: NONE Not user-facing, adding catch blocks
Change-Id: Iee7e1dd0faba5c0c4b9fe71f3678d077a2a8b939
diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp
index 682a68f..a26cf12 100644
--- a/packages/SystemUI/plugin/Android.bp
+++ b/packages/SystemUI/plugin/Android.bp
@@ -23,9 +23,7 @@
 }
 
 java_library {
-
     name: "SystemUIPluginLib",
-
     srcs: [
         "bcsmartspace/src/**/*.java",
         "bcsmartspace/src/**/*.kt",
@@ -40,6 +38,8 @@
         export_proguard_flags_files: true,
     },
 
+    plugins: ["PluginAnnotationProcessor"],
+
     // If you add a static lib here, you may need to also add the package to the ClassLoaderFilter
     // in PluginInstance. That will ensure that loaded plugins have access to the related classes.
     // You should also add it to proguard_common.flags so that proguard does not remove the portions
@@ -53,7 +53,6 @@
         "SystemUILogLib",
         "androidx.annotation_annotation",
     ],
-
 }
 
 android_app {
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/TestPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/TestPlugin.kt
new file mode 100644
index 0000000..33f7b7a
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/TestPlugin.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 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.systemui.plugins
+
+import com.android.systemui.plugins.annotations.ProtectedInterface
+import com.android.systemui.plugins.annotations.ProtectedReturn
+import com.android.systemui.plugins.annotations.ProvidesInterface
+
+@ProtectedInterface
+@ProvidesInterface(action = TestPlugin.ACTION, version = TestPlugin.VERSION)
+/** Interface intended for use in tests */
+interface TestPlugin : Plugin {
+    companion object {
+        const val VERSION = 1
+
+        const val ACTION = "testAction"
+    }
+
+    @ProtectedReturn("return new Object();")
+    /** Test method, implemented by test */
+    fun methodThrowsError(): Object
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index 8dc4815..6d27b6f 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -21,7 +21,11 @@
 import com.android.internal.annotations.Keep
 import com.android.systemui.log.core.MessageBuffer
 import com.android.systemui.plugins.Plugin
+import com.android.systemui.plugins.annotations.GeneratedImport
+import com.android.systemui.plugins.annotations.ProtectedInterface
+import com.android.systemui.plugins.annotations.ProtectedReturn
 import com.android.systemui.plugins.annotations.ProvidesInterface
+import com.android.systemui.plugins.annotations.SimpleProperty
 import java.io.PrintWriter
 import java.util.Locale
 import java.util.TimeZone
@@ -31,6 +35,7 @@
 typealias ClockId = String
 
 /** A Plugin which exposes the ClockProvider interface */
+@ProtectedInterface
 @ProvidesInterface(action = ClockProviderPlugin.ACTION, version = ClockProviderPlugin.VERSION)
 interface ClockProviderPlugin : Plugin, ClockProvider {
     companion object {
@@ -40,31 +45,42 @@
 }
 
 /** Interface for building clocks and providing information about those clocks */
+@ProtectedInterface
+@GeneratedImport("java.util.List")
+@GeneratedImport("java.util.ArrayList")
 interface ClockProvider {
     /** Initializes the clock provider with debug log buffers */
     fun initialize(buffers: ClockMessageBuffers?)
 
+    @ProtectedReturn("return new ArrayList<ClockMetadata>();")
     /** Returns metadata for all clocks this provider knows about */
     fun getClocks(): List<ClockMetadata>
 
+    @ProtectedReturn("return null;")
     /** Initializes and returns the target clock design */
-    fun createClock(settings: ClockSettings): ClockController
+    fun createClock(settings: ClockSettings): ClockController?
 
+    @ProtectedReturn("return new ClockPickerConfig(\"\", \"\", \"\", null);")
     /** Settings configuration parameters for the clock */
     fun getClockPickerConfig(id: ClockId): ClockPickerConfig
 }
 
 /** Interface for controlling an active clock */
+@ProtectedInterface
 interface ClockController {
+    @get:SimpleProperty
     /** A small version of the clock, appropriate for smaller viewports */
     val smallClock: ClockFaceController
 
+    @get:SimpleProperty
     /** A large version of the clock, appropriate when a bigger viewport is available */
     val largeClock: ClockFaceController
 
+    @get:SimpleProperty
     /** Determines the way the hosting app should behave when rendering either clock face */
     val config: ClockConfig
 
+    @get:SimpleProperty
     /** Events that clocks may need to respond to */
     val events: ClockEvents
 
@@ -76,19 +92,26 @@
 }
 
 /** Interface for a specific clock face version rendered by the clock */
+@ProtectedInterface
 interface ClockFaceController {
+    @get:SimpleProperty
+    @Deprecated("Prefer use of layout")
     /** View that renders the clock face */
     val view: View
 
+    @get:SimpleProperty
     /** Layout specification for this clock */
     val layout: ClockFaceLayout
 
+    @get:SimpleProperty
     /** Determines the way the hosting app should behave when rendering this clock face */
     val config: ClockFaceConfig
 
+    @get:SimpleProperty
     /** Events specific to this clock face */
     val events: ClockFaceEvents
 
+    @get:SimpleProperty
     /** Triggers for various animations */
     val animations: ClockAnimations
 }
@@ -107,14 +130,21 @@
 
 data class AodClockBurnInModel(val scale: Float, val translationX: Float, val translationY: Float)
 
-/** Specifies layout information for the */
+/** Specifies layout information for the clock face */
+@ProtectedInterface
+@GeneratedImport("java.util.ArrayList")
+@GeneratedImport("android.view.View")
 interface ClockFaceLayout {
+    @get:ProtectedReturn("return new ArrayList<View>();")
     /** All clock views to add to the root constraint layout before applying constraints. */
     val views: List<View>
 
+    @ProtectedReturn("return constraints;")
     /** Custom constraints to apply to Lockscreen ConstraintLayout. */
     fun applyConstraints(constraints: ConstraintSet): ConstraintSet
 
+    @ProtectedReturn("return constraints;")
+    /** Custom constraints to apply to preview ConstraintLayout. */
     fun applyPreviewConstraints(constraints: ConstraintSet): ConstraintSet
 
     fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel)
@@ -145,7 +175,9 @@
 }
 
 /** Events that should call when various rendering parameters change */
+@ProtectedInterface
 interface ClockEvents {
+    @get:ProtectedReturn("return false;")
     /** Set to enable or disable swipe interaction */
     var isReactiveTouchInteractionEnabled: Boolean
 
@@ -187,6 +219,7 @@
 )
 
 /** Methods which trigger various clock animations */
+@ProtectedInterface
 interface ClockAnimations {
     /** Runs an enter animation (if any) */
     fun enter()
@@ -230,6 +263,7 @@
 }
 
 /** Events that have specific data about the related face */
+@ProtectedInterface
 interface ClockFaceEvents {
     /** Call every time tick */
     fun onTimeTick()
@@ -270,7 +304,9 @@
 /** Some data about a clock design */
 data class ClockMetadata(val clockId: ClockId)
 
-data class ClockPickerConfig(
+data class ClockPickerConfig
+@JvmOverloads
+constructor(
     val id: String,
 
     /** Localized name of the clock */
@@ -338,7 +374,7 @@
     /** Transition to AOD should move smartspace like large clock instead of small clock */
     val useAlternateSmartspaceAODTransition: Boolean = false,
 
-    /** Use ClockPickerConfig.isReactiveToTone instead */
+    /** Deprecated version of isReactiveToTone; moved to ClockPickerConfig */
     @Deprecated("TODO(b/352049256): Remove in favor of ClockPickerConfig.isReactiveToTone")
     val isReactiveToTone: Boolean = true,