Merge branch 'main' of github.com:flutter/plugins into remove_deprecated_api
diff --git a/.ci.yaml b/.ci.yaml
index e48175a..c77aa42 100644
--- a/.ci.yaml
+++ b/.ci.yaml
@@ -45,7 +45,6 @@
   # pod's arch exclusions, so fails to build.
   - name: Mac_x64 lint_podspecs
     recipe: plugins/plugins
-    bringup: true # New target: https://github.com/flutter/plugins/pull/6637
     timeout: 30
     properties:
       add_recipes_cq: "true"
@@ -57,7 +56,6 @@
   # on Intel to give us build coverage of both host types.
   - name: Mac_x64 build_all_plugins master
     recipe: plugins/plugins
-    bringup: true # New target: https://github.com/flutter/plugins/pull/6671
     timeout: 30
     properties:
       add_recipes_cq: "true"
@@ -67,7 +65,6 @@
 
   - name: Mac_x64 build_all_plugins stable
     recipe: plugins/plugins
-    bringup: true # New target: https://github.com/flutter/plugins/pull/6671
     timeout: 30
     properties:
       add_recipes_cq: "true"
@@ -75,6 +72,91 @@
       target_file: mac_build_all_plugins.yaml
       channel: stable
 
+  ### iOS tasks ###
+  # TODO(stuartmorgan): Swap this and ios-build_all_plugins once simulator
+  # tests are reliable on the ARM infrastructure. See discussion at
+  # https://github.com/flutter/plugins/pull/5693#issuecomment-1126011089
+  - name: Mac_x64 ios_platform_tests_1_of_4 master
+    recipe: plugins/plugins
+    timeout: 30
+    properties:
+      add_recipes_cq: "true"
+      version_file: flutter_master.version
+      target_file: mac_ios_platform_tests.yaml
+      package_sharding: "--shardIndex 0 --shardCount 4"
+
+  - name: Mac_x64 ios_platform_tests_2_of_4 master
+    recipe: plugins/plugins
+    timeout: 30
+    properties:
+      add_recipes_cq: "true"
+      version_file: flutter_master.version
+      target_file: mac_ios_platform_tests.yaml
+      package_sharding: "--shardIndex 1 --shardCount 4"
+
+  - name: Mac_x64 ios_platform_tests_3_of_4 master
+    recipe: plugins/plugins
+    timeout: 30
+    properties:
+      add_recipes_cq: "true"
+      version_file: flutter_master.version
+      target_file: mac_ios_platform_tests.yaml
+      package_sharding: "--shardIndex 2 --shardCount 4"
+
+  - name: Mac_x64 ios_platform_tests_4_of_4 master
+    recipe: plugins/plugins
+    timeout: 30
+    properties:
+      add_recipes_cq: "true"
+      version_file: flutter_master.version
+      target_file: mac_ios_platform_tests.yaml
+      package_sharding: "--shardIndex 3 --shardCount 4"
+
+  # Don't run full platform tests on both channels in pre-submit.
+  - name: Mac_x64 ios_platform_tests_1_of_4 stable
+    recipe: plugins/plugins
+    presubmit: false
+    timeout: 30
+    properties:
+      channel: stable
+      add_recipes_cq: "true"
+      version_file: flutter_stable.version
+      target_file: mac_ios_platform_tests.yaml
+      package_sharding: "--shardIndex 0 --shardCount 4"
+
+  - name: Mac_x64 ios_platform_tests_2_of_4 stable
+    recipe: plugins/plugins
+    presubmit: false
+    timeout: 30
+    properties:
+      channel: stable
+      add_recipes_cq: "true"
+      version_file: flutter_stable.version
+      target_file: mac_ios_platform_tests.yaml
+      package_sharding: "--shardIndex 1 --shardCount 4"
+
+  - name: Mac_x64 ios_platform_tests_3_of_4 stable
+    recipe: plugins/plugins
+    presubmit: false
+    timeout: 30
+    properties:
+      channel: stable
+      add_recipes_cq: "true"
+      version_file: flutter_stable.version
+      target_file: mac_ios_platform_tests.yaml
+      package_sharding: "--shardIndex 2 --shardCount 4"
+
+  - name: Mac_x64 ios_platform_tests_4_of_4 stable
+    recipe: plugins/plugins
+    presubmit: false
+    timeout: 30
+    properties:
+      channel: stable
+      add_recipes_cq: "true"
+      version_file: flutter_stable.version
+      target_file: mac_ios_platform_tests.yaml
+      package_sharding: "--shardIndex 3 --shardCount 4"
+
   - name: Windows win32-platform_tests master
     recipe: plugins/plugins
     timeout: 30
diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version
index dda1206..b3d6903 100644
--- a/.ci/flutter_master.version
+++ b/.ci/flutter_master.version
@@ -1 +1 @@
-098aac7ffeef2a1846eb3a7f14788520c8400a14
+06d90b8b9e26b002d706c7f5d750bc4dd9c88210
diff --git a/.ci/scripts/create_simulator.sh b/.ci/scripts/create_simulator.sh
new file mode 100644
index 0000000..3d86739
--- /dev/null
+++ b/.ci/scripts/create_simulator.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+# Copyright 2013 The Flutter Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+device=com.apple.CoreSimulator.SimDeviceType.iPhone-11
+os=com.apple.CoreSimulator.SimRuntime.iOS-16-0
+
+xcrun simctl list
+xcrun simctl create Flutter-iPhone "$device" "$os" | xargs xcrun simctl boot
diff --git a/.ci/targets/mac_ios_platform_tests.yaml b/.ci/targets/mac_ios_platform_tests.yaml
new file mode 100644
index 0000000..ed63f22
--- /dev/null
+++ b/.ci/targets/mac_ios_platform_tests.yaml
@@ -0,0 +1,22 @@
+tasks:
+  - name: create simulator
+    script: .ci/scripts/create_simulator.sh
+  - name: build examples
+    script: script/tool_runner.sh
+    args: ["build-examples", "--ios"]
+  - name: xcode analyze
+    script: script/tool_runner.sh
+    args: ["xcode-analyze", "--ios"]
+  - name: xcode analyze deprecation
+    # Ensure we don't accidentally introduce deprecated code.
+    script: script/tool_runner.sh
+    args: ["xcode-analyze", "--ios", "--ios-min-version=13.0"]
+  - name: native test
+    script: script/tool_runner.sh
+    args: ["native-test", "--ios", "--ios-destination", "platform=iOS Simulator,name=iPhone 11,OS=latest"]
+  - name: drive examples
+    # `drive-examples` contains integration tests, which changes the UI of the application.
+    # This UI change sometimes affects `xctest`.
+    # So we run `drive-examples` after `native-test`; changing the order will result ci failure.
+    script: script/tool_runner.sh
+    args: ["drive-examples", "--ios", "--exclude=script/configs/exclude_integration_ios.yaml"]
diff --git a/.cirrus.yml b/.cirrus.yml
index 5ff275e..5f3c5fe 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -6,6 +6,18 @@
   CHANNEL: "master" # Default to master when not explicitly set by a task.
   PLUGIN_TOOL_COMMAND: "dart ./script/tool/bin/flutter_plugin_tools.dart"
 
+install_chrome_linux_template: &INSTALL_CHROME_LINUX
+  env:
+    CHROME_NO_SANDBOX: true
+    CHROME_DOWNLOAD_DIR: /tmp/chromium
+    CHROME_EXECUTABLE: $CHROME_DOWNLOAD_DIR/chrome-linux/chrome
+    CHROMEDRIVER_EXECUTABLE: $CHROME_DOWNLOAD_DIR/chromedriver/chromedriver
+    PATH: $PATH:$CHROME_DOWNLOAD_DIR/chrome-linux
+  install_chromium_script:
+    # Install a pinned version of Chromium and its corresponding ChromeDriver.
+    # Setting CHROME_EXECUTABLE above causes this version to be used for tests.
+    - ./script/install_chromium.sh
+
 tool_setup_template: &TOOL_SETUP_TEMPLATE
   tool_setup_script:
     - .ci/scripts/prepare_tool.sh
@@ -258,9 +270,7 @@
       firebase_test_lab_script:
         - if [[ -n "$GCLOUD_FIREBASE_TESTLAB_KEY" ]]; then
         -   echo $GCLOUD_FIREBASE_TESTLAB_KEY > ${HOME}/gcloud-service-key.json
-        # (TODO)cyanglaz: add --device model=starqlteue,version=26 back when the device issue is fixed in FTL.
-        # https://github.com/flutter/flutter/issues/114535
-        -   ./script/tool_runner.sh firebase-test-lab --device model=redfin,version=30 --exclude=script/configs/exclude_integration_android.yaml
+        -   ./script/tool_runner.sh firebase-test-lab --device model=redfin,version=30 --device model=starqlteue,version=26 --exclude=script/configs/exclude_integration_android.yaml
         - else
         -   echo "This user does not have permission to run Firebase Test Lab tests."
         - fi
@@ -286,16 +296,9 @@
         matrix:
           CHANNEL: "master"
           CHANNEL: "stable"
-        CHROME_NO_SANDBOX: true
-        CHROME_DIR: /tmp/web_chromium/
-        CHROME_EXECUTABLE: $CHROME_DIR/chrome-linux/chrome
-      install_script:
-        # Install a pinned version of Chromium and its corresponding ChromeDriver.
-        # Setting CHROME_EXECUTABLE above causes this version to be used for tests.
-        - ./script/install_chromium.sh "$CHROME_DIR"
+      << : *INSTALL_CHROME_LINUX
       chromedriver_background_script:
-        - cd "$CHROME_DIR"
-        - ./chromedriver/chromedriver --port=4444
+        - $CHROMEDRIVER_EXECUTABLE --port=4444
       build_script:
         - ./script/tool_runner.sh build-examples --web
       drive_script:
@@ -334,62 +337,3 @@
         - ./script/tool_runner.sh native-test --macos
       drive_script:
         - ./script/tool_runner.sh drive-examples --macos --exclude=script/configs/exclude_integration_macos.yaml
-
-# Intel macOS tasks.
-task:
-  << : *MACOS_INTEL_TEMPLATE
-  << : *FLUTTER_UPGRADE_TEMPLATE
-  matrix:
-    ### iOS+macOS tasks ***
-    # TODO(stuartmorgan): Move this to ARM once google_maps_flutter has ARM
-    # support. `pod lint` makes a synthetic target that doesn't respect the
-    # pod's arch exclusions, so fails to build.
-    - name: darwin-lint_podspecs
-      script:
-        - ./script/tool_runner.sh podspecs
-    ### iOS tasks ###
-    # TODO(stuartmorgan): Swap this and ios-build_all_plugins once simulator
-    # tests are reliable on the ARM infrastructure. See discussion at
-    # https://github.com/flutter/plugins/pull/5693#issuecomment-1126011089
-    - name: ios-platform_tests
-      # Don't run full platform tests on both channels in pre-submit.
-      skip: $CIRRUS_PR != '' && $CHANNEL == 'stable'
-      env:
-        PATH: $PATH:/usr/local/bin
-        matrix:
-          PACKAGE_SHARDING: "--shardIndex 0 --shardCount 4"
-          PACKAGE_SHARDING: "--shardIndex 1 --shardCount 4"
-          PACKAGE_SHARDING: "--shardIndex 2 --shardCount 4"
-          PACKAGE_SHARDING: "--shardIndex 3 --shardCount 4"
-        matrix:
-          CHANNEL: "master"
-          CHANNEL: "stable"
-        SIMCTL_CHILD_MAPS_API_KEY: ENCRYPTED[596a9f6bca436694625ac50851dc5da6b4d34cba8025f7db5bc9465142e8cd44e15f69e3507787753accebfc4910d550]
-      create_simulator_script:
-        - xcrun simctl list
-        - xcrun simctl create Flutter-iPhone com.apple.CoreSimulator.SimDeviceType.iPhone-11 com.apple.CoreSimulator.SimRuntime.iOS-15-0 | xargs xcrun simctl boot
-      build_script:
-        - ./script/tool_runner.sh build-examples --ios
-      xcode_analyze_script:
-        - ./script/tool_runner.sh xcode-analyze --ios
-      xcode_analyze_deprecation_script:
-        # Ensure we don't accidentally introduce deprecated code.
-        - ./script/tool_runner.sh xcode-analyze --ios --ios-min-version=13.0
-      native_test_script:
-        - ./script/tool_runner.sh native-test --ios --ios-destination "platform=iOS Simulator,name=iPhone 11,OS=latest"
-      drive_script:
-        # `drive-examples` contains integration tests, which changes the UI of the application.
-        # This UI change sometimes affects `xctest`.
-        # So we run `drive-examples` after `native-test`; changing the order will result ci failure.
-        - ./script/tool_runner.sh drive-examples --ios --exclude=script/configs/exclude_integration_ios.yaml
-    ### macOS desktop tasks ###
-    # macos-platform_tests builds all the plugins on M1, so this build is run
-    # on Intel to give us build coverage of both host types.
-    - name: macos-build_all_plugins
-      env:
-        BUILD_ALL_ARGS: "macos"
-        matrix:
-          CHANNEL: "master"
-          CHANNEL: "stable"
-      setup_script:
-      << : *BUILD_ALL_PLUGINS_APP_TEMPLATE
diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml
index 55fa7f1..55ec174 100644
--- a/.github/workflows/scorecards-analysis.yml
+++ b/.github/workflows/scorecards-analysis.yml
@@ -27,7 +27,7 @@
           persist-credentials: false
 
       - name: "Run analysis"
-        uses: ossf/scorecard-action@e363bfca00e752f91de7b7d2a77340e2e523cb18
+        uses: ossf/scorecard-action@99c53751e09b9529366343771cc321ec74e9bd3d
         with:
           results_file: results.sarif
           results_format: sarif
diff --git a/analysis_options.yaml b/analysis_options.yaml
index b12af6c..c4fe482 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -75,7 +75,7 @@
     - avoid_field_initializers_in_const_classes
     # - avoid_final_parameters # incompatible with prefer_final_parameters
     - avoid_function_literals_in_foreach_calls
-    # - avoid_implementing_value_types # LOCAL CHANGE - Needs to be enabled and violations fixed.
+    - avoid_implementing_value_types
     - avoid_init_to_null
     - avoid_js_rounded_ints
     # - avoid_multiple_declarations_per_line # seems to be a stylistic choice we don't subscribe to
@@ -87,7 +87,7 @@
     - avoid_relative_lib_imports
     - avoid_renaming_method_parameters
     - avoid_return_types_on_setters
-    # - avoid_returning_null # still violated by some pre-nnbd code that we haven't yet migrated
+    - avoid_returning_null
     - avoid_returning_null_for_future
     - avoid_returning_null_for_void
     # - avoid_returning_this # there are enough valid reasons to return `this` that this lint ends up with too many false positives
@@ -109,15 +109,17 @@
     # - cascade_invocations # doesn't match the typical style of this repo
     - cast_nullable_to_non_nullable
     # - close_sinks # not reliable enough
+    # - combinators_ordering # DIFFERENT FROM FLUTTER/FLUTTER: This isn't available on stable yet.
     # - comment_references # blocked on https://github.com/dart-lang/linter/issues/1142
-    # - conditional_uri_does_not_exist # not yet tested
+    - conditional_uri_does_not_exist
     # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204
     - control_flow_in_finally
-    # - curly_braces_in_flow_control_structures # not required by flutter style
+    - curly_braces_in_flow_control_structures
     - depend_on_referenced_packages
     - deprecated_consistency
     # - diagnostic_describe_all_properties # enabled only at the framework level (packages/flutter/lib)
     - directives_ordering
+    # - discarded_futures # not yet tested
     # - do_not_use_environment # there are appropriate times to use the environment, especially in our tests and build logic
     - empty_catches
     - empty_constructor_bodies
@@ -128,7 +130,6 @@
     - flutter_style_todos
     - hash_and_equals
     - implementation_imports
-    # - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811
     - iterable_contains_unrelated_type
     # - join_return_with_assignment # not required by flutter style
     - leading_newlines_in_multiline_strings
@@ -203,12 +204,12 @@
     - recursive_getters
     # - require_trailing_commas # blocked on https://github.com/dart-lang/sdk/issues/47441
     - secure_pubspec_urls
-    # - sized_box_for_whitespace # LOCAL CHANGE - Needs to be enabled and violations fixed.
+     - sized_box_for_whitespace
     # - sized_box_shrink_expand # not yet tested
     - slash_for_doc_comments
     - sort_child_properties_last
     - sort_constructors_first
-    # - sort_pub_dependencies # prevents separating pinned transitive dependencies
+    - sort_pub_dependencies # DIFFERENT FROM FLUTTER/FLUTTER: Flutter's use case for not sorting does not apply to this repository.
     - sort_unnamed_constructors_first
     - test_types_in_equals
     - throw_in_finally
@@ -216,7 +217,7 @@
     # - type_annotate_public_apis # subset of always_specify_types
     - type_init_formals
     # - unawaited_futures # too many false positives, especially with the way AnimationController works
-    # - unnecessary_await_in_return # LOCAL CHANGE - Needs to be enabled and violations fixed.
+    - unnecessary_await_in_return
     - unnecessary_brace_in_string_interps
     - unnecessary_const
     - unnecessary_constructor_name
@@ -226,6 +227,7 @@
     - unnecessary_late
     - unnecessary_new
     - unnecessary_null_aware_assignments
+    - unnecessary_null_aware_operator_on_extension_on_nullable
     - unnecessary_null_checks
     - unnecessary_null_in_if_null_operators
     - unnecessary_nullable_for_final_variable_declarations
@@ -236,6 +238,7 @@
     - unnecessary_string_escapes
     - unnecessary_string_interpolations
     - unnecessary_this
+    - unnecessary_to_list_in_spreads
     - unrelated_type_equality_checks
     - unsafe_html
     # - use_build_context_synchronously # LOCAL CHANGE - Needs to be enabled and violations fixed.
@@ -263,6 +266,3 @@
     # separately when moving to a shared file.
     - no_runtimeType_toString # use objectRuntimeType from package:foundation
     - public_member_api_docs # see https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#documentation-dartdocs-javadocs-etc
-    # Flutter has a specific use case for dependencies that are intentionally
-    # not sorted, which doesn't apply to this repo.
-    - sort_pub_dependencies
diff --git a/packages/camera/camera_android_camerax/android/build.gradle b/packages/camera/camera_android_camerax/android/build.gradle
index bbc0631..d830bf7 100644
--- a/packages/camera/camera_android_camerax/android/build.gradle
+++ b/packages/camera/camera_android_camerax/android/build.gradle
@@ -56,7 +56,7 @@
 
 dependencies {
     // CameraX core library using the camera2 implementation must use same version number.
-    def camerax_version = "1.2.0-rc01"
+    def camerax_version = "1.3.0-alpha01"
     implementation "androidx.camera:camera-core:${camerax_version}"
     implementation "androidx.camera:camera-camera2:${camerax_version}"
     implementation "androidx.camera:camera-lifecycle:${camerax_version}"
diff --git a/packages/camera/camera_platform_interface/CHANGELOG.md b/packages/camera/camera_platform_interface/CHANGELOG.md
index 3bfc56f..9c93cf9 100644
--- a/packages/camera/camera_platform_interface/CHANGELOG.md
+++ b/packages/camera/camera_platform_interface/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.3.1
+
+* Exports VideoCaptureOptions to allow dependencies to implement concurrent stream and record.
+
 ## 2.3.0
 
 * Adds new capture method for a camera to allow concurrent streaming and recording.
diff --git a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart
index d8f8f9c..b3e5b8f 100644
--- a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart
+++ b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart
@@ -11,7 +11,6 @@
 
 import '../../camera_platform_interface.dart';
 import '../method_channel/method_channel_camera.dart';
-import '../types/video_capture_options.dart';
 
 /// The interface that implementations of camera must implement.
 ///
diff --git a/packages/camera/camera_platform_interface/lib/src/types/types.dart b/packages/camera/camera_platform_interface/lib/src/types/types.dart
index 3eb09fc..a8a4f8c 100644
--- a/packages/camera/camera_platform_interface/lib/src/types/types.dart
+++ b/packages/camera/camera_platform_interface/lib/src/types/types.dart
@@ -10,3 +10,4 @@
 export 'focus_mode.dart';
 export 'image_format_group.dart';
 export 'resolution_preset.dart';
+export 'video_capture_options.dart';
diff --git a/packages/camera/camera_platform_interface/pubspec.yaml b/packages/camera/camera_platform_interface/pubspec.yaml
index 7ddc6d5..19642a0 100644
--- a/packages/camera/camera_platform_interface/pubspec.yaml
+++ b/packages/camera/camera_platform_interface/pubspec.yaml
@@ -4,7 +4,7 @@
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
 # NOTE: We strongly prefer non-breaking changes, even at the expense of a
 # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
-version: 2.3.0
+version: 2.3.1
 
 environment:
   sdk: '>=2.12.0 <3.0.0'
diff --git a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart
index eab518c..e3b6858 100644
--- a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart
+++ b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart
@@ -442,6 +442,52 @@
       );
     });
   });
+
+  group('exports', () {
+    test('CameraDescription is exported', () {
+      const CameraDescription(
+          name: 'abc-123',
+          sensorOrientation: 1,
+          lensDirection: CameraLensDirection.external);
+    });
+
+    test('CameraException is exported', () {
+      CameraException('1', 'error');
+    });
+
+    test('CameraImageData is exported', () {
+      const CameraImageData(
+        width: 1,
+        height: 1,
+        format: CameraImageFormat(ImageFormatGroup.bgra8888, raw: 1),
+        planes: <CameraImagePlane>[],
+      );
+    });
+
+    test('ExposureMode is exported', () {
+      // ignore: unnecessary_statements
+      ExposureMode.auto;
+    });
+
+    test('FlashMode is exported', () {
+      // ignore: unnecessary_statements
+      FlashMode.auto;
+    });
+
+    test('FocusMode is exported', () {
+      // ignore: unnecessary_statements
+      FocusMode.auto;
+    });
+
+    test('ResolutionPreset is exported', () {
+      // ignore: unnecessary_statements
+      ResolutionPreset.high;
+    });
+
+    test('VideoCaptureOptions is exported', () {
+      const VideoCaptureOptions(123);
+    });
+  });
 }
 
 class ImplementsCameraPlatform implements CameraPlatform {
diff --git a/packages/camera/camera_web/example/integration_test/camera_web_test.dart b/packages/camera/camera_web/example/integration_test/camera_web_test.dart
index e3f1138..8aefea6 100644
--- a/packages/camera/camera_web/example/integration_test/camera_web_test.dart
+++ b/packages/camera/camera_web/example/integration_test/camera_web_test.dart
@@ -1664,7 +1664,7 @@
             'with notFound error '
             'if the camera does not exist', (WidgetTester tester) async {
           expect(
-            () async => await CameraPlatform.instance.getMaxZoomLevel(
+            () async => CameraPlatform.instance.getMaxZoomLevel(
               cameraId,
             ),
             throwsA(
@@ -1689,7 +1689,7 @@
           (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;
 
           expect(
-            () async => await CameraPlatform.instance.getMaxZoomLevel(
+            () async => CameraPlatform.instance.getMaxZoomLevel(
               cameraId,
             ),
             throwsA(
@@ -1717,7 +1717,7 @@
           (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;
 
           expect(
-            () async => await CameraPlatform.instance.getMaxZoomLevel(
+            () async => CameraPlatform.instance.getMaxZoomLevel(
               cameraId,
             ),
             throwsA(
@@ -1758,7 +1758,7 @@
             'with notFound error '
             'if the camera does not exist', (WidgetTester tester) async {
           expect(
-            () async => await CameraPlatform.instance.getMinZoomLevel(
+            () async => CameraPlatform.instance.getMinZoomLevel(
               cameraId,
             ),
             throwsA(
@@ -1783,7 +1783,7 @@
           (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;
 
           expect(
-            () async => await CameraPlatform.instance.getMinZoomLevel(
+            () async => CameraPlatform.instance.getMinZoomLevel(
               cameraId,
             ),
             throwsA(
@@ -1811,7 +1811,7 @@
           (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;
 
           expect(
-            () async => await CameraPlatform.instance.getMinZoomLevel(
+            () async => CameraPlatform.instance.getMinZoomLevel(
               cameraId,
             ),
             throwsA(
@@ -1846,7 +1846,7 @@
             'with notFound error '
             'if the camera does not exist', (WidgetTester tester) async {
           expect(
-            () async => await CameraPlatform.instance.setZoomLevel(
+            () async => CameraPlatform.instance.setZoomLevel(
               cameraId,
               100.0,
             ),
@@ -1872,7 +1872,7 @@
           (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;
 
           expect(
-            () async => await CameraPlatform.instance.setZoomLevel(
+            () async => CameraPlatform.instance.setZoomLevel(
               cameraId,
               100.0,
             ),
@@ -1900,7 +1900,7 @@
           (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;
 
           expect(
-            () async => await CameraPlatform.instance.setZoomLevel(
+            () async => CameraPlatform.instance.setZoomLevel(
               cameraId,
               100.0,
             ),
@@ -1929,7 +1929,7 @@
           (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;
 
           expect(
-            () async => await CameraPlatform.instance.setZoomLevel(
+            () async => CameraPlatform.instance.setZoomLevel(
               cameraId,
               100.0,
             ),
@@ -1962,7 +1962,7 @@
             'with notFound error '
             'if the camera does not exist', (WidgetTester tester) async {
           expect(
-            () async => await CameraPlatform.instance.pausePreview(cameraId),
+            () async => CameraPlatform.instance.pausePreview(cameraId),
             throwsA(
               isA<PlatformException>().having(
                 (PlatformException e) => e.code,
@@ -1985,7 +1985,7 @@
           (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;
 
           expect(
-            () async => await CameraPlatform.instance.pausePreview(cameraId),
+            () async => CameraPlatform.instance.pausePreview(cameraId),
             throwsA(
               isA<PlatformException>().having(
                 (PlatformException e) => e.code,
@@ -2017,7 +2017,7 @@
             'with notFound error '
             'if the camera does not exist', (WidgetTester tester) async {
           expect(
-            () async => await CameraPlatform.instance.resumePreview(cameraId),
+            () async => CameraPlatform.instance.resumePreview(cameraId),
             throwsA(
               isA<PlatformException>().having(
                 (PlatformException e) => e.code,
@@ -2040,7 +2040,7 @@
           (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;
 
           expect(
-            () async => await CameraPlatform.instance.resumePreview(cameraId),
+            () async => CameraPlatform.instance.resumePreview(cameraId),
             throwsA(
               isA<PlatformException>().having(
                 (PlatformException e) => e.code,
@@ -2066,7 +2066,7 @@
           (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;
 
           expect(
-            () async => await CameraPlatform.instance.resumePreview(cameraId),
+            () async => CameraPlatform.instance.resumePreview(cameraId),
             throwsA(
               isA<PlatformException>().having(
                 (PlatformException e) => e.code,
@@ -2523,7 +2523,7 @@
               StreamQueue<CameraErrorEvent>(eventStream);
 
           expect(
-            () async => await CameraPlatform.instance.takePicture(cameraId),
+            () async => CameraPlatform.instance.takePicture(cameraId),
             throwsA(
               isA<PlatformException>(),
             ),
@@ -2560,7 +2560,7 @@
               StreamQueue<CameraErrorEvent>(eventStream);
 
           expect(
-            () async => await CameraPlatform.instance.setFlashMode(
+            () async => CameraPlatform.instance.setFlashMode(
               cameraId,
               FlashMode.always,
             ),
@@ -2600,7 +2600,7 @@
               StreamQueue<CameraErrorEvent>(eventStream);
 
           expect(
-            () async => await CameraPlatform.instance.getMaxZoomLevel(
+            () async => CameraPlatform.instance.getMaxZoomLevel(
               cameraId,
             ),
             throwsA(
@@ -2639,7 +2639,7 @@
               StreamQueue<CameraErrorEvent>(eventStream);
 
           expect(
-            () async => await CameraPlatform.instance.getMinZoomLevel(
+            () async => CameraPlatform.instance.getMinZoomLevel(
               cameraId,
             ),
             throwsA(
@@ -2678,7 +2678,7 @@
               StreamQueue<CameraErrorEvent>(eventStream);
 
           expect(
-            () async => await CameraPlatform.instance.setZoomLevel(
+            () async => CameraPlatform.instance.setZoomLevel(
               cameraId,
               100.0,
             ),
@@ -2718,7 +2718,7 @@
               StreamQueue<CameraErrorEvent>(eventStream);
 
           expect(
-            () async => await CameraPlatform.instance.resumePreview(cameraId),
+            () async => CameraPlatform.instance.resumePreview(cameraId),
             throwsA(
               isA<PlatformException>(),
             ),
@@ -2762,8 +2762,7 @@
               StreamQueue<CameraErrorEvent>(eventStream);
 
           expect(
-            () async =>
-                await CameraPlatform.instance.startVideoRecording(cameraId),
+            () async => CameraPlatform.instance.startVideoRecording(cameraId),
             throwsA(
               isA<PlatformException>(),
             ),
@@ -2830,8 +2829,7 @@
               StreamQueue<CameraErrorEvent>(eventStream);
 
           expect(
-            () async =>
-                await CameraPlatform.instance.stopVideoRecording(cameraId),
+            () async => CameraPlatform.instance.stopVideoRecording(cameraId),
             throwsA(
               isA<PlatformException>(),
             ),
@@ -2868,8 +2866,7 @@
               StreamQueue<CameraErrorEvent>(eventStream);
 
           expect(
-            () async =>
-                await CameraPlatform.instance.pauseVideoRecording(cameraId),
+            () async => CameraPlatform.instance.pauseVideoRecording(cameraId),
             throwsA(
               isA<PlatformException>(),
             ),
@@ -2906,8 +2903,7 @@
               StreamQueue<CameraErrorEvent>(eventStream);
 
           expect(
-            () async =>
-                await CameraPlatform.instance.resumeVideoRecording(cameraId),
+            () async => CameraPlatform.instance.resumeVideoRecording(cameraId),
             throwsA(
               isA<PlatformException>(),
             ),
diff --git a/packages/camera/camera_web/example/integration_test/helpers/mocks.dart b/packages/camera/camera_web/example/integration_test/helpers/mocks.dart
index 521c4bf..855ef2b 100644
--- a/packages/camera/camera_web/example/integration_test/helpers/mocks.dart
+++ b/packages/camera/camera_web/example/integration_test/helpers/mocks.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// ignore_for_file: avoid_implementing_value_types
+
 import 'dart:async';
 import 'dart:html';
 import 'dart:ui';
diff --git a/packages/camera/camera_windows/example/integration_test/camera_test.dart b/packages/camera/camera_windows/example/integration_test/camera_test.dart
index cda0f40..01db9e2 100644
--- a/packages/camera/camera_windows/example/integration_test/camera_test.dart
+++ b/packages/camera/camera_windows/example/integration_test/camera_test.dart
@@ -23,7 +23,7 @@
         (WidgetTester _) async {
       final CameraPlatform camera = CameraPlatform.instance;
 
-      expect(() async => await camera.initializeCamera(1234),
+      expect(() async => camera.initializeCamera(1234),
           throwsA(isA<CameraException>()));
     });
   });
@@ -33,7 +33,7 @@
         (WidgetTester _) async {
       final CameraPlatform camera = CameraPlatform.instance;
 
-      expect(() async => await camera.takePicture(1234),
+      expect(() async => camera.takePicture(1234),
           throwsA(isA<PlatformException>()));
     });
   });
@@ -43,7 +43,7 @@
         (WidgetTester _) async {
       final CameraPlatform camera = CameraPlatform.instance;
 
-      expect(() async => await camera.startVideoRecording(1234),
+      expect(() async => camera.startVideoRecording(1234),
           throwsA(isA<PlatformException>()));
     });
   });
@@ -53,7 +53,7 @@
         (WidgetTester _) async {
       final CameraPlatform camera = CameraPlatform.instance;
 
-      expect(() async => await camera.stopVideoRecording(1234),
+      expect(() async => camera.stopVideoRecording(1234),
           throwsA(isA<PlatformException>()));
     });
   });
@@ -63,7 +63,7 @@
         (WidgetTester _) async {
       final CameraPlatform camera = CameraPlatform.instance;
 
-      expect(() async => await camera.pausePreview(1234),
+      expect(() async => camera.pausePreview(1234),
           throwsA(isA<PlatformException>()));
     });
   });
@@ -73,7 +73,7 @@
         (WidgetTester _) async {
       final CameraPlatform camera = CameraPlatform.instance;
 
-      expect(() async => await camera.resumePreview(1234),
+      expect(() async => camera.resumePreview(1234),
           throwsA(isA<PlatformException>()));
     });
   });
diff --git a/packages/file_selector/file_selector_platform_interface/CHANGELOG.md b/packages/file_selector/file_selector_platform_interface/CHANGELOG.md
index ad803fb..e0b08f0 100644
--- a/packages/file_selector/file_selector_platform_interface/CHANGELOG.md
+++ b/packages/file_selector/file_selector_platform_interface/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.4.0
+
+* Adds `getDirectoryPaths` method to the interface.
+
 ## 2.3.0
 
 * Replaces `macUTIs` with `uniformTypeIdentifiers`. `macUTIs` is available as an alias, but will be deprecated in a future release.
diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart b/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart
index d6aebd0..98184ca 100644
--- a/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart
+++ b/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart
@@ -16,7 +16,6 @@
   @visibleForTesting
   MethodChannel get channel => _channel;
 
-  /// Load a file from user's computer and return it as an XFile
   @override
   Future<XFile?> openFile({
     List<XTypeGroup>? acceptedTypeGroups,
@@ -37,7 +36,6 @@
     return path == null ? null : XFile(path.first);
   }
 
-  /// Load multiple files from user's computer and return it as an XFile
   @override
   Future<List<XFile>> openFiles({
     List<XTypeGroup>? acceptedTypeGroups,
@@ -58,7 +56,6 @@
     return pathList?.map((String path) => XFile(path)).toList() ?? <XFile>[];
   }
 
-  /// Gets the path from a save dialog
   @override
   Future<String?> getSavePath({
     List<XTypeGroup>? acceptedTypeGroups,
@@ -79,7 +76,6 @@
     );
   }
 
-  /// Gets a directory path from a dialog
   @override
   Future<String?> getDirectoryPath({
     String? initialDirectory,
@@ -93,4 +89,17 @@
       },
     );
   }
+
+  @override
+  Future<List<String>> getDirectoryPaths(
+      {String? initialDirectory, String? confirmButtonText}) async {
+    final List<String>? pathList = await _channel.invokeListMethod<String>(
+      'getDirectoryPaths',
+      <String, dynamic>{
+        'initialDirectory': initialDirectory,
+        'confirmButtonText': confirmButtonText,
+      },
+    );
+    return pathList ?? <String>[];
+  }
 }
diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/platform_interface/file_selector_interface.dart b/packages/file_selector/file_selector_platform_interface/lib/src/platform_interface/file_selector_interface.dart
index eb4563c..ad4fe61 100644
--- a/packages/file_selector/file_selector_platform_interface/lib/src/platform_interface/file_selector_interface.dart
+++ b/packages/file_selector/file_selector_platform_interface/lib/src/platform_interface/file_selector_interface.dart
@@ -36,7 +36,7 @@
     _instance = instance;
   }
 
-  /// Open file dialog for loading files and return a file path
+  /// Opens a file dialog for loading files and returns a file path.
   /// Returns `null` if user cancels the operation.
   Future<XFile?> openFile({
     List<XTypeGroup>? acceptedTypeGroups,
@@ -46,7 +46,7 @@
     throw UnimplementedError('openFile() has not been implemented.');
   }
 
-  /// Open file dialog for loading files and return a list of file paths
+  /// Opens a file dialog for loading files and returns a list of file paths.
   Future<List<XFile>> openFiles({
     List<XTypeGroup>? acceptedTypeGroups,
     String? initialDirectory,
@@ -55,7 +55,7 @@
     throw UnimplementedError('openFiles() has not been implemented.');
   }
 
-  /// Open file dialog for saving files and return a file path at which to save
+  /// Opens a file dialog for saving files and returns a file path at which to save.
   /// Returns `null` if user cancels the operation.
   Future<String?> getSavePath({
     List<XTypeGroup>? acceptedTypeGroups,
@@ -66,7 +66,7 @@
     throw UnimplementedError('getSavePath() has not been implemented.');
   }
 
-  /// Open file dialog for loading directories and return a directory path
+  /// Opens a file dialog for loading directories and returns a directory path.
   /// Returns `null` if user cancels the operation.
   Future<String?> getDirectoryPath({
     String? initialDirectory,
@@ -74,4 +74,12 @@
   }) {
     throw UnimplementedError('getDirectoryPath() has not been implemented.');
   }
+
+  /// Opens a file dialog for loading directories and returns multiple directory paths.
+  Future<List<String>> getDirectoryPaths({
+    String? initialDirectory,
+    String? confirmButtonText,
+  }) {
+    throw UnimplementedError('getDirectoryPaths() has not been implemented.');
+  }
 }
diff --git a/packages/file_selector/file_selector_platform_interface/pubspec.yaml b/packages/file_selector/file_selector_platform_interface/pubspec.yaml
index ac8727c..4ab63ac 100644
--- a/packages/file_selector/file_selector_platform_interface/pubspec.yaml
+++ b/packages/file_selector/file_selector_platform_interface/pubspec.yaml
@@ -4,7 +4,7 @@
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22
 # NOTE: We strongly prefer non-breaking changes, even at the expense of a
 # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
-version: 2.3.0
+version: 2.4.0
 
 environment:
   sdk: ">=2.12.0 <3.0.0"
diff --git a/packages/file_selector/file_selector_platform_interface/test/file_selector_platform_interface_test.dart b/packages/file_selector/file_selector_platform_interface/test/file_selector_platform_interface_test.dart
index 91e78b4..18334e8 100644
--- a/packages/file_selector/file_selector_platform_interface/test/file_selector_platform_interface_test.dart
+++ b/packages/file_selector/file_selector_platform_interface/test/file_selector_platform_interface_test.dart
@@ -19,6 +19,16 @@
       FileSelectorPlatform.instance = ExtendsFileSelectorPlatform();
     });
   });
+
+  group('#GetDirectoryPaths', () {
+    test('Should throw unimplemented exception', () async {
+      final FileSelectorPlatform fileSelector = ExtendsFileSelectorPlatform();
+
+      await expectLater(() async {
+        return fileSelector.getDirectoryPaths();
+      }, throwsA(isA<UnimplementedError>()));
+    });
+  });
 }
 
 class ExtendsFileSelectorPlatform extends FileSelectorPlatform {}
diff --git a/packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart b/packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart
index 0f5f3a1..9caa76c 100644
--- a/packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart
+++ b/packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart
@@ -43,49 +43,46 @@
         await plugin
             .openFile(acceptedTypeGroups: <XTypeGroup>[group, groupTwo]);
 
-        expect(
+        expectMethodCall(
           log,
-          <Matcher>[
-            isMethodCall('openFile', arguments: <String, dynamic>{
-              'acceptedTypeGroups': <Map<String, dynamic>>[
-                group.toJSON(),
-                groupTwo.toJSON()
-              ],
-              'initialDirectory': null,
-              'confirmButtonText': null,
-              'multiple': false,
-            }),
-          ],
+          'openFile',
+          arguments: <String, dynamic>{
+            'acceptedTypeGroups': <Map<String, dynamic>>[
+              group.toJSON(),
+              groupTwo.toJSON()
+            ],
+            'initialDirectory': null,
+            'confirmButtonText': null,
+            'multiple': false,
+          },
         );
       });
       test('passes initialDirectory correctly', () async {
         await plugin.openFile(initialDirectory: '/example/directory');
 
-        expect(
+        expectMethodCall(
           log,
-          <Matcher>[
-            isMethodCall('openFile', arguments: <String, dynamic>{
-              'acceptedTypeGroups': null,
-              'initialDirectory': '/example/directory',
-              'confirmButtonText': null,
-              'multiple': false,
-            }),
-          ],
+          'openFile',
+          arguments: <String, dynamic>{
+            'acceptedTypeGroups': null,
+            'initialDirectory': '/example/directory',
+            'confirmButtonText': null,
+            'multiple': false,
+          },
         );
       });
       test('passes confirmButtonText correctly', () async {
         await plugin.openFile(confirmButtonText: 'Open File');
 
-        expect(
+        expectMethodCall(
           log,
-          <Matcher>[
-            isMethodCall('openFile', arguments: <String, dynamic>{
-              'acceptedTypeGroups': null,
-              'initialDirectory': null,
-              'confirmButtonText': 'Open File',
-              'multiple': false,
-            }),
-          ],
+          'openFile',
+          arguments: <String, dynamic>{
+            'acceptedTypeGroups': null,
+            'initialDirectory': null,
+            'confirmButtonText': 'Open File',
+            'multiple': false,
+          },
         );
       });
     });
@@ -108,49 +105,46 @@
         await plugin
             .openFiles(acceptedTypeGroups: <XTypeGroup>[group, groupTwo]);
 
-        expect(
+        expectMethodCall(
           log,
-          <Matcher>[
-            isMethodCall('openFile', arguments: <String, dynamic>{
-              'acceptedTypeGroups': <Map<String, dynamic>>[
-                group.toJSON(),
-                groupTwo.toJSON()
-              ],
-              'initialDirectory': null,
-              'confirmButtonText': null,
-              'multiple': true,
-            }),
-          ],
+          'openFile',
+          arguments: <String, dynamic>{
+            'acceptedTypeGroups': <Map<String, dynamic>>[
+              group.toJSON(),
+              groupTwo.toJSON()
+            ],
+            'initialDirectory': null,
+            'confirmButtonText': null,
+            'multiple': true,
+          },
         );
       });
       test('passes initialDirectory correctly', () async {
         await plugin.openFiles(initialDirectory: '/example/directory');
 
-        expect(
+        expectMethodCall(
           log,
-          <Matcher>[
-            isMethodCall('openFile', arguments: <String, dynamic>{
-              'acceptedTypeGroups': null,
-              'initialDirectory': '/example/directory',
-              'confirmButtonText': null,
-              'multiple': true,
-            }),
-          ],
+          'openFile',
+          arguments: <String, dynamic>{
+            'acceptedTypeGroups': null,
+            'initialDirectory': '/example/directory',
+            'confirmButtonText': null,
+            'multiple': true,
+          },
         );
       });
       test('passes confirmButtonText correctly', () async {
         await plugin.openFiles(confirmButtonText: 'Open File');
 
-        expect(
+        expectMethodCall(
           log,
-          <Matcher>[
-            isMethodCall('openFile', arguments: <String, dynamic>{
-              'acceptedTypeGroups': null,
-              'initialDirectory': null,
-              'confirmButtonText': 'Open File',
-              'multiple': true,
-            }),
-          ],
+          'openFile',
+          arguments: <String, dynamic>{
+            'acceptedTypeGroups': null,
+            'initialDirectory': null,
+            'confirmButtonText': 'Open File',
+            'multiple': true,
+          },
         );
       });
     });
@@ -174,79 +168,109 @@
         await plugin
             .getSavePath(acceptedTypeGroups: <XTypeGroup>[group, groupTwo]);
 
-        expect(
+        expectMethodCall(
           log,
-          <Matcher>[
-            isMethodCall('getSavePath', arguments: <String, dynamic>{
-              'acceptedTypeGroups': <Map<String, dynamic>>[
-                group.toJSON(),
-                groupTwo.toJSON()
-              ],
-              'initialDirectory': null,
-              'suggestedName': null,
-              'confirmButtonText': null,
-            }),
-          ],
+          'getSavePath',
+          arguments: <String, dynamic>{
+            'acceptedTypeGroups': <Map<String, dynamic>>[
+              group.toJSON(),
+              groupTwo.toJSON()
+            ],
+            'initialDirectory': null,
+            'suggestedName': null,
+            'confirmButtonText': null,
+          },
         );
       });
       test('passes initialDirectory correctly', () async {
         await plugin.getSavePath(initialDirectory: '/example/directory');
 
-        expect(
+        expectMethodCall(
           log,
-          <Matcher>[
-            isMethodCall('getSavePath', arguments: <String, dynamic>{
-              'acceptedTypeGroups': null,
-              'initialDirectory': '/example/directory',
-              'suggestedName': null,
-              'confirmButtonText': null,
-            }),
-          ],
+          'getSavePath',
+          arguments: <String, dynamic>{
+            'acceptedTypeGroups': null,
+            'initialDirectory': '/example/directory',
+            'suggestedName': null,
+            'confirmButtonText': null,
+          },
         );
       });
       test('passes confirmButtonText correctly', () async {
         await plugin.getSavePath(confirmButtonText: 'Open File');
 
-        expect(
+        expectMethodCall(
           log,
-          <Matcher>[
-            isMethodCall('getSavePath', arguments: <String, dynamic>{
-              'acceptedTypeGroups': null,
-              'initialDirectory': null,
-              'suggestedName': null,
-              'confirmButtonText': 'Open File',
-            }),
-          ],
+          'getSavePath',
+          arguments: <String, dynamic>{
+            'acceptedTypeGroups': null,
+            'initialDirectory': null,
+            'suggestedName': null,
+            'confirmButtonText': 'Open File',
+          },
         );
       });
-      group('#getDirectoryPath', () {
-        test('passes initialDirectory correctly', () async {
-          await plugin.getDirectoryPath(initialDirectory: '/example/directory');
+    });
+    group('#getDirectoryPath', () {
+      test('passes initialDirectory correctly', () async {
+        await plugin.getDirectoryPath(initialDirectory: '/example/directory');
 
-          expect(
-            log,
-            <Matcher>[
-              isMethodCall('getDirectoryPath', arguments: <String, dynamic>{
-                'initialDirectory': '/example/directory',
-                'confirmButtonText': null,
-              }),
-            ],
-          );
-        });
-        test('passes confirmButtonText correctly', () async {
-          await plugin.getDirectoryPath(confirmButtonText: 'Open File');
+        expectMethodCall(
+          log,
+          'getDirectoryPath',
+          arguments: <String, dynamic>{
+            'initialDirectory': '/example/directory',
+            'confirmButtonText': null,
+          },
+        );
+      });
+      test('passes confirmButtonText correctly', () async {
+        await plugin.getDirectoryPath(confirmButtonText: 'Select Folder');
 
-          expect(
-            log,
-            <Matcher>[
-              isMethodCall('getDirectoryPath', arguments: <String, dynamic>{
-                'initialDirectory': null,
-                'confirmButtonText': 'Open File',
-              }),
-            ],
-          );
-        });
+        expectMethodCall(
+          log,
+          'getDirectoryPath',
+          arguments: <String, dynamic>{
+            'initialDirectory': null,
+            'confirmButtonText': 'Select Folder',
+          },
+        );
+      });
+    });
+    group('#getDirectoryPaths', () {
+      test('passes initialDirectory correctly', () async {
+        await plugin.getDirectoryPaths(initialDirectory: '/example/directory');
+
+        expectMethodCall(
+          log,
+          'getDirectoryPaths',
+          arguments: <String, dynamic>{
+            'initialDirectory': '/example/directory',
+            'confirmButtonText': null,
+          },
+        );
+      });
+      test('passes confirmButtonText correctly', () async {
+        await plugin.getDirectoryPaths(
+            confirmButtonText: 'Select one or more Folders');
+
+        expectMethodCall(
+          log,
+          'getDirectoryPaths',
+          arguments: <String, dynamic>{
+            'initialDirectory': null,
+            'confirmButtonText': 'Select one or more Folders',
+          },
+        );
       });
     });
   });
 }
+
+void expectMethodCall(
+  List<MethodCall> log,
+  String methodName, {
+  Map<String, dynamic>? arguments,
+}) {
+  expect(log, <Matcher>[isMethodCall(methodName, arguments: arguments)]);
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
index 3707aa8..4d01646 100644
--- a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
+++ b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
@@ -1,5 +1,6 @@
 ## NEXT
 
+* Updates code for new analysis options.
 * Updates code for `no_leading_underscores_for_local_identifiers` lint.
 
 ## 2.2.1
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/lib/map_ui.dart b/packages/google_maps_flutter/google_maps_flutter/example/lib/map_ui.dart
index 3f56f40..0a3146c 100644
--- a/packages/google_maps_flutter/google_maps_flutter/example/lib/map_ui.dart
+++ b/packages/google_maps_flutter/google_maps_flutter/example/lib/map_ui.dart
@@ -240,7 +240,7 @@
   }
 
   Future<String> _getFileData(String path) async {
-    return await rootBundle.loadString(path);
+    return rootBundle.loadString(path);
   }
 
   void _setMapStyle(String mapStyle) {
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/lib/place_marker.dart b/packages/google_maps_flutter/google_maps_flutter/example/lib/place_marker.dart
index fa49917..8fde950 100644
--- a/packages/google_maps_flutter/google_maps_flutter/example/lib/place_marker.dart
+++ b/packages/google_maps_flutter/google_maps_flutter/example/lib/place_marker.dart
@@ -285,7 +285,7 @@
       bitmapIcon.complete(bitmap);
     }));
 
-    return await bitmapIcon.future;
+    return bitmapIcon.future;
   }
 
   @override
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md
index 9bc8b19..012a508 100644
--- a/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md
+++ b/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## NEXT
+
+* Updates code for new analysis options.
+
 ## 2.3.3
 
 * Update android gradle plugin to 7.3.1.
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/map_ui.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/map_ui.dart
index 009ee71..546cf1d 100644
--- a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/map_ui.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/map_ui.dart
@@ -241,7 +241,7 @@
   }
 
   Future<String> _getFileData(String path) async {
-    return await rootBundle.loadString(path);
+    return rootBundle.loadString(path);
   }
 
   void _setMapStyle(String mapStyle) {
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_marker.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_marker.dart
index 7d12f4c..2c6c725 100644
--- a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_marker.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_marker.dart
@@ -286,7 +286,7 @@
       bitmapIcon.complete(bitmap);
     }));
 
-    return await bitmapIcon.future;
+    return bitmapIcon.future;
   }
 
   @override
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md
index 7788e91..81b9c8f 100644
--- a/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md
@@ -1,3 +1,7 @@
+## NEXT
+
+* Updates code for new analysis options.
+
 ## 2.1.12
 
 * Updates imports for `prefer_relative_imports`.
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/map_ui.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/map_ui.dart
index 009ee71..546cf1d 100644
--- a/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/map_ui.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/map_ui.dart
@@ -241,7 +241,7 @@
   }
 
   Future<String> _getFileData(String path) async {
-    return await rootBundle.loadString(path);
+    return rootBundle.loadString(path);
   }
 
   void _setMapStyle(String mapStyle) {
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/place_marker.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/place_marker.dart
index 7d12f4c..2c6c725 100644
--- a/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/place_marker.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/place_marker.dart
@@ -286,7 +286,7 @@
       bitmapIcon.complete(bitmap);
     }));
 
-    return await bitmapIcon.future;
+    return bitmapIcon.future;
   }
 
   @override
diff --git a/packages/google_sign_in/google_sign_in/test/google_sign_in_test.dart b/packages/google_sign_in/google_sign_in/test/google_sign_in_test.dart
index b8a596b..2296f2d 100644
--- a/packages/google_sign_in/google_sign_in/test/google_sign_in_test.dart
+++ b/packages/google_sign_in/google_sign_in/test/google_sign_in_test.dart
@@ -13,7 +13,7 @@
 import 'google_sign_in_test.mocks.dart';
 
 /// Verify that [GoogleSignInAccount] can be mocked even though it's unused
-// ignore: must_be_immutable
+// ignore: avoid_implementing_value_types, must_be_immutable
 class MockGoogleSignInAccount extends Mock implements GoogleSignInAccount {}
 
 @GenerateMocks(<Type>[GoogleSignInPlatform])
diff --git a/packages/google_sign_in/google_sign_in_android/CHANGELOG.md b/packages/google_sign_in/google_sign_in_android/CHANGELOG.md
index 342166a..340d4a9 100644
--- a/packages/google_sign_in/google_sign_in_android/CHANGELOG.md
+++ b/packages/google_sign_in/google_sign_in_android/CHANGELOG.md
@@ -1,3 +1,11 @@
+## 6.1.3
+
+* Updates play-services-auth version to 20.4.0.
+
+## 6.1.2
+
+* Fixes passing `serverClientId` via the channelled `init` call
+
 ## 6.1.1
 
 * Corrects typos in plugin error logs and removes not actionable warnings.
diff --git a/packages/google_sign_in/google_sign_in_android/android/build.gradle b/packages/google_sign_in/google_sign_in_android/android/build.gradle
index f3a324f..634f99d 100644
--- a/packages/google_sign_in/google_sign_in_android/android/build.gradle
+++ b/packages/google_sign_in/google_sign_in_android/android/build.gradle
@@ -50,7 +50,7 @@
 }
 
 dependencies {
-    implementation 'com.google.android.gms:play-services-auth:20.3.0'
+    implementation 'com.google.android.gms:play-services-auth:20.4.0'
     implementation 'com.google.guava:guava:28.1-android'
     testImplementation 'junit:junit:4.13.2'
     testImplementation 'org.mockito:mockito-inline:4.7.0'
diff --git a/packages/google_sign_in/google_sign_in_android/lib/google_sign_in_android.dart b/packages/google_sign_in/google_sign_in_android/lib/google_sign_in_android.dart
index 731da39..5a2ccab 100644
--- a/packages/google_sign_in/google_sign_in_android/lib/google_sign_in_android.dart
+++ b/packages/google_sign_in/google_sign_in_android/lib/google_sign_in_android.dart
@@ -45,6 +45,7 @@
       'scopes': params.scopes,
       'hostedDomain': params.hostedDomain,
       'clientId': params.clientId,
+      'serverClientId': params.serverClientId,
       'forceCodeForRefreshToken': params.forceCodeForRefreshToken,
     });
   }
diff --git a/packages/google_sign_in/google_sign_in_android/pubspec.yaml b/packages/google_sign_in/google_sign_in_android/pubspec.yaml
index 086a151..bf0327e 100644
--- a/packages/google_sign_in/google_sign_in_android/pubspec.yaml
+++ b/packages/google_sign_in/google_sign_in_android/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Android implementation of the google_sign_in plugin.
 repository: https://github.com/flutter/plugins/tree/main/packages/google_sign_in/google_sign_in_android
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22
-version: 6.1.1
+version: 6.1.3
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
diff --git a/packages/google_sign_in/google_sign_in_android/test/google_sign_in_android_test.dart b/packages/google_sign_in/google_sign_in_android/test/google_sign_in_android_test.dart
index 948ced3..c081418 100644
--- a/packages/google_sign_in/google_sign_in_android/test/google_sign_in_android_test.dart
+++ b/packages/google_sign_in/google_sign_in_android/test/google_sign_in_android_test.dart
@@ -111,6 +111,7 @@
         'scopes': <String>['two', 'scopes'],
         'signInOption': 'SignInOption.games',
         'clientId': 'fakeClientId',
+        'serverClientId': null,
         'forceCodeForRefreshToken': false,
       }),
       () {
@@ -119,12 +120,14 @@
             scopes: <String>['two', 'scopes'],
             signInOption: SignInOption.games,
             clientId: 'fakeClientId',
+            serverClientId: 'fakeServerClientId',
             forceCodeForRefreshToken: true));
       }: isMethodCall('init', arguments: <String, dynamic>{
         'hostedDomain': 'example.com',
         'scopes': <String>['two', 'scopes'],
         'signInOption': 'SignInOption.games',
         'clientId': 'fakeClientId',
+        'serverClientId': 'fakeServerClientId',
         'forceCodeForRefreshToken': true,
       }),
       () {
diff --git a/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md b/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md
index ecb3b6b..7c5ebc0 100644
--- a/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md
+++ b/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md
@@ -1,5 +1,6 @@
-## NEXT
+## 5.5.1
 
+* Fixes passing `serverClientId` via the channelled `init` call
 * Updates minimum Flutter version to 2.10.
 
 ## 5.5.0
diff --git a/packages/google_sign_in/google_sign_in_ios/lib/google_sign_in_ios.dart b/packages/google_sign_in/google_sign_in_ios/lib/google_sign_in_ios.dart
index 07407ea..d7b6f79 100644
--- a/packages/google_sign_in/google_sign_in_ios/lib/google_sign_in_ios.dart
+++ b/packages/google_sign_in/google_sign_in_ios/lib/google_sign_in_ios.dart
@@ -49,6 +49,7 @@
       'scopes': params.scopes,
       'hostedDomain': params.hostedDomain,
       'clientId': params.clientId,
+      'serverClientId': params.serverClientId,
     });
   }
 
diff --git a/packages/google_sign_in/google_sign_in_ios/pubspec.yaml b/packages/google_sign_in/google_sign_in_ios/pubspec.yaml
index 24c08cd..04998d8 100644
--- a/packages/google_sign_in/google_sign_in_ios/pubspec.yaml
+++ b/packages/google_sign_in/google_sign_in_ios/pubspec.yaml
@@ -2,7 +2,7 @@
 description: iOS implementation of the google_sign_in plugin.
 repository: https://github.com/flutter/plugins/tree/main/packages/google_sign_in/google_sign_in_ios
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22
-version: 5.5.0
+version: 5.5.1
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
diff --git a/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart b/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart
index ace6509..4584298 100644
--- a/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart
+++ b/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart
@@ -124,16 +124,19 @@
         'hostedDomain': 'example.com',
         'scopes': <String>['two', 'scopes'],
         'clientId': 'fakeClientId',
+        'serverClientId': null,
       }),
       () {
         googleSignIn.initWithParams(const SignInInitParameters(
             hostedDomain: 'example.com',
             scopes: <String>['two', 'scopes'],
-            clientId: 'fakeClientId'));
+            clientId: 'fakeClientId',
+            serverClientId: 'fakeServerClientId'));
       }: isMethodCall('init', arguments: <String, dynamic>{
         'hostedDomain': 'example.com',
         'scopes': <String>['two', 'scopes'],
         'clientId': 'fakeClientId',
+        'serverClientId': 'fakeServerClientId',
       }),
       () {
         googleSignIn.getTokens(
diff --git a/packages/image_picker/image_picker_windows/test/image_picker_windows_test.dart b/packages/image_picker/image_picker_windows/test/image_picker_windows_test.dart
index c3df2d8..3b27c08 100644
--- a/packages/image_picker/image_picker_windows/test/image_picker_windows_test.dart
+++ b/packages/image_picker/image_picker_windows/test/image_picker_windows_test.dart
@@ -53,7 +53,7 @@
 
       test('pickImage throws UnimplementedError when source is camera',
           () async {
-        expect(() async => await plugin.pickImage(source: ImageSource.camera),
+        expect(() async => plugin.pickImage(source: ImageSource.camera),
             throwsA(isA<UnimplementedError>()));
       });
 
@@ -71,7 +71,7 @@
 
       test('getImage throws UnimplementedError when source is camera',
           () async {
-        expect(() async => await plugin.getImage(source: ImageSource.camera),
+        expect(() async => plugin.getImage(source: ImageSource.camera),
             throwsA(isA<UnimplementedError>()));
       });
 
@@ -102,7 +102,7 @@
 
       test('pickVideo throws UnimplementedError when source is camera',
           () async {
-        expect(() async => await plugin.pickVideo(source: ImageSource.camera),
+        expect(() async => plugin.pickVideo(source: ImageSource.camera),
             throwsA(isA<UnimplementedError>()));
       });
 
@@ -120,7 +120,7 @@
 
       test('getVideo throws UnimplementedError when source is camera',
           () async {
-        expect(() async => await plugin.getVideo(source: ImageSource.camera),
+        expect(() async => plugin.getVideo(source: ImageSource.camera),
             throwsA(isA<UnimplementedError>()));
       });
     });
diff --git a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md
index b595d7e..4aa14a8 100644
--- a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md
+++ b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.2.3+7
+
+* Updates code for new analysis options.
+
 ## 0.2.3+6
 
 * Updates android gradle plugin to 7.3.1.
diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart
index d73ca8e..c8046d6 100644
--- a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart
+++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart
@@ -171,7 +171,7 @@
           'completePurchase unsuccessful. The `purchase.verificationData` is not valid');
     }
 
-    return await billingClient
+    return billingClient
         .acknowledgePurchase(purchase.verificationData.serverVerificationData);
   }
 
diff --git a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml
index 555773a..d70e5df 100644
--- a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml
+++ b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml
@@ -2,7 +2,7 @@
 description: An implementation for the Android platform of the Flutter `in_app_purchase` plugin. This uses the Android BillingClient APIs.
 repository: https://github.com/flutter/plugins/tree/main/packages/in_app_purchase/in_app_purchase_android
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22
-version: 0.2.3+6
+version: 0.2.3+7
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
diff --git a/packages/path_provider/path_provider_android/CHANGELOG.md b/packages/path_provider/path_provider_android/CHANGELOG.md
index ebd6568..4edf0f8 100644
--- a/packages/path_provider/path_provider_android/CHANGELOG.md
+++ b/packages/path_provider/path_provider_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.0.22
+
+* Removes unused Guava dependency.
+
 ## 2.0.21
 
 * Updates code for `no_leading_underscores_for_local_identifiers` lint.
diff --git a/packages/path_provider/path_provider_android/android/build.gradle b/packages/path_provider/path_provider_android/android/build.gradle
index 9661390..c525cd3 100644
--- a/packages/path_provider/path_provider_android/android/build.gradle
+++ b/packages/path_provider/path_provider_android/android/build.gradle
@@ -55,6 +55,5 @@
 
 dependencies {
     implementation 'androidx.annotation:annotation:1.5.0'
-    implementation 'com.google.guava:guava:28.1-android'
     testImplementation 'junit:junit:4.13.2'
 }
diff --git a/packages/path_provider/path_provider_android/android/src/main/java/io/flutter/plugins/pathprovider/PathProviderPlugin.java b/packages/path_provider/path_provider_android/android/src/main/java/io/flutter/plugins/pathprovider/PathProviderPlugin.java
index 7ef8219..285d62e 100644
--- a/packages/path_provider/path_provider_android/android/src/main/java/io/flutter/plugins/pathprovider/PathProviderPlugin.java
+++ b/packages/path_provider/path_provider_android/android/src/main/java/io/flutter/plugins/pathprovider/PathProviderPlugin.java
@@ -9,6 +9,7 @@
 import android.os.Build.VERSION_CODES;
 import android.util.Log;
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import io.flutter.embedding.engine.plugins.FlutterPlugin;
 import io.flutter.plugin.common.BinaryMessenger;
 import io.flutter.plugin.common.BinaryMessenger.TaskQueue;
@@ -17,7 +18,6 @@
 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
-import javax.annotation.Nullable;
 
 public class PathProviderPlugin implements FlutterPlugin, PathProviderApi {
   static final String TAG = "PathProviderPlugin";
diff --git a/packages/path_provider/path_provider_android/pubspec.yaml b/packages/path_provider/path_provider_android/pubspec.yaml
index 5d1e6c7..9a8df83 100644
--- a/packages/path_provider/path_provider_android/pubspec.yaml
+++ b/packages/path_provider/path_provider_android/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Android implementation of the path_provider plugin.
 repository: https://github.com/flutter/plugins/tree/main/packages/path_provider/path_provider_android
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+path_provider%22
-version: 2.0.21
+version: 2.0.22
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
diff --git a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java
index cfcef3e..f401f6f 100644
--- a/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java
+++ b/packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java
@@ -28,7 +28,6 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -86,9 +85,6 @@
     }
   }
 
-  // TODO(bparrishMines): The test is ignored because it fails when ran on Firebase Test Lab. See
-  // https://github.com/flutter/flutter/issues/114246.
-  @Ignore
   @Test
   public void appShortcutLaunchActivityAfterStarting() {
     // Arrange
@@ -119,7 +115,7 @@
         "AppShortcut:" + firstShortcut.getId() + " does not launch the correct activity",
         // We can only find the shortcut type in content description while inspecting it in Ui
         // Automator Viewer.
-        device.hasObject(By.desc(firstShortcut.getId() + appReadySentinel)));
+        device.hasObject(By.descContains(firstShortcut.getId() + appReadySentinel)));
     // This is Android SingleTop behavior in which Android does not destroy the initial activity and
     // launch a new activity.
     Assert.assertEquals(initialActivity.get(), currentActivity.get());
diff --git a/packages/quick_actions/quick_actions_ios/CHANGELOG.md b/packages/quick_actions/quick_actions_ios/CHANGELOG.md
index 31fe438..bded354 100644
--- a/packages/quick_actions/quick_actions_ios/CHANGELOG.md
+++ b/packages/quick_actions/quick_actions_ios/CHANGELOG.md
@@ -1,5 +1,6 @@
-## NEXT
+## 1.0.2
 
+* Migrates remaining components to Swift and removes all Objective-C settings.
 * Migrates `RunnerUITests` to Swift.
 
 ## 1.0.1
diff --git a/packages/quick_actions/quick_actions_ios/example/ios/Podfile b/packages/quick_actions/quick_actions_ios/example/ios/Podfile
index b528052..3924e59 100644
--- a/packages/quick_actions/quick_actions_ios/example/ios/Podfile
+++ b/packages/quick_actions/quick_actions_ios/example/ios/Podfile
@@ -31,7 +31,6 @@
   flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
   target 'RunnerTests' do
     inherit! :search_paths
-    pod 'OCMock', '~> 3.9.1'
   end
 end
 
diff --git a/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj b/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj
index c853a19..f5b708b 100644
--- a/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj
@@ -16,9 +16,12 @@
 		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
 		97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
 		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
+		E092A7ED28D10802005C7F67 /* MockMethodChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7EA28D10801005C7F67 /* MockMethodChannel.swift */; };
+		E092A7EE28D10802005C7F67 /* QuickActionsPluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7EB28D10802005C7F67 /* QuickActionsPluginTests.swift */; };
+		E092A7F128D10890005C7F67 /* MockShortcutItemProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F028D10890005C7F67 /* MockShortcutItemProvider.swift */; };
+		E092A7F428D110B3005C7F67 /* DefaultShortcutItemParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F328D110B3005C7F67 /* DefaultShortcutItemParserTests.swift */; };
 		E092A7F628D128EB005C7F67 /* RunnerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F528D128EB005C7F67 /* RunnerUITests.swift */; };
-		E0C09C29289C729D00E6977E /* FLTQuickActionsPluginTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C09C28289C729D00E6977E /* FLTQuickActionsPluginTests.m */; };
-		E0C09C32289DBFCA00E6977E /* FLTShortcutStateManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C09C31289DBFCA00E6977E /* FLTShortcutStateManagerTests.m */; };
+		E0A075D529147FE200329BAE /* MockShortcutItemParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0A075D429147FE200329BAE /* MockShortcutItemParser.swift */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -75,9 +78,12 @@
 		97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		9D27FE1F0F21D4D47DDA16DE /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
 		C35AD3650AB6BF850E016715 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+		E092A7EA28D10801005C7F67 /* MockMethodChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockMethodChannel.swift; sourceTree = "<group>"; };
+		E092A7EB28D10802005C7F67 /* QuickActionsPluginTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickActionsPluginTests.swift; sourceTree = "<group>"; };
+		E092A7F028D10890005C7F67 /* MockShortcutItemProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockShortcutItemProvider.swift; sourceTree = "<group>"; };
+		E092A7F328D110B3005C7F67 /* DefaultShortcutItemParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultShortcutItemParserTests.swift; sourceTree = "<group>"; };
 		E092A7F528D128EB005C7F67 /* RunnerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerUITests.swift; sourceTree = "<group>"; };
-		E0C09C28289C729D00E6977E /* FLTQuickActionsPluginTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTQuickActionsPluginTests.m; sourceTree = "<group>"; };
-		E0C09C31289DBFCA00E6977E /* FLTShortcutStateManagerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTShortcutStateManagerTests.m; sourceTree = "<group>"; };
+		E0A075D429147FE200329BAE /* MockShortcutItemParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockShortcutItemParser.swift; sourceTree = "<group>"; };
 		F0609304FBCAEC2289164BD5 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
@@ -111,9 +117,10 @@
 		33E20B3326EFCDFC00A4A191 /* RunnerTests */ = {
 			isa = PBXGroup;
 			children = (
+				E092A7F228D10908005C7F67 /* Mocks */,
 				33E20B3626EFCDFC00A4A191 /* Info.plist */,
-				E0C09C31289DBFCA00E6977E /* FLTShortcutStateManagerTests.m */,
-				E0C09C28289C729D00E6977E /* FLTQuickActionsPluginTests.m */,
+				E092A7EB28D10802005C7F67 /* QuickActionsPluginTests.swift */,
+				E092A7F328D110B3005C7F67 /* DefaultShortcutItemParserTests.swift */,
 			);
 			path = RunnerTests;
 			sourceTree = "<group>";
@@ -205,6 +212,16 @@
 			name = Pods;
 			sourceTree = "<group>";
 		};
+		E092A7F228D10908005C7F67 /* Mocks */ = {
+			isa = PBXGroup;
+			children = (
+				E092A7EA28D10801005C7F67 /* MockMethodChannel.swift */,
+				E092A7F028D10890005C7F67 /* MockShortcutItemProvider.swift */,
+				E0A075D429147FE200329BAE /* MockShortcutItemParser.swift */,
+			);
+			path = Mocks;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 
 /* Begin PBXNativeTarget section */
@@ -277,6 +294,7 @@
 				TargetAttributes = {
 					33E20B3126EFCDFC00A4A191 = {
 						CreatedOnToolsVersion = 12.5;
+						LastSwiftMigration = 1330;
 						TestTargetID = 97C146ED1CF9000F007C117D;
 					};
 					686BE82C25E58CCF00862533 = {
@@ -416,8 +434,11 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				E0C09C32289DBFCA00E6977E /* FLTShortcutStateManagerTests.m in Sources */,
-				E0C09C29289C729D00E6977E /* FLTQuickActionsPluginTests.m in Sources */,
+				E092A7EE28D10802005C7F67 /* QuickActionsPluginTests.swift in Sources */,
+				E092A7ED28D10802005C7F67 /* MockMethodChannel.swift in Sources */,
+				E092A7F128D10890005C7F67 /* MockShortcutItemProvider.swift in Sources */,
+				E0A075D529147FE200329BAE /* MockShortcutItemParser.swift in Sources */,
+				E092A7F428D110B3005C7F67 /* DefaultShortcutItemParserTests.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -479,6 +500,7 @@
 			baseConfigurationReference = 9D27FE1F0F21D4D47DDA16DE /* Pods-RunnerTests.debug.xcconfig */;
 			buildSettings = {
 				BUNDLE_LOADER = "$(TEST_HOST)";
+				CLANG_ENABLE_MODULES = YES;
 				INFOPLIST_FILE = RunnerTests/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
@@ -487,6 +509,8 @@
 				);
 				PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests;
 				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+				SWIFT_VERSION = 5.0;
 				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner";
 			};
 			name = Debug;
@@ -496,6 +520,7 @@
 			baseConfigurationReference = 96F949A6B78E2DC62B93C4F8 /* Pods-RunnerTests.release.xcconfig */;
 			buildSettings = {
 				BUNDLE_LOADER = "$(TEST_HOST)";
+				CLANG_ENABLE_MODULES = YES;
 				INFOPLIST_FILE = RunnerTests/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
@@ -504,6 +529,7 @@
 				);
 				PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests;
 				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_VERSION = 5.0;
 				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner";
 			};
 			name = Release;
diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutItemParserTests.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutItemParserTests.swift
new file mode 100644
index 0000000..739f88e
--- /dev/null
+++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutItemParserTests.swift
@@ -0,0 +1,67 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import Flutter
+import XCTest
+
+@testable import quick_actions_ios
+
+class DefaultShortcutItemParserTests: XCTestCase {
+
+  func testParseShortcutItems() {
+    let rawItem = [
+      "type": "SearchTheThing",
+      "localizedTitle": "Search the thing",
+      "icon": "search_the_thing.png",
+    ]
+
+    let expectedItem = UIApplicationShortcutItem(
+      type: "SearchTheThing",
+      localizedTitle: "Search the thing",
+      localizedSubtitle: nil,
+      icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"),
+      userInfo: nil)
+
+    let parser = DefaultShortcutItemParser()
+    XCTAssertEqual(parser.parseShortcutItems([rawItem]), [expectedItem])
+  }
+
+  func testParseShortcutItems_noIcon() {
+    let rawItem: [String: Any] = [
+      "type": "SearchTheThing",
+      "localizedTitle": "Search the thing",
+      "icon": NSNull(),
+    ]
+
+    let expectedItem = UIApplicationShortcutItem(
+      type: "SearchTheThing",
+      localizedTitle: "Search the thing",
+      localizedSubtitle: nil,
+      icon: nil,
+      userInfo: nil)
+
+    let parser = DefaultShortcutItemParser()
+    XCTAssertEqual(parser.parseShortcutItems([rawItem]), [expectedItem])
+  }
+
+  func testParseShortcutItems_noType() {
+    let rawItem = [
+      "localizedTitle": "Search the thing",
+      "icon": "search_the_thing.png",
+    ]
+
+    let parser = DefaultShortcutItemParser()
+    XCTAssertEqual(parser.parseShortcutItems([rawItem]), [])
+  }
+
+  func testParseShortcutItems_noLocalizedTitle() {
+    let rawItem = [
+      "type": "SearchTheThing",
+      "icon": "search_the_thing.png",
+    ]
+
+    let parser = DefaultShortcutItemParser()
+    XCTAssertEqual(parser.parseShortcutItems([rawItem]), [])
+  }
+}
diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/FLTQuickActionsPluginTests.m b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/FLTQuickActionsPluginTests.m
deleted file mode 100644
index 89651b5..0000000
--- a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/FLTQuickActionsPluginTests.m
+++ /dev/null
@@ -1,210 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-@import Flutter;
-@import quick_actions_ios;
-@import XCTest;
-#import <OCMock/OCMock.h>
-
-@interface FLTQuickActionsPluginTests : XCTestCase
-
-@end
-
-@implementation FLTQuickActionsPluginTests
-
-- (void)testHandleMethodCall_setShortcutItems {
-  NSDictionary *rawItem = @{
-    @"type" : @"SearchTheThing",
-    @"localizedTitle" : @"Search the thing",
-    @"icon" : @"search_the_thing.png",
-  };
-
-  FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"setShortcutItems"
-                                                              arguments:@[ rawItem ]];
-
-  FLTShortcutStateManager *mockShortcutStateManager = OCMClassMock([FLTShortcutStateManager class]);
-
-  QuickActionsPlugin *plugin =
-      [[QuickActionsPlugin alloc] initWithChannel:OCMClassMock([FlutterMethodChannel class])
-                             shortcutStateManager:mockShortcutStateManager];
-  XCTestExpectation *resultExpectation =
-      [self expectationWithDescription:@"result block must be called."];
-  [plugin handleMethodCall:call
-                    result:^(id _Nullable result) {
-                      XCTAssertNil(result, @"result block must be called with nil.");
-                      [resultExpectation fulfill];
-                    }];
-  [self waitForExpectationsWithTimeout:1 handler:nil];
-
-  OCMVerify([mockShortcutStateManager setShortcutItems:@[ rawItem ]]);
-}
-
-- (void)testHandleMethodCall_clearShortcutItems {
-  FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"clearShortcutItems"
-                                                              arguments:nil];
-  FLTShortcutStateManager *mockShortcutStateManager = OCMClassMock([FLTShortcutStateManager class]);
-  QuickActionsPlugin *plugin =
-      [[QuickActionsPlugin alloc] initWithChannel:OCMClassMock([FlutterMethodChannel class])
-                             shortcutStateManager:mockShortcutStateManager];
-  XCTestExpectation *resultExpectation =
-      [self expectationWithDescription:@"result block must be called."];
-  [plugin handleMethodCall:call
-                    result:^(id _Nullable result) {
-                      XCTAssertNil(result, @"result block must be called with nil.");
-                      [resultExpectation fulfill];
-                    }];
-  [self waitForExpectationsWithTimeout:1 handler:nil];
-  OCMVerify([mockShortcutStateManager setShortcutItems:@[]]);
-}
-
-- (void)testHandleMethodCall_getLaunchAction {
-  FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"getLaunchAction"
-                                                              arguments:nil];
-
-  QuickActionsPlugin *plugin =
-      [[QuickActionsPlugin alloc] initWithChannel:OCMClassMock([FlutterMethodChannel class])
-                             shortcutStateManager:OCMClassMock([FLTShortcutStateManager class])];
-  XCTestExpectation *resultExpectation =
-      [self expectationWithDescription:@"result block must be called."];
-  [plugin handleMethodCall:call
-                    result:^(id _Nullable result) {
-                      XCTAssertNil(result, @"result block must be called with nil.");
-                      [resultExpectation fulfill];
-                    }];
-  [self waitForExpectationsWithTimeout:1 handler:nil];
-}
-
-- (void)testHandleMethodCall_nonExistMethods {
-  FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"nonExist" arguments:nil];
-
-  QuickActionsPlugin *plugin =
-      [[QuickActionsPlugin alloc] initWithChannel:OCMClassMock([FlutterMethodChannel class])
-                             shortcutStateManager:OCMClassMock([FLTShortcutStateManager class])];
-  XCTestExpectation *resultExpectation =
-      [self expectationWithDescription:@"result must be called."];
-  [plugin
-      handleMethodCall:call
-                result:^(id _Nullable result) {
-                  XCTAssertEqual(result, FlutterMethodNotImplemented,
-                                 @"result block must be called with FlutterMethodNotImplemented");
-                  [resultExpectation fulfill];
-                }];
-
-  [self waitForExpectationsWithTimeout:1 handler:nil];
-}
-
-- (void)testApplicationPerformActionForShortcutItem {
-  id mockChannel = OCMClassMock([FlutterMethodChannel class]);
-  QuickActionsPlugin *plugin =
-      [[QuickActionsPlugin alloc] initWithChannel:mockChannel
-                             shortcutStateManager:OCMClassMock([FLTShortcutStateManager class])];
-
-  UIApplicationShortcutItem *item = [[UIApplicationShortcutItem alloc]
-           initWithType:@"SearchTheThing"
-         localizedTitle:@"Search the thing"
-      localizedSubtitle:nil
-                   icon:[UIApplicationShortcutIcon
-                            iconWithTemplateImageName:@"search_the_thing.png"]
-               userInfo:nil];
-
-  BOOL actionResult = [plugin application:[UIApplication sharedApplication]
-             performActionForShortcutItem:item
-                        completionHandler:^(BOOL succeeded){/* no-op */}];
-  XCTAssert(actionResult, @"performActionForShortcutItem must return true.");
-  OCMVerify([mockChannel invokeMethod:@"launch" arguments:item.type]);
-}
-
-- (void)testApplicationDidFinishLaunchingWithOptions_launchWithShortcut {
-  id mockShortcutStateManager = OCMClassMock([FLTShortcutStateManager class]);
-  QuickActionsPlugin *plugin =
-      [[QuickActionsPlugin alloc] initWithChannel:OCMClassMock([FlutterMethodChannel class])
-                             shortcutStateManager:mockShortcutStateManager];
-
-  UIApplicationShortcutItem *item = [[UIApplicationShortcutItem alloc]
-           initWithType:@"SearchTheThing"
-         localizedTitle:@"Search the thing"
-      localizedSubtitle:nil
-                   icon:[UIApplicationShortcutIcon
-                            iconWithTemplateImageName:@"search_the_thing.png"]
-               userInfo:nil];
-
-  BOOL launchResult = [plugin application:[UIApplication sharedApplication]
-            didFinishLaunchingWithOptions:@{UIApplicationLaunchOptionsShortcutItemKey : item}];
-
-  XCTAssertFalse(launchResult,
-                 @"didFinishLaunchingWithOptions must return false if launched from shortcut.");
-}
-
-- (void)testApplicationDidFinishLaunchingWithOptions_launchWithoutShortcut {
-  QuickActionsPlugin *plugin =
-      [[QuickActionsPlugin alloc] initWithChannel:OCMClassMock([FlutterMethodChannel class])
-                             shortcutStateManager:OCMClassMock([FLTShortcutStateManager class])];
-  BOOL launchResult = [plugin application:[UIApplication sharedApplication]
-            didFinishLaunchingWithOptions:@{}];
-  XCTAssertTrue(launchResult,
-                @"didFinishLaunchingWithOptions must return true if not launched from shortcut.");
-}
-
-- (void)testApplicationDidBecomeActive_launchWithoutShortcut {
-  id mockChannel = OCMClassMock([FlutterMethodChannel class]);
-  id mockShortcutStateManager = OCMClassMock([FLTShortcutStateManager class]);
-  QuickActionsPlugin *plugin =
-      [[QuickActionsPlugin alloc] initWithChannel:mockChannel
-                             shortcutStateManager:mockShortcutStateManager];
-
-  BOOL launchResult = [plugin application:[UIApplication sharedApplication]
-            didFinishLaunchingWithOptions:@{}];
-  XCTAssertTrue(launchResult,
-                @"didFinishLaunchingWithOptions must return true if not launched from shortcut.");
-  [plugin applicationDidBecomeActive:[UIApplication sharedApplication]];
-  OCMVerify(never(), [mockChannel invokeMethod:OCMOCK_ANY arguments:OCMOCK_ANY]);
-}
-
-- (void)testApplicationDidBecomeActive_launchWithShortcut {
-  id mockChannel = OCMClassMock([FlutterMethodChannel class]);
-  id mockShortcutStateManager = OCMClassMock([FLTShortcutStateManager class]);
-  QuickActionsPlugin *plugin =
-      [[QuickActionsPlugin alloc] initWithChannel:mockChannel
-                             shortcutStateManager:mockShortcutStateManager];
-
-  UIApplicationShortcutItem *item = [[UIApplicationShortcutItem alloc]
-           initWithType:@"SearchTheThing"
-         localizedTitle:@"Search the thing"
-      localizedSubtitle:nil
-                   icon:[UIApplicationShortcutIcon
-                            iconWithTemplateImageName:@"search_the_thing.png"]
-               userInfo:nil];
-  BOOL launchResult = [plugin application:[UIApplication sharedApplication]
-            didFinishLaunchingWithOptions:@{UIApplicationLaunchOptionsShortcutItemKey : item}];
-  XCTAssertFalse(launchResult,
-                 @"didFinishLaunchingWithOptions must return false if launched from shortcut.");
-  [plugin applicationDidBecomeActive:[UIApplication sharedApplication]];
-  OCMVerify([mockChannel invokeMethod:@"launch" arguments:item.type]);
-}
-
-- (void)testApplicationDidBecomeActive_launchWithShortcut_becomeActiveTwice {
-  id mockChannel = OCMClassMock([FlutterMethodChannel class]);
-  id mockShortcutStateManager = OCMClassMock([FLTShortcutStateManager class]);
-  QuickActionsPlugin *plugin =
-      [[QuickActionsPlugin alloc] initWithChannel:mockChannel
-                             shortcutStateManager:mockShortcutStateManager];
-
-  UIApplicationShortcutItem *item = [[UIApplicationShortcutItem alloc]
-           initWithType:@"SearchTheThing"
-         localizedTitle:@"Search the thing"
-      localizedSubtitle:nil
-                   icon:[UIApplicationShortcutIcon
-                            iconWithTemplateImageName:@"search_the_thing.png"]
-               userInfo:nil];
-  BOOL launchResult = [plugin application:[UIApplication sharedApplication]
-            didFinishLaunchingWithOptions:@{UIApplicationLaunchOptionsShortcutItemKey : item}];
-  XCTAssertFalse(launchResult,
-                 @"didFinishLaunchingWithOptions must return false if launched from shortcut.");
-  [plugin applicationDidBecomeActive:[UIApplication sharedApplication]];
-  [plugin applicationDidBecomeActive:[UIApplication sharedApplication]];
-  // shortcut should only be handled once per launch.
-  OCMVerify(times(1), [mockChannel invokeMethod:@"launch" arguments:item.type]);
-}
-
-@end
diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/FLTShortcutStateManagerTests.m b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/FLTShortcutStateManagerTests.m
deleted file mode 100644
index 96fbf22..0000000
--- a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/FLTShortcutStateManagerTests.m
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-@import quick_actions_ios;
-@import XCTest;
-#import <OCMock/OCMock.h>
-
-@interface FLTShortcutStateManagerTests : XCTestCase
-@end
-
-@implementation FLTShortcutStateManagerTests
-
-- (void)testSetShortcutItems_shouldSetItem {
-  id mockApplication = OCMPartialMock([UIApplication sharedApplication]);
-  OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
-
-  FLTShortcutStateManager *shortcutStateManager = [[FLTShortcutStateManager alloc] init];
-
-  NSDictionary *rawItem = @{
-    @"type" : @"SearchTheThing",
-    @"localizedTitle" : @"Search the thing",
-    @"icon" : @"search_the_thing.png",
-  };
-
-  [shortcutStateManager setShortcutItems:@[ rawItem ]];
-
-  UIApplicationShortcutItem *expectedItem = [[UIApplicationShortcutItem alloc]
-           initWithType:@"SearchTheThing"
-         localizedTitle:@"Search the thing"
-      localizedSubtitle:nil
-                   icon:[UIApplicationShortcutIcon
-                            iconWithTemplateImageName:@"search_the_thing.png"]
-               userInfo:nil];
-
-  OCMVerify([mockApplication setShortcutItems:@[ expectedItem ]]);
-}
-
-- (void)testSetShortcutItems_shouldSetItemWithoutIcon {
-  id mockApplication = OCMPartialMock([UIApplication sharedApplication]);
-  OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
-
-  NSDictionary *rawItem = @{
-    @"type" : @"SearchTheThing",
-    @"localizedTitle" : @"Search the thing",
-    // Dart's null value is passed to iOS as `NSNull`.
-    // The key value pair is still present in the dictionary.
-    @"icon" : [NSNull null],
-  };
-  FLTShortcutStateManager *shortcutStateManager = [[FLTShortcutStateManager alloc] init];
-  [shortcutStateManager setShortcutItems:@[ rawItem ]];
-
-  UIApplicationShortcutItem *expectedItem =
-      [[UIApplicationShortcutItem alloc] initWithType:@"SearchTheThing"
-                                       localizedTitle:@"Search the thing"
-                                    localizedSubtitle:nil
-                                                 icon:nil
-                                             userInfo:nil];
-  OCMVerify([mockApplication setShortcutItems:@[ expectedItem ]]);
-}
-
-@end
diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockMethodChannel.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockMethodChannel.swift
new file mode 100644
index 0000000..b52fa1d
--- /dev/null
+++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockMethodChannel.swift
@@ -0,0 +1,14 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import Foundation
+
+@testable import quick_actions_ios
+
+final class MockMethodChannel: MethodChannel {
+  var invokeMethodStub: ((_ methods: String, _ arguments: Any?) -> Void)? = nil
+  func invokeMethod(_ method: String, arguments: Any?) {
+    invokeMethodStub?(method, arguments)
+  }
+}
diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemParser.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemParser.swift
new file mode 100644
index 0000000..3b5a096
--- /dev/null
+++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemParser.swift
@@ -0,0 +1,16 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import Foundation
+
+@testable import quick_actions_ios
+
+final class MockShortcutItemParser: ShortcutItemParser {
+
+  var parseShortcutItemsStub: ((_ items: [[String: Any]]) -> [UIApplicationShortcutItem])? = nil
+
+  func parseShortcutItems(_ items: [[String: Any]]) -> [UIApplicationShortcutItem] {
+    return parseShortcutItemsStub?(items) ?? []
+  }
+}
diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemProvider.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemProvider.swift
new file mode 100644
index 0000000..8547741
--- /dev/null
+++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemProvider.swift
@@ -0,0 +1,9 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+@testable import quick_actions_ios
+
+final class MockShortcutItemProvider: ShortcutItemProviding {
+  var shortcutItems: [UIApplicationShortcutItem]? = nil
+}
diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift
new file mode 100644
index 0000000..268a89b
--- /dev/null
+++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift
@@ -0,0 +1,294 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import Flutter
+import XCTest
+
+@testable import quick_actions_ios
+
+class QuickActionsPluginTests: XCTestCase {
+
+  func testHandleMethodCall_setShortcutItems() {
+    let rawItem = [
+      "type": "SearchTheThing",
+      "localizedTitle": "Search the thing",
+      "icon": "search_the_thing.png",
+    ]
+    let item = UIApplicationShortcutItem(
+      type: "SearchTheThing",
+      localizedTitle: "Search the thing",
+      localizedSubtitle: nil,
+      icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"),
+      userInfo: nil)
+
+    let call = FlutterMethodCall(methodName: "setShortcutItems", arguments: [rawItem])
+
+    let mockChannel = MockMethodChannel()
+    let mockShortcutItemProvider = MockShortcutItemProvider()
+    let mockShortcutItemParser = MockShortcutItemParser()
+
+    let plugin = QuickActionsPlugin(
+      channel: mockChannel,
+      shortcutItemProvider: mockShortcutItemProvider,
+      shortcutItemParser: mockShortcutItemParser)
+
+    let parseShortcutItemsExpectation = expectation(
+      description: "parseShortcutItems must be called.")
+    mockShortcutItemParser.parseShortcutItemsStub = { items in
+      XCTAssertEqual(items as? [[String: String]], [rawItem])
+      parseShortcutItemsExpectation.fulfill()
+      return [item]
+    }
+
+    let resultExpectation = expectation(description: "result block must be called.")
+    plugin.handle(call) { result in
+      XCTAssertNil(result, "result block must be called with nil.")
+      resultExpectation.fulfill()
+    }
+    XCTAssertEqual(mockShortcutItemProvider.shortcutItems, [item], "Must set shortcut items.")
+    waitForExpectations(timeout: 1)
+  }
+
+  func testHandleMethodCall_clearShortcutItems() {
+    let item = UIApplicationShortcutItem(
+      type: "SearchTheThing",
+      localizedTitle: "Search the thing",
+      localizedSubtitle: nil,
+      icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"),
+      userInfo: nil)
+
+    let call = FlutterMethodCall(methodName: "clearShortcutItems", arguments: nil)
+    let mockChannel = MockMethodChannel()
+    let mockShortcutItemProvider = MockShortcutItemProvider()
+    let mockShortcutItemParser = MockShortcutItemParser()
+
+    mockShortcutItemProvider.shortcutItems = [item]
+
+    let plugin = QuickActionsPlugin(
+      channel: mockChannel,
+      shortcutItemProvider: mockShortcutItemProvider,
+      shortcutItemParser: mockShortcutItemParser)
+
+    let resultExpectation = expectation(description: "result block must be called.")
+    plugin.handle(call) { result in
+      XCTAssertNil(result, "result block must be called with nil.")
+      resultExpectation.fulfill()
+    }
+
+    XCTAssertEqual(mockShortcutItemProvider.shortcutItems, [], "Must clear shortcut items.")
+    waitForExpectations(timeout: 1)
+  }
+
+  func testHandleMethodCall_getLaunchAction() {
+    let call = FlutterMethodCall(methodName: "getLaunchAction", arguments: nil)
+
+    let mockChannel = MockMethodChannel()
+    let mockShortcutItemProvider = MockShortcutItemProvider()
+    let mockShortcutItemParser = MockShortcutItemParser()
+
+    let plugin = QuickActionsPlugin(
+      channel: mockChannel,
+      shortcutItemProvider: mockShortcutItemProvider,
+      shortcutItemParser: mockShortcutItemParser)
+
+    let resultExpectation = expectation(description: "result block must be called.")
+    plugin.handle(call) { result in
+      XCTAssertNil(result, "result block must be called with nil.")
+      resultExpectation.fulfill()
+    }
+
+    waitForExpectations(timeout: 1)
+  }
+
+  func testHandleMethodCall_nonExistMethods() {
+    let call = FlutterMethodCall(methodName: "nonExist", arguments: nil)
+
+    let mockChannel = MockMethodChannel()
+    let mockShortcutItemProvider = MockShortcutItemProvider()
+    let mockShortcutItemParser = MockShortcutItemParser()
+
+    let plugin = QuickActionsPlugin(
+      channel: mockChannel,
+      shortcutItemProvider: mockShortcutItemProvider,
+      shortcutItemParser: mockShortcutItemParser)
+
+    let resultExpectation = expectation(description: "result block must be called.")
+
+    plugin.handle(call) { result in
+      XCTAssertEqual(
+        result as? NSObject, FlutterMethodNotImplemented,
+        "result block must be called with FlutterMethodNotImplemented")
+      resultExpectation.fulfill()
+    }
+
+    waitForExpectations(timeout: 1)
+  }
+
+  func testApplicationPerformActionForShortcutItem() {
+    let mockChannel = MockMethodChannel()
+    let mockShortcutItemProvider = MockShortcutItemProvider()
+    let mockShortcutItemParser = MockShortcutItemParser()
+
+    let plugin = QuickActionsPlugin(
+      channel: mockChannel,
+      shortcutItemProvider: mockShortcutItemProvider,
+      shortcutItemParser: mockShortcutItemParser)
+
+    let item = UIApplicationShortcutItem(
+      type: "SearchTheThing",
+      localizedTitle: "Search the thing",
+      localizedSubtitle: nil,
+      icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"),
+      userInfo: nil)
+
+    let invokeMethodExpectation = expectation(description: "invokeMethod must be called.")
+    mockChannel.invokeMethodStub = { method, arguments in
+      XCTAssertEqual(method, "launch")
+      XCTAssertEqual(arguments as? String, item.type)
+      invokeMethodExpectation.fulfill()
+    }
+
+    let actionResult = plugin.application(
+      UIApplication.shared,
+      performActionFor: item
+    ) { success in /* no-op */ }
+
+    XCTAssert(actionResult, "performActionForShortcutItem must return true.")
+    waitForExpectations(timeout: 1)
+  }
+
+  func testApplicationDidFinishLaunchingWithOptions_launchWithShortcut() {
+    let mockChannel = MockMethodChannel()
+    let mockShortcutItemProvider = MockShortcutItemProvider()
+    let mockShortcutItemParser = MockShortcutItemParser()
+
+    let plugin = QuickActionsPlugin(
+      channel: mockChannel,
+      shortcutItemProvider: mockShortcutItemProvider,
+      shortcutItemParser: mockShortcutItemParser)
+
+    let item = UIApplicationShortcutItem(
+      type: "SearchTheThing",
+      localizedTitle: "Search the thing",
+      localizedSubtitle: nil,
+      icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"),
+      userInfo: nil)
+
+    let launchResult = plugin.application(
+      UIApplication.shared,
+      didFinishLaunchingWithOptions: [UIApplication.LaunchOptionsKey.shortcutItem: item])
+    XCTAssertFalse(
+      launchResult, "didFinishLaunchingWithOptions must return false if launched from shortcut.")
+  }
+
+  func testApplicationDidFinishLaunchingWithOptions_launchWithoutShortcut() {
+    let mockChannel = MockMethodChannel()
+    let mockShortcutItemProvider = MockShortcutItemProvider()
+    let mockShortcutItemParser = MockShortcutItemParser()
+
+    let plugin = QuickActionsPlugin(
+      channel: mockChannel,
+      shortcutItemProvider: mockShortcutItemProvider,
+      shortcutItemParser: mockShortcutItemParser)
+
+    let launchResult = plugin.application(UIApplication.shared, didFinishLaunchingWithOptions: [:])
+    XCTAssert(
+      launchResult, "didFinishLaunchingWithOptions must return true if not launched from shortcut.")
+  }
+
+  func testApplicationDidBecomeActive_launchWithoutShortcut() {
+    let mockChannel = MockMethodChannel()
+    let mockShortcutItemProvider = MockShortcutItemProvider()
+    let mockShortcutItemParser = MockShortcutItemParser()
+
+    let plugin = QuickActionsPlugin(
+      channel: mockChannel,
+      shortcutItemProvider: mockShortcutItemProvider,
+      shortcutItemParser: mockShortcutItemParser)
+
+    mockChannel.invokeMethodStub = { _, _ in
+      XCTFail("invokeMethod should not be called if launch without shortcut.")
+    }
+
+    let launchResult = plugin.application(UIApplication.shared, didFinishLaunchingWithOptions: [:])
+    XCTAssert(
+      launchResult, "didFinishLaunchingWithOptions must return true if not launched from shortcut.")
+
+    plugin.applicationDidBecomeActive(UIApplication.shared)
+  }
+
+  func testApplicationDidBecomeActive_launchWithShortcut() {
+    let item = UIApplicationShortcutItem(
+      type: "SearchTheThing",
+      localizedTitle: "Search the thing",
+      localizedSubtitle: nil,
+      icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"),
+      userInfo: nil)
+
+    let mockChannel = MockMethodChannel()
+    let mockShortcutItemProvider = MockShortcutItemProvider()
+    let mockShortcutItemParser = MockShortcutItemParser()
+
+    let plugin = QuickActionsPlugin(
+      channel: mockChannel,
+      shortcutItemProvider: mockShortcutItemProvider,
+      shortcutItemParser: mockShortcutItemParser)
+
+    let invokeMethodExpectation = expectation(description: "invokeMethod must be called.")
+    mockChannel.invokeMethodStub = { method, arguments in
+      XCTAssertEqual(method, "launch")
+      XCTAssertEqual(arguments as? String, item.type)
+      invokeMethodExpectation.fulfill()
+    }
+
+    let launchResult = plugin.application(
+      UIApplication.shared,
+      didFinishLaunchingWithOptions: [UIApplication.LaunchOptionsKey.shortcutItem: item])
+
+    XCTAssertFalse(
+      launchResult, "didFinishLaunchingWithOptions must return false if launched from shortcut.")
+
+    plugin.applicationDidBecomeActive(UIApplication.shared)
+    waitForExpectations(timeout: 1)
+  }
+
+  func testApplicationDidBecomeActive_launchWithShortcut_becomeActiveTwice() {
+    let item = UIApplicationShortcutItem(
+      type: "SearchTheThing",
+      localizedTitle: "Search the thing",
+      localizedSubtitle: nil,
+      icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"),
+      userInfo: nil)
+
+    let mockChannel = MockMethodChannel()
+    let mockShortcutItemProvider = MockShortcutItemProvider()
+    let mockShortcutItemParser = MockShortcutItemParser()
+
+    let plugin = QuickActionsPlugin(
+      channel: mockChannel,
+      shortcutItemProvider: mockShortcutItemProvider,
+      shortcutItemParser: mockShortcutItemParser)
+
+    let invokeMethodExpectation = expectation(description: "invokeMethod must be called.")
+
+    var invokeMehtodCount = 0
+    mockChannel.invokeMethodStub = { method, arguments in
+      invokeMehtodCount += 1
+      invokeMethodExpectation.fulfill()
+    }
+
+    let launchResult = plugin.application(
+      UIApplication.shared,
+      didFinishLaunchingWithOptions: [UIApplication.LaunchOptionsKey.shortcutItem: item])
+
+    XCTAssertFalse(
+      launchResult, "didFinishLaunchingWithOptions must return false if launched from shortcut.")
+
+    plugin.applicationDidBecomeActive(UIApplication.shared)
+    waitForExpectations(timeout: 1)
+
+    XCTAssertEqual(invokeMehtodCount, 1, "shortcut should only be handled once per launch.")
+  }
+
+}
diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/FLTShortcutStateManager.h b/packages/quick_actions/quick_actions_ios/ios/Classes/FLTShortcutStateManager.h
deleted file mode 100644
index 05d0433..0000000
--- a/packages/quick_actions/quick_actions_ios/ios/Classes/FLTShortcutStateManager.h
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import <Foundation/Foundation.h>
-
-NS_ASSUME_NONNULL_BEGIN
-
-/// Manages the shortcut related states.
-@interface FLTShortcutStateManager : NSObject
-
-/// Sets the list of shortcut items.
-///
-/// @param items the list of shortcut items to be parsed and set.
-- (void)setShortcutItems:(NSArray *)items API_AVAILABLE(ios(9.0));
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/FLTShortcutStateManager.m b/packages/quick_actions/quick_actions_ios/ios/Classes/FLTShortcutStateManager.m
deleted file mode 100644
index e39edd2..0000000
--- a/packages/quick_actions/quick_actions_ios/ios/Classes/FLTShortcutStateManager.m
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "FLTShortcutStateManager.h"
-
-@implementation FLTShortcutStateManager
-
-- (void)setShortcutItems:(NSArray *)items {
-  NSMutableArray<UIApplicationShortcutItem *> *newShortcuts = [[NSMutableArray alloc] init];
-
-  for (id item in items) {
-    UIApplicationShortcutItem *shortcut = [self deserializeShortcutItem:item];
-    [newShortcuts addObject:shortcut];
-  }
-
-  [UIApplication sharedApplication].shortcutItems = newShortcuts;
-}
-
-- (UIApplicationShortcutItem *)deserializeShortcutItem:(NSDictionary *)serialized {
-  UIApplicationShortcutIcon *icon =
-      [serialized[@"icon"] isKindOfClass:[NSNull class]]
-          ? nil
-          : [UIApplicationShortcutIcon iconWithTemplateImageName:serialized[@"icon"]];
-  return [[UIApplicationShortcutItem alloc] initWithType:serialized[@"type"]
-                                          localizedTitle:serialized[@"localizedTitle"]
-                                       localizedSubtitle:nil
-                                                    icon:icon
-                                                userInfo:nil];
-}
-
-@end
diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/MethodChannel.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/MethodChannel.swift
new file mode 100644
index 0000000..5d52790
--- /dev/null
+++ b/packages/quick_actions/quick_actions_ios/ios/Classes/MethodChannel.swift
@@ -0,0 +1,16 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import Flutter
+
+/// A channel for platform code to communicate with the Dart code.
+protocol MethodChannel {
+  /// Invokes a method in Dart code.
+  /// - Parameter method the method name.
+  /// - Parameter arguments the method arguments.
+  func invokeMethod(_ method: String, arguments: Any?)
+}
+
+/// A default implementation of the `MethodChannel` protocol.
+extension FlutterMethodChannel: MethodChannel {}
diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift
index 26d6d20..8522c5f 100644
--- a/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift
+++ b/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift
@@ -15,19 +15,20 @@
     registrar.addApplicationDelegate(instance)
   }
 
-  private let channel: FlutterMethodChannel
-  private let shortcutStateManager: FLTShortcutStateManager
+  private let channel: MethodChannel
+  private let shortcutItemProvider: ShortcutItemProviding
+  private let shortcutItemParser: ShortcutItemParser
   /// The type of the shortcut item selected when launching the app.
   private var launchingShortcutType: String? = nil
 
-  // TODO: (hellohuanlin) remove `@objc` attribute and make it non-public after migrating tests to Swift.
-  @objc
-  public init(
-    channel: FlutterMethodChannel,
-    shortcutStateManager: FLTShortcutStateManager = FLTShortcutStateManager()
+  init(
+    channel: MethodChannel,
+    shortcutItemProvider: ShortcutItemProviding = UIApplication.shared,
+    shortcutItemParser: ShortcutItemParser = DefaultShortcutItemParser()
   ) {
     self.channel = channel
-    self.shortcutStateManager = shortcutStateManager
+    self.shortcutItemProvider = shortcutItemProvider
+    self.shortcutItemParser = shortcutItemParser
   }
 
   public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
@@ -35,10 +36,10 @@
     case "setShortcutItems":
       // `arguments` must be an array of dictionaries
       let items = call.arguments as! [[String: Any]]
-      shortcutStateManager.setShortcutItems(items)
+      shortcutItemProvider.shortcutItems = shortcutItemParser.parseShortcutItems(items)
       result(nil)
     case "clearShortcutItems":
-      shortcutStateManager.setShortcutItems([])
+      shortcutItemProvider.shortcutItems = []
       result(nil)
     case "getLaunchAction":
       result(nil)
diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemParser.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemParser.swift
new file mode 100644
index 0000000..0945b4a
--- /dev/null
+++ b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemParser.swift
@@ -0,0 +1,46 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import UIKit
+
+/// A parser that parses an array of raw shortcut items.
+protocol ShortcutItemParser {
+
+  /// Parses an array of raw shortcut items into an array of UIApplicationShortcutItems
+  ///
+  /// - Parameter items an array of raw shortcut items to be parsed.
+  /// - Returns an array of parsed shortcut items to be set.
+  ///
+  func parseShortcutItems(_ items: [[String: Any]]) -> [UIApplicationShortcutItem]
+}
+
+/// A default implementation of the `ShortcutItemParser` protocol.
+final class DefaultShortcutItemParser: ShortcutItemParser {
+
+  func parseShortcutItems(_ items: [[String: Any]]) -> [UIApplicationShortcutItem] {
+    return items.compactMap { deserializeShortcutItem(with: $0) }
+  }
+
+  private func deserializeShortcutItem(with serialized: [String: Any]) -> UIApplicationShortcutItem?
+  {
+    guard
+      let type = serialized["type"] as? String,
+      let localizedTitle = serialized["localizedTitle"] as? String
+    else {
+      return nil
+    }
+
+    let icon = (serialized["icon"] as? String).map {
+      UIApplicationShortcutIcon(templateImageName: $0)
+    }
+
+    // type and localizedTitle are required.
+    return UIApplicationShortcutItem(
+      type: type,
+      localizedTitle: localizedTitle,
+      localizedSubtitle: nil,
+      icon: icon,
+      userInfo: nil)
+  }
+}
diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemProviding.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemProviding.swift
new file mode 100644
index 0000000..e885486
--- /dev/null
+++ b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemProviding.swift
@@ -0,0 +1,15 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import UIKit
+
+/// Provides the capability to get and set the app's home screen shortcut items.
+protocol ShortcutItemProviding: AnyObject {
+
+  /// An array of shortcut items for home screen.
+  var shortcutItems: [UIApplicationShortcutItem]? { get set }
+}
+
+/// A default implementation of the `ShortcutItemProviding` protocol.
+extension UIApplication: ShortcutItemProviding {}
diff --git a/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios.podspec b/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios.podspec
index d8090ca..a6fff92 100644
--- a/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios.podspec
+++ b/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios.podspec
@@ -15,12 +15,11 @@
   s.source           = { :http => 'https://github.com/flutter/plugins/tree/main/packages/quick_actions' }
   s.documentation_url = 'https://pub.dev/packages/quick_actions'
   s.swift_version = '5.0'
-  s.source_files = 'Classes/**/*.{h,m,swift}'
+  s.source_files = 'Classes/**/*.swift'
   s.xcconfig = {
      'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift',
      'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift',
   }
-  s.public_header_files = 'Classes/**/*.h'
   s.dependency 'Flutter'
   s.platform = :ios, '9.0'
   s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
diff --git a/packages/quick_actions/quick_actions_ios/pubspec.yaml b/packages/quick_actions/quick_actions_ios/pubspec.yaml
index f01ae4a..6e7fb43 100644
--- a/packages/quick_actions/quick_actions_ios/pubspec.yaml
+++ b/packages/quick_actions/quick_actions_ios/pubspec.yaml
@@ -2,7 +2,7 @@
 description: An implementation for the iOS platform of the Flutter `quick_actions` plugin.
 repository: https://github.com/flutter/plugins/tree/main/packages/quick_actions/quick_actions_ios
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22
-version: 1.0.1
+version: 1.0.2
 
 environment:
   sdk: ">=2.15.0 <3.0.0"
diff --git a/packages/shared_preferences/shared_preferences_android/test/shared_preferences_android_test.dart b/packages/shared_preferences/shared_preferences_android/test/shared_preferences_android_test.dart
index 92d9e8f..d9c9213 100644
--- a/packages/shared_preferences/shared_preferences_android/test/shared_preferences_android_test.dart
+++ b/packages/shared_preferences/shared_preferences_android/test/shared_preferences_android_test.dart
@@ -36,14 +36,14 @@
       channel.setMockMethodCallHandler((MethodCall methodCall) async {
         log.add(methodCall);
         if (methodCall.method == 'getAll') {
-          return await testData.getAll();
+          return testData.getAll();
         }
         if (methodCall.method == 'remove') {
           final String key = methodCall.arguments['key']! as String;
-          return await testData.remove(key);
+          return testData.remove(key);
         }
         if (methodCall.method == 'clear') {
-          return await testData.clear();
+          return testData.clear();
         }
         final RegExp setterRegExp = RegExp(r'set(.*)');
         final Match? match = setterRegExp.matchAsPrefix(methodCall.method);
@@ -51,7 +51,7 @@
           final String valueType = match!.group(1)!;
           final String key = methodCall.arguments['key'] as String;
           final Object value = methodCall.arguments['value'] as Object;
-          return await testData.setValue(valueType, key, value);
+          return testData.setValue(valueType, key, value);
         }
         fail('Unexpected method call: ${methodCall.method}');
       });
diff --git a/packages/shared_preferences/shared_preferences_macos/test/shared_preferences_macos_test.dart b/packages/shared_preferences/shared_preferences_macos/test/shared_preferences_macos_test.dart
index cd858f4..8e71e40 100644
--- a/packages/shared_preferences/shared_preferences_macos/test/shared_preferences_macos_test.dart
+++ b/packages/shared_preferences/shared_preferences_macos/test/shared_preferences_macos_test.dart
@@ -36,14 +36,14 @@
       channel.setMockMethodCallHandler((MethodCall methodCall) async {
         log.add(methodCall);
         if (methodCall.method == 'getAll') {
-          return await testData.getAll();
+          return testData.getAll();
         }
         if (methodCall.method == 'remove') {
           final String key = (methodCall.arguments['key'] as String?)!;
-          return await testData.remove(key);
+          return testData.remove(key);
         }
         if (methodCall.method == 'clear') {
-          return await testData.clear();
+          return testData.clear();
         }
         final RegExp setterRegExp = RegExp(r'set(.*)');
         final Match? match = setterRegExp.matchAsPrefix(methodCall.method);
@@ -51,7 +51,7 @@
           final String valueType = match!.group(1)!;
           final String key = (methodCall.arguments['key'] as String?)!;
           final Object value = (methodCall.arguments['value'] as Object?)!;
-          return await testData.setValue(valueType, key, value);
+          return testData.setValue(valueType, key, value);
         }
         fail('Unexpected method call: ${methodCall.method}');
       });
diff --git a/packages/shared_preferences/shared_preferences_platform_interface/test/method_channel_shared_preferences_test.dart b/packages/shared_preferences/shared_preferences_platform_interface/test/method_channel_shared_preferences_test.dart
index 31ee89e..ed4ecc7 100644
--- a/packages/shared_preferences/shared_preferences_platform_interface/test/method_channel_shared_preferences_test.dart
+++ b/packages/shared_preferences/shared_preferences_platform_interface/test/method_channel_shared_preferences_test.dart
@@ -34,14 +34,14 @@
       channel.setMockMethodCallHandler((MethodCall methodCall) async {
         log.add(methodCall);
         if (methodCall.method == 'getAll') {
-          return await testData.getAll();
+          return testData.getAll();
         }
         if (methodCall.method == 'remove') {
           final String key = (methodCall.arguments['key'] as String?)!;
-          return await testData.remove(key);
+          return testData.remove(key);
         }
         if (methodCall.method == 'clear') {
-          return await testData.clear();
+          return testData.clear();
         }
         final RegExp setterRegExp = RegExp(r'set(.*)');
         final Match? match = setterRegExp.matchAsPrefix(methodCall.method);
@@ -49,7 +49,7 @@
           final String valueType = match!.group(1)!;
           final String key = (methodCall.arguments['key'] as String?)!;
           final Object value = (methodCall.arguments['value'] as Object?)!;
-          return await testData.setValue(valueType, key, value);
+          return testData.setValue(valueType, key, value);
         }
         fail('Unexpected method call: ${methodCall.method}');
       });
diff --git a/packages/url_launcher/url_launcher/CHANGELOG.md b/packages/url_launcher/url_launcher/CHANGELOG.md
index 18a0289..4b365b8 100644
--- a/packages/url_launcher/url_launcher/CHANGELOG.md
+++ b/packages/url_launcher/url_launcher/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 6.1.7
+
+* Updates code for new analysis options.
+
 ## 6.1.6
 
 * Updates imports for `prefer_relative_imports`.
diff --git a/packages/url_launcher/url_launcher/lib/src/legacy_api.dart b/packages/url_launcher/url_launcher/lib/src/legacy_api.dart
index f6faf3f..5bd5ef6 100644
--- a/packages/url_launcher/url_launcher/lib/src/legacy_api.dart
+++ b/packages/url_launcher/url_launcher/lib/src/legacy_api.dart
@@ -130,7 +130,7 @@
 /// details.
 @Deprecated('Use canLaunchUrl instead')
 Future<bool> canLaunch(String urlString) async {
-  return await UrlLauncherPlatform.instance.canLaunch(urlString);
+  return UrlLauncherPlatform.instance.canLaunch(urlString);
 }
 
 /// Closes the current WebView, if one was previously opened via a call to [launch].
@@ -143,7 +143,7 @@
 /// WebView/SafariViewController available to be closed.
 @Deprecated('Use closeInAppWebView instead')
 Future<void> closeWebView() async {
-  return await UrlLauncherPlatform.instance.closeWebView();
+  return UrlLauncherPlatform.instance.closeWebView();
 }
 
 /// This allows a value of type T or T? to be treated as a value of type T?.
diff --git a/packages/url_launcher/url_launcher/lib/src/url_launcher_string.dart b/packages/url_launcher/url_launcher/lib/src/url_launcher_string.dart
index cf96ebc..45193ff 100644
--- a/packages/url_launcher/url_launcher/lib/src/url_launcher_string.dart
+++ b/packages/url_launcher/url_launcher/lib/src/url_launcher_string.dart
@@ -30,7 +30,7 @@
     throw ArgumentError.value(urlString, 'urlString',
         'To use an in-app web view, you must provide an http(s) URL.');
   }
-  return await UrlLauncherPlatform.instance.launchUrl(
+  return UrlLauncherPlatform.instance.launchUrl(
     urlString,
     LaunchOptions(
       mode: convertLaunchMode(mode),
@@ -53,5 +53,5 @@
 /// others will immediately fail if the URL can't be parsed according to the
 /// official standards that define URL formats.
 Future<bool> canLaunchUrlString(String urlString) async {
-  return await UrlLauncherPlatform.instance.canLaunch(urlString);
+  return UrlLauncherPlatform.instance.canLaunch(urlString);
 }
diff --git a/packages/url_launcher/url_launcher/lib/src/url_launcher_uri.dart b/packages/url_launcher/url_launcher/lib/src/url_launcher_uri.dart
index 3032102..b3ce6c2 100644
--- a/packages/url_launcher/url_launcher/lib/src/url_launcher_uri.dart
+++ b/packages/url_launcher/url_launcher/lib/src/url_launcher_uri.dart
@@ -50,7 +50,7 @@
     throw ArgumentError.value(url, 'url',
         'To use an in-app web view, you must provide an http(s) URL.');
   }
-  return await UrlLauncherPlatform.instance.launchUrl(
+  return UrlLauncherPlatform.instance.launchUrl(
     url.toString(),
     LaunchOptions(
       mode: convertLaunchMode(mode),
@@ -75,7 +75,7 @@
 ///   that are always assumed to be supported (such as http(s)), as web pages
 ///   are never allowed to query installed applications.
 Future<bool> canLaunchUrl(Uri url) async {
-  return await UrlLauncherPlatform.instance.canLaunch(url.toString());
+  return UrlLauncherPlatform.instance.canLaunch(url.toString());
 }
 
 /// Closes the current in-app web view, if one was previously opened by
@@ -84,5 +84,5 @@
 /// If [launchUrl] was never called with [LaunchMode.inAppWebView], then this
 /// call will have no effect.
 Future<void> closeInAppWebView() async {
-  return await UrlLauncherPlatform.instance.closeWebView();
+  return UrlLauncherPlatform.instance.closeWebView();
 }
diff --git a/packages/url_launcher/url_launcher/pubspec.yaml b/packages/url_launcher/url_launcher/pubspec.yaml
index 8efda4a..642b4b5 100644
--- a/packages/url_launcher/url_launcher/pubspec.yaml
+++ b/packages/url_launcher/url_launcher/pubspec.yaml
@@ -3,7 +3,7 @@
   web, phone, SMS, and email schemes.
 repository: https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22
-version: 6.1.6
+version: 6.1.7
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
diff --git a/packages/url_launcher/url_launcher/test/src/legacy_api_test.dart b/packages/url_launcher/url_launcher/test/src/legacy_api_test.dart
index 11d7d8f..40336a0 100644
--- a/packages/url_launcher/url_launcher/test/src/legacy_api_test.dart
+++ b/packages/url_launcher/url_launcher/test/src/legacy_api_test.dart
@@ -188,7 +188,7 @@
     });
 
     test('cannot launch a non-web in webview', () async {
-      expect(() async => await launch('tel:555-555-5555', forceWebView: true),
+      expect(() async => launch('tel:555-555-5555', forceWebView: true),
           throwsA(isA<PlatformException>()));
     });
 
@@ -211,16 +211,14 @@
 
     test('cannot send e-mail with forceSafariVC: true', () async {
       expect(
-          () async => await launch(
-              'mailto:gmail-noreply@google.com?subject=Hello',
+          () async => launch('mailto:gmail-noreply@google.com?subject=Hello',
               forceSafariVC: true),
           throwsA(isA<PlatformException>()));
     });
 
     test('cannot send e-mail with forceWebView: true', () async {
       expect(
-          () async => await launch(
-              'mailto:gmail-noreply@google.com?subject=Hello',
+          () async => launch('mailto:gmail-noreply@google.com?subject=Hello',
               forceWebView: true),
           throwsA(isA<PlatformException>()));
     });
@@ -305,7 +303,7 @@
 
     test('cannot open non-parseable url with forceSafariVC: true', () async {
       expect(
-          () async => await launch(
+          () async => launch(
               'rdp://full%20address=s:mypc:3389&audiomode=i:2&disable%20themes=i:1',
               forceSafariVC: true),
           throwsA(isA<PlatformException>()));
@@ -313,7 +311,7 @@
 
     test('cannot open non-parseable url with forceWebView: true', () async {
       expect(
-          () async => await launch(
+          () async => launch(
               'rdp://full%20address=s:mypc:3389&audiomode=i:2&disable%20themes=i:1',
               forceWebView: true),
           throwsA(isA<PlatformException>()));
diff --git a/packages/url_launcher/url_launcher/test/src/url_launcher_string_test.dart b/packages/url_launcher/url_launcher/test/src/url_launcher_string_test.dart
index 0dcbc34..64065ff 100644
--- a/packages/url_launcher/url_launcher/test/src/url_launcher_string_test.dart
+++ b/packages/url_launcher/url_launcher/test/src/url_launcher_string_test.dart
@@ -222,7 +222,7 @@
 
     test('cannot launch a non-web URL in a webview', () async {
       expect(
-          () async => await launchUrlString('tel:555-555-5555',
+          () async => launchUrlString('tel:555-555-5555',
               mode: LaunchMode.inAppWebView),
           throwsA(isA<ArgumentError>()));
     });
diff --git a/packages/url_launcher/url_launcher/test/src/url_launcher_uri_test.dart b/packages/url_launcher/url_launcher/test/src/url_launcher_uri_test.dart
index 7685aef..d71d07f 100644
--- a/packages/url_launcher/url_launcher/test/src/url_launcher_uri_test.dart
+++ b/packages/url_launcher/url_launcher/test/src/url_launcher_uri_test.dart
@@ -223,7 +223,7 @@
 
     test('cannot launch a non-web URL in a webview', () async {
       expect(
-          () async => await launchUrl(Uri(scheme: 'tel', path: '555-555-5555'),
+          () async => launchUrl(Uri(scheme: 'tel', path: '555-555-5555'),
               mode: LaunchMode.inAppWebView),
           throwsA(isA<ArgumentError>()));
     });
diff --git a/packages/url_launcher/url_launcher_android/CHANGELOG.md b/packages/url_launcher/url_launcher_android/CHANGELOG.md
index 934d8da..f2f6594 100644
--- a/packages/url_launcher/url_launcher_android/CHANGELOG.md
+++ b/packages/url_launcher/url_launcher_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 6.0.22
+
+* Updates code for new analysis options.
+
 ## 6.0.21
 
 * Updates androidx.annotation to 1.2.0.
diff --git a/packages/url_launcher/url_launcher_android/lib/url_launcher_android.dart b/packages/url_launcher/url_launcher_android/lib/url_launcher_android.dart
index 1aa093a..bd4c2a5 100644
--- a/packages/url_launcher/url_launcher_android/lib/url_launcher_android.dart
+++ b/packages/url_launcher/url_launcher_android/lib/url_launcher_android.dart
@@ -33,7 +33,7 @@
       // returns true, then there is a browser, which means that there is
       // at least one handler for the original URL.
       if (scheme == 'http' || scheme == 'https') {
-        return await _canLaunchUrl('$scheme://flutter.dev');
+        return _canLaunchUrl('$scheme://flutter.dev');
       }
     }
     return canLaunchSpecificUrl;
diff --git a/packages/url_launcher/url_launcher_android/pubspec.yaml b/packages/url_launcher/url_launcher_android/pubspec.yaml
index e97fde3..d096b0c 100644
--- a/packages/url_launcher/url_launcher_android/pubspec.yaml
+++ b/packages/url_launcher/url_launcher_android/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Android implementation of the url_launcher plugin.
 repository: https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher_android
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22
-version: 6.0.21
+version: 6.0.22
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
diff --git a/packages/url_launcher/url_launcher_web/example/integration_test/link_widget_test.dart b/packages/url_launcher/url_launcher_web/example/integration_test/link_widget_test.dart
index 6b19861..1447112 100644
--- a/packages/url_launcher/url_launcher_web/example/integration_test/link_widget_test.dart
+++ b/packages/url_launcher/url_launcher_web/example/integration_test/link_widget_test.dart
@@ -149,7 +149,7 @@
 
       await tester.scrollUntilVisible(
         find.text('#${itemCount - 1}'),
-        2500,
+        800,
         maxScrolls: 1000,
       );
     });
diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md
index 0885f28..1c7da48 100644
--- a/packages/video_player/video_player/CHANGELOG.md
+++ b/packages/video_player/video_player/CHANGELOG.md
@@ -1,5 +1,6 @@
-## NEXT
+## 2.4.8
 
+* Updates code for new analysis options.
 * Updates code for `no_leading_underscores_for_local_identifiers` lint.
 
 ## 2.4.7
diff --git a/packages/video_player/video_player/lib/video_player.dart b/packages/video_player/video_player/lib/video_player.dart
index c1f4886..2f84b82 100644
--- a/packages/video_player/video_player/lib/video_player.dart
+++ b/packages/video_player/video_player/lib/video_player.dart
@@ -542,7 +542,7 @@
     if (_isDisposed) {
       return null;
     }
-    return await _videoPlayerPlatform.getPosition(_textureId);
+    return _videoPlayerPlatform.getPosition(_textureId);
   }
 
   /// Sets the video's current timestamp to be at [moment]. The next
diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml
index 7e2df60..1f5fed2 100644
--- a/packages/video_player/video_player/pubspec.yaml
+++ b/packages/video_player/video_player/pubspec.yaml
@@ -3,7 +3,7 @@
   widgets on Android, iOS, and web.
 repository: https://github.com/flutter/plugins/tree/main/packages/video_player/video_player
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22
-version: 2.4.7
+version: 2.4.8
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
diff --git a/packages/video_player/video_player/test/video_player_test.dart b/packages/video_player/video_player/test/video_player_test.dart
index 8e5e98b..973fab4 100644
--- a/packages/video_player/video_player/test/video_player_test.dart
+++ b/packages/video_player/video_player/test/video_player_test.dart
@@ -389,7 +389,7 @@
       await controller.initialize();
       await controller.dispose();
 
-      expect(() async => await controller.dispose(), returnsNormally);
+      expect(() async => controller.dispose(), returnsNormally);
     });
 
     test('play', () async {
diff --git a/packages/video_player/video_player_android/CHANGELOG.md b/packages/video_player/video_player_android/CHANGELOG.md
index 4d3f72d..7137e24 100644
--- a/packages/video_player/video_player_android/CHANGELOG.md
+++ b/packages/video_player/video_player_android/CHANGELOG.md
@@ -1,5 +1,6 @@
 ## NEXT
 
+* Updates code for new analysis options.
 * Updates code for `no_leading_underscores_for_local_identifiers` lint.
 * Updates minimum Flutter version to 2.10.
 * Fixes violations of new analysis option use_named_constants.
diff --git a/packages/video_player/video_player_android/example/lib/mini_controller.dart b/packages/video_player/video_player_android/example/lib/mini_controller.dart
index 61959ef..26c60b7 100644
--- a/packages/video_player/video_player_android/example/lib/mini_controller.dart
+++ b/packages/video_player/video_player_android/example/lib/mini_controller.dart
@@ -318,7 +318,7 @@
 
   /// The position in the current video.
   Future<Duration?> get position async {
-    return await _platform.getPosition(_textureId);
+    return _platform.getPosition(_textureId);
   }
 
   /// Sets the video's current timestamp to be at [position].
diff --git a/packages/video_player/video_player_avfoundation/CHANGELOG.md b/packages/video_player/video_player_avfoundation/CHANGELOG.md
index b1cc1ce..1198599 100644
--- a/packages/video_player/video_player_avfoundation/CHANGELOG.md
+++ b/packages/video_player/video_player_avfoundation/CHANGELOG.md
@@ -1,5 +1,6 @@
 ## NEXT
 
+* Updates code for new analysis options.
 * Adds an integration test for a bug where the aspect ratios of some HLS videos are incorrectly inverted.
 * Removes an unnecessary override in example code.
 
diff --git a/packages/video_player/video_player_avfoundation/example/lib/mini_controller.dart b/packages/video_player/video_player_avfoundation/example/lib/mini_controller.dart
index 61959ef..26c60b7 100644
--- a/packages/video_player/video_player_avfoundation/example/lib/mini_controller.dart
+++ b/packages/video_player/video_player_avfoundation/example/lib/mini_controller.dart
@@ -318,7 +318,7 @@
 
   /// The position in the current video.
   Future<Duration?> get position async {
-    return await _platform.getPosition(_textureId);
+    return _platform.getPosition(_textureId);
   }
 
   /// Sets the video's current timestamp to be at [position].
diff --git a/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart
index 63f4384..8dd8321 100644
--- a/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart
+++ b/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart
@@ -1300,7 +1300,7 @@
 Future<String> _runJavascriptReturningResult(
     WebViewController controller, String js) async {
   if (defaultTargetPlatform == TargetPlatform.iOS) {
-    return await controller.runJavascriptReturningResult(js);
+    return controller.runJavascriptReturningResult(js);
   }
   return jsonDecode(await controller.runJavascriptReturningResult(js))
       as String;
diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart
index 73076d9..b6d6239 100644
--- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart
@@ -445,7 +445,7 @@
           await buildWidget(tester);
 
           expect(
-              () async => await testController.loadRequest(
+              () async => testController.loadRequest(
                     WebViewRequest(
                       uri: Uri.parse('www.google.com'),
                       method: WebViewRequestMethod.get,
diff --git a/packages/webview_flutter/webview_flutter_web/test/webview_flutter_web_test.dart b/packages/webview_flutter/webview_flutter_web/test/webview_flutter_web_test.dart
index 08337e4..76dad6f 100644
--- a/packages/webview_flutter/webview_flutter_web/test/webview_flutter_web_test.dart
+++ b/packages/webview_flutter/webview_flutter_web/test/webview_flutter_web_test.dart
@@ -93,7 +93,7 @@
         );
         // Run & Verify
         expect(
-            () async => await controller.loadRequest(
+            () async => controller.loadRequest(
                   WebViewRequest(
                     uri: Uri.parse('flutter.dev'),
                     method: WebViewRequestMethod.get,
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart
index 047d69f..7e5af0f 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart
@@ -1204,7 +1204,7 @@
 
 /// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests.
 Future<String> _getUserAgent(WebViewController controller) async {
-  return await controller.runJavascriptReturningResult('navigator.userAgent;');
+  return controller.runJavascriptReturningResult('navigator.userAgent;');
 }
 
 class ResizableWebView extends StatefulWidget {
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit_webview_widget_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit_webview_widget_test.dart
index 5e6186c..9df910f 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit_webview_widget_test.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit_webview_widget_test.dart
@@ -490,7 +490,7 @@
           await buildWidget(tester);
 
           expect(
-              () async => await testController.loadRequest(
+              () async => testController.loadRequest(
                     WebViewRequest(
                       uri: Uri.parse('www.google.com'),
                       method: WebViewRequestMethod.get,
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/v4/webkit_webview_controller_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/v4/webkit_webview_controller_test.dart
index 87a90db..6672198 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/test/v4/webkit_webview_controller_test.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/test/v4/webkit_webview_controller_test.dart
@@ -147,7 +147,7 @@
         );
 
         expect(
-          () async => await controller.loadRequest(
+          () async => controller.loadRequest(
             LoadRequestParams(
               uri: Uri.parse('www.google.com'),
               method: LoadRequestMethod.get,
diff --git a/script/install_chromium.sh b/script/install_chromium.sh
index 0d360fe..ed55776 100755
--- a/script/install_chromium.sh
+++ b/script/install_chromium.sh
@@ -2,41 +2,51 @@
 # Copyright 2013 The Flutter Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
+
+# This script may be run as:
+# $ CHROME_DOWNLOAD_DIR=./whatever script/install_chromium.sh
 set -e
 set -x
 
-readonly TARGET_DIR=$1
+# The target directory where chromium is going to be downloaded
+: "${CHROME_DOWNLOAD_DIR:=/tmp/chromium}" # Default value for the CHROME_DOWNLOAD_DIR env.
 
 # The build of Chromium used to test web functionality.
 #
 # Chromium builds can be located here: https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html?prefix=Linux_x64/
 #
-# Check: https://github.com/flutter/engine/blob/main/lib/web_ui/dev/browser_lock.yaml
-readonly CHROMIUM_BUILD=929514
+# Check: https://github.com/flutter/engine/blob/master/lib/web_ui/dev/browser_lock.yaml
+: "${CHROMIUM_BUILD:=950363}" # Default value for the CHROMIUM_BUILD env.
+
+# Convenience defaults for CHROME_EXECUTABLE and CHROMEDRIVER_EXECUTABLE. These
+# two values should be set in the environment from CI, so this script can validate
+# that it has completed downloading chrome and driver successfully (and the expected
+# files are executable)
+: "${CHROME_EXECUTABLE:=$CHROME_DOWNLOAD_DIR/chrome-linux/chrome}"
+: "${CHROMEDRIVER_EXECUTABLE:=$CHROME_DOWNLOAD_DIR/chromedriver/chromedriver}"
 
 # The correct ChromeDriver is distributed alongside the chromium build above, as
 # `chromedriver_linux64.zip`, so no need to hardcode any extra info about it.
 readonly DOWNLOAD_ROOT="https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Linux_x64%2F${CHROMIUM_BUILD}%2F"
 
 # Install Chromium.
-mkdir "$TARGET_DIR"
-readonly CHROMIUM_ZIP_FILE="$TARGET_DIR/chromium.zip"
+mkdir "$CHROME_DOWNLOAD_DIR"
+readonly CHROMIUM_ZIP_FILE="$CHROME_DOWNLOAD_DIR/chromium.zip"
 wget --no-verbose "${DOWNLOAD_ROOT}chrome-linux.zip?alt=media" -O "$CHROMIUM_ZIP_FILE"
-unzip -q "$CHROMIUM_ZIP_FILE" -d "$TARGET_DIR/"
+unzip -q "$CHROMIUM_ZIP_FILE" -d "$CHROME_DOWNLOAD_DIR/"
 
 # Install ChromeDriver.
-readonly DRIVER_ZIP_FILE="$TARGET_DIR/chromedriver.zip"
+readonly DRIVER_ZIP_FILE="$CHROME_DOWNLOAD_DIR/chromedriver.zip"
 wget --no-verbose "${DOWNLOAD_ROOT}chromedriver_linux64.zip?alt=media" -O "$DRIVER_ZIP_FILE"
-unzip -q "$DRIVER_ZIP_FILE" -d "$TARGET_DIR/"
-# Rename TARGET_DIR/chromedriver_linux64 to the expected TARGET_DIR/chromedriver
-mv -T "$TARGET_DIR/chromedriver_linux64" "$TARGET_DIR/chromedriver"
-
-export CHROME_EXECUTABLE="$TARGET_DIR/chrome-linux/chrome"
+unzip -q "$DRIVER_ZIP_FILE" -d "$CHROME_DOWNLOAD_DIR/"
+# Rename CHROME_DOWNLOAD_DIR/chromedriver_linux64 to the expected CHROME_DOWNLOAD_DIR/chromedriver
+mv -T "$CHROME_DOWNLOAD_DIR/chromedriver_linux64" "$CHROME_DOWNLOAD_DIR/chromedriver"
 
 # Echo info at the end for ease of debugging.
+#
+# exports from this script cannot be used elsewhere in the .cirrus.yml file.
 set +x
 echo
-readonly CHROMEDRIVER_EXECUTABLE="$TARGET_DIR/chromedriver/chromedriver"
 echo "$CHROME_EXECUTABLE"
 "$CHROME_EXECUTABLE" --version
 echo "$CHROMEDRIVER_EXECUTABLE"
diff --git a/script/tool/lib/src/common/package_looping_command.dart b/script/tool/lib/src/common/package_looping_command.dart
index d8b1cf0..ccfeea0 100644
--- a/script/tool/lib/src/common/package_looping_command.dart
+++ b/script/tool/lib/src/common/package_looping_command.dart
@@ -369,7 +369,7 @@
       }
     }
 
-    return await runForPackage(package);
+    return runForPackage(package);
   }
 
   void _printSuccess(String message) {
diff --git a/script/tool/lib/src/common/package_state_utils.dart b/script/tool/lib/src/common/package_state_utils.dart
index 65f3119..5aabd75 100644
--- a/script/tool/lib/src/common/package_state_utils.dart
+++ b/script/tool/lib/src/common/package_state_utils.dart
@@ -107,6 +107,7 @@
 
 bool _isTestChange(List<String> pathComponents) {
   return pathComponents.contains('test') ||
+      pathComponents.contains('integration_test') ||
       pathComponents.contains('androidTest') ||
       pathComponents.contains('RunnerTests') ||
       pathComponents.contains('RunnerUITests');
diff --git a/script/tool/lib/src/update_release_info_command.dart b/script/tool/lib/src/update_release_info_command.dart
index 67aa994..8d7ceb8 100644
--- a/script/tool/lib/src/update_release_info_command.dart
+++ b/script/tool/lib/src/update_release_info_command.dart
@@ -140,6 +140,9 @@
       if (!state.hasChanges) {
         return PackageResult.skip('No changes to package');
       }
+      if (!state.needsVersionChange && !state.needsChangelogChange) {
+        return PackageResult.skip('No non-exempt changes to package');
+      }
       if (state.needsVersionChange) {
         versionChange = _VersionIncrementType.bugfix;
       }
diff --git a/script/tool/lib/src/version_check_command.dart b/script/tool/lib/src/version_check_command.dart
index b3be672..bb53620 100644
--- a/script/tool/lib/src/version_check_command.dart
+++ b/script/tool/lib/src/version_check_command.dart
@@ -285,8 +285,7 @@
     final String gitPath = path.style == p.Style.windows
         ? p.posix.joinAll(path.split(relativePath))
         : relativePath;
-    return await _gitVersionFinder.getPackageVersion(gitPath,
-        gitRef: _mergeBase);
+    return _gitVersionFinder.getPackageVersion(gitPath, gitRef: _mergeBase);
   }
 
   /// Returns the state of the verison of [package] relative to the comparison
diff --git a/script/tool/test/common/package_looping_command_test.dart b/script/tool/test/common/package_looping_command_test.dart
index c858df0..f90d58e 100644
--- a/script/tool/test/common/package_looping_command_test.dart
+++ b/script/tool/test/common/package_looping_command_test.dart
@@ -143,7 +143,7 @@
     runner = CommandRunner<void>('test_package_looping_command',
         'Test for base package looping functionality');
     runner.addCommand(command);
-    return await runCapturingPrint(
+    return runCapturingPrint(
       runner,
       <String>[command.name, ...arguments],
       errorHandler: errorHandler,
diff --git a/script/tool/test/update_release_info_command_test.dart b/script/tool/test/update_release_info_command_test.dart
index 8cd2e95..cfec938 100644
--- a/script/tool/test/update_release_info_command_test.dart
+++ b/script/tool/test/update_release_info_command_test.dart
@@ -388,7 +388,7 @@
           createFakePackage('a_package', packagesDir, version: '1.0.1');
       processRunner.mockProcessesForExecutable['git-diff'] = <io.Process>[
         MockProcess(stdout: '''
-packages/different_package/test/plugin_test.dart
+packages/different_package/lib/foo.dart
 '''),
       ];
       final String originalChangelog = package.changelogFile.readAsStringSync();
@@ -411,6 +411,35 @@
           ]));
     });
 
+    test('skips for "minimal" when there are only test changes', () async {
+      final RepositoryPackage package =
+          createFakePackage('a_package', packagesDir, version: '1.0.1');
+      processRunner.mockProcessesForExecutable['git-diff'] = <io.Process>[
+        MockProcess(stdout: '''
+packages/a_package/test/a_test.dart
+packages/a_package/example/integration_test/another_test.dart
+'''),
+      ];
+      final String originalChangelog = package.changelogFile.readAsStringSync();
+
+      final List<String> output = await runCapturingPrint(runner, <String>[
+        'update-release-info',
+        '--version=minimal',
+        '--changelog',
+        'A change.',
+      ]);
+
+      final String version = package.parsePubspec().version?.toString() ?? '';
+      expect(version, '1.0.1');
+      expect(package.changelogFile.readAsStringSync(), originalChangelog);
+      expect(
+          output,
+          containsAllInOrder(<Matcher>[
+            contains('No non-exempt changes to package'),
+            contains('Skipped 1 package')
+          ]));
+    });
+
     test('fails if CHANGELOG.md is missing', () async {
       createFakePackage('a_package', packagesDir, includeCommonFiles: false);