Remove tracing config singleton and improve change detection.

New flow:
Trace and dump config initialized in CollectTracesComponent
-> passed into respective TraceConfigComponents
-> updated in TraceConfigComponent
-> propagated back into CollectTracesComponent
-> sent to ProxyConnection on trace/dump start request

The above does not fix change detection issues. There appears to be some bug with change detection when checkboxes/mat select are in ngIf blocks that keep changing.
Added a worker that manually detects changes every 0.1s.

Also fixed wayland trace availability request (was does not firing due to selectedDevice check, but does not need selected device to run).

Fix: 300384655
Test: npm run test:unit:ci

Change-Id: I215e4b4a473bd806d426b31b9a03f6618d931148
diff --git a/tools/winscope/src/app/components/app_component.ts b/tools/winscope/src/app/components/app_component.ts
index 84b8e65..2821db4 100644
--- a/tools/winscope/src/app/components/app_component.ts
+++ b/tools/winscope/src/app/components/app_component.ts
@@ -35,7 +35,6 @@
 import {globalConfig} from 'common/global_config';
 import {InMemoryStorage} from 'common/in_memory_storage';
 import {PersistentStore} from 'common/persistent_store';
-import {PersistentStoreProxy} from 'common/persistent_store_proxy';
 import {Timestamp} from 'common/time';
 import {UrlUtils} from 'common/url_utils';
 import {UserNotifier} from 'common/user_notifier';
@@ -55,10 +54,6 @@
 import {WinscopeEventListener} from 'messaging/winscope_event_listener';
 import {AdbConnection} from 'trace_collection/adb_connection';
 import {ProxyConnection} from 'trace_collection/proxy_connection';
-import {
-  TraceConfigurationMap,
-  TRACES,
-} from 'trace_collection/trace_collection_utils';
 import {iconDividerStyle} from 'viewers/components/styles/icon_divider.styles';
 import {ViewerInputMethodComponent} from 'viewers/components/viewer_input_method_component';
 import {Viewer} from 'viewers/viewer';
@@ -225,8 +220,6 @@
           <div class="card-grid landing-grid">
             <collect-traces
               class="collect-traces-card homepage-card"
-              [traceConfig]="traceConfig"
-              [dumpConfig]="dumpConfig"
               [storage]="traceConfigStorage"
               [adbConnection]="adbConnection"
               (filesCollected)="onFilesCollected($event)"></collect-traces>
@@ -354,8 +347,6 @@
     ]),
   );
   adbConnection: AdbConnection = new ProxyConnection();
-  traceConfig: TraceConfigurationMap;
-  dumpConfig: TraceConfigurationMap;
   traceConfigStorage: Storage;
 
   @ViewChild(UploadTracesComponent)
@@ -460,33 +451,6 @@
     this.traceConfigStorage =
       globalConfig.MODE === 'PROD' ? localStorage : new InMemoryStorage();
 
-    this.traceConfig = PersistentStoreProxy.new<TraceConfigurationMap>(
-      'TracingSettings',
-      TRACES['default'],
-      this.traceConfigStorage,
-    );
-    this.dumpConfig = PersistentStoreProxy.new<TraceConfigurationMap>(
-      'DumpSettings',
-      {
-        window_dump: {
-          name: 'Window Manager',
-          run: true,
-          config: undefined,
-        },
-        layers_dump: {
-          name: 'Surface Flinger',
-          run: true,
-          config: undefined,
-        },
-        screenshot: {
-          name: 'Screenshot',
-          run: true,
-          config: undefined,
-        },
-      },
-      this.traceConfigStorage,
-    );
-
     window.onunhandledrejection = (evt) => {
       Analytics.Error.logGlobalException(evt.reason);
     };
diff --git a/tools/winscope/src/app/components/collect_traces_component.ts b/tools/winscope/src/app/components/collect_traces_component.ts
index 0a6c835..cf64241 100644
--- a/tools/winscope/src/app/components/collect_traces_component.ts
+++ b/tools/winscope/src/app/components/collect_traces_component.ts
@@ -27,7 +27,6 @@
 import {MatDialog} from '@angular/material/dialog';
 import {assertDefined} from 'common/assert_utils';
 import {FunctionUtils} from 'common/function_utils';
-import {PersistentStoreProxy} from 'common/persistent_store_proxy';
 import {Analytics} from 'logging/analytics';
 import {ProgressListener} from 'messaging/progress_listener';
 import {
@@ -46,9 +45,11 @@
 import {ProxyConnection} from 'trace_collection/proxy_connection';
 import {
   EnableConfiguration,
+  makeDefaultDumpConfigMap,
+  makeDefaultTraceConfigMap,
   SelectionConfiguration,
   TraceConfigurationMap,
-} from 'trace_collection/trace_collection_utils';
+} from 'trace_collection/trace_configuration';
 import {TraceRequest, TraceRequestConfig} from 'trace_collection/trace_request';
 import {LoadProgressComponent} from './load_progress_component';
 import {
@@ -152,7 +153,12 @@
               [disabled]="disableTraceSection()">
               <div class="tabbed-section">
                 <div class="trace-section" *ngIf="adbConnection.getState() === ${ConnectionState.IDLE}">
-                  <trace-config [traceConfig]="traceConfig"></trace-config>
+                  <trace-config
+                    title="Trace targets"
+                    [initialTraceConfig]="traceConfig"
+                    [storage]="storage"
+                    traceConfigStoreKey="TraceSettings"
+                    (traceConfigChange)="onTraceConfigChange($event)"></trace-config>
                   <div class="start-btn">
                     <button color="primary" mat-raised-button (click)="startTracing()">
                       Start trace
@@ -211,15 +217,12 @@
             <mat-tab label="Dump" [disabled]="isTracingOrLoading()">
               <div class="tabbed-section">
                 <div class="dump-section" *ngIf="adbConnection.getState() === ${ConnectionState.IDLE} && !refreshDumps">
-                  <h3 class="mat-subheading-2">Dump targets</h3>
-                  <div class="selection">
-                    <mat-checkbox
-                      *ngFor="let dumpKey of objectKeys(dumpConfig)"
-                      color="primary"
-                      class="dump-checkbox"
-                      [(ngModel)]="dumpConfig[dumpKey].run"
-                      >{{ dumpConfig[dumpKey].name }}</mat-checkbox>
-                  </div>
+                  <trace-config
+                    title="Dump targets"
+                    [initialTraceConfig]="dumpConfig"
+                    [storage]="storage"
+                    traceConfigStoreKey="DumpSettings"
+                    (traceConfigChange)="onDumpConfigChange($event)"></trace-config>
                   <div class="dump-btn" *ngIf="!refreshDumps">
                     <button color="primary" mat-raised-button (click)="dumpState()">
                       Dump state
@@ -411,6 +414,8 @@
   lastUiProgressUpdateTimeMs?: number;
   refreshDumps = false;
   selectedTabIndex = 0;
+  traceConfig: TraceConfigurationMap;
+  dumpConfig: TraceConfigurationMap;
 
   private readonly storeKeyImeWarning = 'doNotShowImeWarningDialog';
   private readonly storeKeyLastDevice = 'adb.lastDevice';
@@ -425,8 +430,6 @@
   ];
 
   @Input() adbConnection: AdbConnection | undefined;
-  @Input() traceConfig: TraceConfigurationMap | undefined;
-  @Input() dumpConfig: TraceConfigurationMap | undefined;
   @Input() storage: Storage | undefined;
 
   @Output() readonly filesCollected = new EventEmitter<File[]>();
@@ -435,7 +438,10 @@
     @Inject(ChangeDetectorRef) private changeDetectorRef: ChangeDetectorRef,
     @Inject(MatDialog) private dialog: MatDialog,
     @Inject(NgZone) private ngZone: NgZone,
-  ) {}
+  ) {
+    this.traceConfig = makeDefaultTraceConfigMap();
+    this.dumpConfig = makeDefaultDumpConfigMap();
+  }
 
   ngOnChanges() {
     if (!this.adbConnection) {
@@ -444,7 +450,7 @@
     this.adbConnection.initialize(
       () => this.onConnectionStateChange(),
       (progress) => this.onLoadProgressUpdate(progress),
-      this.setTraceConfigForAvailableTraces,
+      this.toggleAvailabilityOfTraces,
     );
   }
 
@@ -547,6 +553,14 @@
     );
   }
 
+  onTraceConfigChange(newConfig: TraceConfigurationMap) {
+    this.traceConfig = newConfig;
+  }
+
+  onDumpConfigChange(newConfig: TraceConfigurationMap) {
+    this.dumpConfig = newConfig;
+  }
+
   async onChangeDeviceButton() {
     this.storage?.setItem(this.storeKeyLastDevice, '');
     this.selectedDevice = undefined;
@@ -598,14 +612,16 @@
         data,
         disableClose: true,
       });
-      dialogRef.beforeClosed().subscribe((result: WarningDialogResult) => {
-        if (this.storage && result.selectedOptions.includes(optionText)) {
-          this.storage.setItem(this.storeKeyImeWarning, 'true');
-        }
-        if (result.closeActionText === closeText) {
-          this.requestTraces(requestedTraces);
-        }
-      });
+      dialogRef
+        .beforeClosed()
+        .subscribe((result: WarningDialogResult | undefined) => {
+          if (this.storage && result?.selectedOptions.includes(optionText)) {
+            this.storage.setItem(this.storeKeyImeWarning, 'true');
+          }
+          if (result?.closeActionText === closeText) {
+            this.requestTraces(requestedTraces);
+          }
+        });
     });
   }
 
@@ -748,21 +764,21 @@
   private getRequestedTraces(): string[] {
     const tracingConfig = assertDefined(this.traceConfig);
     return Object.keys(tracingConfig).filter((traceKey: string) => {
-      return tracingConfig[traceKey].run;
+      return tracingConfig[traceKey].enabled;
     });
   }
 
   private getRequestedDumps(): string[] {
     const dumpConfig = assertDefined(this.dumpConfig);
     return Object.keys(dumpConfig).filter((dumpKey: string) => {
-      return dumpConfig[dumpKey].run;
+      return dumpConfig[dumpKey].enabled;
     });
   }
 
   private requestedEnabledConfig(traceName: string): TraceRequestConfig[] {
     const req: TraceRequestConfig[] = [];
     const trace = assertDefined(this.traceConfig)[traceName];
-    if (trace?.run) {
+    if (trace?.enabled) {
       trace.config?.enableConfigs?.forEach((con: EnableConfiguration) => {
         if (con.enabled) {
           req.push({key: con.key});
@@ -775,7 +791,7 @@
   private requestedSelectedConfig(traceName: string): TraceRequestConfig[] {
     const tracingConfig = assertDefined(this.traceConfig);
     const trace = tracingConfig[traceName];
-    if (!trace?.run) {
+    if (!trace?.enabled) {
       return [];
     }
     return (
@@ -790,12 +806,9 @@
     this.changeDetectorRef.detectChanges();
   }
 
-  private setTraceConfigForAvailableTraces = (
-    availableTracesConfig: TraceConfigurationMap,
-  ) =>
-    (this.traceConfig = PersistentStoreProxy.new<TraceConfigurationMap>(
-      'TraceConfiguration',
-      availableTracesConfig,
-      assertDefined(this.storage),
-    ));
+  private toggleAvailabilityOfTraces = (traces: string[]) =>
+    traces.forEach((trace) => {
+      const config = assertDefined(this.traceConfig)[trace];
+      config.available = !config.available;
+    });
 }
diff --git a/tools/winscope/src/app/components/collect_traces_component_test.ts b/tools/winscope/src/app/components/collect_traces_component_test.ts
index c310bb2..56810f9 100644
--- a/tools/winscope/src/app/components/collect_traces_component_test.ts
+++ b/tools/winscope/src/app/components/collect_traces_component_test.ts
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+import {CommonModule} from '@angular/common';
 import {Component, NO_ERRORS_SCHEMA, ViewChild} from '@angular/core';
 import {ComponentFixture, TestBed} from '@angular/core/testing';
 import {FormsModule} from '@angular/forms';
@@ -28,17 +29,12 @@
 import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
 import {assertDefined} from 'common/assert_utils';
 import {InMemoryStorage} from 'common/in_memory_storage';
-import {PersistentStoreProxy} from 'common/persistent_store_proxy';
 import {NoTraceTargetsSelected, WinscopeEvent} from 'messaging/winscope_event';
 import {MockAdbConnection} from 'test/unit/mock_adb_connection';
 import {AdbConnection} from 'trace_collection/adb_connection';
 import {AdbDevice} from 'trace_collection/adb_device';
 import {ConnectionState} from 'trace_collection/connection_state';
 import {ProxyConnection} from 'trace_collection/proxy_connection';
-import {
-  TraceConfigurationMap,
-  TRACES,
-} from 'trace_collection/trace_collection_utils';
 import {AdbProxyComponent} from './adb_proxy_component';
 import {CollectTracesComponent} from './collect_traces_component';
 import {LoadProgressComponent} from './load_progress_component';
@@ -59,6 +55,7 @@
   beforeEach(async () => {
     await TestBed.configureTestingModule({
       imports: [
+        CommonModule,
         MatIconModule,
         MatCardModule,
         MatListModule,
@@ -241,11 +238,61 @@
     expect(el.innerHTML).toContain('Pixel 6');
     expect(el.innerHTML).toContain('35562');
 
-    const traceSection = htmlElement.querySelector('.trace-section');
-    expect(traceSection).toBeTruthy();
+    const traceSection = assertDefined(
+      htmlElement.querySelector('.trace-section'),
+    );
+    expect(traceSection.querySelector('trace-config')?.textContent).toContain(
+      'Trace targets',
+    );
+    expect(traceSection.querySelector('.start-btn')?.textContent).toContain(
+      'Start trace',
+    );
 
-    const dumpSection = htmlElement.querySelector('.dump-section');
-    expect(dumpSection).toBeTruthy();
+    const dumpSection = assertDefined(
+      htmlElement.querySelector('.dump-section'),
+    );
+    expect(dumpSection.querySelector('trace-config')?.textContent).toContain(
+      'Dump targets',
+    );
+    expect(dumpSection.querySelector('.dump-btn')?.textContent).toContain(
+      'Dump state',
+    );
+  });
+
+  it('updates config on change in trace config component', async () => {
+    goToConfigSection();
+    await fixture.whenStable();
+    fixture.detectChanges();
+
+    expect(
+      component.collectTracesComponent?.traceConfig['window_trace']?.enabled,
+    ).toBeTrue();
+    const traceSection = assertDefined(
+      htmlElement.querySelector('.trace-section'),
+    );
+    const traceCheckboxInput = assertDefined(
+      traceSection.querySelector<HTMLInputElement>('.trace-checkbox input'),
+    );
+    traceCheckboxInput.click();
+    fixture.detectChanges();
+    expect(
+      component.collectTracesComponent?.traceConfig['window_trace']?.enabled,
+    ).toBeFalse();
+
+    expect(
+      component.collectTracesComponent?.dumpConfig['window_dump']?.enabled,
+    ).toBeTrue();
+    const dumpSection = assertDefined(
+      htmlElement.querySelector('.dump-section'),
+    );
+    const dumpCheckboxInput = assertDefined(
+      dumpSection.querySelector<HTMLInputElement>('.trace-checkbox input'),
+    );
+    dumpCheckboxInput.click();
+    fixture.detectChanges();
+    expect(
+      component.collectTracesComponent?.dumpConfig['window_dump']?.enabled,
+    ).toBeFalse();
   });
 
   it('start trace button works as expected', async () => {
@@ -264,9 +311,9 @@
       lastEvent = event;
     });
 
-    Object.values(assertDefined(component.traceConfig)).forEach(
-      (c) => (c.run = false),
-    );
+    Object.values(
+      assertDefined(component.collectTracesComponent?.traceConfig),
+    ).forEach((c) => (c.enabled = false));
     const spy = spyOn(getConnection(), 'startTrace');
     await clickStartTraceButton();
 
@@ -291,9 +338,9 @@
       lastEvent = event;
     });
 
-    Object.values(assertDefined(component.dumpConfig)).forEach(
-      (c) => (c.run = false),
-    );
+    Object.values(
+      assertDefined(component.collectTracesComponent?.dumpConfig),
+    ).forEach((c) => (c.enabled = false));
     const filesSpy = spyOn(getCollectTracesComponent().filesCollected, 'emit');
     await clickDumpStateButton();
 
@@ -546,6 +593,17 @@
     expect(spy).toHaveBeenCalled();
   });
 
+  it('update available traces from connection', () => {
+    expect(
+      component.collectTracesComponent?.traceConfig['wayland_trace']?.available,
+    ).toBeFalse();
+    getConnection().availableTracesChangeCallback(['wayland_trace']);
+    fixture.detectChanges();
+    expect(
+      component.collectTracesComponent?.traceConfig['wayland_trace']?.available,
+    ).toBeTrue();
+  });
+
   describe('ProxyConnection', () => {
     beforeEach(async () => {
       await startProxyConnection();
@@ -606,9 +664,9 @@
   }
 
   function updateTraceConfigToInvalidIMEFrameMapping() {
-    const config = assertDefined(component.traceConfig);
-    config['ime'].run = true;
-    config['layers_trace'].run = false;
+    const config = assertDefined(component.collectTracesComponent?.traceConfig);
+    config['ime'].enabled = true;
+    config['layers_trace'].enabled = false;
   }
 
   async function clickStartTraceButton() {
@@ -656,35 +714,12 @@
     template: `
       <collect-traces
         [adbConnection]="adbConnection"
-        [storage]="storage"
-        [traceConfig]="traceConfig"
-        [dumpConfig]="dumpConfig"></collect-traces>
+        [storage]="storage"></collect-traces>
     `,
   })
   class TestHostComponent {
     adbConnection: AdbConnection = new MockAdbConnection();
     storage = new InMemoryStorage();
-    traceConfig = PersistentStoreProxy.new<TraceConfigurationMap>(
-      'TracingSettings',
-      TRACES['default'],
-      this.storage,
-    );
-    dumpConfig = PersistentStoreProxy.new<TraceConfigurationMap>(
-      'DumpSettings',
-      {
-        window_dump: {
-          name: 'Window Manager',
-          run: true,
-          config: undefined,
-        },
-        layers_dump: {
-          name: 'Surface Flinger',
-          run: true,
-          config: undefined,
-        },
-      },
-      this.storage,
-    );
 
     @ViewChild(CollectTracesComponent)
     collectTracesComponent: CollectTracesComponent | undefined;
diff --git a/tools/winscope/src/app/components/trace_config_component.ts b/tools/winscope/src/app/components/trace_config_component.ts
index 385737c..3dc9b36 100644
--- a/tools/winscope/src/app/components/trace_config_component.ts
+++ b/tools/winscope/src/app/components/trace_config_component.ts
@@ -13,27 +13,38 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {Component, Input} from '@angular/core';
+import {
+  ChangeDetectorRef,
+  Component,
+  EventEmitter,
+  Inject,
+  Input,
+  Output,
+} from '@angular/core';
+import {MatSelect, MatSelectChange} from '@angular/material/select';
 import {assertDefined} from 'common/assert_utils';
+import {globalConfig} from 'common/global_config';
+import {PersistentStoreProxy} from 'common/persistent_store_proxy';
 import {
   EnableConfiguration,
   SelectionConfiguration,
   TraceConfiguration,
   TraceConfigurationMap,
-} from 'trace_collection/trace_collection_utils';
+} from 'trace_collection/trace_configuration';
 
 @Component({
   selector: 'trace-config',
   template: `
-    <h3 class="mat-subheading-2">Trace targets</h3>
+    <h3 class="mat-subheading-2">{{title}}</h3>
 
     <div class="checkboxes">
       <mat-checkbox
         *ngFor="let traceKey of objectKeys(this.traceConfig)"
         color="primary"
         class="trace-checkbox"
-        [checked]="this.traceConfig[traceKey].run"
-        (change)="changeRunTrace($event.checked, this.traceConfig[traceKey])"
+        [disabled]="!this.traceConfig[traceKey].available"
+        [(ngModel)]="this.traceConfig[traceKey].enabled"
+        (ngModelChange)="onTraceConfigChange()"
         >{{ this.traceConfig[traceKey].name }}</mat-checkbox
       >
     </div>
@@ -50,8 +61,9 @@
           *ngFor="let enableConfig of traceEnableConfigs(this.traceConfig[traceKey])"
           color="primary"
           class="enable-config"
-          [disabled]="!this.traceConfig[traceKey].run"
+          [disabled]="!this.traceConfig[traceKey].enabled"
           [(ngModel)]="enableConfig.enabled"
+          (ngModelChange)="onTraceConfigChange()"
           >{{ enableConfig.name }}</mat-checkbox
         >
       </div>
@@ -59,21 +71,26 @@
       <div
         *ngIf="this.traceConfig[traceKey].config?.selectionConfigs.length > 0"
         class="selection-config-opt">
-        <mat-form-field
-          *ngFor="let selectionConfig of traceSelectionConfigs(this.traceConfig[traceKey])"
-          class="config-selection"
-          appearance="fill">
-          <mat-label>{{ selectionConfig.name }}</mat-label>
+        <ng-container *ngFor="let selectionConfig of traceSelectionConfigs(this.traceConfig[traceKey])">
+          <mat-form-field
+            class="config-selection"
+            appearance="fill">
+            <mat-label>{{ selectionConfig.name }}</mat-label>
 
-          <mat-select
-            class="selected-value"
-            [(value)]="selectionConfig.value"
-            [disabled]="!this.traceConfig[traceKey].run">
-            <mat-option *ngFor="let option of selectionConfig.options" value="{{ option }}">{{
-              option
-            }}</mat-option>
-          </mat-select>
-        </mat-form-field>
+            <mat-select
+              #matSelect
+              disableOptionCentering
+              class="selected-value"
+              [attr.label]="traceKey + selectionConfig.name"
+              [value]="selectionConfig.value"
+              [disabled]="!this.traceConfig[traceKey].enabled"
+              (selectionChange)="onSelectChange($event, selectionConfig)">
+              <mat-option *ngFor="let option of selectionConfig.options" (click)="onOptionClick(matSelect, option, traceKey + selectionConfig.name)" [value]="option">{{
+                option
+              }}</mat-option>
+            </mat-select>
+          </mat-form-field>
+        </ng-container>
       </div>
     </ng-container>
   `,
@@ -91,13 +108,50 @@
         flex-wrap: wrap;
         gap: 10px;
       }
+      .config-panel {
+        position: absolute;
+        left: 0px;
+        top: 100px;
+      }
     `,
   ],
 })
 export class TraceConfigComponent {
   objectKeys = Object.keys;
+  changeDetectionWorker: number | undefined;
+  traceConfig: TraceConfigurationMap | undefined;
 
-  @Input() traceConfig: TraceConfigurationMap | undefined;
+  @Input() title: string | undefined;
+  @Input() traceConfigStoreKey: string | undefined;
+  @Input() initialTraceConfig: TraceConfigurationMap | undefined;
+  @Input() storage: Storage | undefined;
+  @Output() readonly traceConfigChange =
+    new EventEmitter<TraceConfigurationMap>();
+
+  constructor(
+    @Inject(ChangeDetectorRef) private changeDetectorRef: ChangeDetectorRef,
+  ) {}
+
+  ngOnInit() {
+    this.traceConfig = PersistentStoreProxy.new<TraceConfigurationMap>(
+      assertDefined(this.traceConfigStoreKey),
+      assertDefined(
+        this.initialTraceConfig,
+        () => 'component initialized without config',
+      ),
+      assertDefined(this.storage),
+    );
+    if (globalConfig.MODE !== 'KARMA_TEST') {
+      this.changeDetectionWorker = window.setInterval(
+        () => this.changeDetectorRef.detectChanges(),
+        200,
+      );
+    }
+  }
+
+  ngOnDestroy() {
+    window.clearInterval(this.changeDetectionWorker);
+  }
 
   advancedConfigTraces() {
     const advancedConfigs: string[] = [];
@@ -127,12 +181,27 @@
 
   someTraces(trace: TraceConfiguration): boolean {
     return (
-      !trace.run &&
+      !trace.enabled &&
       this.traceEnableConfigs(trace).filter((trace) => trace.enabled).length > 0
     );
   }
 
-  changeRunTrace(run: boolean, trace: TraceConfiguration): void {
-    trace.run = run;
+  onSelectChange(event: MatSelectChange, config: SelectionConfiguration) {
+    config.value = event.value;
+    event.source.close();
+    this.onTraceConfigChange();
+  }
+
+  onOptionClick(select: MatSelect, option: string, configName: string) {
+    if (select.value === option) {
+      const selectElement = assertDefined(
+        document.querySelector(`mat-select[label="${configName}"]`),
+      );
+      (selectElement as HTMLElement).blur();
+    }
+  }
+
+  onTraceConfigChange() {
+    this.traceConfigChange.emit(this.traceConfig);
   }
 }
diff --git a/tools/winscope/src/app/components/trace_config_component_test.ts b/tools/winscope/src/app/components/trace_config_component_test.ts
index 5eacb54..66dcfb1 100644
--- a/tools/winscope/src/app/components/trace_config_component_test.ts
+++ b/tools/winscope/src/app/components/trace_config_component_test.ts
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 import {CommonModule} from '@angular/common';
-import {NO_ERRORS_SCHEMA} from '@angular/core';
 import {ComponentFixture, TestBed} from '@angular/core/testing';
+import {FormsModule, ReactiveFormsModule} from '@angular/forms';
 import {MatCheckboxModule} from '@angular/material/checkbox';
 import {MatDividerModule} from '@angular/material/divider';
 import {MatFormFieldModule} from '@angular/material/form-field';
@@ -23,12 +23,14 @@
 import {MatSelectModule} from '@angular/material/select';
 import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
 import {assertDefined} from 'common/assert_utils';
+import {InMemoryStorage} from 'common/in_memory_storage';
 import {TraceConfigComponent} from './trace_config_component';
 
 describe('TraceConfigComponent', () => {
   let fixture: ComponentFixture<TraceConfigComponent>;
   let component: TraceConfigComponent;
   let htmlElement: HTMLElement;
+  let configChangeSpy: jasmine.Spy;
 
   beforeEach(async () => {
     await TestBed.configureTestingModule({
@@ -40,17 +42,22 @@
         MatInputModule,
         MatSelectModule,
         BrowserAnimationsModule,
+        FormsModule,
+        ReactiveFormsModule,
       ],
       declarations: [TraceConfigComponent],
-      schemas: [NO_ERRORS_SCHEMA],
     }).compileComponents();
     fixture = TestBed.createComponent(TraceConfigComponent);
     component = fixture.componentInstance;
     htmlElement = fixture.nativeElement;
-    component.traceConfig = {
+    configChangeSpy = spyOn(component.traceConfigChange, 'emit');
+
+    component.title = 'Targets';
+    component.initialTraceConfig = {
       layers_trace: {
         name: 'layers_trace',
-        run: false,
+        enabled: true,
+        available: true,
         config: {
           enableConfigs: [
             {
@@ -69,7 +76,22 @@
           ],
         },
       },
+      window_trace: {
+        name: 'window_trace',
+        enabled: false,
+        available: true,
+        config: undefined,
+      },
+      unavailable_trace: {
+        name: 'unavailable_trace',
+        enabled: false,
+        available: false,
+        config: undefined,
+      },
     };
+    component.traceConfigStoreKey = 'TestConfigSettings';
+    component.storage = new InMemoryStorage();
+    await detectNgModelChanges();
     fixture.detectChanges();
   });
 
@@ -77,22 +99,62 @@
     expect(component).toBeTruthy();
   });
 
-  it('check that trace checkbox ticked on default run', () => {
-    assertDefined(component.traceConfig)['layers_trace'].run = true;
-    fixture.detectChanges();
+  it('trace checkbox enabled by default', () => {
+    const config = assertDefined(component.traceConfig);
+
     const box = assertDefined(htmlElement.querySelector('.trace-checkbox'));
-    expect(box.innerHTML).toContain('aria-checked="true"');
-    expect(box.innerHTML).toContain('layers_trace');
+    const inputElement = assertDefined(
+      box.querySelector<HTMLInputElement>('input'),
+    );
+
+    expect(box.textContent).toContain('layers_trace');
+    expect(inputElement.checked).toBeTrue();
+    expect(inputElement.ariaChecked).toEqual('true');
+    expect(config['layers_trace'].enabled).toBeTrue();
+
+    inputElement.click();
+    fixture.detectChanges();
+    expect(inputElement.checked).toBeFalse();
+    expect(inputElement.ariaChecked).toEqual('false');
+    expect(config['layers_trace'].enabled).toBeFalse();
+    expect(configChangeSpy).toHaveBeenCalledTimes(1);
   });
 
-  it('check that trace checkbox not ticked on default run', () => {
-    assertDefined(component.traceConfig)['layers_trace'].run = false;
+  it('trace checkbox not enabled by default', () => {
+    const config = assertDefined(component.traceConfig);
+
+    const box = assertDefined(
+      htmlElement.querySelectorAll('.trace-checkbox').item(1),
+    );
+    const inputElement = assertDefined(
+      box.querySelector<HTMLInputElement>('input'),
+    );
+
+    expect(box.textContent).toContain('window_trace');
+    expect(inputElement.checked).toBeFalse();
+    expect(inputElement.ariaChecked).toEqual('false');
+    expect(config['window_trace'].enabled).toBeFalse();
+
+    inputElement.click();
     fixture.detectChanges();
-    const box = assertDefined(htmlElement.querySelector('.trace-checkbox'));
-    expect(box.innerHTML).toContain('aria-checked="false"');
+    expect(inputElement.checked).toBeTrue();
+    expect(inputElement.ariaChecked).toEqual('true');
+    expect(config['window_trace'].enabled).toBeTrue();
+    expect(configChangeSpy).toHaveBeenCalledTimes(1);
   });
 
-  it('check that advanced configs show', () => {
+  it('disables checkbox for unavailable trace', () => {
+    const boxes = htmlElement.querySelectorAll('.trace-checkbox');
+
+    const unavailableBox = assertDefined(boxes.item(2));
+    const unavailableInput = assertDefined(
+      unavailableBox.querySelector('input'),
+    );
+    expect(unavailableInput.disabled).toBeTrue();
+    expect(unavailableBox.textContent).toContain('unavailable_trace');
+  });
+
+  it('enable and select configs show', () => {
     const enable_config_opt = assertDefined(
       htmlElement.querySelector('.enable-config-opt'),
     );
@@ -106,29 +168,52 @@
     expect(selection_config_opt.innerHTML).toContain('tracing level');
   });
 
-  it('check that changing enable config causes box to change', async () => {
-    assertDefined(component.traceConfig)[
-      'layers_trace'
-    ].config!.enableConfigs[0].enabled = false;
-    fixture.detectChanges();
-    await fixture.whenStable();
-    expect(htmlElement.querySelector('.enable-config')?.innerHTML).toContain(
-      'aria-checked="false"',
+  it('changing enable config model value causes box to change', async () => {
+    const inputElement = assertDefined(
+      htmlElement.querySelector<HTMLInputElement>('.enable-config input'),
     );
+    assertDefined(
+      assertDefined(component.traceConfig)['layers_trace'].config,
+    ).enableConfigs[0].enabled = false;
+    await detectNgModelChanges();
+    expect(inputElement.checked).toBeFalse();
+    expect(inputElement.ariaChecked).toEqual('false');
+
+    assertDefined(
+      assertDefined(component.traceConfig)['layers_trace'].config,
+    ).enableConfigs[0].enabled = true;
+    await detectNgModelChanges();
+    expect(inputElement.checked).toBeTrue();
+    expect(inputElement.ariaChecked).toEqual('true');
+  });
+
+  it('changing enable config by DOM interaction emits event', async () => {
+    const inputElement = assertDefined(
+      htmlElement.querySelector<HTMLInputElement>('.enable-config input'),
+    );
+    inputElement.click();
+    fixture.detectChanges();
+    expect(configChangeSpy).toHaveBeenCalledTimes(1);
   });
 
   it('check that changing selected config causes select to change', async () => {
-    fixture.detectChanges();
-    expect(htmlElement.querySelector('.config-selection')?.innerHTML).toContain(
-      'value="debug"',
+    const selectTrigger = assertDefined(
+      htmlElement.querySelector('.mat-select-trigger'),
     );
-    assertDefined(component.traceConfig)[
-      'layers_trace'
-    ].config!.selectionConfigs[0].value = 'verbose';
+    (selectTrigger as HTMLElement).click();
     fixture.detectChanges();
     await fixture.whenStable();
-    expect(htmlElement.querySelector('.config-selection')?.innerHTML).toContain(
-      'value="verbose"',
+    const newOption = assertDefined(
+      document.querySelector<HTMLElement>('mat-option'),
     );
+    newOption.click();
+    fixture.detectChanges();
+    expect(configChangeSpy).toHaveBeenCalledTimes(1);
   });
+
+  async function detectNgModelChanges() {
+    fixture.detectChanges();
+    await fixture.whenStable();
+    fixture.detectChanges();
+  }
 });
diff --git a/tools/winscope/src/common/assert_utils.ts b/tools/winscope/src/common/assert_utils.ts
index acb7924..f3ad977 100644
--- a/tools/winscope/src/common/assert_utils.ts
+++ b/tools/winscope/src/common/assert_utils.ts
@@ -14,9 +14,16 @@
  * limitations under the License.
  */
 
-export function assertDefined<A>(value: A | null | undefined): A {
+export function assertDefined<A>(
+  value: A | null | undefined,
+  lazyErrorMessage?: () => string,
+): A {
   if (value === undefined || value === null) {
-    throw new Error(`Expected value, but found '${value}'`);
+    throw new Error(
+      lazyErrorMessage
+        ? lazyErrorMessage()
+        : `Expected value, but found '${value}'`,
+    );
   }
   return value;
 }
diff --git a/tools/winscope/src/test/unit/mock_adb_connection.ts b/tools/winscope/src/test/unit/mock_adb_connection.ts
index 654a9a6..183509a 100644
--- a/tools/winscope/src/test/unit/mock_adb_connection.ts
+++ b/tools/winscope/src/test/unit/mock_adb_connection.ts
@@ -24,6 +24,8 @@
   state: ConnectionState = ConnectionState.CONNECTING;
   errorText = '';
   files = [new File([], 'test_file')];
+  availableTracesChangeCallback: (traces: string[]) => void =
+    FunctionUtils.DO_NOTHING;
   devices: AdbDevice[] = [];
   private detectStateChangeInUi: () => Promise<void> =
     FunctionUtils.DO_NOTHING_ASYNC;
@@ -32,9 +34,11 @@
   async initialize(
     detectStateChangeInUi: () => Promise<void>,
     progressCallback: OnProgressUpdateType,
+    availableTracesChangeCallback: (traces: string[]) => void,
   ) {
     this.detectStateChangeInUi = detectStateChangeInUi;
     this.progressCallback = progressCallback;
+    this.availableTracesChangeCallback = availableTracesChangeCallback;
     this.setState(ConnectionState.CONNECTING);
   }
 
diff --git a/tools/winscope/src/trace_collection/adb_connection.ts b/tools/winscope/src/trace_collection/adb_connection.ts
index 323794b..551520b 100644
--- a/tools/winscope/src/trace_collection/adb_connection.ts
+++ b/tools/winscope/src/trace_collection/adb_connection.ts
@@ -19,16 +19,13 @@
 import {HttpRequestStatus, HttpResponse} from 'common/http_request';
 import {AdbDevice} from './adb_device';
 import {ConnectionState} from './connection_state';
-import {TraceConfigurationMap} from './trace_collection_utils';
 import {TraceRequest} from './trace_request';
 
 export abstract class AdbConnection {
   abstract initialize(
     detectStateChangeInUi: () => Promise<void>,
     progressCallback: OnProgressUpdateType,
-    availableTracesChangeCallback: (
-      availableTracesConfig: TraceConfigurationMap,
-    ) => void,
+    availableTracesChangeCallback: (traces: string[]) => void,
   ): Promise<void>;
   abstract restartConnection(): Promise<void>;
   abstract setSecurityToken(token: string): void;
diff --git a/tools/winscope/src/trace_collection/proxy_connection.ts b/tools/winscope/src/trace_collection/proxy_connection.ts
index 053830c..275e629 100644
--- a/tools/winscope/src/trace_collection/proxy_connection.ts
+++ b/tools/winscope/src/trace_collection/proxy_connection.ts
@@ -29,7 +29,6 @@
 import {AdbDevice} from './adb_device';
 import {ConnectionState} from './connection_state';
 import {ProxyEndpoint} from './proxy_endpoint';
-import {TraceConfigurationMap, TRACES} from './trace_collection_utils';
 import {TraceRequest} from './trace_request';
 
 export class ProxyConnection extends AdbConnection {
@@ -43,7 +42,7 @@
   private errorText = '';
   private securityToken = '';
   private devices: AdbDevice[] = [];
-  private selectedDevice: AdbDevice | undefined;
+  selectedDevice: AdbDevice | undefined;
   private requestedTraces: TraceRequest[] = [];
   private adbData: File[] = [];
   private keepTraceAliveWorker: number | undefined;
@@ -51,16 +50,13 @@
   private detectStateChangeInUi: () => Promise<void> =
     FunctionUtils.DO_NOTHING_ASYNC;
   private progressCallback: OnProgressUpdateType = FunctionUtils.DO_NOTHING;
-  private availableTracesChangeCallback: (
-    availableTracesConfig: TraceConfigurationMap,
-  ) => void = FunctionUtils.DO_NOTHING;
+  private availableTracesChangeCallback: (traces: string[]) => void =
+    FunctionUtils.DO_NOTHING;
 
   async initialize(
     detectStateChangeInUi: () => Promise<void>,
     progressCallback: OnProgressUpdateType,
-    availableTracesChangeCallback: (
-      availableTracesConfig: TraceConfigurationMap,
-    ) => void,
+    availableTracesChangeCallback: (traces: string[]) => void,
   ): Promise<void> {
     this.detectStateChangeInUi = detectStateChangeInUi;
     this.progressCallback = progressCallback;
@@ -182,12 +178,10 @@
         return;
 
       case ConnectionState.IDLE:
-        if (this.selectedDevice) {
+        {
           const isWaylandAvailable = await this.isWaylandAvailable();
           if (isWaylandAvailable) {
-            const availableTracesConfig = TRACES['default'];
-            Object.assign(availableTracesConfig, TRACES['arc']);
-            this.availableTracesChangeCallback(availableTracesConfig);
+            this.availableTracesChangeCallback(['wayland_trace']);
           }
         }
         return;
diff --git a/tools/winscope/src/trace_collection/proxy_connection_test.ts b/tools/winscope/src/trace_collection/proxy_connection_test.ts
index d80c08f..52d5f33 100644
--- a/tools/winscope/src/trace_collection/proxy_connection_test.ts
+++ b/tools/winscope/src/trace_collection/proxy_connection_test.ts
@@ -300,6 +300,42 @@
     });
   });
 
+  describe('wayland trace availability', () => {
+    beforeEach(() => {
+      availableTracesChangeCallback.calls.reset();
+    });
+
+    afterEach(() => {
+      localStorage.clear();
+    });
+
+    it('updates availability of wayland trace if available', async () => {
+      const successfulResponse: HttpResponse = {
+        status: HttpRequestStatus.SUCCESS,
+        type: '',
+        text: 'true',
+        body: undefined,
+        getHeader: getVersionHeader,
+      };
+      await setUpTestEnvironment(successfulResponse);
+      expect(availableTracesChangeCallback).toHaveBeenCalledOnceWith([
+        'wayland_trace',
+      ]);
+    });
+
+    it('does not update availability of traces if call fails', async () => {
+      const unsuccessfulResponse: HttpResponse = {
+        status: HttpRequestStatus.SUCCESS,
+        type: '',
+        text: 'false',
+        body: undefined,
+        getHeader: getVersionHeader,
+      };
+      await setUpTestEnvironment(unsuccessfulResponse);
+      expect(availableTracesChangeCallback).not.toHaveBeenCalled();
+    });
+  });
+
   describe('finding devices', () => {
     afterEach(() => {
       localStorage.clear();
@@ -322,7 +358,7 @@
     });
 
     it('fetches devices', async () => {
-      const noDevicesResponse: HttpResponse = {
+      const devicesResponse: HttpResponse = {
         status: HttpRequestStatus.SUCCESS,
         type: 'text',
         text: JSON.stringify({
@@ -331,7 +367,7 @@
         body: undefined,
         getHeader: getVersionHeader,
       };
-      await setUpTestEnvironment(noDevicesResponse);
+      await setUpTestEnvironment(devicesResponse);
       checkGetDevicesRequest();
       expect(connection.getState()).toEqual(ConnectionState.IDLE);
       expect(connection.getDevices()).toEqual([mockDevice]);
@@ -427,7 +463,7 @@
   }
 
   function checkGetDevicesRequest(header = '') {
-    expect(getSpy).toHaveBeenCalledOnceWith(
+    expect(getSpy).toHaveBeenCalledWith(
       ProxyConnection.WINSCOPE_PROXY_URL + ProxyEndpoint.DEVICES,
       [['Winscope-Token', header]],
       undefined,
diff --git a/tools/winscope/src/trace_collection/trace_collection_utils.ts b/tools/winscope/src/trace_collection/trace_configuration.ts
similarity index 67%
rename from tools/winscope/src/trace_collection/trace_collection_utils.ts
rename to tools/winscope/src/trace_collection/trace_configuration.ts
index 4f7d94d..c9da47e 100644
--- a/tools/winscope/src/trace_collection/trace_collection_utils.ts
+++ b/tools/winscope/src/trace_collection/trace_configuration.ts
@@ -18,9 +18,10 @@
 import {TraceType} from 'trace/trace_type';
 
 export interface TraceConfiguration {
-  name: string | undefined;
-  run: boolean | undefined;
+  name: string;
+  enabled: boolean;
   config: ConfigurationOptions | undefined;
+  available: boolean;
 }
 
 export interface TraceConfigurationMap {
@@ -112,84 +113,116 @@
   },
 ];
 
-const traceConfigurations: TraceConfigurationMap = {
+const traceDefaultConfig: TraceConfigurationMap = {
   layers_trace: {
     name: TRACE_INFO[TraceType.SURFACE_FLINGER].name,
-    run: true,
+    enabled: true,
     config: {
       enableConfigs: sfTraceEnableConfigs,
       selectionConfigs: sfTraceSelectionConfigs,
     },
+    available: true,
   },
   window_trace: {
     name: TRACE_INFO[TraceType.WINDOW_MANAGER].name,
-    run: true,
+    enabled: true,
     config: {
       enableConfigs: [],
       selectionConfigs: wmTraceSelectionConfigs,
     },
+    available: true,
   },
   screen_recording: {
     name: TRACE_INFO[TraceType.SCREEN_RECORDING].name,
-    run: true,
+    enabled: true,
     config: undefined,
+    available: true,
   },
   ime: {
     name: 'IME',
-    run: true,
+    enabled: true,
     config: undefined,
+    available: true,
   },
   transactions: {
     name: TRACE_INFO[TraceType.TRANSACTIONS].name,
-    run: true,
+    enabled: true,
     config: undefined,
+    available: true,
   },
   proto_log: {
     name: TRACE_INFO[TraceType.PROTO_LOG].name,
-    run: false,
+    enabled: false,
     config: undefined,
+    available: true,
   },
   wayland_trace: {
     name: TRACE_INFO[TraceType.WAYLAND].name,
-    run: false,
+    enabled: false,
     config: undefined,
+    available: false,
   },
   eventlog: {
     name: TRACE_INFO[TraceType.EVENT_LOG].name,
-    run: false,
+    enabled: false,
     config: undefined,
+    available: true,
   },
   transition_traces: {
     name: TRACE_INFO[TraceType.SHELL_TRANSITION].name,
-    run: false,
+    enabled: false,
     config: undefined,
+    available: true,
   },
   view_capture_traces: {
     name: 'View Capture',
-    run: false,
+    enabled: false,
     config: undefined,
+    available: true,
   },
   input: {
     name: 'Input',
-    run: false,
+    enabled: false,
     config: undefined,
+    available: true,
   },
 };
 
-export const TRACES: {[key: string]: TraceConfigurationMap} = {
-  default: {
-    window_trace: traceConfigurations['window_trace'],
-    layers_trace: traceConfigurations['layers_trace'],
-    transactions: traceConfigurations['transactions'],
-    proto_log: traceConfigurations['proto_log'],
-    screen_recording: traceConfigurations['screen_recording'],
-    ime: traceConfigurations['ime'],
-    eventlog: traceConfigurations['eventlog'],
-    transition_traces: traceConfigurations['transition_traces'],
-    view_capture_trace: traceConfigurations['view_capture_traces'],
-    input: traceConfigurations['input'],
-  },
-  arc: {
-    wayland_trace: traceConfigurations['wayland_trace'],
-  },
-};
+export function makeDefaultTraceConfigMap(): TraceConfigurationMap {
+  return structuredClone({
+    window_trace: traceDefaultConfig['window_trace'],
+    layers_trace: traceDefaultConfig['layers_trace'],
+    transactions: traceDefaultConfig['transactions'],
+    proto_log: traceDefaultConfig['proto_log'],
+    screen_recording: traceDefaultConfig['screen_recording'],
+    ime: traceDefaultConfig['ime'],
+    eventlog: traceDefaultConfig['eventlog'],
+    transition_traces: traceDefaultConfig['transition_traces'],
+    view_capture_trace: traceDefaultConfig['view_capture_traces'],
+    input: traceDefaultConfig['input'],
+    wayland_trace: traceDefaultConfig['wayland_trace'],
+  });
+}
+
+export function makeDefaultDumpConfigMap(): TraceConfigurationMap {
+  return structuredClone({
+    window_dump: {
+      name: 'Window Manager',
+      enabled: true,
+      config: undefined,
+      available: true,
+    },
+    layers_dump: {
+      name: 'Surface Flinger',
+      enabled: true,
+      config: undefined,
+      available: true,
+    },
+    screenshot: {
+      name: 'Screenshot',
+      enabled: true,
+      config: undefined,
+      available: true,
+    },
+  });
+}