feat: Add --no-uninstall flag to flutter test for integration tests (#182714)

## Description

Add `--no-uninstall` flag to `flutter test` to skip app uninstallation
after integration tests complete.

When running integration tests on Android (`flutter test
integration_test/`), the `IntegrationTestTestDevice.kill()` method
always calls `device.uninstallApp()` after tests finish. For Device
Policy Manager (DPM) apps, Android prevents uninstallation, causing the
error:

```bash
adb uninstall failed: Failure [DELETE_FAILED_DEVICE_POLICY_MANAGER]
```

This PR adds a `--uninstall` flag (defaulting to true) to `flutter
test`, so users can pass `--no-uninstall` to skip the post-test
uninstallation step:

```bash
flutter test integration_test/ --no-uninstall
```

Fixes #166709 

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.
diff --git a/packages/flutter_tools/lib/src/commands/test.dart b/packages/flutter_tools/lib/src/commands/test.dart
index eaac1ad..cbb40ea 100644
--- a/packages/flutter_tools/lib/src/commands/test.dart
+++ b/packages/flutter_tools/lib/src/commands/test.dart
@@ -295,6 +295,13 @@
             'and this flag can be used to override the default. To disable this for the '
             'skwasm renderer, use "--no-cross-origin-isolation".',
         hide: !verboseHelp,
+      )
+      ..addFlag(
+        'uninstall',
+        defaultsTo: true,
+        help:
+            'Whether to uninstall the app after running integration tests. '
+            'Set "--no-uninstall" to keep the app installed on the device.',
       );
 
     addDdsOptions(verboseHelp: verboseHelp);
@@ -477,6 +484,7 @@
           : null,
       printDtd: boolArg(FlutterGlobalOptions.kPrintDtd, global: true),
       webUseWasm: useWasm,
+      uninstallApp: boolArg('uninstall'),
     );
 
     final Uri? nativeAssetsJson = _isIntegrationTest
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index f67e69b..bd93029 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -982,6 +982,7 @@
     this.enableFlutterGpu = false,
     this.enableVulkanValidation = false,
     this.uninstallFirst = false,
+    this.uninstallApp = true,
     this.enableDartProfiling = true,
     this.profileStartup = false,
     this.enableEmbedderApi = false,
@@ -1016,6 +1017,7 @@
     this.enableFlutterGpu = false,
     this.enableVulkanValidation = false,
     this.uninstallFirst = false,
+    this.uninstallApp = true,
     this.enableDartProfiling = true,
     this.profileStartup = false,
     this.enableEmbedderApi = false,
@@ -1099,6 +1101,7 @@
     required this.enableFlutterGpu,
     required this.enableVulkanValidation,
     required this.uninstallFirst,
+    required this.uninstallApp,
     required this.enableDartProfiling,
     required this.profileStartup,
     required this.enableEmbedderApi,
@@ -1162,6 +1165,12 @@
   /// This is not implemented for every platform.
   final bool uninstallFirst;
 
+  /// Whether the tool should uninstall the app after running.
+  ///
+  /// This is currently only implemented for integration tests.
+  /// Defaults to true.
+  final bool uninstallApp;
+
   /// Whether to run the browser in headless mode.
   ///
   /// Some CI environments do not provide a display and fail to launch the
@@ -1295,6 +1304,7 @@
     'enableImpeller': enableImpeller.asBool,
     'enableFlutterGpu': enableFlutterGpu,
     'enableVulkanValidation': enableVulkanValidation,
+    'uninstallApp': uninstallApp,
     'enableDartProfiling': enableDartProfiling,
     'profileStartup': profileStartup,
     'enableEmbedderApi': enableEmbedderApi,
@@ -1364,6 +1374,7 @@
         enableFlutterGpu: json['enableFlutterGpu']! as bool,
         enableVulkanValidation: (json['enableVulkanValidation'] as bool?) ?? false,
         uninstallFirst: (json['uninstallFirst'] as bool?) ?? false,
+        uninstallApp: (json['uninstallApp'] as bool?) ?? true,
         enableDartProfiling: (json['enableDartProfiling'] as bool?) ?? true,
         profileStartup: (json['profileStartup'] as bool?) ?? false,
         enableEmbedderApi: (json['enableEmbedderApi'] as bool?) ?? false,
diff --git a/packages/flutter_tools/lib/src/test/integration_test_device.dart b/packages/flutter_tools/lib/src/test/integration_test_device.dart
index 0c6d0b6..d4e6ba2 100644
--- a/packages/flutter_tools/lib/src/test/integration_test_device.dart
+++ b/packages/flutter_tools/lib/src/test/integration_test_device.dart
@@ -141,8 +141,10 @@
       if (!await device.stopApp(applicationPackage, userIdentifier: userIdentifier)) {
         globals.printTrace('Could not stop the Integration Test app.');
       }
-      if (!await device.uninstallApp(applicationPackage, userIdentifier: userIdentifier)) {
-        globals.printTrace('Could not uninstall the Integration Test app.');
+      if (debuggingOptions.uninstallApp) {
+        if (!await device.uninstallApp(applicationPackage, userIdentifier: userIdentifier)) {
+          globals.printTrace('Could not uninstall the Integration Test app.');
+        }
       }
     }
 
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart
index b9a77ed..fd347f5 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart
@@ -1523,6 +1523,40 @@
     );
 
     testUsingContext(
+      'uninstallApp defaults to true',
+      () async {
+        final testRunner = FakeFlutterTestRunner(0);
+
+        final testCommand = TestCommand(testRunner: testRunner);
+        final CommandRunner<void> commandRunner = createTestCommandRunner(testCommand);
+
+        await commandRunner.run(const <String>['test', '--no-pub']);
+        expect(testRunner.lastDebuggingOptionsValue.uninstallApp, true);
+      },
+      overrides: <Type, Generator>{
+        FileSystem: () => fs,
+        ProcessManager: () => FakeProcessManager.any(),
+      },
+    );
+
+    testUsingContext(
+      '--no-uninstall sets uninstallApp to false',
+      () async {
+        final testRunner = FakeFlutterTestRunner(0);
+
+        final testCommand = TestCommand(testRunner: testRunner);
+        final CommandRunner<void> commandRunner = createTestCommandRunner(testCommand);
+
+        await commandRunner.run(const <String>['test', '--no-pub', '--no-uninstall']);
+        expect(testRunner.lastDebuggingOptionsValue.uninstallApp, false);
+      },
+      overrides: <Type, Generator>{
+        FileSystem: () => fs,
+        ProcessManager: () => FakeProcessManager.any(),
+      },
+    );
+
+    testUsingContext(
       'Passes web renderer into debugging options',
       () async {
         final testRunner = FakeFlutterTestRunner(0);
diff --git a/packages/flutter_tools/test/general.shard/integration_test_device_test.dart b/packages/flutter_tools/test/general.shard/integration_test_device_test.dart
index 422d1e6..86ed356 100644
--- a/packages/flutter_tools/test/general.shard/integration_test_device_test.dart
+++ b/packages/flutter_tools/test/general.shard/integration_test_device_test.dart
@@ -240,6 +240,76 @@
   );
 
   testUsingContext(
+    'kill() calls uninstallApp when uninstallApp is true',
+    () async {
+      final trackingDevice = FakeDeviceTrackingUninstall();
+      final testDeviceWithUninstall = IntegrationTestTestDevice(
+        id: 1,
+        device: trackingDevice,
+        debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
+        userIdentifier: '',
+        compileExpression: null,
+      );
+
+      await testDeviceWithUninstall.start('entrypointPath');
+      await testDeviceWithUninstall.kill();
+
+      expect(trackingDevice.uninstallAppCalled, isTrue);
+      expect(testDeviceWithUninstall.finished, completes);
+    },
+    overrides: <Type, Generator>{
+      ApplicationPackageFactory: () => FakeApplicationPackageFactory(),
+      VMServiceConnector: () =>
+          (
+            Uri httpUri, {
+            ReloadSources? reloadSources,
+            Restart? restart,
+            CompileExpression? compileExpression,
+            FlutterProject? flutterProject,
+            PrintStructuredErrorLogMethod? printStructuredErrorLogMethod,
+            io.CompressionOptions? compression,
+            Device? device,
+            Logger? logger,
+          }) async => fakeVmServiceHost.vmService,
+    },
+  );
+
+  testUsingContext(
+    'kill() does not call uninstallApp when uninstallApp is false',
+    () async {
+      final trackingDevice = FakeDeviceTrackingUninstall();
+      final testDeviceWithoutUninstall = IntegrationTestTestDevice(
+        id: 1,
+        device: trackingDevice,
+        debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, uninstallApp: false),
+        userIdentifier: '',
+        compileExpression: null,
+      );
+
+      await testDeviceWithoutUninstall.start('entrypointPath');
+      await testDeviceWithoutUninstall.kill();
+
+      expect(trackingDevice.uninstallAppCalled, isFalse);
+      expect(testDeviceWithoutUninstall.finished, completes);
+    },
+    overrides: <Type, Generator>{
+      ApplicationPackageFactory: () => FakeApplicationPackageFactory(),
+      VMServiceConnector: () =>
+          (
+            Uri httpUri, {
+            ReloadSources? reloadSources,
+            Restart? restart,
+            CompileExpression? compileExpression,
+            FlutterProject? flutterProject,
+            PrintStructuredErrorLogMethod? printStructuredErrorLogMethod,
+            io.CompressionOptions? compression,
+            Device? device,
+            Logger? logger,
+          }) async => fakeVmServiceHost.vmService,
+    },
+  );
+
+  testUsingContext(
     'Can handle closing of the VM service',
     () async {
       final StreamChannel<String> channel = await testDevice.start('entrypointPath');
@@ -274,3 +344,21 @@
 }
 
 class FakeApplicationPackage extends Fake implements ApplicationPackage {}
+
+class FakeDeviceTrackingUninstall extends FakeDevice {
+  FakeDeviceTrackingUninstall()
+    : super(
+        'ephemeral',
+        'ephemeral',
+        type: PlatformType.android,
+        launchResult: LaunchResult.succeeded(vmServiceUri: vmServiceUri),
+      );
+
+  bool uninstallAppCalled = false;
+
+  @override
+  Future<bool> uninstallApp(ApplicationPackage app, {String? userIdentifier}) async {
+    uninstallAppCalled = true;
+    return true;
+  }
+}