ui: Add global settings toggles for input extensions

Registers input lifecycle extensions in the settings page to let users
enable/disable them. Disabling an extension prevents its SQL modules
from loading and excludes its stages from the visualizer.

Bug: 454761692
diff --git a/ui/src/plugins/com.android.AndroidInputLifecycle/extensions/interface.ts b/ui/src/plugins/com.android.AndroidInputLifecycle/extensions/interface.ts
index 5ab29b9..ac28a63 100644
--- a/ui/src/plugins/com.android.AndroidInputLifecycle/extensions/interface.ts
+++ b/ui/src/plugins/com.android.AndroidInputLifecycle/extensions/interface.ts
@@ -62,6 +62,7 @@
  */
 export interface InputLifecycleExtension {
   readonly id: string;
+  readonly name: string;
   readonly requiredModules?: string[];
   isEligible(trace: Trace): Promise<boolean>;
 
diff --git a/ui/src/plugins/com.android.AndroidInputLifecycle/extensions/pixel_extension.ts b/ui/src/plugins/com.android.AndroidInputLifecycle/extensions/pixel_extension.ts
index 0ab7085..5ef0963 100644
--- a/ui/src/plugins/com.android.AndroidInputLifecycle/extensions/pixel_extension.ts
+++ b/ui/src/plugins/com.android.AndroidInputLifecycle/extensions/pixel_extension.ts
@@ -22,6 +22,7 @@
 
 export class PixelInputLifecycleExtension implements InputLifecycleExtension {
   readonly id = 'com.google.PixelInputLifecycle';
+  readonly name = 'Google Pixel Touch';
   readonly requiredModules = ['pixel.touch'];
 
   async isEligible(trace: Trace): Promise<boolean> {
diff --git a/ui/src/plugins/com.android.AndroidInputLifecycle/index.ts b/ui/src/plugins/com.android.AndroidInputLifecycle/index.ts
index 2f05983..71920fe 100644
--- a/ui/src/plugins/com.android.AndroidInputLifecycle/index.ts
+++ b/ui/src/plugins/com.android.AndroidInputLifecycle/index.ts
@@ -18,6 +18,9 @@
 import type {ArrowConnection} from '../../components/related_events/arrow_visualiser';
 import {TrackPinningManager} from '../../components/related_events/utils';
 import {Time} from '../../base/time';
+import {z} from 'zod';
+import type {App} from '../../public/app';
+import type {Setting} from '../../public/settings';
 import type {PerfettoPlugin} from '../../public/plugin';
 import type {Trace} from '../../public/trace';
 
@@ -30,31 +33,53 @@
 import type {InputLifecycleExtension, NavTarget} from './extensions/interface';
 import {PixelInputLifecycleExtension} from './extensions/pixel_extension';
 
+const EXTENSIONS: InputLifecycleExtension[] = [
+  new PixelInputLifecycleExtension(),
+];
+
 export default class AndroidInputLifecyclePlugin implements PerfettoPlugin {
   static readonly id = 'com.android.AndroidInputLifecycle';
   static readonly description =
     'Visualise connected input events in the lifecycle from touch to frame, ' +
     "with latencies for the various input stages. Activate by running the command 'Android: View Input Lifecycle'.";
 
+  private static extensionSettings = new Map<string, Setting<boolean>>();
+
+  static onActivate(app: App): void {
+    for (const ext of EXTENSIONS) {
+      const setting = app.settings.register({
+        id: `com.android.AndroidInputLifecycle.extension.${ext.id}`,
+        name: `Enable ${ext.name} extension`,
+        description: `Enable custom stages in the tab and overlay for ${ext.name} extension.`,
+        schema: z.boolean(),
+        defaultValue: true,
+        requiresReload: true,
+      });
+      AndroidInputLifecyclePlugin.extensionSettings.set(ext.id, setting);
+    }
+  }
+
+  static isExtensionEnabled(id: string): boolean {
+    return AndroidInputLifecyclePlugin.extensionSettings.get(id)?.get() ?? true;
+  }
+
   private visibleRowIds = new Set<string>();
   private lastAppliedEventId?: number;
 
   async onTraceLoad(trace: Trace): Promise<void> {
     await trace.engine.query('INCLUDE PERFETTO MODULE android.input;');
 
-    const extensions: InputLifecycleExtension[] = [
-      new PixelInputLifecycleExtension(),
-    ];
-
     const activeExtensions: InputLifecycleExtension[] = [];
-    for (const ext of extensions) {
-      if (await ext.isEligible(trace)) {
-        if (ext.requiredModules !== undefined) {
-          for (const mod of ext.requiredModules) {
-            await trace.engine.query(`INCLUDE PERFETTO MODULE ${mod};`);
+    for (const ext of EXTENSIONS) {
+      if (AndroidInputLifecyclePlugin.isExtensionEnabled(ext.id)) {
+        if (await ext.isEligible(trace)) {
+          if (ext.requiredModules !== undefined) {
+            for (const mod of ext.requiredModules) {
+              await trace.engine.query(`INCLUDE PERFETTO MODULE ${mod};`);
+            }
           }
+          activeExtensions.push(ext);
         }
-        activeExtensions.push(ext);
       }
     }