diff --git a/DEPS b/DEPS index ba632b9b04382..0c0a6b9a364d3 100644 --- a/DEPS +++ b/DEPS @@ -785,7 +785,7 @@ deps = { 'packages': [ { 'package': 'flutter/android/embedding_bundle', - 'version': 'last_updated:2023-08-11T11:35:44-0700' + 'version': 'last_updated:2024-06-12T14:15:49-0700' } ], 'condition': 'download_android_deps', diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 0829a94c73052..40e6912ec335f 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -374,6 +374,7 @@ embedding_dependencies_jars = [ "//third_party/android_embedding_dependencies/lib/lifecycle-common-java8-2.2.0.jar", "//third_party/android_embedding_dependencies/lib/lifecycle-livedata-2.0.0.jar", "//third_party/android_embedding_dependencies/lib/lifecycle-livedata-core-2.0.0.jar", + "//third_party/android_embedding_dependencies/lib/lifecycle-process-2.2.0.jar", "//third_party/android_embedding_dependencies/lib/lifecycle-runtime-2.2.0.jar", "//third_party/android_embedding_dependencies/lib/lifecycle-viewmodel-2.1.0.jar", "//third_party/android_embedding_dependencies/lib/loader-1.0.0.jar", diff --git a/shell/platform/android/build.gradle b/shell/platform/android/build.gradle index cd813bf8883b9..5d8bded77f378 100644 --- a/shell/platform/android/build.gradle +++ b/shell/platform/android/build.gradle @@ -62,4 +62,3 @@ android { implementation "org.mockito:mockito-android:$mockitoVersion" } } - diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java index cbab9e99ef593..13b57c7d98e75 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -24,6 +24,9 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.ProcessLifecycleOwner; import io.flutter.Log; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.view.TextureRegistry; @@ -78,6 +81,8 @@ public class FlutterRenderer implements TextureRegistry { private final Set> onTrimMemoryListeners = new HashSet<>(); + @NonNull private final List imageReaderProducers = new ArrayList<>(); + @NonNull private final FlutterUiDisplayListener flutterUiDisplayListener = new FlutterUiDisplayListener() { @@ -95,6 +100,20 @@ public void onFlutterUiNoLongerDisplayed() { public FlutterRenderer(@NonNull FlutterJNI flutterJNI) { this.flutterJNI = flutterJNI; this.flutterJNI.addIsDisplayingFlutterUiListener(flutterUiDisplayListener); + ProcessLifecycleOwner.get() + .getLifecycle() + .addObserver( + new DefaultLifecycleObserver() { + @Override + public void onResume(@NonNull LifecycleOwner owner) { + Log.v(TAG, "onResume called; notifying SurfaceProducers"); + for (ImageReaderSurfaceProducer producer : imageReaderProducers) { + if (producer.callback != null) { + producer.callback.onSurfaceCreated(); + } + } + } + }); } /** @@ -197,6 +216,7 @@ public SurfaceProducer createSurfaceProducer() { final ImageReaderSurfaceProducer producer = new ImageReaderSurfaceProducer(id); registerImageTexture(id, producer); addOnTrimMemoryListener(producer); + imageReaderProducers.add(producer); Log.v(TAG, "New ImageReaderSurfaceProducer ID: " + id); entry = producer; } else { @@ -453,6 +473,7 @@ final class ImageReaderSurfaceProducer new HashMap(); private PerImage lastDequeuedImage = null; private PerImageReader lastReaderDequeuedFrom = null; + private Callback callback = null; /** Internal class: state held per Image produced by ImageReaders. */ private class PerImage { @@ -673,11 +694,15 @@ public void onTrimMemory(int level) { } cleanup(); createNewReader = true; + if (this.callback != null) { + this.callback.onSurfaceDestroyed(); + } } private void releaseInternal() { cleanup(); released = true; + imageReaderProducers.remove(this); } private void cleanup() { @@ -732,6 +757,11 @@ private void maybeWaitOnFence(Image image) { this.id = id; } + @Override + public void setCallback(Callback callback) { + this.callback = callback; + } + @Override public long id() { return id; diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureSurfaceProducer.java b/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureSurfaceProducer.java index 0592a0bab1299..cefd5774a117e 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureSurfaceProducer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureSurfaceProducer.java @@ -55,6 +55,11 @@ public void release() { released = true; } + @Override + public void setCallback(Callback callback) { + // Intentionally blank: SurfaceTextures don't get platform notifications or cleanup. + } + @Override @NonNull public SurfaceTexture getSurfaceTexture() { diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index 62f2a17505174..8dbdae146ef83 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -902,6 +902,7 @@ public ImageTextureEntry createImageTexture() { throw new UnsupportedOperationException("Image textures are not supported in this mode."); } + @NonNull @Override public SurfaceProducer createSurfaceProducer() { throw new UnsupportedOperationException( diff --git a/shell/platform/android/io/flutter/view/TextureRegistry.java b/shell/platform/android/io/flutter/view/TextureRegistry.java index a8b5a52d0e6c5..caeb799d7598a 100644 --- a/shell/platform/android/io/flutter/view/TextureRegistry.java +++ b/shell/platform/android/io/flutter/view/TextureRegistry.java @@ -62,7 +62,7 @@ interface TextureEntry { /** @return The identity of this texture. */ long id(); - /** Deregisters and releases all resources . */ + /** De-registers and releases all resources . */ void release(); } @@ -79,18 +79,52 @@ interface SurfaceProducer extends TextureEntry { int getHeight(); /** - * Get a Surface that can be used to update the texture contents. + * Direct access to the surface object. * - *

NOTE: You should not cache the returned surface but instead invoke getSurface each time - * you need to draw. The surface may change when the texture is resized or has its format + *

When using this API, you will usually need to implement {@link SurfaceProducer.Callback} + * and provide it to {@link #setCallback(Callback)} in order to be notified when an existing + * surface has been destroyed (such as when the application goes to the background) or a new + * surface has been created (such as when the application is resumed back to the foreground). + * + *

NOTE: You should not cache the returned surface but instead invoke {@code getSurface} each + * time you need to draw. The surface may change when the texture is resized or has its format * changed. * * @return a Surface to use for a drawing target for various APIs. */ Surface getSurface(); + /** + * Sets a callback that is notified when a previously created {@link Surface} returned by {@link + * SurfaceProducer#getSurface()} is no longer valid, either due to being destroyed or being + * changed. + * + * @param callback The callback to notify, or null to remove the callback. + */ + void setCallback(Callback callback); + + /** Callback invoked by {@link #setCallback(Callback)}. */ + interface Callback { + /** + * Invoked when a previous surface is now invalid and a new surface is now available. + * + *

Typically plugins will use this callback as a signal to redraw, such as due to the + * texture being resized, the format being changed, or the application being resumed after + * being suspended in the background. + */ + void onSurfaceCreated(); + + /** + * Invoked when a previous surface is now invalid. + * + *

Typically plugins will use this callback as a signal to release resources. + */ + void onSurfaceDestroyed(); + } + + /** This method is not officially part of the public API surface and will be deprecated. */ void scheduleFrame(); - }; + } /** A registry entry for a managed SurfaceTexture. */ @Keep @@ -144,7 +178,7 @@ interface ImageConsumer { * @return Image or null. */ @Nullable - public Image acquireLatestImage(); + Image acquireLatestImage(); } @Keep @@ -155,6 +189,6 @@ interface GLTextureConsumer { * @return SurfaceTexture. */ @NonNull - public SurfaceTexture getSurfaceTexture(); + SurfaceTexture getSurfaceTexture(); } } diff --git a/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java b/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java index ed79d7b6a08f0..1779d6fc56077 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java @@ -23,6 +23,8 @@ import android.os.Looper; import android.view.Surface; import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleRegistry; +import androidx.lifecycle.ProcessLifecycleOwner; import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.ext.junit.runners.AndroidJUnit4; import io.flutter.embedding.android.FlutterActivity; @@ -728,8 +730,46 @@ public void SurfaceTextureSurfaceProducerCreatesAConnectedTexture() { } @Test - public void CanLaunchActivityUsingFlutterEngine() { - // This is a placeholder test that will be used to test lifecycle events w/ SurfaceProducer. - scenarioRule.getScenario().moveToState(Lifecycle.State.RESUMED); + public void ImageReaderSurfaceProducerIsDestroyedOnTrimMemory() { + FlutterRenderer flutterRenderer = engineRule.getFlutterEngine().getRenderer(); + TextureRegistry.SurfaceProducer producer = flutterRenderer.createSurfaceProducer(); + + // Create and set a mock callback. + TextureRegistry.SurfaceProducer.Callback callback = + mock(TextureRegistry.SurfaceProducer.Callback.class); + producer.setCallback(callback); + + // Trim memory. + ((FlutterRenderer.ImageReaderSurfaceProducer) producer).onTrimMemory(40); + + // Verify. + verify(callback).onSurfaceDestroyed(); + } + + @Test + public void ImageReaderSurfaceProducerIsCreatedOnLifecycleResume() throws Exception { + FlutterRenderer flutterRenderer = engineRule.getFlutterEngine().getRenderer(); + TextureRegistry.SurfaceProducer producer = flutterRenderer.createSurfaceProducer(); + + // Create a callback. + CountDownLatch latch = new CountDownLatch(1); + TextureRegistry.SurfaceProducer.Callback callback = + new TextureRegistry.SurfaceProducer.Callback() { + @Override + public void onSurfaceCreated() { + latch.countDown(); + } + + @Override + public void onSurfaceDestroyed() {} + }; + producer.setCallback(callback); + + // Trigger a resume. + ((LifecycleRegistry) ProcessLifecycleOwner.get().getLifecycle()) + .setCurrentState(Lifecycle.State.RESUMED); + + // Verify. + latch.await(); } } diff --git a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java index 0ecc09838bc33..af3a79e7adc22 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java @@ -1582,13 +1582,16 @@ private static void attachToFlutterView( new TextureRegistry() { public void TextureRegistry() {} + @NonNull @Override public SurfaceTextureEntry createSurfaceTexture() { return registerSurfaceTexture(mock(SurfaceTexture.class)); } + @NonNull @Override - public SurfaceTextureEntry registerSurfaceTexture(SurfaceTexture surfaceTexture) { + public SurfaceTextureEntry registerSurfaceTexture( + @NonNull SurfaceTexture surfaceTexture) { return new SurfaceTextureEntry() { @NonNull @Override @@ -1606,6 +1609,7 @@ public void release() {} }; } + @NonNull @Override public ImageTextureEntry createImageTexture() { return new ImageTextureEntry() { @@ -1622,9 +1626,13 @@ public void pushImage(Image image) {} }; } + @NonNull @Override public SurfaceProducer createSurfaceProducer() { return new SurfaceProducer() { + @Override + public void setCallback(SurfaceProducer.Callback cb) {} + @Override public long id() { return 0; diff --git a/testing/scenario_app/android/app/gradle.lockfile b/testing/scenario_app/android/app/gradle.lockfile index fedf60cf04806..f79689a036e99 100644 --- a/testing/scenario_app/android/app/gradle.lockfile +++ b/testing/scenario_app/android/app/gradle.lockfile @@ -28,6 +28,7 @@ androidx.lifecycle:lifecycle-common-java8:2.2.0=debugAndroidTestCompileClasspath androidx.lifecycle:lifecycle-common:2.3.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.lifecycle:lifecycle-livedata-core:2.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.lifecycle:lifecycle-livedata:2.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-process:2.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.lifecycle:lifecycle-runtime:2.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.lifecycle:lifecycle-viewmodel:2.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.loader:loader:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath diff --git a/tools/androidx/files.json b/tools/androidx/files.json index 79ee484d6e737..3e03b721f82a5 100644 --- a/tools/androidx/files.json +++ b/tools/androidx/files.json @@ -13,17 +13,19 @@ "url": "https://maven.google.com/androidx/lifecycle/lifecycle-common-java8/2.2.0/lifecycle-common-java8-2.2.0.jar", "out_file_name": "androidx_lifecycle_common_java8.jar", "maven_dependency": "androidx.lifecycle:lifecycle-common-java8:2.2.0", - "provides": [ - "androidx.lifecycle.DefaultLifecycleObserver" - ] + "provides": ["androidx.lifecycle.DefaultLifecycleObserver"] + }, + { + "url": "https://maven.google.com/androidx/lifecycle/lifecycle-process/2.2.0/lifecycle-process-2.2.0.aar", + "out_file_name": "androidx_lifecycle_process.aar", + "maven_dependency": "androidx.lifecycle:lifecycle-process:2.2.0", + "provides": ["androidx.lifecycle.ProcessLifecycleOwner"] }, { "url": "https://maven.google.com/androidx/lifecycle/lifecycle-runtime/2.2.0/lifecycle-runtime-2.2.0.aar", "out_file_name": "androidx_lifecycle_runtime.aar", "maven_dependency": "androidx.lifecycle:lifecycle-runtime:2.2.0", - "provides": [ - "androidx.lifecycle.LifecycleRegistry" - ] + "provides": ["androidx.lifecycle.LifecycleRegistry"] }, { "url": "https://maven.google.com/androidx/fragment/fragment/1.1.0/fragment-1.1.0.aar", @@ -54,17 +56,13 @@ "url": "https://maven.google.com/androidx/tracing/tracing/1.0.0/tracing-1.0.0.aar", "out_file_name": "androidx_tracing.aar", "maven_dependency": "androidx.tracing:tracing:1.0.0", - "provides": [ - "androidx.tracing.Trace" - ] + "provides": ["androidx.tracing.Trace"] }, { "url": "https://dl.google.com/android/maven2/androidx/core/core/1.6.0/core-1.6.0.aar", "out_file_name": "androidx_core.aar", "maven_dependency": "androidx.core:core:1.6.0", - "provides": [ - "androidx.core.view.WindowInsetsControllerCompat" - ] + "provides": ["androidx.core.view.WindowInsetsControllerCompat"] }, { "url": "https://maven.google.com/androidx/window/window-java/1.0.0-beta04/window-java-1.0.0-beta04.aar", diff --git a/tools/cipd/android_embedding_bundle/build.gradle b/tools/cipd/android_embedding_bundle/build.gradle index 9b93d0a0afbdf..c10b3c9ec33b0 100644 --- a/tools/cipd/android_embedding_bundle/build.gradle +++ b/tools/cipd/android_embedding_bundle/build.gradle @@ -13,7 +13,7 @@ buildscript { mavenCentral() } dependencies { - classpath "com.android.tools.build:gradle:3.5.0" + classpath "com.android.tools.build:gradle:7.0.2" } }