Support brightness override in a11y page  (#9898)

* Update accessibility_controller.dart

1

* 1

Update overrides_pane.dart

Update accessibility_controller.dart

tests

Update service_extensions.dart

* resolve comments
diff --git a/packages/devtools_app/lib/src/screens/accessibility/accessibility_controller.dart b/packages/devtools_app/lib/src/screens/accessibility/accessibility_controller.dart
index 887ddf5..1fd71a3 100644
--- a/packages/devtools_app/lib/src/screens/accessibility/accessibility_controller.dart
+++ b/packages/devtools_app/lib/src/screens/accessibility/accessibility_controller.dart
@@ -2,21 +2,31 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
 
+import 'dart:async';
+
+import 'package:devtools_app_shared/service.dart';
 import 'package:devtools_app_shared/utils.dart';
 import 'package:flutter/foundation.dart';
 
+import '../../service/service_extensions.dart' as extensions;
 import '../../shared/framework/screen.dart';
 import '../../shared/framework/screen_controllers.dart';
+import '../../shared/globals.dart';
 
 /// Modes for brightness override in the accessibility controls.
 enum BrightnessOverride {
-  system('System Default'),
-  light('Light Mode'),
-  dark('Dark Mode');
+  system('System Default', 'system'),
+  light('Light Mode', 'Brightness.light'),
+  dark('Dark Mode', 'Brightness.dark');
 
-  const BrightnessOverride(this.display);
+  const BrightnessOverride(this.display, this.value);
 
+  /// The user-facing display label for this override option.
   final String display;
+
+  /// The raw value associated with this override option sent to or received
+  /// from the VM service extension.
+  final String value;
 }
 
 /// Controller for the Accessibility screen.
@@ -26,6 +36,12 @@
     _initListeners();
   }
 
+  @override
+  void init() {
+    super.init();
+    _initServiceExtensionStates();
+  }
+
   void _initListeners() {
     addAutoDisposeListener(brightness, _onBrightnessChanged);
     addAutoDisposeListener(textScale, _onTextScaleChanged);
@@ -34,9 +50,34 @@
     addAutoDisposeListener(highContrast, _onHighContrastChanged);
   }
 
+  void _initServiceExtensionStates() {
+    final state = serviceConnection.serviceManager.serviceExtensionManager
+        .getServiceExtensionState(extensions.brightnessMode.extension);
+
+    void updateFromDeviceState(ServiceExtensionState state) {
+      final newBrightness = !state.enabled || state.value == null
+          ? BrightnessOverride.system
+          : BrightnessOverride.values.firstWhere(
+              (b) => b.value == state.value,
+              orElse: () => BrightnessOverride.system,
+            );
+      brightness.value = newBrightness;
+    }
+
+    updateFromDeviceState(state.value);
+    addAutoDisposeListener(state, () => updateFromDeviceState(state.value));
+  }
+
   void _onBrightnessChanged() {
-    // TODO(hannah-hyj): Implement VM service extension call for brightness override.
-    // e.g. using 'ext.flutter.brightnessOverride'.
+    final value = brightness.value;
+    unawaited(
+      serviceConnection.serviceManager.serviceExtensionManager
+          .setServiceExtensionState(
+            extensions.brightnessMode.extension,
+            enabled: value != BrightnessOverride.system,
+            value: value.value,
+          ),
+    );
   }
 
   void _onTextScaleChanged() {
diff --git a/packages/devtools_app/lib/src/service/service_extensions.dart b/packages/devtools_app/lib/src/service/service_extensions.dart
index 7e20efa..b2bf785 100644
--- a/packages/devtools_app/lib/src/service/service_extensions.dart
+++ b/packages/devtools_app/lib/src/service/service_extensions.dart
@@ -379,6 +379,16 @@
   tooltip: 'Override Target Platform',
 );
 
+/// Service extension description for overriding brightness in accessibility controls.
+final brightnessMode = ServiceExtensionDescription<String>.from(
+  extensions.brightnessMode,
+  title: 'Override Brightness',
+  iconData: Icons.brightness_6,
+  gaScreenName: gac.accessibility,
+  gaItem: gac.brightnessOverride,
+  tooltip: 'Override Brightness',
+);
+
 final disableClipLayers = ToggleableServiceExtensionDescription<bool>.from(
   extensions.disableClipLayers,
   title: 'Render Clip layers',
diff --git a/packages/devtools_app/lib/src/shared/analytics/constants.dart b/packages/devtools_app/lib/src/shared/analytics/constants.dart
index 3d29326..c56654f 100644
--- a/packages/devtools_app/lib/src/shared/analytics/constants.dart
+++ b/packages/devtools_app/lib/src/shared/analytics/constants.dart
@@ -44,6 +44,7 @@
 const console = 'console';
 final simple = ScreenMetaData.simple.id;
 final deeplink = ScreenMetaData.deepLinks.id;
+final accessibility = ScreenMetaData.accessibility.id;
 
 // GA events not associated with a any screen e.g., hotReload, hotRestart, etc
 const devToolsMain = 'main';
@@ -86,6 +87,7 @@
 const repaintRainbowDocs = 'repaintRainbowDocs';
 const debugBanner = 'debugBanner';
 const togglePlatform = 'togglePlatform';
+const brightnessOverride = 'brightnessOverride';
 const highlightOversizedImages = 'highlightOversizedImages';
 const highlightOversizedImagesDocs = 'highlightOversizedImagesDocs';
 const selectWidgetMode = 'selectWidgetMode';
diff --git a/packages/devtools_app/test/screens/accessibility/accessibility_controller_test.dart b/packages/devtools_app/test/screens/accessibility/accessibility_controller_test.dart
new file mode 100644
index 0000000..1363f9d
--- /dev/null
+++ b/packages/devtools_app/test/screens/accessibility/accessibility_controller_test.dart
@@ -0,0 +1,118 @@
+// Copyright 2026 The Flutter Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
+
+@TestOn('vm')
+library;
+
+import 'package:devtools_app/devtools_app.dart';
+import 'package:devtools_app_shared/utils.dart';
+import 'package:devtools_test/devtools_test.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+
+void main() {
+  group('AccessibilityController', () {
+    late AccessibilityController controller;
+
+    setUp(() {
+      final fakeServiceConnection = FakeServiceConnectionManager();
+      when(
+        fakeServiceConnection.serviceManager.connectedApp!.isFlutterWebAppNow,
+      ).thenReturn(false);
+      when(
+        fakeServiceConnection.serviceManager.connectedApp!.isProfileBuildNow,
+      ).thenReturn(false);
+
+      setGlobal(NotificationService, NotificationService());
+      setGlobal(
+        DevToolsEnvironmentParameters,
+        ExternalDevToolsEnvironmentParameters(),
+      );
+      setGlobal(PreferencesController, PreferencesController());
+      setGlobal(ServiceConnectionManager, fakeServiceConnection);
+
+      controller = AccessibilityController()..init();
+    });
+
+    test('initial state', () {
+      expect(controller.brightness.value, BrightnessOverride.system);
+    });
+
+    test(
+      'service extension state change updates controller brightness state',
+      () {
+        final fakeServiceExtensionManager =
+            serviceConnection.serviceManager.serviceExtensionManager
+                as FakeServiceExtensionManager;
+
+        expect(controller.brightness.value, BrightnessOverride.system);
+
+        // Simulate service extension state change from device to dark mode
+        fakeServiceExtensionManager.fakeServiceExtensionStateChanged(
+          brightnessMode.extension,
+          'Brightness.dark',
+        );
+        expect(controller.brightness.value, BrightnessOverride.dark);
+
+        // Simulate service extension state change from device to light mode
+        fakeServiceExtensionManager.fakeServiceExtensionStateChanged(
+          brightnessMode.extension,
+          'Brightness.light',
+        );
+        expect(controller.brightness.value, BrightnessOverride.light);
+
+        // Simulate service extension state change from device to system
+        fakeServiceExtensionManager.fakeServiceExtensionStateChanged(
+          brightnessMode.extension,
+          'system',
+        );
+        expect(controller.brightness.value, BrightnessOverride.system);
+      },
+    );
+
+    test(
+      'setting controller brightness updates service extension state',
+      () async {
+        final fakeServiceExtensionManager =
+            serviceConnection.serviceManager.serviceExtensionManager
+                as FakeServiceExtensionManager;
+
+        // Initial state
+        expect(controller.brightness.value, BrightnessOverride.system);
+
+        // Set to dark mode
+        controller.brightness.value = BrightnessOverride.dark;
+
+        // Wait for async operations to complete
+        await Future<void>.delayed(Duration.zero);
+
+        final darkState = fakeServiceExtensionManager
+            .getServiceExtensionState(brightnessMode.extension)
+            .value;
+        expect(darkState.value, equals('Brightness.dark'));
+        expect(darkState.enabled, isTrue);
+
+        // Set to light mode
+        controller.brightness.value = BrightnessOverride.light;
+        await Future<void>.delayed(Duration.zero);
+
+        final lightState = fakeServiceExtensionManager
+            .getServiceExtensionState(brightnessMode.extension)
+            .value;
+        expect(lightState.value, equals('Brightness.light'));
+        expect(lightState.enabled, isTrue);
+
+        // Set to system
+        controller.brightness.value = BrightnessOverride.system;
+        await Future<void>.delayed(Duration.zero);
+
+        final systemState = fakeServiceExtensionManager
+            .getServiceExtensionState(brightnessMode.extension)
+            .value;
+        expect(systemState.value, equals('system'));
+        expect(systemState.enabled, isFalse);
+      },
+    );
+  });
+}
diff --git a/packages/devtools_app_shared/lib/src/service/service_extensions.dart b/packages/devtools_app_shared/lib/src/service/service_extensions.dart
index cada4b0..608c7c8 100644
--- a/packages/devtools_app_shared/lib/src/service/service_extensions.dart
+++ b/packages/devtools_app_shared/lib/src/service/service_extensions.dart
@@ -201,6 +201,13 @@
   values: ['iOS', 'android', 'fuchsia', 'macOS', 'linux'],
 );
 
+/// Override the platform brightness (light mode, dark mode, or system default).
+final brightnessMode = ServiceExtension<String>(
+  extension:
+      '$flutterExtensionPrefix${FoundationServiceExtensions.brightnessOverride.name}',
+  values: ['system', 'Brightness.light', 'Brightness.dark'],
+);
+
 /// Toggle whether interacting with the device selects widgets or triggers
 /// normal interactions.
 final toggleSelectWidgetMode = ToggleableServiceExtension<bool>(
@@ -259,6 +266,7 @@
   toggleSelectWidgetMode,
   countWidgetBuilds,
   profilePlatformChannels,
+  brightnessMode,
 ];
 
 /// Service extensions that are not safe to call unless a frame has already
@@ -281,6 +289,7 @@
     enableOnDeviceInspector,
     togglePlatformMode,
     slowAnimations,
+    brightnessMode,
   ].map((extension) => extension.extension),
 );
 
diff --git a/packages/devtools_app_shared/test/service/service_extensions_test.dart b/packages/devtools_app_shared/test/service/service_extensions_test.dart
new file mode 100644
index 0000000..2ee39d2
--- /dev/null
+++ b/packages/devtools_app_shared/test/service/service_extensions_test.dart
@@ -0,0 +1,32 @@
+// Copyright 2026 The Flutter Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
+
+import 'package:devtools_app_shared/service_extensions.dart';
+import 'package:test/test.dart';
+
+void main() {
+  group('ServiceExtensions', () {
+    test('brightnessMode is properly configured', () {
+      expect(
+        brightnessMode.extension,
+        equals('ext.flutter.brightnessOverride'),
+      );
+      expect(
+        brightnessMode.values,
+        equals(['system', 'Brightness.light', 'Brightness.dark']),
+      );
+      expect(
+        serviceExtensionsAllowlist[brightnessMode.extension],
+        equals(brightnessMode),
+      );
+    });
+
+    test('brightnessMode is in unsafe before first frame set', () {
+      expect(
+        isUnsafeBeforeFirstFlutterFrame('ext.flutter.brightnessOverride'),
+        isTrue,
+      );
+    });
+  });
+}