From bfa34380ea05631aa21997f4908f74eda65f651e Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Mon, 13 Dec 2021 17:20:35 -0500 Subject: [PATCH 01/12] [video_player] Fix a flaky test (#4611) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 'reports buffering status' test was written in an unnecessary complicated way that used pairs of completers and booleans that tracked the same thing, and more importantly had a bug (hidden by the fact that this package is still on legacy analysis options) where the `await` statements weren't actually doing anything, because they were not actually `await`ing futures. This fixes the lack of awaits—which should fix the flake—and removes the unnecessary booleans. Fixes https://github.com/flutter/flutter/issues/94775 --- packages/video_player/video_player/CHANGELOG.md | 1 + .../integration_test/video_player_test.dart | 17 +++++------------ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index f9031685b583..9d4e36d72017 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -2,6 +2,7 @@ * Fixes integration tests. * Updates Android compileSdkVersion to 31. +* Fixes a flaky integration test. ## 2.2.7 diff --git a/packages/video_player/video_player/example/integration_test/video_player_test.dart b/packages/video_player/video_player/example/integration_test/video_player_test.dart index 866b5bce0a8d..63a38290a613 100644 --- a/packages/video_player/video_player/example/integration_test/video_player_test.dart +++ b/packages/video_player/video_player/example/integration_test/video_player_test.dart @@ -48,17 +48,13 @@ void main() { await networkController.setVolume(0); final Completer started = Completer(); final Completer ended = Completer(); - bool startedBuffering = false; - bool endedBuffering = false; networkController.addListener(() { - if (networkController.value.isBuffering && !startedBuffering) { - startedBuffering = true; + if (!started.isCompleted && networkController.value.isBuffering) { started.complete(); } - if (startedBuffering && + if (started.isCompleted && !networkController.value.isBuffering && - !endedBuffering) { - endedBuffering = true; + !ended.isCompleted) { ended.complete(); } }); @@ -72,11 +68,8 @@ void main() { expect(networkController.value.position, (Duration position) => position > const Duration(seconds: 0)); - await started; - expect(startedBuffering, true); - - await ended; - expect(endedBuffering, true); + await expectLater(started.future, completes); + await expectLater(ended.future, completes); }, skip: !(kIsWeb || defaultTargetPlatform == TargetPlatform.android), ); From 453089d1fa9b45e17352807c6e73adc059a027c2 Mon Sep 17 00:00:00 2001 From: keyonghan <54558023+keyonghan@users.noreply.github.com> Date: Tue, 14 Dec 2021 16:56:10 -0800 Subject: [PATCH 02/12] update firebase test key (#4615) --- .cirrus.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cirrus.yml b/.cirrus.yml index 95bd6092674b..5dff2d1b035f 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -195,7 +195,7 @@ task: CHANNEL: "master" CHANNEL: "stable" MAPS_API_KEY: ENCRYPTED[596a9f6bca436694625ac50851dc5da6b4d34cba8025f7db5bc9465142e8cd44e15f69e3507787753accebfc4910d550] - GCLOUD_FIREBASE_TESTLAB_KEY: ENCRYPTED[!c9446a7b11d5520c2ebce3c64ccc82fe6d146272cb06a4a4590e22c389f33153f951347a25422522df1a81fe2f085e9a!] + GCLOUD_FIREBASE_TESTLAB_KEY: ENCRYPTED[!cc769765170bebc37e0556e2da5915ca64ee37f4ec8c966ec147e2f59578b476c99e457eafce4e2f8b1a4e305f7096b8!] build_script: # Unsetting CIRRUS_CHANGE_MESSAGE and CIRRUS_COMMIT_MESSAGE as they # might include non-ASCII characters which makes Gradle crash. From 910055b2b53259efef8c76a50f5ae2efa217c270 Mon Sep 17 00:00:00 2001 From: keyonghan <54558023+keyonghan@users.noreply.github.com> Date: Tue, 14 Dec 2021 19:26:33 -0800 Subject: [PATCH 03/12] update firebase key (#4616) --- .cirrus.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cirrus.yml b/.cirrus.yml index 5dff2d1b035f..bcd0e49327a3 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -195,7 +195,7 @@ task: CHANNEL: "master" CHANNEL: "stable" MAPS_API_KEY: ENCRYPTED[596a9f6bca436694625ac50851dc5da6b4d34cba8025f7db5bc9465142e8cd44e15f69e3507787753accebfc4910d550] - GCLOUD_FIREBASE_TESTLAB_KEY: ENCRYPTED[!cc769765170bebc37e0556e2da5915ca64ee37f4ec8c966ec147e2f59578b476c99e457eafce4e2f8b1a4e305f7096b8!] + GCLOUD_FIREBASE_TESTLAB_KEY: ENCRYPTED[de02374f8d2d14d50792c6b521af2dfb86cbb522efed104f905002e4332546104d387d2bb8710956b729b4bd6533bba0] build_script: # Unsetting CIRRUS_CHANGE_MESSAGE and CIRRUS_COMMIT_MESSAGE as they # might include non-ASCII characters which makes Gradle crash. From 92539fc24765acb755bcd942ea598c1c4aa01b56 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Wed, 15 Dec 2021 11:22:52 -0500 Subject: [PATCH 04/12] [flutter_plugin_tools] Auto-retry failed FTL tests (#4610) Currently the flake situation for Firebase Test Lab tests is very bad, and the task running those tests are some of the slowest tasks in the CI. Re-running failed tests is slow, manual work that is signficantly affecting productivity. There are plans to actually address the flake in the short-to-medium term, but in the immediate term this will improve the CI situation, as well as reducing the drag on engineering time that could be spent on the root causes. Part of https://github.com/flutter/flutter/issues/95063 --- script/tool/CHANGELOG.md | 2 + .../lib/src/firebase_test_lab_command.dart | 74 +++++++++++++------ .../test/firebase_test_lab_command_test.dart | 41 +++++++++- 3 files changed, 93 insertions(+), 24 deletions(-) diff --git a/script/tool/CHANGELOG.md b/script/tool/CHANGELOG.md index 72539c24c5fc..12ccf17d4d0b 100644 --- a/script/tool/CHANGELOG.md +++ b/script/tool/CHANGELOG.md @@ -13,6 +13,8 @@ - Fix `federation-safety-check` handling of plugin deletion, and of top-level files in unfederated plugins whose names match federated plugin heuristics (e.g., `packages/foo/foo_android.iml`). +- Add an auto-retry for failed Firebase Test Lab tests as a short-term patch + for flake issues. ## 0.7.3 diff --git a/script/tool/lib/src/firebase_test_lab_command.dart b/script/tool/lib/src/firebase_test_lab_command.dart index 4e53ee8fbace..e824d8ad1a90 100644 --- a/script/tool/lib/src/firebase_test_lab_command.dart +++ b/script/tool/lib/src/firebase_test_lab_command.dart @@ -176,30 +176,22 @@ class FirebaseTestLabCommand extends PackageLoopingCommand { final String testRunId = getStringArg('test-run-id'); final String resultsDir = 'plugins_android_test/${package.displayName}/$buildId/$testRunId/${resultsCounter++}/'; - final List args = [ - 'firebase', - 'test', - 'android', - 'run', - '--type', - 'instrumentation', - '--app', - 'build/app/outputs/apk/debug/app-debug.apk', - '--test', - 'build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk', - '--timeout', - '7m', - '--results-bucket=${getStringArg('results-bucket')}', - '--results-dir=$resultsDir', - ]; - for (final String device in getStringListArg('device')) { - args.addAll(['--device', device]); - } - final int exitCode = await processRunner.runAndStream('gcloud', args, - workingDir: example.directory); - if (exitCode != 0) { - printError('Test failure for $testName'); + // Automatically retry failures; there is significant flake with these + // tests whose cause isn't yet understood, and having to re-run the + // entire shard for a flake in any one test is extremely slow. This should + // be removed once the root cause of the flake is understood. + // See https://github.com/flutter/flutter/issues/95063 + const int maxRetries = 2; + bool passing = false; + for (int i = 1; i <= maxRetries && !passing; ++i) { + if (i > 1) { + logWarning('$testName failed on attempt ${i - 1}. Retrying...'); + } + passing = await _runFirebaseTest(example, test, resultsDir: resultsDir); + } + if (!passing) { + printError('Test failure for $testName after $maxRetries attempts'); errors.add('$testName failed tests'); } } @@ -238,6 +230,42 @@ class FirebaseTestLabCommand extends PackageLoopingCommand { return true; } + /// Runs [test] from [example] as a Firebase Test Lab test, returning true if + /// the test passed. + /// + /// [resultsDir] should be a unique-to-the-test-run directory to store the + /// results on the server. + Future _runFirebaseTest( + RepositoryPackage example, + File test, { + required String resultsDir, + }) async { + final List args = [ + 'firebase', + 'test', + 'android', + 'run', + '--type', + 'instrumentation', + '--app', + 'build/app/outputs/apk/debug/app-debug.apk', + '--test', + 'build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk', + '--timeout', + '7m', + '--results-bucket=${getStringArg('results-bucket')}', + '--results-dir=$resultsDir', + for (final String device in getStringListArg('device')) ...[ + '--device', + device + ], + ]; + final int exitCode = await processRunner.runAndStream('gcloud', args, + workingDir: example.directory); + + return exitCode == 0; + } + /// Builds [target] using Gradle in the given [project]. Assumes Gradle is /// already configured. /// diff --git a/script/tool/test/firebase_test_lab_command_test.dart b/script/tool/test/firebase_test_lab_command_test.dart index 65f398b32ca8..1dfd8ba66b58 100644 --- a/script/tool/test/firebase_test_lab_command_test.dart +++ b/script/tool/test/firebase_test_lab_command_test.dart @@ -271,7 +271,7 @@ public class MainActivityTest { ); }); - test('fails if a test fails', () async { + test('fails if a test fails twice', () async { const String javaTestFileRelativePath = 'example/android/app/src/androidTest/MainActivityTest.java'; final Directory pluginDir = @@ -287,6 +287,7 @@ public class MainActivityTest { MockProcess(), // auth MockProcess(), // config MockProcess(exitCode: 1), // integration test #1 + MockProcess(exitCode: 1), // integration test #1 retry MockProcess(), // integration test #2 ]; @@ -315,6 +316,44 @@ public class MainActivityTest { ); }); + test('passes with warning if a test fails once, then passes on retry', + () async { + const String javaTestFileRelativePath = + 'example/android/app/src/androidTest/MainActivityTest.java'; + final Directory pluginDir = + createFakePlugin('plugin', packagesDir, extraFiles: [ + 'example/integration_test/bar_test.dart', + 'example/integration_test/foo_test.dart', + 'example/android/gradlew', + javaTestFileRelativePath, + ]); + _writeJavaTestFile(pluginDir, javaTestFileRelativePath); + + processRunner.mockProcessesForExecutable['gcloud'] = [ + MockProcess(), // auth + MockProcess(), // config + MockProcess(exitCode: 1), // integration test #1 + MockProcess(), // integration test #1 retry + MockProcess(), // integration test #2 + ]; + + final List output = await runCapturingPrint(runner, [ + 'firebase-test-lab', + '--device', + 'model=redfin,version=30', + ]); + + expect( + output, + containsAllInOrder([ + contains('Testing example/integration_test/bar_test.dart...'), + contains('bar_test.dart failed on attempt 1. Retrying...'), + contains('Testing example/integration_test/foo_test.dart...'), + contains('Ran for 1 package(s) (1 with warnings)'), + ]), + ); + }); + test('fails for packages with no androidTest directory', () async { createFakePlugin('plugin', packagesDir, extraFiles: [ 'example/integration_test/foo_test.dart', From ee97b6534dd575c0df517d7bcffc89a6db43b3ed Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Thu, 16 Dec 2021 16:29:20 -0500 Subject: [PATCH 05/12] [video_player] Eliminate platform channel from mock platform (#4588) --- .../video_player/video_player/CHANGELOG.md | 5 +- .../video_player/lib/video_player.dart | 16 +- .../video_player/video_player/pubspec.yaml | 8 +- .../video_player_initialization_test.dart | 2 + .../video_player/test/video_player_test.dart | 215 ++++++------------ 5 files changed, 89 insertions(+), 157 deletions(-) diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index 9d4e36d72017..6f2a2b7250d0 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -1,5 +1,8 @@ -## NEXT +## 2.2.8 +* Changes the way the `VideoPlayerPlatform` instance is cached in the + controller, so that it's no longer impossible to change after the first use. +* Updates unit tests to be self-contained. * Fixes integration tests. * Updates Android compileSdkVersion to 31. * Fixes a flaky integration test. diff --git a/packages/video_player/video_player/lib/video_player.dart b/packages/video_player/video_player/lib/video_player.dart index ff727f1432c8..523a1adc5425 100644 --- a/packages/video_player/video_player/lib/video_player.dart +++ b/packages/video_player/video_player/lib/video_player.dart @@ -17,10 +17,18 @@ export 'package:video_player_platform_interface/video_player_platform_interface. import 'src/closed_caption_file.dart'; export 'src/closed_caption_file.dart'; -final VideoPlayerPlatform _videoPlayerPlatform = VideoPlayerPlatform.instance - // This will clear all open videos on the platform when a full restart is - // performed. - ..init(); +VideoPlayerPlatform? _lastVideoPlayerPlatform; + +VideoPlayerPlatform get _videoPlayerPlatform { + VideoPlayerPlatform currentInstance = VideoPlayerPlatform.instance; + if (_lastVideoPlayerPlatform != currentInstance) { + // This will clear all open videos on the platform when a full restart is + // performed. + currentInstance.init(); + _lastVideoPlayerPlatform = currentInstance; + } + return currentInstance; +} /// The duration, current position, buffering state, error state and settings /// of a [VideoPlayerController]. diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml index f0d5951b1403..f8c091786526 100644 --- a/packages/video_player/video_player/pubspec.yaml +++ b/packages/video_player/video_player/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for displaying inline video with other Flutter widgets on Android, iOS, and web. repository: https://github.com/flutter/plugins/tree/master/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.2.7 +version: 2.2.8 environment: sdk: ">=2.14.0 <3.0.0" @@ -25,12 +25,6 @@ dependencies: sdk: flutter meta: ^1.3.0 video_player_platform_interface: ^4.2.0 - # The design on https://flutter.dev/go/federated-plugins was to leave - # this constraint as "any". We cannot do it right now as it fails pub publish - # validation, so we set a ^ constraint. The exact value doesn't matter since - # the constraints on the interface pins it. - # TODO(amirh): Revisit this (either update this part in the design or the pub tool). - # https://github.com/flutter/flutter/issues/46264 video_player_web: ^2.0.0 html: ^0.15.0 diff --git a/packages/video_player/video_player/test/video_player_initialization_test.dart b/packages/video_player/video_player/test/video_player_initialization_test.dart index 13bfd7be7889..1870934a931e 100644 --- a/packages/video_player/video_player/test/video_player_initialization_test.dart +++ b/packages/video_player/video_player/test/video_player_initialization_test.dart @@ -4,6 +4,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:video_player/video_player.dart'; +import 'package:video_player_platform_interface/video_player_platform_interface.dart'; import 'video_player_test.dart' show FakeVideoPlayerPlatform; @@ -13,6 +14,7 @@ void main() { test('plugin initialized', () async { TestWidgetsFlutterBinding.ensureInitialized(); FakeVideoPlayerPlatform fakeVideoPlayerPlatform = FakeVideoPlayerPlatform(); + VideoPlayerPlatform.instance = fakeVideoPlayerPlatform; final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', 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 08fd9dc604f1..959f98f25e28 100644 --- a/packages/video_player/video_player/test/video_player_test.dart +++ b/packages/video_player/video_player/test/video_player_test.dart @@ -11,8 +11,6 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:video_player/video_player.dart'; -import 'package:video_player_platform_interface/messages.dart'; -import 'package:video_player_platform_interface/test.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; class FakeController extends ValueNotifier @@ -187,6 +185,7 @@ void main() { setUp(() { fakeVideoPlayerPlatform = FakeVideoPlayerPlatform(); + VideoPlayerPlatform.instance = fakeVideoPlayerPlatform; }); group('initialize', () { @@ -196,10 +195,8 @@ void main() { ); await controller.initialize(); - expect( - fakeVideoPlayerPlatform.dataSourceDescriptions[0].asset, 'a.avi'); - expect(fakeVideoPlayerPlatform.dataSourceDescriptions[0].packageName, - null); + expect(fakeVideoPlayerPlatform.dataSources[0].asset, 'a.avi'); + expect(fakeVideoPlayerPlatform.dataSources[0].package, null); }); test('network', () async { @@ -209,15 +206,15 @@ void main() { await controller.initialize(); expect( - fakeVideoPlayerPlatform.dataSourceDescriptions[0].uri, + fakeVideoPlayerPlatform.dataSources[0].uri, 'https://127.0.0.1', ); expect( - fakeVideoPlayerPlatform.dataSourceDescriptions[0].formatHint, + fakeVideoPlayerPlatform.dataSources[0].formatHint, null, ); expect( - fakeVideoPlayerPlatform.dataSourceDescriptions[0].httpHeaders, + fakeVideoPlayerPlatform.dataSources[0].httpHeaders, {}, ); }); @@ -230,16 +227,16 @@ void main() { await controller.initialize(); expect( - fakeVideoPlayerPlatform.dataSourceDescriptions[0].uri, + fakeVideoPlayerPlatform.dataSources[0].uri, 'https://127.0.0.1', ); expect( - fakeVideoPlayerPlatform.dataSourceDescriptions[0].formatHint, - 'dash', + fakeVideoPlayerPlatform.dataSources[0].formatHint, + VideoFormat.dash, ); expect( - fakeVideoPlayerPlatform.dataSourceDescriptions[0].httpHeaders, - {}, + fakeVideoPlayerPlatform.dataSources[0].httpHeaders, + {}, ); }); @@ -251,15 +248,15 @@ void main() { await controller.initialize(); expect( - fakeVideoPlayerPlatform.dataSourceDescriptions[0].uri, + fakeVideoPlayerPlatform.dataSources[0].uri, 'https://127.0.0.1', ); expect( - fakeVideoPlayerPlatform.dataSourceDescriptions[0].formatHint, + fakeVideoPlayerPlatform.dataSources[0].formatHint, null, ); expect( - fakeVideoPlayerPlatform.dataSourceDescriptions[0].httpHeaders, + fakeVideoPlayerPlatform.dataSources[0].httpHeaders, {'Authorization': 'Bearer token'}, ); }); @@ -268,15 +265,12 @@ void main() { final VideoPlayerController controller = VideoPlayerController.network( 'http://testing.com/invalid_url', ); - try { - late dynamic error; - fakeVideoPlayerPlatform.forceInitError = true; - await controller.initialize().catchError((dynamic e) => error = e); - final PlatformException platformEx = error; - expect(platformEx.code, equals('VideoError')); - } finally { - fakeVideoPlayerPlatform.forceInitError = false; - } + + late dynamic error; + fakeVideoPlayerPlatform.forceInitError = true; + await controller.initialize().catchError((dynamic e) => error = e); + final PlatformException platformEx = error; + expect(platformEx.code, equals('VideoError')); }); test('file', () async { @@ -284,8 +278,7 @@ void main() { VideoPlayerController.file(File('a.avi')); await controller.initialize(); - expect(fakeVideoPlayerPlatform.dataSourceDescriptions[0].uri, - 'file://a.avi'); + expect(fakeVideoPlayerPlatform.dataSources[0].uri, 'file://a.avi'); }); }); @@ -294,8 +287,7 @@ void main() { VideoPlayerController.contentUri(Uri.parse('content://video')); await controller.initialize(); - expect(fakeVideoPlayerPlatform.dataSourceDescriptions[0].uri, - 'content://video'); + expect(fakeVideoPlayerPlatform.dataSources[0].uri, 'content://video'); }); test('dispose', () async { @@ -571,11 +563,11 @@ void main() { expect(controller.value.isPlaying, isFalse); await controller.play(); expect(controller.value.isPlaying, isTrue); - final FakeVideoEventStream fakeVideoEventStream = + final StreamController fakeVideoEventStream = fakeVideoPlayerPlatform.streams[controller.textureId]!; - fakeVideoEventStream.eventsChannel - .sendEvent({'event': 'completed'}); + fakeVideoEventStream + .add(VideoEvent(eventType: VideoEventType.completed)); await tester.pumpAndSettle(); expect(controller.value.isPlaying, isFalse); @@ -589,30 +581,30 @@ void main() { await controller.initialize(); expect(controller.value.isBuffering, false); expect(controller.value.buffered, isEmpty); - final FakeVideoEventStream fakeVideoEventStream = + final StreamController fakeVideoEventStream = fakeVideoPlayerPlatform.streams[controller.textureId]!; - fakeVideoEventStream.eventsChannel - .sendEvent({'event': 'bufferingStart'}); + fakeVideoEventStream + .add(VideoEvent(eventType: VideoEventType.bufferingStart)); await tester.pumpAndSettle(); expect(controller.value.isBuffering, isTrue); const Duration bufferStart = Duration(seconds: 0); const Duration bufferEnd = Duration(milliseconds: 500); - fakeVideoEventStream.eventsChannel.sendEvent({ - 'event': 'bufferingUpdate', - 'values': >[ - [bufferStart.inMilliseconds, bufferEnd.inMilliseconds] - ], - }); + fakeVideoEventStream + ..add(VideoEvent( + eventType: VideoEventType.bufferingUpdate, + buffered: [ + DurationRange(bufferStart, bufferEnd), + ])); await tester.pumpAndSettle(); expect(controller.value.isBuffering, isTrue); expect(controller.value.buffered.length, 1); expect(controller.value.buffered[0].toString(), DurationRange(bufferStart, bufferEnd).toString()); - fakeVideoEventStream.eventsChannel - .sendEvent({'event': 'bufferingEnd'}); + fakeVideoEventStream + .add(VideoEvent(eventType: VideoEventType.bufferingEnd)); await tester.pumpAndSettle(); expect(controller.value.isBuffering, isFalse); }); @@ -807,155 +799,88 @@ void main() { }); } -class FakeVideoPlayerPlatform extends TestHostVideoPlayerApi { - FakeVideoPlayerPlatform() { - TestHostVideoPlayerApi.setup(this); - } - +class FakeVideoPlayerPlatform extends VideoPlayerPlatform { Completer initialized = Completer(); List calls = []; - List dataSourceDescriptions = []; - final Map streams = {}; + List dataSources = []; + final Map> streams = + >{}; bool forceInitError = false; int nextTextureId = 0; final Map _positions = {}; @override - TextureMessage create(CreateMessage arg) { + Future create(DataSource dataSource) async { calls.add('create'); - streams[nextTextureId] = FakeVideoEventStream( - nextTextureId, 100, 100, const Duration(seconds: 1), forceInitError); - TextureMessage result = TextureMessage(); - result.textureId = nextTextureId++; - dataSourceDescriptions.add(arg); - return result; + StreamController stream = StreamController(); + streams[nextTextureId] = stream; + if (forceInitError) { + stream.addError(PlatformException( + code: 'VideoError', message: 'Video player had error XYZ')); + } else { + stream.add(VideoEvent( + eventType: VideoEventType.initialized, + size: Size(100, 100), + duration: Duration(seconds: 1))); + } + dataSources.add(dataSource); + return nextTextureId++; } @override - void dispose(TextureMessage arg) { + Future dispose(int textureId) async { calls.add('dispose'); } @override - void initialize() { + Future init() async { calls.add('init'); initialized.complete(true); } + Stream videoEventsFor(int textureId) { + return streams[textureId]!.stream; + } + @override - void pause(TextureMessage arg) { + Future pause(int textureId) async { calls.add('pause'); } @override - void play(TextureMessage arg) { + Future play(int textureId) async { calls.add('play'); } @override - PositionMessage position(TextureMessage arg) { + Future getPosition(int textureId) async { calls.add('position'); - final Duration position = - _positions[arg.textureId] ?? const Duration(seconds: 0); - return PositionMessage()..position = position.inMilliseconds; + return _positions[textureId] ?? const Duration(seconds: 0); } @override - void seekTo(PositionMessage arg) { + Future seekTo(int textureId, Duration position) async { calls.add('seekTo'); - _positions[arg.textureId!] = Duration(milliseconds: arg.position!); + _positions[textureId] = position; } @override - void setLooping(LoopingMessage arg) { + Future setLooping(int textureId, bool looping) async { calls.add('setLooping'); } @override - void setVolume(VolumeMessage arg) { + Future setVolume(int textureId, double volume) async { calls.add('setVolume'); } @override - void setPlaybackSpeed(PlaybackSpeedMessage arg) { + Future setPlaybackSpeed(int textureId, double speed) async { calls.add('setPlaybackSpeed'); } @override - void setMixWithOthers(MixWithOthersMessage arg) { + Future setMixWithOthers(bool mixWithOthers) async { calls.add('setMixWithOthers'); } } - -class FakeVideoEventStream { - FakeVideoEventStream(this.textureId, this.width, this.height, this.duration, - this.initWithError) { - eventsChannel = FakeEventsChannel( - 'flutter.io/videoPlayer/videoEvents$textureId', onListen); - } - - int textureId; - int width; - int height; - Duration duration; - bool initWithError; - late FakeEventsChannel eventsChannel; - - void onListen() { - if (!initWithError) { - eventsChannel.sendEvent({ - 'event': 'initialized', - 'duration': duration.inMilliseconds, - 'width': width, - 'height': height, - }); - } else { - eventsChannel.sendError('VideoError', 'Video player had error XYZ'); - } - } -} - -class FakeEventsChannel { - FakeEventsChannel(String name, this.onListen) { - eventsMethodChannel = MethodChannel(name); - eventsMethodChannel.setMockMethodCallHandler(onMethodCall); - } - - late MethodChannel eventsMethodChannel; - VoidCallback onListen; - - Future onMethodCall(MethodCall call) { - switch (call.method) { - case 'listen': - onListen(); - break; - } - return Future.sync(() {}); - } - - void sendEvent(dynamic event) { - _sendMessage(const StandardMethodCodec().encodeSuccessEnvelope(event)); - } - - void sendError(String code, [String? message, dynamic details]) { - _sendMessage(const StandardMethodCodec().encodeErrorEnvelope( - code: code, - message: message, - details: details, - )); - } - - void _sendMessage(ByteData data) { - _ambiguate(ServicesBinding.instance)! - .defaultBinaryMessenger - .handlePlatformMessage( - eventsMethodChannel.name, data, (ByteData? data) {}); - } -} - -/// This allows a value of type T or T? to be treated as a value of type T?. -/// -/// We use this so that APIs that have become non-nullable can still be used -/// with `!` and `?` on the stable branch. -// TODO(ianh): Remove this once we roll stable in late 2021. -T? _ambiguate(T? value) => value; From 73cb8a7c7fa74686cb3fb60cb8688b90d1a73e62 Mon Sep 17 00:00:00 2001 From: Maurice Parrish Date: Fri, 17 Dec 2021 10:54:02 -0500 Subject: [PATCH 06/12] Prevent setting user agent string to default value on every rebuild (#4618) --- .../webview_flutter_android/CHANGELOG.md | 5 ++++ .../lib/webview_android_widget.dart | 7 ++--- .../webview_flutter_android/pubspec.yaml | 2 +- .../test/webview_android_widget_test.dart | 26 +++++++++++++++++++ 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index 7bd33879cd7d..1337bab304b8 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.8.1 + +* Fixes bug where the default user agent string was being set for every rebuild. See + https://github.com/flutter/flutter/issues/94847. + ## 2.8.0 * Implements new cookie manager for setting cookies and providing initial cookies. diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart index 1dec9c105741..de55b5207a84 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart @@ -418,11 +418,12 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { } Future _setUserAgent(WebSetting userAgent) { - if (userAgent.isPresent && userAgent.value != null) { - return webView.settings.setUserAgentString(userAgent.value!); + if (userAgent.isPresent) { + // If the string is empty, the system default value will be used. + return webView.settings.setUserAgentString(userAgent.value ?? ''); } - return webView.settings.setUserAgentString(''); + return Future.value(); } Future _setZoomEnabled(bool zoomEnabled) { diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index 34bea570ae43..0b78c72ecc4c 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_android description: A Flutter plugin that provides a WebView widget on Android. repository: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 2.8.0 +version: 2.8.1 environment: sdk: ">=2.14.0 <3.0.0" 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 c203ef04a2ce..2b25022f9087 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 @@ -483,6 +483,32 @@ void main() { }); }); + testWidgets('no update to userAgentString when there is no change', + (WidgetTester tester) async { + await buildWidget(tester); + + reset(mockWebSettings); + + await testController.updateSettings(WebSettings( + userAgent: const WebSetting.absent(), + )); + + verifyNever(mockWebSettings.setUserAgentString(any)); + }); + + testWidgets('update null userAgentString with empty string', + (WidgetTester tester) async { + await buildWidget(tester); + + reset(mockWebSettings); + + await testController.updateSettings(WebSettings( + userAgent: const WebSetting.of(null), + )); + + verify(mockWebSettings.setUserAgentString('')); + }); + testWidgets('currentUrl', (WidgetTester tester) async { await buildWidget(tester); From d383fada852f4924e37d6ee9673950088413bac3 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 17 Dec 2021 17:59:21 +0100 Subject: [PATCH 07/12] [webview_flutter] Enable setAllowFileAccess on Android setting when loading files (#4601) --- .../webview_flutter_android/CHANGELOG.md | 4 +++ .../GeneratedAndroidWebView.java | 33 +++++++++++++++++++ .../WebSettingsHostApiImpl.java | 6 ++++ .../lib/src/android_webview.dart | 10 ++++++ .../lib/src/android_webview.pigeon.dart | 25 ++++++++++++++ .../lib/src/android_webview_api_impls.dart | 11 +++++++ .../lib/webview_android_widget.dart | 1 + .../pigeons/android_webview.dart | 2 ++ .../webview_flutter_android/pubspec.yaml | 2 +- .../test/android_webview.pigeon.dart | 23 +++++++++++++ .../test/android_webview_test.dart | 8 +++++ .../test/android_webview_test.mocks.dart | 8 ++--- .../test/webview_android_widget_test.dart | 9 +++++ .../webview_android_widget_test.mocks.dart | 9 ++--- 14 files changed, 142 insertions(+), 9 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index 1337bab304b8..818a13439c95 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.8.2 + +* Adds the `WebSettings.setAllowFileAccess()` method and ensure that file access is allowed when the `WebViewAndroidWidget.loadFile()` method is executed. + ## 2.8.1 * Fixes bug where the default user agent string was being set for every rebuild. See diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java index 8ef0b8d11f96..15b78b718115 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java @@ -1152,6 +1152,8 @@ public interface WebSettingsHostApi { void setBuiltInZoomControls(Long instanceId, Boolean enabled); + void setAllowFileAccess(Long instanceId, Boolean enabled); + /** The codec used by WebSettingsHostApi. */ static MessageCodec getCodec() { return WebSettingsHostApiCodec.INSTANCE; @@ -1556,6 +1558,37 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.WebSettingsHostApi.setAllowFileAccess", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + ArrayList args = (ArrayList) message; + Number instanceIdArg = (Number) args.get(0); + if (instanceIdArg == null) { + throw new NullPointerException("instanceIdArg unexpectedly null."); + } + Boolean enabledArg = (Boolean) args.get(1); + if (enabledArg == null) { + throw new NullPointerException("enabledArg unexpectedly null."); + } + api.setAllowFileAccess(instanceIdArg.longValue(), enabledArg); + wrapped.put("result", null); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsHostApiImpl.java index 239ef473b546..b168e206214f 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsHostApiImpl.java @@ -118,4 +118,10 @@ public void setBuiltInZoomControls(Long instanceId, Boolean enabled) { final WebSettings webSettings = (WebSettings) instanceManager.getInstance(instanceId); webSettings.setBuiltInZoomControls(enabled); } + + @Override + public void setAllowFileAccess(Long instanceId, Boolean enabled) { + final WebSettings webSettings = (WebSettings) instanceManager.getInstance(instanceId); + webSettings.setAllowFileAccess(enabled); + } } diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart index dfa05cd92ee6..10989321a9bb 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart @@ -546,6 +546,16 @@ class WebSettings { Future setBuiltInZoomControls(bool enabled) { return api.setBuiltInZoomControlsFromInstance(this, enabled); } + + /// Enables or disables file access within WebView. + /// + /// This enables or disables file system access only. Assets and resources are + /// still accessible using file:///android_asset and file:///android_res. The + /// default value is true for apps targeting Build.VERSION_CODES.Q and below, + /// and false when targeting Build.VERSION_CODES.R and above. + Future setAllowFileAccess(bool enabled) { + return api.setAllowFileAccessFromInstance(this, enabled); + } } /// Exposes a channel to receive calls from javaScript. diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart index 810a71732e7b..20391c43d966 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart @@ -1179,6 +1179,31 @@ class WebSettingsHostApi { return; } } + + Future setAllowFileAccess(int arg_instanceId, bool arg_enabled) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.WebSettingsHostApi.setAllowFileAccess', codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = await channel + .send([arg_instanceId, arg_enabled]) as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + details: null, + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else { + return; + } + } } class _JavaScriptChannelHostApiCodec extends StandardMessageCodec { diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart index 1db5ed449c56..ead60f6a2b35 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart @@ -437,6 +437,17 @@ class WebSettingsHostApiImpl extends WebSettingsHostApi { enabled, ); } + + /// Helper method to convert instances ids to objects. + Future setAllowFileAccessFromInstance( + WebSettings instance, + bool enabled, + ) { + return setAllowFileAccess( + instanceManager.getInstanceId(instance)!, + enabled, + ); + } } /// Host api implementation for [JavaScriptChannel]. diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart index de55b5207a84..bf85ac97687e 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart @@ -179,6 +179,7 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { ? absoluteFilePath : 'file://$absoluteFilePath'; + webView.settings.setAllowFileAccess(true); return webView.loadUrl(url, {}); } diff --git a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart index 36862f7cbacc..b29835266717 100644 --- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart @@ -132,6 +132,8 @@ abstract class WebSettingsHostApi { void setDisplayZoomControls(int instanceId, bool enabled); void setBuiltInZoomControls(int instanceId, bool enabled); + + void setAllowFileAccess(int instanceId, bool enabled); } @HostApi(dartHostTestHandler: 'TestJavaScriptChannelHostApi') diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index 0b78c72ecc4c..8905d7fb66e2 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_android description: A Flutter plugin that provides a WebView widget on Android. repository: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 2.8.1 +version: 2.8.2 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart index 1e47c79d32b7..720fe408d96c 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart @@ -649,6 +649,7 @@ abstract class TestWebSettingsHostApi { void setUseWideViewPort(int instanceId, bool use); void setDisplayZoomControls(int instanceId, bool enabled); void setBuiltInZoomControls(int instanceId, bool enabled); + void setAllowFileAccess(int instanceId, bool enabled); static void setup(TestWebSettingsHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -940,6 +941,28 @@ abstract class TestWebSettingsHostApi { }); } } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.WebSettingsHostApi.setAllowFileAccess', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setAllowFileAccess was null.'); + final List args = (message as List?)!; + final int? arg_instanceId = (args[0] as int?); + assert(arg_instanceId != null, + 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setAllowFileAccess was null, expected non-null int.'); + final bool? arg_enabled = (args[1] as bool?); + assert(arg_enabled != null, + 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setAllowFileAccess was null, expected non-null bool.'); + api.setAllowFileAccess(arg_instanceId!, arg_enabled!); + return {}; + }); + } + } } } diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart index cc29fc755067..8688a1977d83 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart @@ -430,6 +430,14 @@ void main() { true, )); }); + + test('setAllowFileAccess', () { + webSettings.setAllowFileAccess(true); + verify(mockPlatformHostApi.setAllowFileAccess( + webSettingsInstanceId, + true, + )); + }); }); group('$JavaScriptChannel', () { diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart index d25d2338886b..2134de54a415 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart @@ -1,7 +1,3 @@ -// 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. - // Mocks generated by Mockito 5.0.16 from annotations // in webview_flutter_android/test/android_webview_test.dart. // Do not manually edit this file. @@ -211,6 +207,10 @@ class MockTestWebSettingsHostApi extends _i1.Mock Invocation.method(#setBuiltInZoomControls, [instanceId, enabled]), returnValueForMissingStub: null); @override + void setAllowFileAccess(int? instanceId, bool? enabled) => super.noSuchMethod( + Invocation.method(#setAllowFileAccess, [instanceId, enabled]), + returnValueForMissingStub: null); + @override String toString() => super.toString(); } 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 2b25022f9087..fed1c1113e55 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 @@ -304,6 +304,15 @@ void main() { )); }); + testWidgets('loadFile should setAllowFileAccess to true', + (WidgetTester tester) async { + await buildWidget(tester); + + await testController.loadFile('file:///path/to/file.html'); + + verify(mockWebSettings.setAllowFileAccess(true)); + }); + testWidgets('loadFlutterAsset', (WidgetTester tester) async { await buildWidget(tester); const String assetKey = 'test_assets/index.html'; diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart index ece17ad61cb8..12e993bafa31 100644 --- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart @@ -1,7 +1,3 @@ -// 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. - // Mocks generated by Mockito 5.0.16 from annotations // in webview_flutter_android/test/webview_android_widget_test.dart. // Do not manually edit this file. @@ -120,6 +116,11 @@ class MockWebSettings extends _i1.Mock implements _i2.WebSettings { returnValue: Future.value(), returnValueForMissingStub: Future.value()) as _i4.Future); @override + _i4.Future setAllowFileAccess(bool? enabled) => + (super.noSuchMethod(Invocation.method(#setAllowFileAccess, [enabled]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override String toString() => super.toString(); } From 29b637301dfbafe7f84c0c6166f2b5c4dd186697 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Sun, 19 Dec 2021 14:35:48 -0500 Subject: [PATCH 08/12] [ci] Move Android build-all CI to heavy workload (#4624) This task's memory usage is very close to the 4GB limit of the light-workload machines, and sometimes goes over and causes flaky failures. --- .cirrus.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index bcd0e49327a3..50b6cca0362a 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -128,14 +128,6 @@ task: # Restore the tree to a clean state, to avoid accidental issues if # other script steps are added to this task. - git checkout . - ### Android tasks ### - - name: android-build_all_plugins - env: - BUILD_ALL_ARGS: "apk" - matrix: - CHANNEL: "master" - CHANNEL: "stable" - << : *BUILD_ALL_PLUGINS_APP_TEMPLATE ### Web tasks ### - name: web-build_all_plugins env: @@ -238,6 +230,13 @@ task: path: "**/reports/lint-results-debug.xml" type: text/xml format: android-lint + - name: android-build_all_plugins + env: + BUILD_ALL_ARGS: "apk" + matrix: + CHANNEL: "master" + CHANNEL: "stable" + << : *BUILD_ALL_PLUGINS_APP_TEMPLATE ### Web tasks ### - name: web-platform_tests env: From 6531740363e539e1751a97c56ad5c1a87c985be9 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Sun, 19 Dec 2021 14:41:00 -0500 Subject: [PATCH 09/12] [video_player] Remove test code from platform interface (#4589) This is a breaking change to video_player_platform_interface to remove Pigeon mocks from the public interface. These should never have been there, as tests in packages outside of the platform interface should be mocking the platform interface rather than the method channel, but the app-facing tests weren't rewritten when they should have been so they ended up published to support the legacy tests. This creates an unwanted non-dev dependency on `flutter_test`, in addition to promoting an anti-pattern. While making the breaking change, this also adopts `PlatformInterface`, removing `isMock` in favor of the standard mixin pattern provided by the base class. Part of https://github.com/flutter/flutter/issues/83562 --- .../CHANGELOG.md | 8 ++++ .../lib/video_player_platform_interface.dart | 44 +++++-------------- .../pubspec.yaml | 8 ++-- .../method_channel_video_player_test.dart | 3 +- .../{lib => test}/test.dart | 3 +- 5 files changed, 25 insertions(+), 41 deletions(-) rename packages/video_player/video_player_platform_interface/{lib => test}/test.dart (99%) diff --git a/packages/video_player/video_player_platform_interface/CHANGELOG.md b/packages/video_player/video_player_platform_interface/CHANGELOG.md index b3da9c8924ef..e0e6a11065ee 100644 --- a/packages/video_player/video_player_platform_interface/CHANGELOG.md +++ b/packages/video_player/video_player_platform_interface/CHANGELOG.md @@ -1,3 +1,11 @@ +## 5.0.0 + +* **BREAKING CHANGES**: + * Updates to extending `PlatformInterface`. Removes `isMock`, in favor of the + now-standard `MockPlatformInterfaceMixin`. + * Removes test.dart from the public interface. Tests in other packages should + mock `VideoPlatformInterface` rather than the method channel. + ## 4.2.0 * Add `contentUri` to `DataSourceType`. diff --git a/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart b/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart index 21ad972d8e06..66b6d709e9fe 100644 --- a/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart +++ b/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart @@ -2,12 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; -import 'dart:ui'; - import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; -import 'package:meta/meta.dart' show visibleForTesting; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'method_channel_video_player.dart'; @@ -18,37 +15,24 @@ import 'method_channel_video_player.dart'; /// (using `extends`) ensures that the subclass will get the default implementation, while /// platform implementations that `implements` this interface will be broken by newly added /// [VideoPlayerPlatform] methods. -abstract class VideoPlayerPlatform { - /// Only mock implementations should set this to true. - /// - /// Mockito mocks are implementing this class with `implements` which is forbidden for anything - /// other than mocks (see class docs). This property provides a backdoor for mockito mocks to - /// skip the verification that the class isn't implemented with `implements`. - @visibleForTesting - bool get isMock => false; +abstract class VideoPlayerPlatform extends PlatformInterface { + /// Constructs a VideoPlayerPlatform. + VideoPlayerPlatform() : super(token: _token); + + static final Object _token = Object(); static VideoPlayerPlatform _instance = MethodChannelVideoPlayer(); /// The default instance of [VideoPlayerPlatform] to use. /// - /// Platform-specific plugins should override this with their own - /// platform-specific class that extends [VideoPlayerPlatform] when they - /// register themselves. - /// /// Defaults to [MethodChannelVideoPlayer]. static VideoPlayerPlatform get instance => _instance; - // TODO(amirh): Extract common platform interface logic. - // https://github.com/flutter/flutter/issues/43368 + /// Platform-specific plugins should override this with their own + /// platform-specific class that extends [VideoPlayerPlatform] when they + /// register themselves. static set instance(VideoPlayerPlatform instance) { - if (!instance.isMock) { - try { - instance._verifyProvidesDefaultImplementations(); - } on NoSuchMethodError catch (_) { - throw AssertionError( - 'Platform interfaces must not be implemented with `implements`'); - } - } + PlatformInterface.verifyToken(instance, _token); _instance = instance; } @@ -119,14 +103,6 @@ abstract class VideoPlayerPlatform { Future setMixWithOthers(bool mixWithOthers) { throw UnimplementedError('setMixWithOthers() has not been implemented.'); } - - // This method makes sure that VideoPlayer isn't implemented with `implements`. - // - // See class doc for more details on why implementing this class is forbidden. - // - // This private method is called by the instance setter, which fails if the class is - // implemented with `implements`. - void _verifyProvidesDefaultImplementations() {} } /// Description of the data source used to create an instance of diff --git a/packages/video_player/video_player_platform_interface/pubspec.yaml b/packages/video_player/video_player_platform_interface/pubspec.yaml index 35b30793a20f..b8404772bffa 100644 --- a/packages/video_player/video_player_platform_interface/pubspec.yaml +++ b/packages/video_player/video_player_platform_interface/pubspec.yaml @@ -4,7 +4,7 @@ repository: https://github.com/flutter/plugins/tree/master/packages/video_player issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%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: 4.2.0 +version: 5.0.0 environment: sdk: ">=2.12.0 <3.0.0" @@ -13,9 +13,9 @@ environment: dependencies: flutter: sdk: flutter - flutter_test: - sdk: flutter - meta: ^1.3.0 + plugin_platform_interface: ^2.0.0 dev_dependencies: + flutter_test: + sdk: flutter pedantic: ^1.10.0 diff --git a/packages/video_player/video_player_platform_interface/test/method_channel_video_player_test.dart b/packages/video_player/video_player_platform_interface/test/method_channel_video_player_test.dart index f5439b844045..4d1c9b78fc34 100644 --- a/packages/video_player/video_player_platform_interface/test/method_channel_video_player_test.dart +++ b/packages/video_player/video_player_platform_interface/test/method_channel_video_player_test.dart @@ -8,9 +8,10 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:video_player_platform_interface/messages.dart'; import 'package:video_player_platform_interface/method_channel_video_player.dart'; -import 'package:video_player_platform_interface/test.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; +import 'test.dart'; + class _ApiLogger implements TestHostVideoPlayerApi { final List log = []; TextureMessage? textureMessage; diff --git a/packages/video_player/video_player_platform_interface/lib/test.dart b/packages/video_player/video_player_platform_interface/test/test.dart similarity index 99% rename from packages/video_player/video_player_platform_interface/lib/test.dart rename to packages/video_player/video_player_platform_interface/test/test.dart index b4fd81f44f41..a12ae45e59db 100644 --- a/packages/video_player/video_player_platform_interface/lib/test.dart +++ b/packages/video_player/video_player_platform_interface/test/test.dart @@ -10,8 +10,7 @@ import 'dart:async'; import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; - -import 'messages.dart'; +import 'package:video_player_platform_interface/messages.dart'; abstract class TestHostVideoPlayerApi { void initialize(); From 39722125e26fdbb2aff69d7b9eb260623150eeff Mon Sep 17 00:00:00 2001 From: hellohuanlin <41930132+hellohuanlin@users.noreply.github.com> Date: Mon, 20 Dec 2021 11:30:17 -0800 Subject: [PATCH 10/12] [camera]fix a crash related to calling engine api in the background thread (#4608) --- packages/camera/camera/CHANGELOG.md | 6 +- .../ios/Runner.xcodeproj/project.pbxproj | 14 +++- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- .../RunnerTests/ThreadSafeEventChannelTests.m | 44 +++++++++++ .../ThreadSafeMethodChannelTests.m | 46 ++++++++++++ .../ThreadSafeTextureRegistryTests.m | 74 +++++++++++++++++++ .../camera/camera/ios/Classes/CameraPlugin.m | 27 ++++--- .../ios/Classes/FLTThreadSafeEventChannel.h | 27 +++++++ .../ios/Classes/FLTThreadSafeEventChannel.m | 29 ++++++++ .../ios/Classes/FLTThreadSafeMethodChannel.h | 27 +++++++ .../ios/Classes/FLTThreadSafeMethodChannel.m | 29 ++++++++ .../Classes/FLTThreadSafeTextureRegistry.h | 51 +++++++++++++ .../Classes/FLTThreadSafeTextureRegistry.m | 58 +++++++++++++++ .../camera/ios/Classes/camera-umbrella.h | 3 + 14 files changed, 425 insertions(+), 12 deletions(-) create mode 100644 packages/camera/camera/example/ios/RunnerTests/ThreadSafeEventChannelTests.m create mode 100644 packages/camera/camera/example/ios/RunnerTests/ThreadSafeMethodChannelTests.m create mode 100644 packages/camera/camera/example/ios/RunnerTests/ThreadSafeTextureRegistryTests.m create mode 100644 packages/camera/camera/ios/Classes/FLTThreadSafeEventChannel.h create mode 100644 packages/camera/camera/ios/Classes/FLTThreadSafeEventChannel.m create mode 100644 packages/camera/camera/ios/Classes/FLTThreadSafeMethodChannel.h create mode 100644 packages/camera/camera/ios/Classes/FLTThreadSafeMethodChannel.m create mode 100644 packages/camera/camera/ios/Classes/FLTThreadSafeTextureRegistry.h create mode 100644 packages/camera/camera/ios/Classes/FLTThreadSafeTextureRegistry.m diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index 1a6eceb957b1..f5b783d5114b 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,4 +1,8 @@ -## 0.9.4+5 +## NEXT + +* Fixed a crash in iOS when using image stream due to threading issue. + +## 0.9.4+5 * Fixes bug where calling a method after the camera was closed resulted in a Java `IllegalStateException` exception. * Fixes integration tests. diff --git a/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj index feb789f2ecba..ad0ece9adbd2 100644 --- a/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj @@ -20,6 +20,9 @@ 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 */; }; + E0C6E2002770F01A00EA6AA3 /* ThreadSafeMethodChannelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C6E1FD2770F01A00EA6AA3 /* ThreadSafeMethodChannelTests.m */; }; + E0C6E2012770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C6E1FE2770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m */; }; + E0C6E2022770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */; }; E487C86026D686A10034AC92 /* CameraPreviewPauseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E487C85F26D686A10034AC92 /* CameraPreviewPauseTests.m */; }; F6EE622F2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m in Sources */ = {isa = PBXBuildFile; fileRef = F6EE622E2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m */; }; /* End PBXBuildFile section */ @@ -74,6 +77,9 @@ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9C5CC6CAD53AD388B2694F3A /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; A24F9E418BA48BCC7409B117 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + E0C6E1FD2770F01A00EA6AA3 /* ThreadSafeMethodChannelTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadSafeMethodChannelTests.m; sourceTree = ""; }; + E0C6E1FE2770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadSafeTextureRegistryTests.m; sourceTree = ""; }; + E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadSafeEventChannelTests.m; sourceTree = ""; }; E487C85F26D686A10034AC92 /* CameraPreviewPauseTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraPreviewPauseTests.m; sourceTree = ""; }; F63F9EED27143B19002479BF /* MockFLTThreadSafeFlutterResult.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockFLTThreadSafeFlutterResult.h; sourceTree = ""; }; F6EE622E2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockFLTThreadSafeFlutterResult.m; sourceTree = ""; }; @@ -107,6 +113,9 @@ 03BB766C2665316900CE5A93 /* Info.plist */, 033B94BD269C40A200B4DF97 /* CameraMethodChannelTests.m */, 03F6F8B126CBB4670024B8D3 /* ThreadSafeFlutterResultTests.m */, + E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */, + E0C6E1FD2770F01A00EA6AA3 /* ThreadSafeMethodChannelTests.m */, + E0C6E1FE2770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m */, E487C85F26D686A10034AC92 /* CameraPreviewPauseTests.m */, F6EE622E2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m */, F63F9EED27143B19002479BF /* MockFLTThreadSafeFlutterResult.h */, @@ -239,7 +248,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1100; + LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 03BB76672665316900CE5A93 = { @@ -378,6 +387,9 @@ E487C86026D686A10034AC92 /* CameraPreviewPauseTests.m in Sources */, F6EE622F2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m in Sources */, 334733EA2668111C00DCC49E /* CameraOrientationTests.m in Sources */, + E0C6E2022770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m in Sources */, + E0C6E2012770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m in Sources */, + E0C6E2002770F01A00EA6AA3 /* ThreadSafeMethodChannelTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/packages/camera/camera/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/camera/camera/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 1447e08231be..f4b3c1099001 100644 --- a/packages/camera/camera/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/camera/camera/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + +@interface ThreadSafeEventChannelTests : XCTestCase +@end + +@implementation ThreadSafeEventChannelTests { + FLTThreadSafeEventChannel *_channel; + XCTestExpectation *_mainThreadExpectation; +} + +- (void)setUp { + [super setUp]; + id mockEventChannel = OCMClassMock([FlutterEventChannel class]); + + _mainThreadExpectation = + [[XCTestExpectation alloc] initWithDescription:@"invokeMethod must be called in main thread"]; + _channel = [[FLTThreadSafeEventChannel alloc] initWithEventChannel:mockEventChannel]; + + OCMStub([mockEventChannel setStreamHandler:[OCMArg any]]).andDo(^(NSInvocation *invocation) { + if (NSThread.isMainThread) { + [self->_mainThreadExpectation fulfill]; + } + }); +} + +- (void)testSetStreamHandler_shouldStayOnMainThreadIfCalledFromMainThread { + [_channel setStreamHandler:nil]; + [self waitForExpectations:@[ _mainThreadExpectation ] timeout:1]; +} + +- (void)testSetStreamHandler_shouldDispatchToMainThreadIfCalledFromBackgroundThread { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [self->_channel setStreamHandler:nil]; + }); + [self waitForExpectations:@[ _mainThreadExpectation ] timeout:1]; +} + +@end diff --git a/packages/camera/camera/example/ios/RunnerTests/ThreadSafeMethodChannelTests.m b/packages/camera/camera/example/ios/RunnerTests/ThreadSafeMethodChannelTests.m new file mode 100644 index 000000000000..a7cabbd92e37 --- /dev/null +++ b/packages/camera/camera/example/ios/RunnerTests/ThreadSafeMethodChannelTests.m @@ -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 camera; +@import XCTest; +#import + +@interface ThreadSafeMethodChannelTests : XCTestCase +@end + +@implementation ThreadSafeMethodChannelTests { + FLTThreadSafeMethodChannel *_channel; + XCTestExpectation *_mainThreadExpectation; +} + +- (void)setUp { + [super setUp]; + id mockMethodChannel = OCMClassMock([FlutterMethodChannel class]); + + _mainThreadExpectation = + [[XCTestExpectation alloc] initWithDescription:@"invokeMethod must be called in main thread"]; + _channel = [[FLTThreadSafeMethodChannel alloc] initWithMethodChannel:mockMethodChannel]; + + OCMStub([mockMethodChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]) + .andDo(^(NSInvocation *invocation) { + if (NSThread.isMainThread) { + [self->_mainThreadExpectation fulfill]; + } + }); +} + +- (void)testInvokeMethod_shouldStayOnMainThreadIfCalledFromMainThread { + [_channel invokeMethod:@"foo" arguments:nil]; + + [self waitForExpectations:@[ _mainThreadExpectation ] timeout:1]; +} + +- (void)testInvokeMethod__shouldDispatchToMainThreadIfCalledFromBackgroundThread { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [self->_channel invokeMethod:@"foo" arguments:nil]; + }); + [self waitForExpectations:@[ _mainThreadExpectation ] timeout:1]; +} + +@end diff --git a/packages/camera/camera/example/ios/RunnerTests/ThreadSafeTextureRegistryTests.m b/packages/camera/camera/example/ios/RunnerTests/ThreadSafeTextureRegistryTests.m new file mode 100644 index 000000000000..73a9fb8e3c3d --- /dev/null +++ b/packages/camera/camera/example/ios/RunnerTests/ThreadSafeTextureRegistryTests.m @@ -0,0 +1,74 @@ +// 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 camera; +@import XCTest; +#import + +@interface ThreadSafeTextureRegistryTests : XCTestCase +@end + +@implementation ThreadSafeTextureRegistryTests { + FLTThreadSafeTextureRegistry *_registry; + XCTestExpectation *_registerTextureExpectation; + XCTestExpectation *_unregisterTextureExpectation; + XCTestExpectation *_textureFrameAvailableExpectation; +} + +- (void)setUp { + [super setUp]; + id mockTextureRegistry = OCMProtocolMock(@protocol(FlutterTextureRegistry)); + _registry = [[FLTThreadSafeTextureRegistry alloc] initWithTextureRegistry:mockTextureRegistry]; + + _registerTextureExpectation = [[XCTestExpectation alloc] + initWithDescription:@"registerTexture must be called in main thread"]; + _unregisterTextureExpectation = [[XCTestExpectation alloc] + initWithDescription:@"unregisterTexture must be called in main thread"]; + _textureFrameAvailableExpectation = [[XCTestExpectation alloc] + initWithDescription:@"textureFrameAvailable must be called in main thread"]; + + OCMStub([mockTextureRegistry registerTexture:[OCMArg any]]).andDo(^(NSInvocation *invocation) { + if (NSThread.isMainThread) { + [self->_registerTextureExpectation fulfill]; + } + }); + + OCMStub([mockTextureRegistry unregisterTexture:0]).andDo(^(NSInvocation *invocation) { + if (NSThread.isMainThread) { + [self->_unregisterTextureExpectation fulfill]; + } + }); + + OCMStub([mockTextureRegistry textureFrameAvailable:0]).andDo(^(NSInvocation *invocation) { + if (NSThread.isMainThread) { + [self->_textureFrameAvailableExpectation fulfill]; + } + }); +} + +- (void)testShouldStayOnMainThreadIfCalledFromMainThread { + NSObject *anyTexture = OCMProtocolMock(@protocol(FlutterTexture)); + [_registry registerTextureSync:anyTexture]; + [_registry textureFrameAvailable:0]; + [_registry unregisterTexture:0]; + [self waitForExpectations:@[ + _registerTextureExpectation, _unregisterTextureExpectation, _textureFrameAvailableExpectation + ] + timeout:1]; +} + +- (void)testShouldDispatchToMainThreadIfCalledFromBackgroundThread { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSObject *anyTexture = OCMProtocolMock(@protocol(FlutterTexture)); + [self->_registry registerTextureSync:anyTexture]; + [self->_registry textureFrameAvailable:0]; + [self->_registry unregisterTexture:0]; + }); + [self waitForExpectations:@[ + _registerTextureExpectation, _unregisterTextureExpectation, _textureFrameAvailableExpectation + ] + timeout:1]; +} + +@end diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index 2c12081da807..448727963ab3 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -10,7 +10,10 @@ #import #import #import +#import "FLTThreadSafeEventChannel.h" #import "FLTThreadSafeFlutterResult.h" +#import "FLTThreadSafeMethodChannel.h" +#import "FLTThreadSafeTextureRegistry.h" @interface FLTSavePhotoDelegate : NSObject @property(readonly, nonatomic) NSString *path; @@ -305,7 +308,7 @@ @interface FLTCam : NSObject *)messen FlutterEventChannel *eventChannel = [FlutterEventChannel eventChannelWithName:@"plugins.flutter.io/camera/imageStream" binaryMessenger:messenger]; + FLTThreadSafeEventChannel *threadSafeEventChannel = + [[FLTThreadSafeEventChannel alloc] initWithEventChannel:eventChannel]; _imageStreamHandler = [[FLTImageStreamHandler alloc] init]; - [eventChannel setStreamHandler:_imageStreamHandler]; + [threadSafeEventChannel setStreamHandler:_imageStreamHandler]; _isStreamingImages = YES; } else { @@ -1285,10 +1290,10 @@ - (void)setUpCaptureSessionForAudio { @end @interface CameraPlugin () -@property(readonly, nonatomic) NSObject *registry; +@property(readonly, nonatomic) FLTThreadSafeTextureRegistry *registry; @property(readonly, nonatomic) NSObject *messenger; @property(readonly, nonatomic) FLTCam *camera; -@property(readonly, nonatomic) FlutterMethodChannel *deviceEventMethodChannel; +@property(readonly, nonatomic) FLTThreadSafeMethodChannel *deviceEventMethodChannel; @end @implementation CameraPlugin { @@ -1308,7 +1313,7 @@ - (instancetype)initWithRegistry:(NSObject *)registry messenger:(NSObject *)messenger { self = [super init]; NSAssert(self, @"super init cannot be nil"); - _registry = registry; + _registry = [[FLTThreadSafeTextureRegistry alloc] initWithTextureRegistry:registry]; _messenger = messenger; [self initDeviceEventMethodChannel]; [self startOrientationListener]; @@ -1316,9 +1321,11 @@ - (instancetype)initWithRegistry:(NSObject *)registry } - (void)initDeviceEventMethodChannel { - _deviceEventMethodChannel = + FlutterMethodChannel *methodChannel = [FlutterMethodChannel methodChannelWithName:@"flutter.io/cameraPlugin/device" binaryMessenger:_messenger]; + _deviceEventMethodChannel = + [[FLTThreadSafeMethodChannel alloc] initWithMethodChannel:methodChannel]; } - (void)startOrientationListener { @@ -1417,7 +1424,7 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call if (_camera) { [_camera close]; } - int64_t textureId = [self.registry registerTexture:cam]; + int64_t textureId = [self.registry registerTextureSync:cam]; _camera = cam; [result sendSuccessWithData:@{ @"cameraId" : @(textureId), @@ -1446,8 +1453,10 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call methodChannelWithName:[NSString stringWithFormat:@"flutter.io/cameraPlugin/camera%lu", (unsigned long)cameraId] binaryMessenger:_messenger]; - _camera.methodChannel = methodChannel; - [methodChannel + FLTThreadSafeMethodChannel *threadSafeMethodChannel = + [[FLTThreadSafeMethodChannel alloc] initWithMethodChannel:methodChannel]; + _camera.methodChannel = threadSafeMethodChannel; + [threadSafeMethodChannel invokeMethod:@"initialized" arguments:@{ @"previewWidth" : @(_camera.previewSize.width), diff --git a/packages/camera/camera/ios/Classes/FLTThreadSafeEventChannel.h b/packages/camera/camera/ios/Classes/FLTThreadSafeEventChannel.h new file mode 100644 index 000000000000..dd84a6733d64 --- /dev/null +++ b/packages/camera/camera/ios/Classes/FLTThreadSafeEventChannel.h @@ -0,0 +1,27 @@ +// 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 + +NS_ASSUME_NONNULL_BEGIN + +/** + * Wrapper for FlutterEventChannel that always sends events on the main thread + */ +@interface FLTThreadSafeEventChannel : NSObject + +/** + * Creates a FLTThreadSafeEventChannel by wrapping a FlutterEventChannel object. + * @param channel The FlutterEventChannel object to be wrapped. + */ +- (instancetype)initWithEventChannel:(FlutterEventChannel *)channel; + +/* + * Registers a handler for stream setup requests from the Flutter side on main thread. + */ +- (void)setStreamHandler:(nullable NSObject *)handler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera/ios/Classes/FLTThreadSafeEventChannel.m b/packages/camera/camera/ios/Classes/FLTThreadSafeEventChannel.m new file mode 100644 index 000000000000..418d83c37849 --- /dev/null +++ b/packages/camera/camera/ios/Classes/FLTThreadSafeEventChannel.m @@ -0,0 +1,29 @@ +// 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 "FLTThreadSafeEventChannel.h" + +@implementation FLTThreadSafeEventChannel { + FlutterEventChannel *_channel; +} + +- (instancetype)initWithEventChannel:(FlutterEventChannel *)channel { + self = [super init]; + if (self) { + _channel = channel; + } + return self; +} + +- (void)setStreamHandler:(NSObject *)handler { + if (!NSThread.isMainThread) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self->_channel setStreamHandler:handler]; + }); + } else { + [_channel setStreamHandler:handler]; + } +} + +@end diff --git a/packages/camera/camera/ios/Classes/FLTThreadSafeMethodChannel.h b/packages/camera/camera/ios/Classes/FLTThreadSafeMethodChannel.h new file mode 100644 index 000000000000..db99d0f7f66a --- /dev/null +++ b/packages/camera/camera/ios/Classes/FLTThreadSafeMethodChannel.h @@ -0,0 +1,27 @@ +// 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 + +NS_ASSUME_NONNULL_BEGIN + +/** + * Wrapper for FlutterMethodChannel that always invokes messages on the main thread + */ +@interface FLTThreadSafeMethodChannel : NSObject + +/** + * Creates a FLTThreadSafeMethodChannel by wrapping a FlutterMethodChannel object. + * @param channel The FlutterMethodChannel object to be wrapped. + */ +- (instancetype)initWithMethodChannel:(FlutterMethodChannel *)channel; + +/** + * Invokes the specified flutter method with the specified arguments on main thread. + */ +- (void)invokeMethod:(NSString *)method arguments:(nullable id)arguments; + +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera/ios/Classes/FLTThreadSafeMethodChannel.m b/packages/camera/camera/ios/Classes/FLTThreadSafeMethodChannel.m new file mode 100644 index 000000000000..e62fd5bb3af8 --- /dev/null +++ b/packages/camera/camera/ios/Classes/FLTThreadSafeMethodChannel.m @@ -0,0 +1,29 @@ +// 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 "FLTThreadSafeMethodChannel.h" + +@implementation FLTThreadSafeMethodChannel { + FlutterMethodChannel *_channel; +} + +- (instancetype)initWithMethodChannel:(FlutterMethodChannel *)channel { + self = [super init]; + if (self) { + _channel = channel; + } + return self; +} + +- (void)invokeMethod:(NSString *)method arguments:(id)arguments { + if (!NSThread.isMainThread) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self->_channel invokeMethod:method arguments:arguments]; + }); + } else { + [_channel invokeMethod:method arguments:arguments]; + } +} + +@end diff --git a/packages/camera/camera/ios/Classes/FLTThreadSafeTextureRegistry.h b/packages/camera/camera/ios/Classes/FLTThreadSafeTextureRegistry.h new file mode 100644 index 000000000000..34ee4400bcc1 --- /dev/null +++ b/packages/camera/camera/ios/Classes/FLTThreadSafeTextureRegistry.h @@ -0,0 +1,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. + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * Wrapper for FlutterTextureRegistry that always sends events on the main thread + */ +@interface FLTThreadSafeTextureRegistry : NSObject + +/** + * Creates a FLTThreadSafeTextureRegistry by wrapping an object conforming to + * FlutterTextureRegistry. + * @param registry The FlutterTextureRegistry object to be wrapped. + */ +- (instancetype)initWithTextureRegistry:(NSObject *)registry; + +/** + * Registers a `FlutterTexture` for usage in Flutter and returns an id that can be used to reference + * that texture when calling into Flutter with channels. Textures must be registered on the + * main thread. On success returns the pointer to the registered texture, else returns 0. + * + * Runs on main thread. + */ +- (int64_t)registerTextureSync:(NSObject *)texture; + +/** + * Notifies Flutter that the content of the previously registered texture has been updated. + * + * This will trigger a call to `-[FlutterTexture copyPixelBuffer]` on the raster thread. + * + * Runs on main thread. + */ +- (void)textureFrameAvailable:(int64_t)textureId; + +/** + * Unregisters a `FlutterTexture` that has previously regeistered with `registerTexture:`. Textures + * must be unregistered on the main thread. + * + * Runs on main thread. + * + * @param textureId The result that was previously returned from `registerTexture:`. + */ +- (void)unregisterTexture:(int64_t)textureId; + +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera/ios/Classes/FLTThreadSafeTextureRegistry.m b/packages/camera/camera/ios/Classes/FLTThreadSafeTextureRegistry.m new file mode 100644 index 000000000000..103c61cce6fd --- /dev/null +++ b/packages/camera/camera/ios/Classes/FLTThreadSafeTextureRegistry.m @@ -0,0 +1,58 @@ +// 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 "FLTThreadSafeTextureRegistry.h" + +@implementation FLTThreadSafeTextureRegistry { + NSObject *_registry; +} + +- (instancetype)initWithTextureRegistry:(NSObject *)registry { + self = [super init]; + if (self) { + _registry = registry; + } + return self; +} + +- (int64_t)registerTextureSync:(NSObject *)texture { + if (!NSThread.isMainThread) { + __block int64_t textureId; + // We cannot use async API (with completion block) because completion block does not work for + // separate functions (e.g. `dispose` and `create` are separately registered functions). It's + // hard to tell if the developers had made implicit assumption of the synchronous nature of the + // original API when implementing this plugin. Use dispatch_sync to keep + // FlutterTextureRegistry's sychronous API, so that we don't introduce new potential race + // conditions. We do not break priority inversion here since it's the background thread waiting + // for main thread. + dispatch_sync(dispatch_get_main_queue(), ^{ + textureId = [self->_registry registerTexture:texture]; + }); + return textureId; + } else { + return [_registry registerTexture:texture]; + } +} + +- (void)textureFrameAvailable:(int64_t)textureId { + if (!NSThread.isMainThread) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self->_registry textureFrameAvailable:textureId]; + }); + } else { + [_registry textureFrameAvailable:textureId]; + } +} + +- (void)unregisterTexture:(int64_t)textureId { + if (!NSThread.isMainThread) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self->_registry unregisterTexture:textureId]; + }); + } else { + [_registry unregisterTexture:textureId]; + } +} + +@end diff --git a/packages/camera/camera/ios/Classes/camera-umbrella.h b/packages/camera/camera/ios/Classes/camera-umbrella.h index b0fd493b24df..c55276e31147 100644 --- a/packages/camera/camera/ios/Classes/camera-umbrella.h +++ b/packages/camera/camera/ios/Classes/camera-umbrella.h @@ -4,7 +4,10 @@ #import #import +#import #import +#import +#import FOUNDATION_EXPORT double cameraVersionNumber; FOUNDATION_EXPORT const unsigned char cameraVersionString[]; From c9c234a53e563a0312f96ca7a77a8d015bfaab5f Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Mon, 20 Dec 2021 14:41:55 -0500 Subject: [PATCH 11/12] [video_player] Add 5.0 interface support (#4627) Allows the other video_player packages to use either 4.x or 5.x of `video_player_platform_interface`, since the only breaking change doesn't affect them (as it just removes test code that they no longer use). This allows clients of `video_player` to no longer have a transitive dependency on test packages, but doesn't create any version lock with any unendorsed implementations that might exist. Fixes https://github.com/flutter/flutter/issues/83562 --- .../video_player/video_player/CHANGELOG.md | 5 ++ .../video_player/video_player/CONTRIBUTING.md | 49 ------------------- .../video_player/video_player/pubspec.yaml | 4 +- .../video_player_web/CHANGELOG.md | 7 ++- .../video_player_web/pubspec.yaml | 4 +- 5 files changed, 15 insertions(+), 54 deletions(-) diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index 6f2a2b7250d0..b87ac4bbeded 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.2.9 + +* Adds compatibility with `video_player_platform_interface` 5.0, which does not + include non-dev test dependencies. + ## 2.2.8 * Changes the way the `VideoPlayerPlatform` instance is cached in the diff --git a/packages/video_player/video_player/CONTRIBUTING.md b/packages/video_player/video_player/CONTRIBUTING.md index 15c48038f6fc..387551bda2f6 100644 --- a/packages/video_player/video_player/CONTRIBUTING.md +++ b/packages/video_player/video_player/CONTRIBUTING.md @@ -31,52 +31,3 @@ pigeon, not your version or the version on master. In either case, the configuration will be obtained automatically from the `pigeons/messages.dart` file (see `configurePigeon` at the bottom of that file). - -While contributing, you may also want to set the following dependency -overrides: - -```yaml -dependency_overrides: - video_player_platform_interface: - path: - ../video_player_platform_interface - video_player_web: - path: - ../video_player_web -``` - -## Publishing plugin updates that span multiple plugin packages - -If your change affects both the interface package and the -implementation packages, then you will need to publish a version of -the plugin in between landing the interface changes and the -implementation changes, since the implementations depend on the -interface via pub. - -To do this, follow these steps: - -1. Create a PR that has all the changes, and update the -`pubspec.yaml`s to have path-based dependency overrides as described -in the "Updating pigeon-generated files" section above. - -2. Upload that PR and get it reviewed and into a state where the only -test failure is the one complaining that you can't publish a package -that has dependency overrides. - -3. Create a PR that's a subset of the one in the previous step that -only includes the interface changes, with no dependency overrides, and -submit that. - -4. Once you have had that reviewed and landed, publish the interface -parts of the plugin to pub. - -5. Now, update the original full PR to not use dependency overrides -but to instead refer to the new version of the plugin, and sync it to -master (so that the interface changes are gone from the PR). Submit -that PR. - -6. Once you have had _that_ PR reviewed and landed, publish the -implementation parts of the plugin to pub. - -You may need to publish each implementation package independently of -the main package also, depending on exactly what your change entails. diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml index f8c091786526..3ae9c0dfd301 100644 --- a/packages/video_player/video_player/pubspec.yaml +++ b/packages/video_player/video_player/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for displaying inline video with other Flutter widgets on Android, iOS, and web. repository: https://github.com/flutter/plugins/tree/master/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.2.8 +version: 2.2.9 environment: sdk: ">=2.14.0 <3.0.0" @@ -24,7 +24,7 @@ dependencies: flutter: sdk: flutter meta: ^1.3.0 - video_player_platform_interface: ^4.2.0 + video_player_platform_interface: ">=4.2.0 <6.0.0" video_player_web: ^2.0.0 html: ^0.15.0 diff --git a/packages/video_player/video_player_web/CHANGELOG.md b/packages/video_player/video_player_web/CHANGELOG.md index 4eb7c9d610b5..13cbf2e24dae 100644 --- a/packages/video_player/video_player_web/CHANGELOG.md +++ b/packages/video_player/video_player_web/CHANGELOG.md @@ -1,7 +1,12 @@ +## 2.0.5 + +* Adds compatibility with `video_player_platform_interface` 5.0, which does not + include non-dev test dependencies. + ## 2.0.4 * Adopt `video_player_platform_interface` 4.2 and opt out of `contentUri` data source. - + ## 2.0.3 * Add `implements` to pubspec. diff --git a/packages/video_player/video_player_web/pubspec.yaml b/packages/video_player/video_player_web/pubspec.yaml index ac0754b1a5d0..ec2377ea1fb1 100644 --- a/packages/video_player/video_player_web/pubspec.yaml +++ b/packages/video_player/video_player_web/pubspec.yaml @@ -2,7 +2,7 @@ name: video_player_web description: Web platform implementation of video_player. repository: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22 -version: 2.0.4 +version: 2.0.5 environment: sdk: ">=2.12.0 <3.0.0" @@ -23,7 +23,7 @@ dependencies: sdk: flutter meta: ^1.3.0 pedantic: ^1.10.0 - video_player_platform_interface: ^4.2.0 + video_player_platform_interface: ">=4.2.0 <6.0.0" dev_dependencies: flutter_test: From 19468e0950f5ec237ca9319c20055ff08ec963c3 Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Mon, 20 Dec 2021 14:44:17 -0800 Subject: [PATCH 12/12] Revert "[camera]Fix crash due to calling engine APIs from background thread" (#4630) --- packages/camera/camera/CHANGELOG.md | 6 +- .../ios/Runner.xcodeproj/project.pbxproj | 14 +--- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- .../RunnerTests/ThreadSafeEventChannelTests.m | 44 ----------- .../ThreadSafeMethodChannelTests.m | 46 ------------ .../ThreadSafeTextureRegistryTests.m | 74 ------------------- .../camera/camera/ios/Classes/CameraPlugin.m | 27 +++---- .../ios/Classes/FLTThreadSafeEventChannel.h | 27 ------- .../ios/Classes/FLTThreadSafeEventChannel.m | 29 -------- .../ios/Classes/FLTThreadSafeMethodChannel.h | 27 ------- .../ios/Classes/FLTThreadSafeMethodChannel.m | 29 -------- .../Classes/FLTThreadSafeTextureRegistry.h | 51 ------------- .../Classes/FLTThreadSafeTextureRegistry.m | 58 --------------- .../camera/ios/Classes/camera-umbrella.h | 3 - 14 files changed, 12 insertions(+), 425 deletions(-) delete mode 100644 packages/camera/camera/example/ios/RunnerTests/ThreadSafeEventChannelTests.m delete mode 100644 packages/camera/camera/example/ios/RunnerTests/ThreadSafeMethodChannelTests.m delete mode 100644 packages/camera/camera/example/ios/RunnerTests/ThreadSafeTextureRegistryTests.m delete mode 100644 packages/camera/camera/ios/Classes/FLTThreadSafeEventChannel.h delete mode 100644 packages/camera/camera/ios/Classes/FLTThreadSafeEventChannel.m delete mode 100644 packages/camera/camera/ios/Classes/FLTThreadSafeMethodChannel.h delete mode 100644 packages/camera/camera/ios/Classes/FLTThreadSafeMethodChannel.m delete mode 100644 packages/camera/camera/ios/Classes/FLTThreadSafeTextureRegistry.h delete mode 100644 packages/camera/camera/ios/Classes/FLTThreadSafeTextureRegistry.m diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index f5b783d5114b..1a6eceb957b1 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,8 +1,4 @@ -## NEXT - -* Fixed a crash in iOS when using image stream due to threading issue. - -## 0.9.4+5 +## 0.9.4+5 * Fixes bug where calling a method after the camera was closed resulted in a Java `IllegalStateException` exception. * Fixes integration tests. diff --git a/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj index ad0ece9adbd2..feb789f2ecba 100644 --- a/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj @@ -20,9 +20,6 @@ 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 */; }; - E0C6E2002770F01A00EA6AA3 /* ThreadSafeMethodChannelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C6E1FD2770F01A00EA6AA3 /* ThreadSafeMethodChannelTests.m */; }; - E0C6E2012770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C6E1FE2770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m */; }; - E0C6E2022770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */; }; E487C86026D686A10034AC92 /* CameraPreviewPauseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E487C85F26D686A10034AC92 /* CameraPreviewPauseTests.m */; }; F6EE622F2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m in Sources */ = {isa = PBXBuildFile; fileRef = F6EE622E2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m */; }; /* End PBXBuildFile section */ @@ -77,9 +74,6 @@ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9C5CC6CAD53AD388B2694F3A /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; A24F9E418BA48BCC7409B117 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; - E0C6E1FD2770F01A00EA6AA3 /* ThreadSafeMethodChannelTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadSafeMethodChannelTests.m; sourceTree = ""; }; - E0C6E1FE2770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadSafeTextureRegistryTests.m; sourceTree = ""; }; - E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadSafeEventChannelTests.m; sourceTree = ""; }; E487C85F26D686A10034AC92 /* CameraPreviewPauseTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraPreviewPauseTests.m; sourceTree = ""; }; F63F9EED27143B19002479BF /* MockFLTThreadSafeFlutterResult.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockFLTThreadSafeFlutterResult.h; sourceTree = ""; }; F6EE622E2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockFLTThreadSafeFlutterResult.m; sourceTree = ""; }; @@ -113,9 +107,6 @@ 03BB766C2665316900CE5A93 /* Info.plist */, 033B94BD269C40A200B4DF97 /* CameraMethodChannelTests.m */, 03F6F8B126CBB4670024B8D3 /* ThreadSafeFlutterResultTests.m */, - E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */, - E0C6E1FD2770F01A00EA6AA3 /* ThreadSafeMethodChannelTests.m */, - E0C6E1FE2770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m */, E487C85F26D686A10034AC92 /* CameraPreviewPauseTests.m */, F6EE622E2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m */, F63F9EED27143B19002479BF /* MockFLTThreadSafeFlutterResult.h */, @@ -248,7 +239,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1100; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 03BB76672665316900CE5A93 = { @@ -387,9 +378,6 @@ E487C86026D686A10034AC92 /* CameraPreviewPauseTests.m in Sources */, F6EE622F2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m in Sources */, 334733EA2668111C00DCC49E /* CameraOrientationTests.m in Sources */, - E0C6E2022770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m in Sources */, - E0C6E2012770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m in Sources */, - E0C6E2002770F01A00EA6AA3 /* ThreadSafeMethodChannelTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/packages/camera/camera/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/camera/camera/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index f4b3c1099001..1447e08231be 100644 --- a/packages/camera/camera/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/camera/camera/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ - -@interface ThreadSafeEventChannelTests : XCTestCase -@end - -@implementation ThreadSafeEventChannelTests { - FLTThreadSafeEventChannel *_channel; - XCTestExpectation *_mainThreadExpectation; -} - -- (void)setUp { - [super setUp]; - id mockEventChannel = OCMClassMock([FlutterEventChannel class]); - - _mainThreadExpectation = - [[XCTestExpectation alloc] initWithDescription:@"invokeMethod must be called in main thread"]; - _channel = [[FLTThreadSafeEventChannel alloc] initWithEventChannel:mockEventChannel]; - - OCMStub([mockEventChannel setStreamHandler:[OCMArg any]]).andDo(^(NSInvocation *invocation) { - if (NSThread.isMainThread) { - [self->_mainThreadExpectation fulfill]; - } - }); -} - -- (void)testSetStreamHandler_shouldStayOnMainThreadIfCalledFromMainThread { - [_channel setStreamHandler:nil]; - [self waitForExpectations:@[ _mainThreadExpectation ] timeout:1]; -} - -- (void)testSetStreamHandler_shouldDispatchToMainThreadIfCalledFromBackgroundThread { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self->_channel setStreamHandler:nil]; - }); - [self waitForExpectations:@[ _mainThreadExpectation ] timeout:1]; -} - -@end diff --git a/packages/camera/camera/example/ios/RunnerTests/ThreadSafeMethodChannelTests.m b/packages/camera/camera/example/ios/RunnerTests/ThreadSafeMethodChannelTests.m deleted file mode 100644 index a7cabbd92e37..000000000000 --- a/packages/camera/camera/example/ios/RunnerTests/ThreadSafeMethodChannelTests.m +++ /dev/null @@ -1,46 +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 camera; -@import XCTest; -#import - -@interface ThreadSafeMethodChannelTests : XCTestCase -@end - -@implementation ThreadSafeMethodChannelTests { - FLTThreadSafeMethodChannel *_channel; - XCTestExpectation *_mainThreadExpectation; -} - -- (void)setUp { - [super setUp]; - id mockMethodChannel = OCMClassMock([FlutterMethodChannel class]); - - _mainThreadExpectation = - [[XCTestExpectation alloc] initWithDescription:@"invokeMethod must be called in main thread"]; - _channel = [[FLTThreadSafeMethodChannel alloc] initWithMethodChannel:mockMethodChannel]; - - OCMStub([mockMethodChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]) - .andDo(^(NSInvocation *invocation) { - if (NSThread.isMainThread) { - [self->_mainThreadExpectation fulfill]; - } - }); -} - -- (void)testInvokeMethod_shouldStayOnMainThreadIfCalledFromMainThread { - [_channel invokeMethod:@"foo" arguments:nil]; - - [self waitForExpectations:@[ _mainThreadExpectation ] timeout:1]; -} - -- (void)testInvokeMethod__shouldDispatchToMainThreadIfCalledFromBackgroundThread { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self->_channel invokeMethod:@"foo" arguments:nil]; - }); - [self waitForExpectations:@[ _mainThreadExpectation ] timeout:1]; -} - -@end diff --git a/packages/camera/camera/example/ios/RunnerTests/ThreadSafeTextureRegistryTests.m b/packages/camera/camera/example/ios/RunnerTests/ThreadSafeTextureRegistryTests.m deleted file mode 100644 index 73a9fb8e3c3d..000000000000 --- a/packages/camera/camera/example/ios/RunnerTests/ThreadSafeTextureRegistryTests.m +++ /dev/null @@ -1,74 +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 camera; -@import XCTest; -#import - -@interface ThreadSafeTextureRegistryTests : XCTestCase -@end - -@implementation ThreadSafeTextureRegistryTests { - FLTThreadSafeTextureRegistry *_registry; - XCTestExpectation *_registerTextureExpectation; - XCTestExpectation *_unregisterTextureExpectation; - XCTestExpectation *_textureFrameAvailableExpectation; -} - -- (void)setUp { - [super setUp]; - id mockTextureRegistry = OCMProtocolMock(@protocol(FlutterTextureRegistry)); - _registry = [[FLTThreadSafeTextureRegistry alloc] initWithTextureRegistry:mockTextureRegistry]; - - _registerTextureExpectation = [[XCTestExpectation alloc] - initWithDescription:@"registerTexture must be called in main thread"]; - _unregisterTextureExpectation = [[XCTestExpectation alloc] - initWithDescription:@"unregisterTexture must be called in main thread"]; - _textureFrameAvailableExpectation = [[XCTestExpectation alloc] - initWithDescription:@"textureFrameAvailable must be called in main thread"]; - - OCMStub([mockTextureRegistry registerTexture:[OCMArg any]]).andDo(^(NSInvocation *invocation) { - if (NSThread.isMainThread) { - [self->_registerTextureExpectation fulfill]; - } - }); - - OCMStub([mockTextureRegistry unregisterTexture:0]).andDo(^(NSInvocation *invocation) { - if (NSThread.isMainThread) { - [self->_unregisterTextureExpectation fulfill]; - } - }); - - OCMStub([mockTextureRegistry textureFrameAvailable:0]).andDo(^(NSInvocation *invocation) { - if (NSThread.isMainThread) { - [self->_textureFrameAvailableExpectation fulfill]; - } - }); -} - -- (void)testShouldStayOnMainThreadIfCalledFromMainThread { - NSObject *anyTexture = OCMProtocolMock(@protocol(FlutterTexture)); - [_registry registerTextureSync:anyTexture]; - [_registry textureFrameAvailable:0]; - [_registry unregisterTexture:0]; - [self waitForExpectations:@[ - _registerTextureExpectation, _unregisterTextureExpectation, _textureFrameAvailableExpectation - ] - timeout:1]; -} - -- (void)testShouldDispatchToMainThreadIfCalledFromBackgroundThread { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSObject *anyTexture = OCMProtocolMock(@protocol(FlutterTexture)); - [self->_registry registerTextureSync:anyTexture]; - [self->_registry textureFrameAvailable:0]; - [self->_registry unregisterTexture:0]; - }); - [self waitForExpectations:@[ - _registerTextureExpectation, _unregisterTextureExpectation, _textureFrameAvailableExpectation - ] - timeout:1]; -} - -@end diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index 448727963ab3..2c12081da807 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -10,10 +10,7 @@ #import #import #import -#import "FLTThreadSafeEventChannel.h" #import "FLTThreadSafeFlutterResult.h" -#import "FLTThreadSafeMethodChannel.h" -#import "FLTThreadSafeTextureRegistry.h" @interface FLTSavePhotoDelegate : NSObject @property(readonly, nonatomic) NSString *path; @@ -308,7 +305,7 @@ @interface FLTCam : NSObject *)messen FlutterEventChannel *eventChannel = [FlutterEventChannel eventChannelWithName:@"plugins.flutter.io/camera/imageStream" binaryMessenger:messenger]; - FLTThreadSafeEventChannel *threadSafeEventChannel = - [[FLTThreadSafeEventChannel alloc] initWithEventChannel:eventChannel]; _imageStreamHandler = [[FLTImageStreamHandler alloc] init]; - [threadSafeEventChannel setStreamHandler:_imageStreamHandler]; + [eventChannel setStreamHandler:_imageStreamHandler]; _isStreamingImages = YES; } else { @@ -1290,10 +1285,10 @@ - (void)setUpCaptureSessionForAudio { @end @interface CameraPlugin () -@property(readonly, nonatomic) FLTThreadSafeTextureRegistry *registry; +@property(readonly, nonatomic) NSObject *registry; @property(readonly, nonatomic) NSObject *messenger; @property(readonly, nonatomic) FLTCam *camera; -@property(readonly, nonatomic) FLTThreadSafeMethodChannel *deviceEventMethodChannel; +@property(readonly, nonatomic) FlutterMethodChannel *deviceEventMethodChannel; @end @implementation CameraPlugin { @@ -1313,7 +1308,7 @@ - (instancetype)initWithRegistry:(NSObject *)registry messenger:(NSObject *)messenger { self = [super init]; NSAssert(self, @"super init cannot be nil"); - _registry = [[FLTThreadSafeTextureRegistry alloc] initWithTextureRegistry:registry]; + _registry = registry; _messenger = messenger; [self initDeviceEventMethodChannel]; [self startOrientationListener]; @@ -1321,11 +1316,9 @@ - (instancetype)initWithRegistry:(NSObject *)registry } - (void)initDeviceEventMethodChannel { - FlutterMethodChannel *methodChannel = + _deviceEventMethodChannel = [FlutterMethodChannel methodChannelWithName:@"flutter.io/cameraPlugin/device" binaryMessenger:_messenger]; - _deviceEventMethodChannel = - [[FLTThreadSafeMethodChannel alloc] initWithMethodChannel:methodChannel]; } - (void)startOrientationListener { @@ -1424,7 +1417,7 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call if (_camera) { [_camera close]; } - int64_t textureId = [self.registry registerTextureSync:cam]; + int64_t textureId = [self.registry registerTexture:cam]; _camera = cam; [result sendSuccessWithData:@{ @"cameraId" : @(textureId), @@ -1453,10 +1446,8 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call methodChannelWithName:[NSString stringWithFormat:@"flutter.io/cameraPlugin/camera%lu", (unsigned long)cameraId] binaryMessenger:_messenger]; - FLTThreadSafeMethodChannel *threadSafeMethodChannel = - [[FLTThreadSafeMethodChannel alloc] initWithMethodChannel:methodChannel]; - _camera.methodChannel = threadSafeMethodChannel; - [threadSafeMethodChannel + _camera.methodChannel = methodChannel; + [methodChannel invokeMethod:@"initialized" arguments:@{ @"previewWidth" : @(_camera.previewSize.width), diff --git a/packages/camera/camera/ios/Classes/FLTThreadSafeEventChannel.h b/packages/camera/camera/ios/Classes/FLTThreadSafeEventChannel.h deleted file mode 100644 index dd84a6733d64..000000000000 --- a/packages/camera/camera/ios/Classes/FLTThreadSafeEventChannel.h +++ /dev/null @@ -1,27 +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 - -NS_ASSUME_NONNULL_BEGIN - -/** - * Wrapper for FlutterEventChannel that always sends events on the main thread - */ -@interface FLTThreadSafeEventChannel : NSObject - -/** - * Creates a FLTThreadSafeEventChannel by wrapping a FlutterEventChannel object. - * @param channel The FlutterEventChannel object to be wrapped. - */ -- (instancetype)initWithEventChannel:(FlutterEventChannel *)channel; - -/* - * Registers a handler for stream setup requests from the Flutter side on main thread. - */ -- (void)setStreamHandler:(nullable NSObject *)handler; - -@end - -NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera/ios/Classes/FLTThreadSafeEventChannel.m b/packages/camera/camera/ios/Classes/FLTThreadSafeEventChannel.m deleted file mode 100644 index 418d83c37849..000000000000 --- a/packages/camera/camera/ios/Classes/FLTThreadSafeEventChannel.m +++ /dev/null @@ -1,29 +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 "FLTThreadSafeEventChannel.h" - -@implementation FLTThreadSafeEventChannel { - FlutterEventChannel *_channel; -} - -- (instancetype)initWithEventChannel:(FlutterEventChannel *)channel { - self = [super init]; - if (self) { - _channel = channel; - } - return self; -} - -- (void)setStreamHandler:(NSObject *)handler { - if (!NSThread.isMainThread) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self->_channel setStreamHandler:handler]; - }); - } else { - [_channel setStreamHandler:handler]; - } -} - -@end diff --git a/packages/camera/camera/ios/Classes/FLTThreadSafeMethodChannel.h b/packages/camera/camera/ios/Classes/FLTThreadSafeMethodChannel.h deleted file mode 100644 index db99d0f7f66a..000000000000 --- a/packages/camera/camera/ios/Classes/FLTThreadSafeMethodChannel.h +++ /dev/null @@ -1,27 +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 - -NS_ASSUME_NONNULL_BEGIN - -/** - * Wrapper for FlutterMethodChannel that always invokes messages on the main thread - */ -@interface FLTThreadSafeMethodChannel : NSObject - -/** - * Creates a FLTThreadSafeMethodChannel by wrapping a FlutterMethodChannel object. - * @param channel The FlutterMethodChannel object to be wrapped. - */ -- (instancetype)initWithMethodChannel:(FlutterMethodChannel *)channel; - -/** - * Invokes the specified flutter method with the specified arguments on main thread. - */ -- (void)invokeMethod:(NSString *)method arguments:(nullable id)arguments; - -@end - -NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera/ios/Classes/FLTThreadSafeMethodChannel.m b/packages/camera/camera/ios/Classes/FLTThreadSafeMethodChannel.m deleted file mode 100644 index e62fd5bb3af8..000000000000 --- a/packages/camera/camera/ios/Classes/FLTThreadSafeMethodChannel.m +++ /dev/null @@ -1,29 +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 "FLTThreadSafeMethodChannel.h" - -@implementation FLTThreadSafeMethodChannel { - FlutterMethodChannel *_channel; -} - -- (instancetype)initWithMethodChannel:(FlutterMethodChannel *)channel { - self = [super init]; - if (self) { - _channel = channel; - } - return self; -} - -- (void)invokeMethod:(NSString *)method arguments:(id)arguments { - if (!NSThread.isMainThread) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self->_channel invokeMethod:method arguments:arguments]; - }); - } else { - [_channel invokeMethod:method arguments:arguments]; - } -} - -@end diff --git a/packages/camera/camera/ios/Classes/FLTThreadSafeTextureRegistry.h b/packages/camera/camera/ios/Classes/FLTThreadSafeTextureRegistry.h deleted file mode 100644 index 34ee4400bcc1..000000000000 --- a/packages/camera/camera/ios/Classes/FLTThreadSafeTextureRegistry.h +++ /dev/null @@ -1,51 +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 - -NS_ASSUME_NONNULL_BEGIN - -/** - * Wrapper for FlutterTextureRegistry that always sends events on the main thread - */ -@interface FLTThreadSafeTextureRegistry : NSObject - -/** - * Creates a FLTThreadSafeTextureRegistry by wrapping an object conforming to - * FlutterTextureRegistry. - * @param registry The FlutterTextureRegistry object to be wrapped. - */ -- (instancetype)initWithTextureRegistry:(NSObject *)registry; - -/** - * Registers a `FlutterTexture` for usage in Flutter and returns an id that can be used to reference - * that texture when calling into Flutter with channels. Textures must be registered on the - * main thread. On success returns the pointer to the registered texture, else returns 0. - * - * Runs on main thread. - */ -- (int64_t)registerTextureSync:(NSObject *)texture; - -/** - * Notifies Flutter that the content of the previously registered texture has been updated. - * - * This will trigger a call to `-[FlutterTexture copyPixelBuffer]` on the raster thread. - * - * Runs on main thread. - */ -- (void)textureFrameAvailable:(int64_t)textureId; - -/** - * Unregisters a `FlutterTexture` that has previously regeistered with `registerTexture:`. Textures - * must be unregistered on the main thread. - * - * Runs on main thread. - * - * @param textureId The result that was previously returned from `registerTexture:`. - */ -- (void)unregisterTexture:(int64_t)textureId; - -@end - -NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera/ios/Classes/FLTThreadSafeTextureRegistry.m b/packages/camera/camera/ios/Classes/FLTThreadSafeTextureRegistry.m deleted file mode 100644 index 103c61cce6fd..000000000000 --- a/packages/camera/camera/ios/Classes/FLTThreadSafeTextureRegistry.m +++ /dev/null @@ -1,58 +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 "FLTThreadSafeTextureRegistry.h" - -@implementation FLTThreadSafeTextureRegistry { - NSObject *_registry; -} - -- (instancetype)initWithTextureRegistry:(NSObject *)registry { - self = [super init]; - if (self) { - _registry = registry; - } - return self; -} - -- (int64_t)registerTextureSync:(NSObject *)texture { - if (!NSThread.isMainThread) { - __block int64_t textureId; - // We cannot use async API (with completion block) because completion block does not work for - // separate functions (e.g. `dispose` and `create` are separately registered functions). It's - // hard to tell if the developers had made implicit assumption of the synchronous nature of the - // original API when implementing this plugin. Use dispatch_sync to keep - // FlutterTextureRegistry's sychronous API, so that we don't introduce new potential race - // conditions. We do not break priority inversion here since it's the background thread waiting - // for main thread. - dispatch_sync(dispatch_get_main_queue(), ^{ - textureId = [self->_registry registerTexture:texture]; - }); - return textureId; - } else { - return [_registry registerTexture:texture]; - } -} - -- (void)textureFrameAvailable:(int64_t)textureId { - if (!NSThread.isMainThread) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self->_registry textureFrameAvailable:textureId]; - }); - } else { - [_registry textureFrameAvailable:textureId]; - } -} - -- (void)unregisterTexture:(int64_t)textureId { - if (!NSThread.isMainThread) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self->_registry unregisterTexture:textureId]; - }); - } else { - [_registry unregisterTexture:textureId]; - } -} - -@end diff --git a/packages/camera/camera/ios/Classes/camera-umbrella.h b/packages/camera/camera/ios/Classes/camera-umbrella.h index c55276e31147..b0fd493b24df 100644 --- a/packages/camera/camera/ios/Classes/camera-umbrella.h +++ b/packages/camera/camera/ios/Classes/camera-umbrella.h @@ -4,10 +4,7 @@ #import #import -#import #import -#import -#import FOUNDATION_EXPORT double cameraVersionNumber; FOUNDATION_EXPORT const unsigned char cameraVersionString[];