diff --git a/BUILD.gn b/BUILD.gn index 7a0e6db7ac758..fe1135605fc13 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -121,6 +121,11 @@ group("flutter") { public_deps += [ "//flutter/testing/scenario_app" ] } + if (is_android && flutter_runtime_mode == "profile" && + current_cpu == "arm64") { + public_deps += [ "//flutter/testing/android_background_image" ] + } + # Compile all unittests targets if enabled. if (enable_unittests) { public_deps += [ diff --git a/ci/firebase_testlab.py b/ci/firebase_testlab.py index 2b5f440a436cb..7aef50c37918b 100755 --- a/ci/firebase_testlab.py +++ b/ci/firebase_testlab.py @@ -19,28 +19,28 @@ def RunFirebaseTest(apk, results_dir): - try: - # game-loop tests are meant for OpenGL apps. - # This type of test will give the application a handle to a file, and - # we'll write the timeline JSON to that file. - # See https://firebase.google.com/docs/test-lab/android/game-loop - # Pixel 4. As of this commit, this is a highly available device in FTL. - subprocess.check_output([ - 'gcloud', - '--project', 'flutter-infra', - 'firebase', 'test', 'android', 'run', - '--type', 'game-loop', - '--app', apk, - '--timeout', '2m', - '--results-bucket', bucket, - '--results-dir', results_dir, - '--device', 'model=flame,version=29', - ]) - except subprocess.CalledProcessError as ex: - print(ex.output) - # Recipe will retry return codes from firebase that indicate an infra - # failure. - sys.exit(ex.returncode) + # game-loop tests are meant for OpenGL apps. + # This type of test will give the application a handle to a file, and + # we'll write the timeline JSON to that file. + # See https://firebase.google.com/docs/test-lab/android/game-loop + # Pixel 4. As of this commit, this is a highly available device in FTL. + process = subprocess.Popen( + [ + 'gcloud', + '--project', 'flutter-infra', + 'firebase', 'test', 'android', 'run', + '--type', 'game-loop', + '--app', apk, + '--timeout', '2m', + '--results-bucket', bucket, + '--results-dir', results_dir, + '--device', 'model=flame,version=29', + ], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True, + ) + return process def CheckLogcat(results_dir): @@ -87,13 +87,25 @@ def main(): git_revision = subprocess.check_output( ['git', 'rev-parse', 'HEAD'], cwd=script_dir).strip() + results = [] for apk in apks: results_dir = '%s/%s/%s' % (os.path.basename(apk), git_revision, args.build_id) - - RunFirebaseTest(apk, results_dir) + process = RunFirebaseTest(apk, results_dir) + results.append((results_dir, process)) + + for results_dir, process in results: + for line in iter(process.stdout.readline, ""): + print(line.strip()) + return_code = process.wait() + if return_code != 0: + print('Firebase test failed ' + returncode) + sys.exit(process.returncode) + + print('Checking logcat for %s' % results_dir) CheckLogcat(results_dir) # scenario_app produces a timeline, but the android image test does not. if 'scenario' in apk: + print('Checking timeline for %s' % results_dir) CheckTimeline(results_dir) return 0 diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index e9a22a5a7cb26..7e52124912e24 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -725,6 +725,7 @@ FILE: ../../../flutter/shell/common/shell_unittests.cc FILE: ../../../flutter/shell/common/skia_event_tracer_impl.cc FILE: ../../../flutter/shell/common/skia_event_tracer_impl.h FILE: ../../../flutter/shell/common/skp_shader_warmup_unittests.cc +FILE: ../../../flutter/shell/common/snapshot_surface_producer.h FILE: ../../../flutter/shell/common/switches.cc FILE: ../../../flutter/shell/common/switches.h FILE: ../../../flutter/shell/common/thread_host.cc @@ -932,6 +933,8 @@ FILE: ../../../flutter/shell/platform/android/surface/android_surface.cc FILE: ../../../flutter/shell/platform/android/surface/android_surface.h FILE: ../../../flutter/shell/platform/android/surface/android_surface_mock.cc FILE: ../../../flutter/shell/platform/android/surface/android_surface_mock.h +FILE: ../../../flutter/shell/platform/android/surface/snapshot_surface_producer.cc +FILE: ../../../flutter/shell/platform/android/surface/snapshot_surface_producer.h FILE: ../../../flutter/shell/platform/android/vsync_waiter_android.cc FILE: ../../../flutter/shell/platform/android/vsync_waiter_android.h FILE: ../../../flutter/shell/platform/common/accessibility_bridge.cc diff --git a/shell/common/BUILD.gn b/shell/common/BUILD.gn index d7c4f09eabf49..dcb28e800b6d7 100644 --- a/shell/common/BUILD.gn +++ b/shell/common/BUILD.gn @@ -89,6 +89,7 @@ source_set("common") { "shell_io_manager.h", "skia_event_tracer_impl.cc", "skia_event_tracer_impl.h", + "snapshot_surface_producer.h", "switches.cc", "switches.h", "thread_host.cc", diff --git a/shell/common/platform_view.cc b/shell/common/platform_view.cc index b2c552599e547..d48442604ff5c 100644 --- a/shell/common/platform_view.cc +++ b/shell/common/platform_view.cc @@ -67,7 +67,6 @@ void PlatformView::SetViewportMetrics(const ViewportMetrics& metrics) { void PlatformView::NotifyCreated() { std::unique_ptr surface; - // Threading: We want to use the platform view on the non-platform thread. // Using the weak pointer is illegal. But, we are going to introduce a latch // so that the platform view is not collected till the surface is obtained. @@ -182,4 +181,9 @@ void PlatformView::UpdateAssetResolverByType( delegate_.UpdateAssetResolverByType(std::move(updated_asset_resolver), type); } +std::unique_ptr +PlatformView::CreateSnapshotSurfaceProducer() { + return nullptr; +} + } // namespace flutter diff --git a/shell/common/platform_view.h b/shell/common/platform_view.h index 186237b02ad33..1cbf73ce4abea 100644 --- a/shell/common/platform_view.h +++ b/shell/common/platform_view.h @@ -62,7 +62,7 @@ class PlatformView { /// Metal, Vulkan) specific. This is usually a sign to the /// rasterizer to set up and begin rendering to that surface. /// - /// @param[in] surface The surface + /// @param[in] surface The surface /// virtual void OnPlatformViewCreated(std::unique_ptr surface) = 0; @@ -796,6 +796,22 @@ class PlatformView { std::unique_ptr updated_asset_resolver, AssetResolver::AssetResolverType type); + //-------------------------------------------------------------------------- + /// @brief Creates an object that produces surfaces suitable for raster + /// snapshotting. The rasterizer will request this surface if no + /// on screen surface is currently available when an application + /// requests a snapshot, e.g. if `Scene.toImage` or + /// `Picture.toImage` are called while the application is in the + /// background. + /// + /// Not all backends support this kind of surface usage, and the + /// default implementation returns nullptr. Platforms should + /// override this if they can support GPU operations in the + /// background and support GPU resource context usage. + /// + virtual std::unique_ptr + CreateSnapshotSurfaceProducer(); + protected: PlatformView::Delegate& delegate_; const TaskRunners task_runners_; @@ -804,8 +820,7 @@ class PlatformView { SkISize size_; fml::WeakPtrFactory weak_factory_; - // Unlike all other methods on the platform view, this is called on the - // raster task runner. + // This is the only method called on the raster task runner. virtual std::unique_ptr CreateRenderingSurface(); private: diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc index 8a149bebd5b2f..89f47ace61dc1 100644 --- a/shell/common/rasterizer.cc +++ b/shell/common/rasterizer.cc @@ -258,11 +258,22 @@ sk_sp Rasterizer::DoMakeRasterSnapshot( sk_sp result; SkImageInfo image_info = SkImageInfo::MakeN32Premul( size.width(), size.height(), SkColorSpace::MakeSRGB()); - if (surface_ == nullptr || surface_->GetContext() == nullptr) { + + std::unique_ptr pbuffer_surface; + Surface* snapshot_surface = nullptr; + if (surface_ && surface_->GetContext()) { + snapshot_surface = surface_.get(); + } else if (snapshot_surface_producer_) { + pbuffer_surface = snapshot_surface_producer_->CreateSnapshotSurface(); + if (pbuffer_surface && pbuffer_surface->GetContext()) + snapshot_surface = pbuffer_surface.get(); + } + + if (!snapshot_surface) { // Raster surface is fine if there is no on screen surface. This might // happen in case of software rendering. - sk_sp surface = SkSurface::MakeRaster(image_info); - result = DrawSnapshot(surface, draw_callback); + sk_sp sk_surface = SkSurface::MakeRaster(image_info); + result = DrawSnapshot(sk_surface, draw_callback); } else { delegate_.GetIsGpuDisabledSyncSwitch()->Execute( fml::SyncSwitch::Handlers() @@ -271,12 +282,14 @@ sk_sp Rasterizer::DoMakeRasterSnapshot( result = DrawSnapshot(surface, draw_callback); }) .SetIfFalse([&] { - auto context_switch = surface_->MakeRenderContextCurrent(); + FML_DCHECK(snapshot_surface); + auto context_switch = + snapshot_surface->MakeRenderContextCurrent(); if (!context_switch->GetResult()) { return; } - GrRecordingContext* context = surface_->GetContext(); + GrRecordingContext* context = snapshot_surface->GetContext(); auto max_size = context->maxRenderTargetSize(); double scale_factor = std::min( 1.0, static_cast(max_size) / @@ -294,19 +307,19 @@ sk_sp Rasterizer::DoMakeRasterSnapshot( // When there is an on screen surface, we need a render target // SkSurface because we want to access texture backed images. - sk_sp surface = + sk_sp sk_surface = SkSurface::MakeRenderTarget(context, // context SkBudgeted::kNo, // budgeted image_info // image info ); - if (!surface) { + if (!sk_surface) { FML_LOG(ERROR) << "DoMakeRasterSnapshot can not create GPU render target"; return; } - surface->getCanvas()->scale(scale_factor, scale_factor); - result = DrawSnapshot(surface, draw_callback); + sk_surface->getCanvas()->scale(scale_factor, scale_factor); + result = DrawSnapshot(sk_surface, draw_callback); })); } @@ -700,6 +713,11 @@ void Rasterizer::SetExternalViewEmbedder( external_view_embedder_ = view_embedder; } +void Rasterizer::SetSnapshotSurfaceProducer( + std::unique_ptr producer) { + snapshot_surface_producer_ = std::move(producer); +} + void Rasterizer::FireNextFrameCallbackIfPresent() { if (!next_frame_callback_) { return; diff --git a/shell/common/rasterizer.h b/shell/common/rasterizer.h index aa7334179e970..311428802d386 100644 --- a/shell/common/rasterizer.h +++ b/shell/common/rasterizer.h @@ -24,6 +24,7 @@ #include "flutter/fml/time/time_point.h" #include "flutter/lib/ui/snapshot_delegate.h" #include "flutter/shell/common/pipeline.h" +#include "flutter/shell/common/snapshot_surface_producer.h" namespace flutter { @@ -98,7 +99,7 @@ class Rasterizer final : public SnapshotDelegate { /// currently only created by the shell (which also sets itself up /// as the rasterizer delegate). /// - /// @param[in] delegate The rasterizer delegate. + /// @param[in] delegate The rasterizer delegate. /// Rasterizer(Delegate& delegate); @@ -349,6 +350,17 @@ class Rasterizer final : public SnapshotDelegate { void SetExternalViewEmbedder( const std::shared_ptr& view_embedder); + //---------------------------------------------------------------------------- + /// @brief Set the snapshot surface producer. This is done on shell + /// initialization. This is non-null on platforms that support taking + /// GPU accelerated raster snapshots in the background. + /// + /// @param[in] producer A surface producer for raster snapshotting when the + /// onscreen surface is not available. + /// + void SetSnapshotSurfaceProducer( + std::unique_ptr producer); + //---------------------------------------------------------------------------- /// @brief Returns a pointer to the compositor context used by this /// rasterizer. This pointer will never be `nullptr`. @@ -434,6 +446,7 @@ class Rasterizer final : public SnapshotDelegate { private: Delegate& delegate_; std::unique_ptr surface_; + std::unique_ptr snapshot_surface_producer_; std::unique_ptr compositor_context_; // This is the last successfully rasterized layer tree. std::unique_ptr last_layer_tree_; diff --git a/shell/common/rasterizer_unittests.cc b/shell/common/rasterizer_unittests.cc index 0ecc3d576253a..cb54fef27a097 100644 --- a/shell/common/rasterizer_unittests.cc +++ b/shell/common/rasterizer_unittests.cc @@ -30,6 +30,7 @@ class MockDelegate : public Rasterizer::Delegate { MOCK_CONST_METHOD0(GetTaskRunners, const TaskRunners&()); MOCK_CONST_METHOD0(GetIsGpuDisabledSyncSwitch, std::shared_ptr()); + MOCK_METHOD0(CreateSnapshotSurface, std::unique_ptr()); }; class MockSurface : public Surface { diff --git a/shell/common/shell.cc b/shell/common/shell.cc index beea55e86736e..01b85075ad344 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -634,6 +634,8 @@ bool Shell::Setup(std::unique_ptr platform_view, // Set the external view embedder for the rasterizer. auto view_embedder = platform_view_->CreateExternalViewEmbedder(); rasterizer_->SetExternalViewEmbedder(view_embedder); + rasterizer_->SetSnapshotSurfaceProducer( + platform_view_->CreateSnapshotSurfaceProducer()); // The weak ptr must be generated in the platform thread which owns the unique // ptr. diff --git a/shell/common/snapshot_surface_producer.h b/shell/common/snapshot_surface_producer.h new file mode 100644 index 0000000000000..ae505109beda8 --- /dev/null +++ b/shell/common/snapshot_surface_producer.h @@ -0,0 +1,22 @@ +// 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. + +#ifndef SHELL_COMMON_SNAPSHOT_SURFACE_PRODUCER_H_ +#define SHELL_COMMON_SNAPSHOT_SURFACE_PRODUCER_H_ + +#include + +#include "flutter/flow/surface.h" + +namespace flutter { + +class SnapshotSurfaceProducer { + public: + virtual ~SnapshotSurfaceProducer() = default; + + virtual std::unique_ptr CreateSnapshotSurface() = 0; +}; + +} // namespace flutter +#endif // SHELL_COMMON_SNAPSHOT_SURFACE_PRODUCER_H_ diff --git a/shell/platform/android/android_context_gl.cc b/shell/platform/android/android_context_gl.cc index 04e8b43d902be..ee056925d05a4 100644 --- a/shell/platform/android/android_context_gl.cc +++ b/shell/platform/android/android_context_gl.cc @@ -227,6 +227,16 @@ std::unique_ptr AndroidContextGL::CreateOffscreenSurface() resource_context_); } +std::unique_ptr AndroidContextGL::CreatePbufferSurface() + const { + EGLDisplay display = environment_->Display(); + + const EGLint attribs[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE}; + + EGLSurface surface = eglCreatePbufferSurface(display, config_, attribs); + return std::make_unique(surface, display, context_); +} + fml::RefPtr AndroidContextGL::Environment() const { return environment_; } diff --git a/shell/platform/android/android_context_gl.h b/shell/platform/android/android_context_gl.h index b888409ac22b0..b9e34ecf6f854 100644 --- a/shell/platform/android/android_context_gl.h +++ b/shell/platform/android/android_context_gl.h @@ -93,6 +93,14 @@ class AndroidContextGL : public AndroidContext { /// std::unique_ptr CreateOffscreenSurface() const; + //---------------------------------------------------------------------------- + /// @brief Allocates an 1x1 pbuffer surface that is used for making the + /// onscreen context current for snapshotting. + /// + /// @return The pbuffer surface. + /// + std::unique_ptr CreatePbufferSurface() const; + //---------------------------------------------------------------------------- /// @return The Android environment that contains a reference to the /// display. diff --git a/shell/platform/android/android_surface_gl.cc b/shell/platform/android/android_surface_gl.cc index f8a933dedeb69..224b2280d22e2 100644 --- a/shell/platform/android/android_surface_gl.cc +++ b/shell/platform/android/android_surface_gl.cc @@ -175,4 +175,16 @@ AndroidContextGL* AndroidSurfaceGL::GLContextPtr() const { return reinterpret_cast(android_context_.get()); } +std::unique_ptr AndroidSurfaceGL::CreatePbufferSurface() { + onscreen_surface_ = GLContextPtr()->CreatePbufferSurface(); + sk_sp main_skia_context = + GLContextPtr()->GetMainSkiaContext(); + if (!main_skia_context) { + main_skia_context = GPUSurfaceGL::MakeGLContext(this); + GLContextPtr()->SetMainSkiaContext(main_skia_context); + } + + return std::make_unique(main_skia_context, this, true); +} + } // namespace flutter diff --git a/shell/platform/android/android_surface_gl.h b/shell/platform/android/android_surface_gl.h index 8db60f3176b3e..31ea82727bf52 100644 --- a/shell/platform/android/android_surface_gl.h +++ b/shell/platform/android/android_surface_gl.h @@ -48,6 +48,9 @@ class AndroidSurfaceGL final : public GPUSurfaceGLDelegate, // |AndroidSurface| bool SetNativeWindow(fml::RefPtr window) override; + // |AndroidSurface| + virtual std::unique_ptr CreatePbufferSurface() override; + // |GPUSurfaceGLDelegate| std::unique_ptr GLContextMakeCurrent() override; diff --git a/shell/platform/android/platform_view_android.cc b/shell/platform/android/platform_view_android.cc index 0cb40991f0f2b..698aee991478e 100644 --- a/shell/platform/android/platform_view_android.cc +++ b/shell/platform/android/platform_view_android.cc @@ -16,6 +16,7 @@ #include "flutter/shell/platform/android/android_surface_software.h" #include "flutter/shell/platform/android/external_view_embedder/external_view_embedder.h" #include "flutter/shell/platform/android/surface/android_surface.h" +#include "flutter/shell/platform/android/surface/snapshot_surface_producer.h" #if SHELL_ENABLE_VULKAN #include "flutter/shell/platform/android/android_surface_vulkan.h" @@ -316,6 +317,15 @@ PlatformViewAndroid::CreateExternalViewEmbedder() { *android_context_, jni_facade_, surface_factory_); } +// |PlatformView| +std::unique_ptr +PlatformViewAndroid::CreateSnapshotSurfaceProducer() { + if (!android_surface_) { + return nullptr; + } + return std::make_unique(*android_surface_); +} + // |PlatformView| sk_sp PlatformViewAndroid::CreateResourceContext() const { if (!android_surface_) { diff --git a/shell/platform/android/platform_view_android.h b/shell/platform/android/platform_view_android.h index 725b0f077c7a3..6ea2abb8a1a19 100644 --- a/shell/platform/android/platform_view_android.h +++ b/shell/platform/android/platform_view_android.h @@ -15,6 +15,7 @@ #include "flutter/fml/platform/android/scoped_java_ref.h" #include "flutter/lib/ui/window/platform_message.h" #include "flutter/shell/common/platform_view.h" +#include "flutter/shell/common/snapshot_surface_producer.h" #include "flutter/shell/platform/android/context/android_context.h" #include "flutter/shell/platform/android/jni/platform_view_android_jni.h" #include "flutter/shell/platform/android/platform_view_android_delegate/platform_view_android_delegate.h" @@ -125,6 +126,7 @@ class PlatformViewAndroid final : public PlatformView { PlatformViewAndroidDelegate platform_view_android_delegate_; std::unique_ptr android_surface_; + // We use id 0 to mean that no response is expected. int next_response_id_ = 1; std::unordered_map> @@ -151,6 +153,10 @@ class PlatformViewAndroid final : public PlatformView { // |PlatformView| std::shared_ptr CreateExternalViewEmbedder() override; + // |PlatformView| + std::unique_ptr CreateSnapshotSurfaceProducer() + override; + // |PlatformView| sk_sp CreateResourceContext() const override; diff --git a/shell/platform/android/surface/BUILD.gn b/shell/platform/android/surface/BUILD.gn index 7764b932a9eac..73cab0833c192 100644 --- a/shell/platform/android/surface/BUILD.gn +++ b/shell/platform/android/surface/BUILD.gn @@ -6,6 +6,8 @@ source_set("surface") { sources = [ "android_surface.cc", "android_surface.h", + "snapshot_surface_producer.cc", + "snapshot_surface_producer.h", ] public_configs = [ "//flutter:config" ] @@ -14,6 +16,7 @@ source_set("surface") { ":native_window", "//flutter/flow", "//flutter/fml", + "//flutter/shell/common", "//flutter/shell/platform/android/context", "//flutter/shell/platform/android/jni", "//third_party/skia", diff --git a/shell/platform/android/surface/android_surface.cc b/shell/platform/android/surface/android_surface.cc index afa4a1ba73479..22162544f88b5 100644 --- a/shell/platform/android/surface/android_surface.cc +++ b/shell/platform/android/surface/android_surface.cc @@ -15,4 +15,8 @@ AndroidSurface::AndroidSurface( AndroidSurface::~AndroidSurface() = default; +std::unique_ptr AndroidSurface::CreatePbufferSurface() { + return nullptr; +} + } // namespace flutter diff --git a/shell/platform/android/surface/android_surface.h b/shell/platform/android/surface/android_surface.h index 74b3fe0efe656..70cee01bfe380 100644 --- a/shell/platform/android/surface/android_surface.h +++ b/shell/platform/android/surface/android_surface.h @@ -37,6 +37,8 @@ class AndroidSurface { virtual bool SetNativeWindow(fml::RefPtr window) = 0; + virtual std::unique_ptr CreatePbufferSurface(); + protected: explicit AndroidSurface( const std::shared_ptr& android_context); diff --git a/shell/platform/android/surface/snapshot_surface_producer.cc b/shell/platform/android/surface/snapshot_surface_producer.cc new file mode 100644 index 0000000000000..5ec41e85471ee --- /dev/null +++ b/shell/platform/android/surface/snapshot_surface_producer.cc @@ -0,0 +1,18 @@ +// 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. + +#include "flutter/shell/platform/android/surface/snapshot_surface_producer.h" + +namespace flutter { + +AndroidSnapshotSurfaceProducer::AndroidSnapshotSurfaceProducer( + AndroidSurface& android_surface) + : android_surface_(android_surface) {} + +std::unique_ptr +AndroidSnapshotSurfaceProducer::CreateSnapshotSurface() { + return android_surface_.CreatePbufferSurface(); +} + +} // namespace flutter diff --git a/shell/platform/android/surface/snapshot_surface_producer.h b/shell/platform/android/surface/snapshot_surface_producer.h new file mode 100644 index 0000000000000..5ec8c68b2f457 --- /dev/null +++ b/shell/platform/android/surface/snapshot_surface_producer.h @@ -0,0 +1,26 @@ +// 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. + +#ifndef SHELL_PLATFORM_ANDROID_SURFACE_SNAPSHOT_SURFACE_PRODUCER_H_ +#define SHELL_PLATFORM_ANDROID_SURFACE_SNAPSHOT_SURFACE_PRODUCER_H_ + +#include "flutter/flow/surface.h" +#include "flutter/shell/common/snapshot_surface_producer.h" +#include "flutter/shell/platform/android/surface/android_surface.h" + +namespace flutter { + +class AndroidSnapshotSurfaceProducer : public SnapshotSurfaceProducer { + public: + explicit AndroidSnapshotSurfaceProducer(AndroidSurface& android_surface); + + // |SnapshotSurfaceProducer| + std::unique_ptr CreateSnapshotSurface() override; + + private: + AndroidSurface& android_surface_; +}; + +} // namespace flutter +#endif // SHELL_PLATFORM_ANDROID_SURFACE_SNAPSHOT_SURFACE_PRODUCER_H_ diff --git a/testing/android_background_image/.gitignore b/testing/android_background_image/.gitignore new file mode 100644 index 0000000000000..7da18b117ef65 --- /dev/null +++ b/testing/android_background_image/.gitignore @@ -0,0 +1,11 @@ +.packages +pubspec.lock # This only has local dependencies in it. +build/ + +ios/Scenarios/*.framework/ +ios/Scenarios/*.xcframework/ +android/app/libs/flutter.jar +android/app/src/main/jniLibs/arm64-v8a/libapp.so +android/gradle-home/.cache + +!.vpython diff --git a/testing/android_background_image/BUILD.gn b/testing/android_background_image/BUILD.gn new file mode 100644 index 0000000000000..6be88bd4e2c90 --- /dev/null +++ b/testing/android_background_image/BUILD.gn @@ -0,0 +1,37 @@ +# Copyright 2013 The Flutter Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//flutter/build/dart/rules.gni") +import("//flutter/testing/rules/runtime_mode.gni") + +assert(is_android) + +dart_snapshot("android_background_image_snapshot") { + main_dart = "lib/main.dart" + package_config = ".dart_tool/package_config.json" +} + +if (!is_aot) { + copy("copy_jit_assets") { + visibility = [ ":*" ] + sources = [ + "$target_gen_dir/isolate_snapshot_data", + "$target_gen_dir/isolate_snapshot_instr", + "$target_gen_dir/kernel_blob.bin", + ] + outputs = [ "$root_out_dir/android_background_image/app/assets/flutter_assets/{{source_file_part}}" ] + deps = [ ":android_background_image_snapshot" ] + } +} + +group("android_background_image") { + deps = [ + ":android_background_image_snapshot", + "android", + ] + + if (!is_aot) { + deps += [ ":copy_jit_assets" ] + } +} diff --git a/testing/android_background_image/README.md b/testing/android_background_image/README.md new file mode 100644 index 0000000000000..fcc6359532ef0 --- /dev/null +++ b/testing/android_background_image/README.md @@ -0,0 +1,3 @@ +# Android Background Image + +A test application that verifies Picture.toImage in the background on Android. diff --git a/testing/android_background_image/android/.gitignore b/testing/android_background_image/android/.gitignore new file mode 100644 index 0000000000000..9a6a5bef7fbe6 --- /dev/null +++ b/testing/android_background_image/android/.gitignore @@ -0,0 +1,19 @@ +*.iml +.gradle +/local.properties +.settings +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cache + +!gradlew +!gradlew.bat +!gradle/wrapper/** diff --git a/testing/android_background_image/android/BUILD.gn b/testing/android_background_image/android/BUILD.gn new file mode 100644 index 0000000000000..cb82db53a9f3c --- /dev/null +++ b/testing/android_background_image/android/BUILD.gn @@ -0,0 +1,41 @@ +# Copyright 2013 The Flutter Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//flutter/testing/rules/android.gni") + +_android_sources = [ + "app/build.gradle", + "app/src/main/AndroidManifest.xml", + "app/src/main/java/dev/flutter/android_background_image/MainActivity.java", + "build.gradle", +] + +gradle_task("android_lint") { + app_name = "android_background_image" + task = "lint" + gradlew_dir = rebase_path(".") + sources = _android_sources + outputs = [ "$root_out_dir/android_background_image/build/reports" ] + + deps = [ "//flutter/testing/android_background_image:android_background_image_snapshot" ] +} + +gradle_task("build_apk") { + app_name = "android_background_image" + task = "assembleDebug" + gradlew_dir = rebase_path(".") + sources = _android_sources + outputs = [ "$root_out_dir/android_background_image/app/outputs/apk/debug/app-debug.apk" ] + + deps = [ + ":android_lint", + "//flutter/testing/android_background_image:android_background_image_snapshot", + ] +} + +copy("android") { + sources = get_target_outputs(":build_apk") + outputs = [ "$root_out_dir/firebase_apks/android_background_image.apk" ] + deps = [ ":build_apk" ] +} diff --git a/testing/android_background_image/android/app/.gitignore b/testing/android_background_image/android/app/.gitignore new file mode 100644 index 0000000000000..796b96d1c4023 --- /dev/null +++ b/testing/android_background_image/android/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/testing/android_background_image/android/app/build.gradle b/testing/android_background_image/android/app/build.gradle new file mode 100644 index 0000000000000..a14505436f3df --- /dev/null +++ b/testing/android_background_image/android/app/build.gradle @@ -0,0 +1,47 @@ +apply plugin: 'com.android.application' + +android { + lintOptions { + abortOnError true + checkAllWarnings true + showAll true + warningsAsErrors true + checkTestSources true + // UnpackedNativeCode can break stack unwinding - see b/193408481 + // NewerVersionAvailable and GradleDependency need to be taken care of + // by a roller rather than as part of CI. + // The others are irrelevant for a test application. + disable 'UnpackedNativeCode','MissingApplicationIcon','GoogleAppIndexingApiWarning','GoogleAppIndexingWarning','GradleDependency','NewerVersionAvailable' + } + compileSdkVersion 30 + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + defaultConfig { + applicationId 'dev.flutter.android_background_image' + minSdkVersion 16 + targetSdkVersion 30 + versionCode 1 + versionName '1.0' + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + sourceSets.main { + assets.srcDirs += "${project.buildDir}/assets" + if (project.hasProperty('libapp')) { + jni.srcDirs = [] + jniLibs.srcDirs = [project.property('libapp')] + } + } +} + +dependencies { + implementation files(project.property('flutter_jar')) + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.lifecycle:lifecycle-common-java8:2.2.0-alpha01' +} diff --git a/testing/android_background_image/android/app/proguard-rules.pro b/testing/android_background_image/android/app/proguard-rules.pro new file mode 100644 index 0000000000000..f1b424510da51 --- /dev/null +++ b/testing/android_background_image/android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/testing/android_background_image/android/app/src/main/AndroidManifest.xml b/testing/android_background_image/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000000..dd5e4aae259cc --- /dev/null +++ b/testing/android_background_image/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + diff --git a/testing/android_background_image/android/app/src/main/java/dev/flutter/android_background_image/MainActivity.java b/testing/android_background_image/android/app/src/main/java/dev/flutter/android_background_image/MainActivity.java new file mode 100644 index 0000000000000..73001786ba308 --- /dev/null +++ b/testing/android_background_image/android/app/src/main/java/dev/flutter/android_background_image/MainActivity.java @@ -0,0 +1,42 @@ +// 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. + +package dev.flutter.android_background_image; + +import android.content.Intent; +import android.util.Log; +import androidx.annotation.NonNull; +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.plugin.common.BinaryMessenger; +import java.nio.ByteBuffer; + +public class MainActivity extends FlutterActivity { + @NonNull + private final BinaryMessenger.BinaryMessageHandler finishHandler = + new BinaryMessenger.BinaryMessageHandler() { + @Override + public void onMessage(ByteBuffer message, final BinaryMessenger.BinaryReply callback) { + if (message != null) { + // Make CI see that there is an error in the logs from Flutter. + Log.e("flutter", "Images did not match."); + } + final Intent intent = new Intent(MainActivity.this, MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + MainActivity.this.startActivity(intent); + MainActivity.this.finish(); + } + }; + + @Override + public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { + flutterEngine.getDartExecutor().getBinaryMessenger().setMessageHandler("finish", finishHandler); + + final boolean moved = moveTaskToBack(true); + if (!moved) { + Log.e("flutter", "Failed to move to back."); + finish(); + } + } +} diff --git a/testing/android_background_image/android/build.gradle b/testing/android_background_image/android/build.gradle new file mode 100644 index 0000000000000..b6931a6ee732f --- /dev/null +++ b/testing/android_background_image/android/build.gradle @@ -0,0 +1,36 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:4.2.0' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +rootProject.buildDir = project.hasProperty('out_dir') + ? project.property('out_dir') + : '../build' + +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/testing/android_background_image/android/gradle-home/.vpython b/testing/android_background_image/android/gradle-home/.vpython new file mode 100644 index 0000000000000..741711e4e06bc --- /dev/null +++ b/testing/android_background_image/android/gradle-home/.vpython @@ -0,0 +1,11 @@ +# +# "vpython" specification file for build +# +# Pillow is required by com.facebook.testing.screenshot. + +python_version: "2.7" + +wheel: < + name: "infra/python/wheels/pillow/${vpython_platform}" + version: "version:6.0.0" +> diff --git a/testing/android_background_image/android/gradle-home/bin/python b/testing/android_background_image/android/gradle-home/bin/python new file mode 100755 index 0000000000000..367e9b679d413 --- /dev/null +++ b/testing/android_background_image/android/gradle-home/bin/python @@ -0,0 +1,14 @@ +#!/bin/bash + +# This script is added to the PATH on LUCI when executing the junit tests. +# The Python scripts used to compare the screenshots are contained in a jar file. +# As a result, vpython cannot find the .vpython spec file because it's outside +# of the jar file. + +set -e + +SCRIPT_DIR="$( dirname "${BASH_SOURCE[0]}" )" + +echo "Running from $SCRIPT_DIR/bin/python" + +vpython -vpython-spec "$SCRIPT_DIR/../.vpython" "$@" diff --git a/testing/android_background_image/android/gradle.properties b/testing/android_background_image/android/gradle.properties new file mode 100644 index 0000000000000..38c8d4544ff1c --- /dev/null +++ b/testing/android_background_image/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1536M +android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true diff --git a/testing/android_background_image/android/gradle/wrapper/gradle-wrapper.jar b/testing/android_background_image/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000..f6b961fd5a86a Binary files /dev/null and b/testing/android_background_image/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/testing/android_background_image/android/gradle/wrapper/gradle-wrapper.properties b/testing/android_background_image/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000000..56db662b86061 --- /dev/null +++ b/testing/android_background_image/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Jul 22 20:16:02 PDT 2019 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip diff --git a/testing/android_background_image/android/gradlew b/testing/android_background_image/android/gradlew new file mode 100755 index 0000000000000..cccdd3d517fc5 --- /dev/null +++ b/testing/android_background_image/android/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/testing/android_background_image/android/gradlew.bat b/testing/android_background_image/android/gradlew.bat new file mode 100644 index 0000000000000..e95643d6a2ca6 --- /dev/null +++ b/testing/android_background_image/android/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/testing/android_background_image/android/settings.gradle b/testing/android_background_image/android/settings.gradle new file mode 100644 index 0000000000000..e7b4def49cb53 --- /dev/null +++ b/testing/android_background_image/android/settings.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/testing/android_background_image/lib/main.dart b/testing/android_background_image/lib/main.dart new file mode 100644 index 0000000000000..6335b51ea2c0f --- /dev/null +++ b/testing/android_background_image/lib/main.dart @@ -0,0 +1,59 @@ +// 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 'dart:async'; +import 'dart:typed_data'; +import 'dart:ui'; + +final Completer _completer = Completer(); + +Future main() async { + channelBuffers.setListener('flutter/lifecycle', _lifecycle); + final bool success = await _completer.future; + final Uint8List? message = success ? null : Uint8List.fromList(const [1]); + PlatformDispatcher.instance.sendPlatformMessage('finish', message?.buffer.asByteData(), null); +} + +Future _lifecycle(ByteData? data, PlatformMessageResponseCallback? callback) async { + final String lifecycleState = String.fromCharCodes(data!.buffer.asUint8List()); + if (lifecycleState == AppLifecycleState.paused.toString()) { + await _testImage(); + } +} + +Future _testImage() async { + // A single pixel image. + final Uint8List pixels = Uint8List.fromList(const [0, 1, 2, 3]); + + // As long as we're using the GL backend, this will go down a path that uses + // a cross context image. + final Completer imageCompleter = Completer(); + decodeImageFromPixels( + pixels, + 1, + 1, + PixelFormat.rgba8888, + (Image image) { + imageCompleter.complete(image); + }, + ); + final Image image = await imageCompleter.future; + final PictureRecorder recorder = PictureRecorder(); + final Canvas canvas = Canvas(recorder); + canvas.drawImage(image, Offset.zero, Paint()); + final Picture picture = recorder.endRecording(); + final Image newImage = await picture.toImage(1, 1); + final ByteData imageData = (await newImage.toByteData())!; + final Uint32List newPixels = imageData.buffer.asUint32List(); + + if (pixels.buffer.asUint32List()[0] != newPixels[0]) { + print('Pixels do not match'); + print('original pixels: $pixels'); + print('new pixels: ${newPixels.buffer.asUint8List()}'); + _completer.complete(false); + } else { + print('Images are identical!'); + _completer.complete(true); + } +} diff --git a/testing/android_background_image/pubspec.yaml b/testing/android_background_image/pubspec.yaml new file mode 100644 index 0000000000000..361922d8fd21f --- /dev/null +++ b/testing/android_background_image/pubspec.yaml @@ -0,0 +1,22 @@ +# 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. + +name: android_background_image +publish_to: none +environment: + sdk: '>=2.12.0 <3.0.0' + +# Do not add any dependencies that require more than what is provided in +# //third_party/dart/pkg, //third_party/dart/third_party/pkg, or +# //third_party/pkg. In particular, package:test is not usable here. + +# If you do add packages here, make sure you can run `pub get --offline`, and +# check the .packages and .package_config to make sure all the paths are +# relative to this directory into //third_party/dart, or //third_party/pkg +dependencies: + sky_engine: any + +dependency_overrides: + sky_engine: + path: ../../sky/packages/sky_engine diff --git a/testing/rules/.gradle/6.5/fileChanges/last-build.bin b/testing/rules/.gradle/6.5/fileChanges/last-build.bin new file mode 100644 index 0000000000000..f76dd238ade08 Binary files /dev/null and b/testing/rules/.gradle/6.5/fileChanges/last-build.bin differ diff --git a/testing/rules/.gradle/6.5/gc.properties b/testing/rules/.gradle/6.5/gc.properties new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/testing/rules/.gradle/vcs-1/gc.properties b/testing/rules/.gradle/vcs-1/gc.properties new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/testing/rules/android.gni b/testing/rules/android.gni new file mode 100644 index 0000000000000..53b096b1364a8 --- /dev/null +++ b/testing/rules/android.gni @@ -0,0 +1,43 @@ +# Copyright 2013 The Flutter Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//flutter/testing/rules/runtime_mode.gni") + +template("gradle_task") { + action(target_name) { + assert(defined(invoker.task), "task is a required parmaeter") + assert(defined(invoker.outputs), "outputs is a required parameter") + assert(defined(invoker.sources), "sources is a required parameter") + assert(defined(invoker.gradlew_dir), + "gradlew_dir is required (normally: rebase_path(\".\"") + assert(defined(invoker.app_name), "app_name is a required parameter") + + script = "//flutter/testing/rules/run_gradle.py" + + inputs = [ "$root_out_dir/flutter.jar" ] + sources = invoker.sources + outputs = invoker.outputs + out_dir = rebase_path("$root_out_dir/${invoker.app_name}") + args = [ + invoker.gradlew_dir, + invoker.task, + "--no-daemon", + "-Pflutter_jar=" + rebase_path("$root_out_dir/flutter.jar"), + "-Pout_dir=$out_dir", + "--project-cache-dir=$out_dir/.gradle", + "--gradle-user-home=$out_dir/.gradle", + ] + + if (is_aot) { + args += [ "-Plibapp=" + rebase_path("$target_gen_dir/libs") ] + inputs += [ "$target_gen_dir/libs/$android_app_abi/libapp.so" ] + } + + deps = [ "//flutter/shell/platform/android:android_jar" ] + + if (defined(invoker.deps)) { + deps += invoker.deps + } + } +} diff --git a/testing/scenario_app/android/run_gradle.py b/testing/rules/run_gradle.py similarity index 81% rename from testing/scenario_app/android/run_gradle.py rename to testing/rules/run_gradle.py index 2163c54670bee..41bb007ea6cc9 100644 --- a/testing/scenario_app/android/run_gradle.py +++ b/testing/rules/run_gradle.py @@ -12,7 +12,7 @@ import subprocess SCRIPT_PATH = os.path.dirname(os.path.realpath(__file__)) -ANDROID_HOME = os.path.join(SCRIPT_PATH, '..', '..', '..', '..', 'third_party', +ANDROID_HOME = os.path.join(SCRIPT_PATH, '..', '..', '..', 'third_party', 'android_tools', 'sdk') def main(): @@ -20,10 +20,10 @@ def main(): raise Exception('%s (ANDROID_HOME) is not a directory' % ANDROID_HOME) BAT = '.bat' if sys.platform.startswith(('cygwin', 'win')) else '' - android_dir = os.path.abspath(os.path.dirname(__file__)) + android_dir = sys.argv[1] gradle_bin = os.path.join('.', 'gradlew%s' % BAT) result = subprocess.check_output( - args=[gradle_bin] + sys.argv[1:], + args=[gradle_bin] + sys.argv[2:], cwd=android_dir, env=dict(os.environ, ANDROID_HOME=ANDROID_HOME), ) diff --git a/testing/scenario_app/runtime_mode.gni b/testing/rules/runtime_mode.gni similarity index 100% rename from testing/scenario_app/runtime_mode.gni rename to testing/rules/runtime_mode.gni diff --git a/testing/scenario_app/BUILD.gn b/testing/scenario_app/BUILD.gn index 10b9bcf8191ea..ac9a8adb497f4 100644 --- a/testing/scenario_app/BUILD.gn +++ b/testing/scenario_app/BUILD.gn @@ -3,7 +3,7 @@ # found in the LICENSE file. import("//flutter/build/dart/rules.gni") -import("//flutter/testing/scenario_app/runtime_mode.gni") +import("//flutter/testing/rules/runtime_mode.gni") dart_snapshot("scenario_app_snapshot") { main_dart = "lib/main.dart" diff --git a/testing/scenario_app/android/.gitignore b/testing/scenario_app/android/.gitignore index 88b214e75574c..9a6a5bef7fbe6 100644 --- a/testing/scenario_app/android/.gitignore +++ b/testing/scenario_app/android/.gitignore @@ -13,3 +13,7 @@ /captures .externalNativeBuild .cache + +!gradlew +!gradlew.bat +!gradle/wrapper/** diff --git a/testing/scenario_app/android/BUILD.gn b/testing/scenario_app/android/BUILD.gn index 43014b2276245..27a64aec5a19f 100644 --- a/testing/scenario_app/android/BUILD.gn +++ b/testing/scenario_app/android/BUILD.gn @@ -2,74 +2,49 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -import("//flutter/testing/scenario_app/runtime_mode.gni") - -template("gradle_task") { - action(target_name) { - assert(defined(invoker.task), "task is a required parmaeter") - assert(defined(invoker.outputs), "outputs is a required parameter") - - script = "run_gradle.py" - - inputs = [ "$root_out_dir/flutter.jar" ] - sources = [ - "app/build.gradle", - "app/src/androidTest/java/dev/flutter/TestRunner.java", - "app/src/androidTest/java/dev/flutter/scenarios/EngineLaunchE2ETest.java", - "app/src/androidTest/java/dev/flutter/scenarios/ExampleInstrumentedTest.java", - "app/src/androidTest/java/dev/flutter/scenariosui/MemoryLeakTests.java", - "app/src/androidTest/java/dev/flutter/scenariosui/PlatformTextureUiTests.java", - "app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewUiTests.java", - "app/src/androidTest/java/dev/flutter/scenariosui/ScreenshotUtil.java", - "app/src/androidTest/java/dev/flutter/scenariosui/SpawnEngineTests.java", - "app/src/main/AndroidManifest.xml", - "app/src/main/java/dev/flutter/scenarios/SpawnedEngineActivity.java", - "app/src/main/java/dev/flutter/scenarios/StrictModeFlutterActivity.java", - "app/src/main/java/dev/flutter/scenarios/TestActivity.java", - "app/src/main/java/dev/flutter/scenarios/TestableFlutterActivity.java", - "app/src/main/java/dev/flutter/scenarios/TextPlatformView.java", - "app/src/main/java/dev/flutter/scenarios/TextPlatformViewActivity.java", - "app/src/main/java/dev/flutter/scenarios/TextPlatformViewFactory.java", - "build.gradle", - ] - - outputs = invoker.outputs - - args = [ - invoker.task, - "--no-daemon", - "-Pflutter_jar=" + rebase_path("$root_out_dir/flutter.jar"), - "-Pout_dir=" + rebase_path("$root_out_dir/scenario_app"), - ] - - if (is_aot) { - args += [ "-Plibapp=" + rebase_path("$target_gen_dir/libs") ] - inputs += [ "$target_gen_dir/libs/$android_app_abi/libapp.so" ] - } - - deps = [ - "//flutter/shell/platform/android:android_jar", - "//flutter/testing/scenario_app:scenario_app_snapshot", - ] - - if (defined(invoker.deps)) { - deps += invoker.deps - } - } -} +import("//flutter/testing/rules/android.gni") + +_android_sources = [ + "app/build.gradle", + "app/src/androidTest/java/dev/flutter/TestRunner.java", + "app/src/androidTest/java/dev/flutter/scenarios/EngineLaunchE2ETest.java", + "app/src/androidTest/java/dev/flutter/scenarios/ExampleInstrumentedTest.java", + "app/src/androidTest/java/dev/flutter/scenariosui/MemoryLeakTests.java", + "app/src/androidTest/java/dev/flutter/scenariosui/PlatformTextureUiTests.java", + "app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewUiTests.java", + "app/src/androidTest/java/dev/flutter/scenariosui/ScreenshotUtil.java", + "app/src/androidTest/java/dev/flutter/scenariosui/SpawnEngineTests.java", + "app/src/main/AndroidManifest.xml", + "app/src/main/java/dev/flutter/scenarios/SpawnedEngineActivity.java", + "app/src/main/java/dev/flutter/scenarios/StrictModeFlutterActivity.java", + "app/src/main/java/dev/flutter/scenarios/TestActivity.java", + "app/src/main/java/dev/flutter/scenarios/TestableFlutterActivity.java", + "app/src/main/java/dev/flutter/scenarios/TextPlatformView.java", + "app/src/main/java/dev/flutter/scenarios/TextPlatformViewActivity.java", + "app/src/main/java/dev/flutter/scenarios/TextPlatformViewFactory.java", + "build.gradle", +] gradle_task("android_lint") { + app_name = "scenario_app" task = "lint" - outputs = [ - "$root_out_dir/scenario_app/build/reports/lint-results.html", - "$root_out_dir/scenario_app/build/reports/lint-results.xml", - ] + gradlew_dir = rebase_path(".") + sources = _android_sources + outputs = [ "$root_out_dir/scenario_app/build/reports" ] + deps = [ "//flutter/testing/scenario_app:scenario_app_snapshot" ] } gradle_task("build_apk") { + app_name = "scenario_app" task = "assembleDebug" + gradlew_dir = rebase_path(".") + sources = _android_sources outputs = [ "$root_out_dir/scenario_app/app/outputs/apk/debug/app-debug.apk" ] - deps = [ ":android_lint" ] + + deps = [ + ":android_lint", + "//flutter/testing/scenario_app:scenario_app_snapshot", + ] } copy("android") { diff --git a/testing/scenario_app/android/app/build.gradle b/testing/scenario_app/android/app/build.gradle index adb67a80e27a7..cded6070e75b8 100644 --- a/testing/scenario_app/android/app/build.gradle +++ b/testing/scenario_app/android/app/build.gradle @@ -19,7 +19,7 @@ android { // NewerVersionAvailable and GradleDependency need to be taken care of // by a roller rather than as part of CI. // The others are irrelevant for a test application. - disable 'UnpackedNativeCode','MissingApplicationIcon','GoogleAppIndexingApiWarning','GoogleAppIndexingWarning','GradleDependency','NewerVersionAvailable' + disable 'UnpackedNativeCode','MissingApplicationIcon','GoogleAppIndexingApiWarning','GoogleAppIndexingWarning','GradleDependency','NewerVersionAvailable','Registered' } compileSdkVersion 30 compileOptions { diff --git a/testing/scenario_app/android/build.gradle b/testing/scenario_app/android/build.gradle index 9e1b24001d0d7..da806b25697b5 100644 --- a/testing/scenario_app/android/build.gradle +++ b/testing/scenario_app/android/build.gradle @@ -6,7 +6,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' + classpath 'com.android.tools.build:gradle:4.2.0' classpath 'com.facebook.testing.screenshot:plugin:0.12.0' // NOTE: Do not place your application dependencies here; they belong diff --git a/testing/scenario_app/android/gradle/wrapper/gradle-wrapper.properties b/testing/scenario_app/android/gradle/wrapper/gradle-wrapper.properties index 37c1aa69ad8a7..56db662b86061 100644 --- a/testing/scenario_app/android/gradle/wrapper/gradle-wrapper.properties +++ b/testing/scenario_app/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip diff --git a/testing/scenario_app/ios/BUILD.gn b/testing/scenario_app/ios/BUILD.gn index 5209d480d4f4f..605de6f33dce1 100644 --- a/testing/scenario_app/ios/BUILD.gn +++ b/testing/scenario_app/ios/BUILD.gn @@ -2,7 +2,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -import("//flutter/testing/scenario_app/runtime_mode.gni") +import("//flutter/testing/rules/runtime_mode.gni") import("//third_party/dart/build/dart/copy_tree.gni") _app_framework_dir = "$root_out_dir/scenario_app/Scenarios/App.framework" diff --git a/tools/pub_get_offline.py b/tools/pub_get_offline.py index 5eb20b27806d0..f8a1d2f7b3242 100644 --- a/tools/pub_get_offline.py +++ b/tools/pub_get_offline.py @@ -20,6 +20,7 @@ os.path.join("src", "flutter", "testing", "benchmark"), os.path.join("src", "flutter", "testing", "dart"), os.path.join("src", "flutter", "testing", "litetest"), + os.path.join("src", "flutter", "testing", "android_background_image"), os.path.join("src", "flutter", "testing", "scenario_app"), os.path.join("src", "flutter", "testing", "smoke_test_failure"), os.path.join("src", "flutter", "testing", "symbols"),