Flutter stable framework 1.20.3 cherrypicks (#64984)

* Update engine hash to 1.20.3
* Re-enable image_list test with updated certificate(good for 3650 days). (#64519)
* [Material] Relanding fix to ensure time picker input mode lays out correctly in RTL (#64097)
* allow null in compute for weak mode (#63515)
* [flutter_tools] fix recursive asset variant issue (#61129)
* [flutter_tool] Handle Windows line endings in packages_test.dart (#63806)
* [flutter_tool] Fix some create_test.dart tests on Windows (#63796)

Co-authored-by: Alexander Aprelev <aam@google.com>
Co-authored-by: Rami <2364772+rami-a@users.noreply.github.com>
Co-authored-by: Alexandre Ardhuin <alexandre.ardhuin@gmail.com>
Co-authored-by: Jonah Williams <jonahwilliams@google.com>
Co-authored-by: Zachary Anderson <zanderso@users.noreply.github.com>
diff --git a/bin/internal/engine.version b/bin/internal/engine.version
index 08f6890..c87a848 100644
--- a/bin/internal/engine.version
+++ b/bin/internal/engine.version
@@ -1 +1 @@
-9d5b21729ff53dbf8eadd8bc97e0e30d77abec95
+d1bc06f032f9d6c148ea6b96b48261d6f545004f
diff --git a/examples/image_list/lib/main.dart b/examples/image_list/lib/main.dart
index f5d0431..83c524c 100644
--- a/examples/image_list/lib/main.dart
+++ b/examples/image_list/lib/main.dart
@@ -16,28 +16,36 @@
 ///
 /// This is used in [$FH/flutter/devicelab/bin/tasks/image_list_reported_duration.dart] test.
 ///
+///
+/// To generate new certificate:
+///
+/// $ openssl req -new -out image_list.csr
+///   Generating a 2048 bit RSA private key
+///   Enter PEM pass phrase: <random string>
+///   ...
+///   Common Name (eg, fully qualified host name) []:localhost
+///
+/// Copy content of the privateKey below into image_list.key file, then
+/// $ openssl x509 -req -sha256 -days 3650 -in image_list.csr -signkey image_list.key -out image_list.crt
+///
+/// Copy content of the image_list.crt into certificate string below.
 String certificate = '''
 -----BEGIN CERTIFICATE-----
-MIIDlTCCAn2gAwIBAgIUNfw2s1D9qwYw8bMEZ1rdNVQXi9YwDQYJKoZIhvcNAQEL
-BQAwcjELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMRIwEAYDVQQHDAlS
-b2NoZXN0ZXIxEjAQBgNVBAoMCWxvY2FsaG9zdDEUMBIGA1UECwwLRGV2ZWxvcG1l
-bnQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xOTA4MjIyMzMwMjdaFw0yMDA4MjEy
-MzMwMjdaMHIxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhOZXcgWW9yazESMBAGA1UE
-BwwJUm9jaGVzdGVyMRIwEAYDVQQKDAlsb2NhbGhvc3QxFDASBgNVBAsMC0RldmVs
-b3BtZW50MRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IB
-DwAwggEKAoIBAQCi/fmozdYuCIZbJS7y4zYPp2NRboLXrpUcUzzvzz+24k/TYUPN
-eRvf6wiNXHvr1ijMg1j3wQP72RphxI7cY7XCwzRiM5QeQy3EtRz4ETYBzOev3mHL
-LEgZ9RnSq/u42siSS9CNjoz97liPyQUq8h37/09qhYG0hR/2pRN+YB9g7sNYoGe2
-B7zkh3azRS0/LtgltXwHUId7QzJc15W9Q7adsNVTpOCo7dOj2KWz6sEtFGkYfwLV
-5uiTslRdWCCOUD9iZjCtlPqALkGnWyhNiFJESLbVNC6MURyMngcALW0JTMwc2oDj
-MxtdNkMl0cdzPlhXMDKIKpY9bWbRKUUdsfOnAgMBAAGjIzAhMB8GA1UdEQQYMBaC
-CWxvY2FsaG9zdIIJMTI3LjAuMC4xMA0GCSqGSIb3DQEBCwUAA4IBAQAOYegYQ05v
-S/220FvmwH2X+/r0rcjJ5ZyOGq/MTNMhqXZKHgcNELpOLpDMZVvseXHeUKDrqZFi
-4aemMT5zbuYy3yRkQ/X39GEsksi2/Ii6Xk2zw6IJGL6hvneJEbUP3LN1POy2JLJ/
-Wt7Uda8CWa+H9+0sognT6+/86Q2fWMYnFFnAQk5wW4pYDlgInupoXzjcG0kmPgOO
-ijFsQ67xa0nUn1Llviy3pHX50IU2C+cwlWKNgxfmj6HKG9XIlu8gC/R+Ruf4aSUZ
-QT170jY8Lf6PzqLmbIW86tcKftbkP5RiEp8ESg9jDdNjhwZjxlF13aAVE8uJxP0j
-I12tWIG+68AM
+MIICpDCCAYwCCQD1kfAz8IhbazANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAls
+b2NhbGhvc3QwHhcNMjAwODI0MjE1MTUwWhcNMzAwODIyMjE1MTUwWjAUMRIwEAYD
+VQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCi
+/fmozdYuCIZbJS7y4zYPp2NRboLXrpUcUzzvzz+24k/TYUPNeRvf6wiNXHvr1ijM
+g1j3wQP72RphxI7cY7XCwzRiM5QeQy3EtRz4ETYBzOev3mHLLEgZ9RnSq/u42siS
+S9CNjoz97liPyQUq8h37/09qhYG0hR/2pRN+YB9g7sNYoGe2B7zkh3azRS0/Ltgl
+tXwHUId7QzJc15W9Q7adsNVTpOCo7dOj2KWz6sEtFGkYfwLV5uiTslRdWCCOUD9i
+ZjCtlPqALkGnWyhNiFJESLbVNC6MURyMngcALW0JTMwc2oDjMxtdNkMl0cdzPlhX
+MDKIKpY9bWbRKUUdsfOnAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAHZo/Io7hE9P
+jDhSSP+4iSwx6xjnkRjBHReoea+XwSCv1s9Alwe1Nub6u5jUBhCpGCyU4diKFV1W
+zhunXKY+zRGGtr09nYoN9UVizS5fAFb+h2x3Tw8lsxs4JpPQeWTbGK9Ci+jyfuZu
+xPvdU8I8oxiTRPoWa1KpPm6UVvcrjyftvbqJ4l7cZ8KZN4JNSZlphX8lIM14xR4H
+12sFFTcYWPNDTqO1A9MSflG4OkG59LDHV36JAEqB61pP8hipowVp48+rzD2DVpqb
+r/Mw+0x0HENUTMVExSA5rj/3fxNMggUSl2YsujVJjkb1LiQNPORX7rBndcjknAMt
+TvaTkrwwZA4=
 -----END CERTIFICATE-----
 ''';
 
diff --git a/packages/flutter/lib/src/foundation/_isolates_io.dart b/packages/flutter/lib/src/foundation/_isolates_io.dart
index e612530..da39776 100644
--- a/packages/flutter/lib/src/foundation/_isolates_io.dart
+++ b/packages/flutter/lib/src/foundation/_isolates_io.dart
@@ -52,7 +52,7 @@
     }
   });
   resultPort.listen((dynamic resultData) {
-    assert(resultData is R);
+    assert(resultData == null || resultData is R);
     if (!result.isCompleted)
       result.complete(resultData as R);
   });
diff --git a/packages/flutter/lib/src/material/time_picker.dart b/packages/flutter/lib/src/material/time_picker.dart
index f8797d4..17f248c 100644
--- a/packages/flutter/lib/src/material/time_picker.dart
+++ b/packages/flutter/lib/src/material/time_picker.dart
@@ -1416,58 +1416,69 @@
                 ),
                 const SizedBox(width: 12.0),
               ],
-              Expanded(child: Column(
-                crossAxisAlignment: CrossAxisAlignment.start,
-                children: <Widget>[
-                  const SizedBox(height: 8.0),
-                  _HourMinuteTextField(
-                    selectedTime: _selectedTime,
-                    isHour: true,
-                    style: hourMinuteStyle,
-                    validator: _validateHour,
-                    onSavedSubmitted: _handleHourSavedSubmitted,
-                    onChanged: _handleHourChanged,
-                  ),
-                  const SizedBox(height: 8.0),
-                  if (!hourHasError && !minuteHasError)
-                    ExcludeSemantics(
-                      child: Text(
-                        MaterialLocalizations.of(context).timePickerHourLabel,
-                        style: theme.textTheme.caption,
-                        maxLines: 1,
-                        overflow: TextOverflow.ellipsis,
+              Expanded(
+                child: Row(
+                  crossAxisAlignment: CrossAxisAlignment.start,
+                  // Hour/minutes should not change positions in RTL locales.
+                  textDirection: TextDirection.ltr,
+                  children: <Widget>[
+                    Expanded(
+                      child: Column(
+                        crossAxisAlignment: CrossAxisAlignment.start,
+                        children: <Widget>[
+                          const SizedBox(height: 8.0),
+                          _HourTextField(
+                            selectedTime: _selectedTime,
+                            style: hourMinuteStyle,
+                            validator: _validateHour,
+                            onSavedSubmitted: _handleHourSavedSubmitted,
+                            onChanged: _handleHourChanged,
+                          ),
+                          const SizedBox(height: 8.0),
+                          if (!hourHasError && !minuteHasError)
+                            ExcludeSemantics(
+                              child: Text(
+                                MaterialLocalizations.of(context).timePickerHourLabel,
+                                style: theme.textTheme.caption,
+                                maxLines: 1,
+                                overflow: TextOverflow.ellipsis,
+                              ),
+                            ),
+                        ],
                       ),
                     ),
-                ],
-              )),
-              Container(
-                margin: const EdgeInsets.only(top: 8.0),
-                height: _kTimePickerHeaderControlHeight,
-                child: _StringFragment(timeOfDayFormat: timeOfDayFormat),
+                    Container(
+                      margin: const EdgeInsets.only(top: 8.0),
+                      height: _kTimePickerHeaderControlHeight,
+                      child: _StringFragment(timeOfDayFormat: timeOfDayFormat),
+                    ),
+                    Expanded(
+                      child: Column(
+                        crossAxisAlignment: CrossAxisAlignment.start,
+                        children: <Widget>[
+                          const SizedBox(height: 8.0),
+                          _MinuteTextField(
+                            selectedTime: _selectedTime,
+                            style: hourMinuteStyle,
+                            validator: _validateMinute,
+                            onSavedSubmitted: _handleMinuteSavedSubmitted,
+                          ),
+                          const SizedBox(height: 8.0),
+                          if (!hourHasError && !minuteHasError)
+                            ExcludeSemantics(
+                              child: Text(
+                                MaterialLocalizations.of(context).timePickerMinuteLabel,
+                                style: theme.textTheme.caption,
+                                maxLines: 1,
+                                overflow: TextOverflow.ellipsis,
+                              ),
+                            ),
+                        ],
+                      ),
+                    ),
+                  ],
+                ),
               ),
-              Expanded(child: Column(
-                crossAxisAlignment: CrossAxisAlignment.start,
-                children: <Widget>[
-                  const SizedBox(height: 8.0),
-                  _HourMinuteTextField(
-                    selectedTime: _selectedTime,
-                    isHour: false,
-                    style: hourMinuteStyle,
-                    validator: _validateMinute,
-                    onSavedSubmitted: _handleMinuteSavedSubmitted,
-                  ),
-                  const SizedBox(height: 8.0),
-                  if (!hourHasError && !minuteHasError)
-                    ExcludeSemantics(
-                      child: Text(
-                        MaterialLocalizations.of(context).timePickerMinuteLabel,
-                        style: theme.textTheme.caption,
-                        maxLines: 1,
-                        overflow: TextOverflow.ellipsis,
-                      ),
-                    ),
-                ],
-              )),
               if (!use24HourDials && timeOfDayFormat != TimeOfDayFormat.a_space_h_colon_mm) ...<Widget>[
                 const SizedBox(width: 12.0),
                 _DayPeriodControl(
@@ -1491,6 +1502,61 @@
   }
 }
 
+class _HourTextField extends StatelessWidget {
+  const _HourTextField({
+    Key key,
+    @required this.selectedTime,
+    @required this.style,
+    @required this.validator,
+    @required this.onSavedSubmitted,
+    @required this.onChanged,
+  }) : super(key: key);
+
+  final TimeOfDay selectedTime;
+  final TextStyle style;
+  final FormFieldValidator<String> validator;
+  final ValueChanged<String> onSavedSubmitted;
+  final ValueChanged<String> onChanged;
+
+  @override
+  Widget build(BuildContext context) {
+    return _HourMinuteTextField(
+      selectedTime: selectedTime,
+      isHour: true,
+      style: style,
+      validator: validator,
+      onSavedSubmitted: onSavedSubmitted,
+      onChanged: onChanged,
+    );
+  }
+}
+
+class _MinuteTextField extends StatelessWidget {
+  const _MinuteTextField({
+    Key key,
+    @required this.selectedTime,
+    @required this.style,
+    @required this.validator,
+    @required this.onSavedSubmitted,
+  }) : super(key: key);
+
+  final TimeOfDay selectedTime;
+  final TextStyle style;
+  final FormFieldValidator<String> validator;
+  final ValueChanged<String> onSavedSubmitted;
+
+  @override
+  Widget build(BuildContext context) {
+    return _HourMinuteTextField(
+      selectedTime: selectedTime,
+      isHour: false,
+      style: style,
+      validator: validator,
+      onSavedSubmitted: onSavedSubmitted,
+    );
+  }
+}
+
 class _HourMinuteTextField extends StatefulWidget {
   const _HourMinuteTextField({
     Key key,
diff --git a/packages/flutter/test/material/time_picker_test.dart b/packages/flutter/test/material/time_picker_test.dart
index 3919bbc..b483ea6 100644
--- a/packages/flutter/test/material/time_picker_test.dart
+++ b/packages/flutter/test/material/time_picker_test.dart
@@ -760,6 +760,16 @@
     await finishPicker(tester);
     expect(result, equals(const TimeOfDay(hour: 8, minute: 15)));
   });
+
+  // Fixes regression that was reverted in https://github.com/flutter/flutter/pull/64094#pullrequestreview-469836378.
+  testWidgets('Ensure hour/minute fields are top-aligned with the separator', (WidgetTester tester) async {
+    await startPicker(tester, (TimeOfDay time) { }, entryMode: TimePickerEntryMode.input);
+    final double hourFieldTop = tester.getTopLeft(find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_HourTextField')).dy;
+    final double minuteFieldTop = tester.getTopLeft(find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_MinuteTextField')).dy;
+    final double separatorTop = tester.getTopLeft(find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_StringFragment')).dy;
+    expect(hourFieldTop, separatorTop);
+    expect(minuteFieldTop, separatorTop);
+  });
 }
 
 final Finder findDialPaint = find.descendant(
diff --git a/packages/flutter_localizations/test/material/time_picker_test.dart b/packages/flutter_localizations/test/material/time_picker_test.dart
index 48c4d0b..a7141b1 100644
--- a/packages/flutter_localizations/test/material/time_picker_test.dart
+++ b/packages/flutter_localizations/test/material/time_picker_test.dart
@@ -8,10 +8,16 @@
 import 'package:flutter_test/flutter_test.dart';
 
 class _TimePickerLauncher extends StatelessWidget {
-  const _TimePickerLauncher({ Key key, this.onChanged, this.locale }) : super(key: key);
+  const _TimePickerLauncher({
+    Key key,
+    this.onChanged,
+    this.locale,
+    this.entryMode = TimePickerEntryMode.dial,
+  }) : super(key: key);
 
   final ValueChanged<TimeOfDay> onChanged;
   final Locale locale;
+  final TimePickerEntryMode entryMode;
 
   @override
   Widget build(BuildContext context) {
@@ -28,6 +34,7 @@
                 onPressed: () async {
                   onChanged(await showTimePicker(
                     context: context,
+                    initialEntryMode: entryMode,
                     initialTime: const TimeOfDay(hour: 7, minute: 0),
                   ));
                 },
@@ -207,6 +214,73 @@
     tester.binding.window.devicePixelRatioTestValue = null;
   });
 
+  testWidgets('can localize input mode in all known formats', (WidgetTester tester) async {
+    final Finder stringFragmentTextFinder = find.descendant(
+      of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_StringFragment'),
+      matching: find.byType(Text),
+    ).first;
+    final Finder hourControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_HourTextField');
+    final Finder minuteControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_MinuteTextField');
+    final Finder dayPeriodControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DayPeriodControl');
+
+    // TODO(yjbanov): also test `HH.mm` (in_ID), `a h:mm` (ko_KR) and `HH:mm น.` (th_TH) when we have .arb files for them
+    final List<Locale> locales = <Locale>[
+      const Locale('en', 'US'), //'h:mm a'
+      const Locale('en', 'GB'), //'HH:mm'
+      const Locale('es', 'ES'), //'H:mm'
+      const Locale('fr', 'CA'), //'HH \'h\' mm'
+      const Locale('zh', 'ZH'), //'ah:mm'
+      const Locale('fa', 'IR'), //'H:mm' but RTL
+    ];
+
+    for (final Locale locale in locales) {
+      await tester.pumpWidget(_TimePickerLauncher(onChanged: (TimeOfDay time) { }, locale: locale, entryMode: TimePickerEntryMode.input));
+      await tester.tap(find.text('X'));
+      await tester.pumpAndSettle(const Duration(seconds: 1));
+
+      final Text stringFragmentText = tester.widget(stringFragmentTextFinder);
+      final double hourLeftOffset = tester.getTopLeft(hourControlFinder).dx;
+      final double minuteLeftOffset = tester.getTopLeft(minuteControlFinder).dx;
+      final double stringFragmentLeftOffset = tester.getTopLeft(stringFragmentTextFinder).dx;
+
+      if (locale == const Locale('en', 'US')) {
+        final double dayPeriodLeftOffset = tester.getTopLeft(dayPeriodControlFinder).dx;
+        expect(stringFragmentText.data, ':');
+        expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
+        expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
+        expect(minuteLeftOffset, lessThan(dayPeriodLeftOffset));
+      } else if (locale == const Locale('en', 'GB')) {
+        expect(stringFragmentText.data, ':');
+        expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
+        expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
+        expect(dayPeriodControlFinder, findsNothing);
+      } else if (locale == const Locale('es', 'ES')) {
+        expect(stringFragmentText.data, ':');
+        expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
+        expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
+        expect(dayPeriodControlFinder, findsNothing);
+      } else if (locale == const Locale('fr', 'CA')) {
+        expect(stringFragmentText.data, 'h');
+        expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
+        expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
+        expect(dayPeriodControlFinder, findsNothing);
+      } else if (locale == const Locale('zh', 'ZH')) {
+        final double dayPeriodLeftOffset = tester.getTopLeft(dayPeriodControlFinder).dx;
+        expect(stringFragmentText.data, ':');
+        expect(dayPeriodLeftOffset, lessThan(hourLeftOffset));
+        expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
+        expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
+      } else if (locale == const Locale('fa', 'IR')) {
+        // Even though this is an RTL locale, the hours and minutes positions should remain the same.
+        expect(stringFragmentText.data, ':');
+        expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
+        expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
+        expect(dayPeriodControlFinder, findsNothing);
+      }
+      await finishPicker(tester);
+    }
+  });
+
   testWidgets('uses single-ring 24-hour dial for all formats', (WidgetTester tester) async {
     const List<Locale> locales = <Locale>[
       Locale('en', 'US'), // h
diff --git a/packages/flutter_tools/lib/src/asset.dart b/packages/flutter_tools/lib/src/asset.dart
index 0473f18..7cb9b48 100644
--- a/packages/flutter_tools/lib/src/asset.dart
+++ b/packages/flutter_tools/lib/src/asset.dart
@@ -18,6 +18,7 @@
 import 'devfs.dart';
 import 'flutter_manifest.dart';
 import 'globals.dart' as globals;
+import 'project.dart';
 
 const AssetBundleFactory _kManifestFactory = _ManifestAssetBundleFactory();
 
@@ -127,22 +128,18 @@
     bool reportLicensedPackages = false,
   }) async {
     assetDirPath ??= getAssetBuildDirectory();
-    FlutterManifest flutterManifest;
+    FlutterProject flutterProject;
     try {
-      flutterManifest = FlutterManifest.createFromPath(
-        manifestPath,
-        logger: globals.logger,
-        fileSystem: globals.fs,
-      );
+      flutterProject = FlutterProject.fromDirectory(globals.fs.file(manifestPath).parent);
     } on Exception catch (e) {
       globals.printStatus('Error detected in pubspec.yaml:', emphasis: true);
       globals.printError('$e');
       return 1;
     }
-    if (flutterManifest == null) {
+    if (flutterProject == null) {
       return 1;
     }
-
+    final FlutterManifest flutterManifest = flutterProject.manifest;
     // If the last build time isn't set before this early return, empty pubspecs will
     // hang on hot reload, as the incremental dill files will never be copied to the
     // device.
@@ -168,7 +165,18 @@
       flutterManifest,
       wildcardDirectories,
       assetBasePath,
-      excludeDirs: <String>[assetDirPath, getBuildDirectory()],
+      excludeDirs: <String>[
+        assetDirPath,
+        getBuildDirectory(),
+        if (flutterProject.ios.existsSync())
+          flutterProject.ios.hostAppRoot.path,
+        if (flutterProject.macos.existsSync())
+          flutterProject.macos.managedDirectory.path,
+        if (flutterProject.windows.existsSync())
+          flutterProject.windows.managedDirectory.path,
+        if (flutterProject.linux.existsSync())
+          flutterProject.linux.managedDirectory.path,
+      ],
     );
 
     if (assetVariants == null) {
@@ -594,11 +602,12 @@
 // variantsFor('assets/foo') => ['/assets/var1/foo', '/assets/var2/foo']
 // variantsFor('assets/bar') => []
 class _AssetDirectoryCache {
-  _AssetDirectoryCache(Iterable<String> excluded) {
-    _excluded = excluded.map<String>((String path) => globals.fs.path.absolute(path) + globals.fs.path.separator);
-  }
+  _AssetDirectoryCache(Iterable<String> excluded)
+    : _excluded = excluded
+        .map<String>(globals.fs.path.absolute)
+        .toList();
 
-  Iterable<String> _excluded;
+  final List<String> _excluded;
   final Map<String, Map<String, List<String>>> _cache = <String, Map<String, List<String>>>{};
 
   List<String> variantsFor(String assetPath) {
@@ -613,7 +622,9 @@
       final List<String> paths = <String>[];
       for (final FileSystemEntity entity in globals.fs.directory(directory).listSync(recursive: true)) {
         final String path = entity.path;
-        if (globals.fs.isFileSync(path) && !_excluded.any((String exclude) => path.startsWith(exclude))) {
+        if (globals.fs.isFileSync(path)
+          && assetPath != path
+          && !_excluded.any((String exclude) => globals.fs.path.isWithin(exclude, path))) {
           paths.add(path);
         }
       }
diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart
index dd714b2..43a3748 100644
--- a/packages/flutter_tools/lib/src/context_runner.dart
+++ b/packages/flutter_tools/lib/src/context_runner.dart
@@ -124,7 +124,7 @@
         client: globals.httpClientFactory?.call() ?? HttpClient(),
       ),
       DevFSConfig: () => DevFSConfig(),
-      DeviceManager: () => DeviceManager(),
+      DeviceManager: () => FlutterDeviceManager(),
       Doctor: () => Doctor(logger: globals.logger),
       DoctorValidatorsProvider: () => DoctorValidatorsProvider.defaultInstance,
       EmulatorManager: () => EmulatorManager(
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index db157cd..2b9912a 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -67,46 +67,11 @@
 }
 
 /// A class to get all available devices.
-class DeviceManager {
+abstract class DeviceManager {
 
   /// Constructing DeviceManagers is cheap; they only do expensive work if some
   /// of their methods are called.
-  List<DeviceDiscovery> get deviceDiscoverers => _deviceDiscoverers;
-  final List<DeviceDiscovery> _deviceDiscoverers = List<DeviceDiscovery>.unmodifiable(<DeviceDiscovery>[
-    AndroidDevices(
-      logger: globals.logger,
-      androidSdk: globals.androidSdk,
-      androidWorkflow: androidWorkflow,
-      processManager: globals.processManager,
-    ),
-    IOSDevices(
-      platform: globals.platform,
-      xcdevice: globals.xcdevice,
-      iosWorkflow: globals.iosWorkflow,
-      logger: globals.logger,
-    ),
-    IOSSimulators(iosSimulatorUtils: globals.iosSimulatorUtils),
-    FuchsiaDevices(
-      fuchsiaSdk: fuchsiaSdk,
-      logger: globals.logger,
-      fuchsiaWorkflow: fuchsiaWorkflow,
-      platform: globals.platform,
-    ),
-    FlutterTesterDevices(),
-    MacOSDevices(),
-    LinuxDevices(
-      platform: globals.platform,
-      featureFlags: featureFlags,
-    ),
-    WindowsDevices(),
-    WebDevices(
-      featureFlags: featureFlags,
-      fileSystem: globals.fs,
-      platform: globals.platform,
-      processManager: globals.processManager,
-      logger: globals.logger,
-    ),
-  ]);
+  List<DeviceDiscovery> get deviceDiscoverers;
 
   String _specifiedDeviceId;
 
@@ -303,6 +268,45 @@
   }
 }
 
+class FlutterDeviceManager extends DeviceManager {
+  @override
+  final List<DeviceDiscovery> deviceDiscoverers = <DeviceDiscovery>[
+    AndroidDevices(
+      logger: globals.logger,
+      androidSdk: globals.androidSdk,
+      androidWorkflow: androidWorkflow,
+      processManager: globals.processManager,
+    ),
+    IOSDevices(
+      platform: globals.platform,
+      xcdevice: globals.xcdevice,
+      iosWorkflow: globals.iosWorkflow,
+      logger: globals.logger,
+    ),
+    IOSSimulators(iosSimulatorUtils: globals.iosSimulatorUtils),
+    FuchsiaDevices(
+      fuchsiaSdk: fuchsiaSdk,
+      logger: globals.logger,
+      fuchsiaWorkflow: fuchsiaWorkflow,
+      platform: globals.platform,
+    ),
+    FlutterTesterDevices(),
+    MacOSDevices(),
+    LinuxDevices(
+      platform: globals.platform,
+      featureFlags: featureFlags,
+    ),
+    WindowsDevices(),
+    WebDevices(
+      featureFlags: featureFlags,
+      fileSystem: globals.fs,
+      platform: globals.platform,
+      processManager: globals.processManager,
+      logger: globals.logger,
+    ),
+  ];
+}
+
 /// An abstract class to discover and enumerate a specific type of devices.
 abstract class DeviceDiscovery {
   bool get supportsPlatform;
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/devices_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/devices_test.dart
index 0e6dd41..21d09e2 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/devices_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/devices_test.dart
@@ -176,6 +176,9 @@
   Future<List<String>> getDeviceDiagnostics() => Future<List<String>>.value(
     <String>['Cannot connect to device ABC']
   );
+
+  @override
+  List<DeviceDiscovery> get deviceDiscoverers => <DeviceDiscovery>[];
 }
 
 class NoDevicesManager extends DeviceManager {
@@ -185,6 +188,9 @@
   @override
   Future<List<Device>> refreshAllConnectedDevices({Duration timeout}) =>
     getAllConnectedDevices();
+
+@override
+  List<DeviceDiscovery> get deviceDiscoverers => <DeviceDiscovery>[];
 }
 
 class MockCache extends Mock implements Cache {}
diff --git a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart
index 9a9bb99..dc5419c 100644
--- a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart
+++ b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart
@@ -1109,7 +1109,7 @@
     await runner.run(<String>['create', '--no-pub', projectDir.path]);
 
     final String metadata = globals.fs.file(globals.fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
-    expect(metadata, contains('project_type: app\n'));
+    expect(LineSplitter.split(metadata), contains('project_type: app'));
   });
 
   testUsingContext('can re-gen default template over existing app project with no metadta and detect the type', () async {
@@ -1126,7 +1126,7 @@
     await runner.run(<String>['create', '--no-pub', projectDir.path]);
 
     final String metadata = globals.fs.file(globals.fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
-    expect(metadata, contains('project_type: app\n'));
+    expect(LineSplitter.split(metadata), contains('project_type: app'));
   });
 
   testUsingContext('can re-gen app template over existing app project and detect the type', () async {
@@ -1140,7 +1140,7 @@
     await runner.run(<String>['create', '--no-pub', projectDir.path]);
 
     final String metadata = globals.fs.file(globals.fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
-    expect(metadata, contains('project_type: app\n'));
+    expect(LineSplitter.split(metadata), contains('project_type: app'));
   });
 
   testUsingContext('can re-gen template over existing module project and detect the type', () async {
@@ -1154,7 +1154,7 @@
     await runner.run(<String>['create', '--no-pub', projectDir.path]);
 
     final String metadata = globals.fs.file(globals.fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
-    expect(metadata, contains('project_type: module\n'));
+    expect(LineSplitter.split(metadata), contains('project_type: module'));
   });
 
   testUsingContext('can re-gen default template over existing plugin project and detect the type', () async {
@@ -1168,7 +1168,7 @@
     await runner.run(<String>['create', '--no-pub', projectDir.path]);
 
     final String metadata = globals.fs.file(globals.fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
-    expect(metadata, contains('project_type: plugin'));
+    expect(LineSplitter.split(metadata), contains('project_type: plugin'));
   });
 
   testUsingContext('can re-gen default template over existing package project and detect the type', () async {
@@ -1182,7 +1182,7 @@
     await runner.run(<String>['create', '--no-pub', projectDir.path]);
 
     final String metadata = globals.fs.file(globals.fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
-    expect(metadata, contains('project_type: package'));
+    expect(LineSplitter.split(metadata), contains('project_type: package'));
   });
 
   testUsingContext('can re-gen module .android/ folder, reusing custom org', () async {
diff --git a/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart b/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart
index fd4b612..232676e 100644
--- a/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart
+++ b/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'dart:async';
+import 'dart:convert';
 
 import 'package:args/command_runner.dart';
 import 'package:flutter_tools/src/base/bot_detector.dart';
@@ -38,10 +39,14 @@
       final String projectPath = await createProject(tempDir, arguments: arguments);
       final File pubspec = globals.fs.file(globals.fs.path.join(projectPath, 'pubspec.yaml'));
       String content = await pubspec.readAsString();
-      content = content.replaceFirst(
-        '\ndependencies:\n',
-        '\ndependencies:\n  $plugin:\n',
-      );
+      final List<String> contentLines = LineSplitter.split(content).toList();
+      final int depsIndex = contentLines.indexOf('dependencies:');
+      expect(depsIndex, isNot(-1));
+      contentLines.replaceRange(depsIndex, depsIndex + 1, <String>[
+        'dependencies:',
+        '  $plugin:',
+      ]);
+      content = contentLines.join('\n');
       await pubspec.writeAsString(content, flush: true);
       return projectPath;
     }
diff --git a/packages/flutter_tools/test/general.shard/asset_bundle_test.dart b/packages/flutter_tools/test/general.shard/asset_bundle_test.dart
index 155a6f5..3a9f3ed 100644
--- a/packages/flutter_tools/test/general.shard/asset_bundle_test.dart
+++ b/packages/flutter_tools/test/general.shard/asset_bundle_test.dart
@@ -437,6 +437,39 @@
     ProcessManager: () => FakeProcessManager.any(),
     Platform: () => FakePlatform(operatingSystem: 'linux'),
   });
+
+  testUsingContext('does not include assets in project directories as asset variants', () async {
+    globals.fs.file('.packages').writeAsStringSync(r'''
+example:lib/
+''');
+    globals.fs.file('pubspec.yaml')
+      ..createSync()
+      ..writeAsStringSync(r'''
+name: example
+
+flutter:
+  assets:
+    - foo.txt
+''');
+    globals.fs.file('assets/foo.txt').createSync(recursive: true);
+
+    // Potential build artifacts outisde of build directory.
+    globals.fs.file('linux/flutter/foo.txt').createSync(recursive: true);
+    globals.fs.file('windows/flutter/foo.txt').createSync(recursive: true);
+    globals.fs.file('windows/CMakeLists.txt').createSync();
+    globals.fs.file('macos/Flutter/foo.txt').createSync(recursive: true);
+    globals.fs.file('ios/foo.txt').createSync(recursive: true);
+    globals.fs.file('build/foo.txt').createSync(recursive: true);
+
+    final AssetBundle bundle = AssetBundleFactory.instance.createBundle();
+
+    expect(await bundle.build(manifestPath: 'pubspec.yaml', packagesPath: '.packages'), 0);
+    expect(bundle.entries.length, 4);
+  }, overrides: <Type, Generator>{
+    FileSystem: () => MemoryFileSystem.test(),
+    ProcessManager: () => FakeProcessManager.any(),
+    Platform: () => FakePlatform(operatingSystem: 'linux'),
+  });
 }
 
 class MockDirectory extends Mock implements Directory {}