Flutter 1.21.0-9.2.pre cherry picks (#64737)

* update engine hash
* allow null in compute for weak mode (#63515)
* [Material] Relanding fix to ensure time picker input mode lays out correctly in RTL (#64097)
* pin customer-testing
* [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: Alexandre Ardhuin <alexandre.ardhuin@gmail.com>
Co-authored-by: Rami <2364772+rami-a@users.noreply.github.com>
Co-authored-by: Zachary Anderson <zanderso@users.noreply.github.com>
diff --git a/.cirrus.yml b/.cirrus.yml
index c3020f3..ba7ca8f 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -277,6 +277,7 @@
       script:
         - rm -rf bin/cache/pkg/tests
         - git clone https://github.com/flutter/tests.git bin/cache/pkg/tests
+        - (cd bin/cache/pkg/tests; git checkout c72bcde)
         - dart --enable-asserts dev/customer_testing/run_tests.dart --skip-on-fetch-failure --skip-template bin/cache/pkg/tests/registry/*.test
 
     # firebase_test_lab_tests are linux-only
@@ -458,6 +459,9 @@
       script:
         - CMD /S /C "IF EXIST "bin\cache\pkg\tests\" RMDIR /S /Q bin\cache\pkg\tests"
         - git clone https://github.com/flutter/tests.git bin\cache\pkg\tests
+        - cd bin\cache\pkg\tests
+        - git checkout c72bcde
+        - cd ..\..\..\..
         - dart --enable-asserts dev\customer_testing\run_tests.dart --skip-on-fetch-failure --skip-template bin/cache/pkg/tests/registry/*.test
 
 # MACOS SHARDS
@@ -568,6 +572,7 @@
         - ulimit -S -n 2048 # https://github.com/flutter/flutter/issues/2976
         - rm -rf bin/cache/pkg/tests
         - git clone https://github.com/flutter/tests.git bin/cache/pkg/tests
+        - (cd bin/cache/pkg/tests; git checkout c72bcde)
         - dart --enable-asserts dev/customer_testing/run_tests.dart --skip-on-fetch-failure --skip-template bin/cache/pkg/tests/registry/*.test
 
     - name: deploy_gallery-macos # linux- and macos- only
diff --git a/bin/internal/engine.version b/bin/internal/engine.version
index 52c3615..803fb59 100644
--- a/bin/internal/engine.version
+++ b/bin/internal/engine.version
@@ -1 +1 @@
-267070c17a6956de1a03dbe09cda56f0c485f41b
+20a953183580250aac2e15d36007664118bda5ab
diff --git a/packages/flutter/lib/src/foundation/_isolates_io.dart b/packages/flutter/lib/src/foundation/_isolates_io.dart
index 3760c43..1de7de1 100644
--- a/packages/flutter/lib/src/foundation/_isolates_io.dart
+++ b/packages/flutter/lib/src/foundation/_isolates_io.dart
@@ -50,7 +50,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 fc72ea3..8e1941a 100644
--- a/packages/flutter/lib/src/material/time_picker.dart
+++ b/packages/flutter/lib/src/material/time_picker.dart
@@ -1414,58 +1414,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(
@@ -1489,6 +1500,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 74e0211..945067f 100644
--- a/packages/flutter/test/material/time_picker_test.dart
+++ b/packages/flutter/test/material/time_picker_test.dart
@@ -806,6 +806,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 870d06c..2d07994 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/test/commands.shard/permeable/create_test.dart b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart
index e872457..bd7f4c1 100755
--- a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart
+++ b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart
@@ -1121,7 +1121,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 {
@@ -1138,7 +1138,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 {
@@ -1152,7 +1152,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 {
@@ -1166,7 +1166,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 {
@@ -1180,7 +1180,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 {
@@ -1194,7 +1194,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 b4b08a8..d2623ad 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';
@@ -37,10 +38,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;
     }