From f80f3a21a8635ae9066be2c7ac44b520748cf67b Mon Sep 17 00:00:00 2001 From: camsim99 Date: Wed, 26 Jul 2023 12:58:53 -0700 Subject: [PATCH 1/3] Deletion round 1 --- ci/licenses_golden/licenses_flutter | 8 - shell/platform/android/BUILD.gn | 4 - .../android/DrawableSplashScreen.java | 125 -- .../embedding/android/FlutterActivity.java | 43 - .../FlutterActivityAndFragmentDelegate.java | 25 +- .../android/FlutterActivityLaunchConfigs.java | 2 - .../embedding/android/FlutterFragment.java | 12 - .../android/FlutterFragmentActivity.java | 41 +- .../embedding/android/FlutterSplashView.java | 295 ----- .../embedding/android/SplashScreen.java | 108 -- .../android/SplashScreenProvider.java | 28 - ...lutterActivityAndFragmentDelegateTest.java | 5 - .../android/FlutterActivityTest.java | 89 -- .../android/FlutterAndroidComponentTest.java | 6 - .../android/FlutterFragmentActivityTest.java | 93 -- .../embedding/android/FlutterViewTest.java | 1104 ----------------- tools/android_lint/project.xml | 4 - 17 files changed, 3 insertions(+), 1989 deletions(-) delete mode 100644 shell/platform/android/io/flutter/embedding/android/DrawableSplashScreen.java delete mode 100644 shell/platform/android/io/flutter/embedding/android/FlutterSplashView.java delete mode 100644 shell/platform/android/io/flutter/embedding/android/SplashScreen.java delete mode 100644 shell/platform/android/io/flutter/embedding/android/SplashScreenProvider.java delete mode 100644 shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 16704f1c2eee7..6bbe76b177f91 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -2326,7 +2326,6 @@ ORIGIN: ../../../flutter/shell/platform/android/io/flutter/app/FlutterApplicatio ORIGIN: ../../../flutter/shell/platform/android/io/flutter/app/FlutterFragmentActivity.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/app/FlutterPlayStoreSplitApplication.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/app/FlutterPluginRegistry.java + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/android/DrawableSplashScreen.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/android/ExclusiveAppComponent.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java + ../../../flutter/LICENSE @@ -2337,7 +2336,6 @@ ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/android/Flu ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterPlayStoreSplitApplication.java + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterSplashView.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterTextureView.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterView.java + ../../../flutter/LICENSE @@ -2347,8 +2345,6 @@ ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/android/Key ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/android/KeyboardManager.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/android/KeyboardMap.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/android/RenderMode.java + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/android/SplashScreen.java + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/android/SplashScreenProvider.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/android/TransparencyMode.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/android/WindowInfoRepositoryCallbackAdapterWrapper.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java + ../../../flutter/LICENSE @@ -5024,7 +5020,6 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterFragmentActi FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterPlayStoreSplitApplication.java FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterPluginRegistry.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java -FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/DrawableSplashScreen.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/ExclusiveAppComponent.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -5035,7 +5030,6 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/Flutt FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterPlayStoreSplitApplication.java -FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterSplashView.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterTextureView.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -5046,8 +5040,6 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/Keybo FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/KeyboardMap.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/MotionEventTracker.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/RenderMode.java -FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/SplashScreen.java -FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/SplashScreenProvider.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/TransparencyMode.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/WindowInfoRepositoryCallbackAdapterWrapper.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 89871decbf6f5..80fd97c3991f6 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -180,7 +180,6 @@ android_java_sources = [ "io/flutter/app/FlutterPlayStoreSplitApplication.java", "io/flutter/app/FlutterPluginRegistry.java", "io/flutter/embedding/android/AndroidTouchProcessor.java", - "io/flutter/embedding/android/DrawableSplashScreen.java", "io/flutter/embedding/android/ExclusiveAppComponent.java", "io/flutter/embedding/android/FlutterActivity.java", "io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java", @@ -191,7 +190,6 @@ android_java_sources = [ "io/flutter/embedding/android/FlutterFragmentActivity.java", "io/flutter/embedding/android/FlutterImageView.java", "io/flutter/embedding/android/FlutterPlayStoreSplitApplication.java", - "io/flutter/embedding/android/FlutterSplashView.java", "io/flutter/embedding/android/FlutterSurfaceView.java", "io/flutter/embedding/android/FlutterTextureView.java", "io/flutter/embedding/android/FlutterView.java", @@ -202,8 +200,6 @@ android_java_sources = [ "io/flutter/embedding/android/KeyboardMap.java", "io/flutter/embedding/android/MotionEventTracker.java", "io/flutter/embedding/android/RenderMode.java", - "io/flutter/embedding/android/SplashScreen.java", - "io/flutter/embedding/android/SplashScreenProvider.java", "io/flutter/embedding/android/TransparencyMode.java", "io/flutter/embedding/android/WindowInfoRepositoryCallbackAdapterWrapper.java", "io/flutter/embedding/engine/FlutterEngine.java", diff --git a/shell/platform/android/io/flutter/embedding/android/DrawableSplashScreen.java b/shell/platform/android/io/flutter/embedding/android/DrawableSplashScreen.java deleted file mode 100644 index d943e653bccd6..0000000000000 --- a/shell/platform/android/io/flutter/embedding/android/DrawableSplashScreen.java +++ /dev/null @@ -1,125 +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. - -package io.flutter.embedding.android; - -import android.animation.Animator; -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.util.AttributeSet; -import android.view.View; -import android.widget.ImageView; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -/** - * {@link SplashScreen} that displays a given {@link Drawable}, which then fades its alpha to zero - * when instructed to {@link #transitionToFlutter(Runnable)}. - * - *

Please use the new Splash screen API available on Android S. On lower versions of Android, - * it's no longer necessary to display a splash screen to wait for the Flutter first frame. - * - * @deprecated - */ -@Deprecated -public final class DrawableSplashScreen implements SplashScreen { - private final Drawable drawable; - private final ImageView.ScaleType scaleType; - private final long crossfadeDurationInMillis; - private DrawableSplashScreenView splashView; - - /** - * Constructs a {@code DrawableSplashScreen} that displays the given {@code drawable} and - * crossfades to Flutter content in 500 milliseconds. - * - * @param drawable A drawable to display on the Splash screen view. - */ - public DrawableSplashScreen(@NonNull Drawable drawable) { - this(drawable, ImageView.ScaleType.FIT_XY, 500); - } - - /** - * Constructs a {@code DrawableSplashScreen} that displays the given {@code drawable} and - * crossfades to Flutter content in the given {@code crossfadeDurationInMillis}. - * - * @param drawable The {@code Drawable} to be displayed as a splash screen. - * @param scaleType The {@link android.widget.ImageView.ScaleType} to be applied to the {@code - * Drawable} when the {@code Drawable} is displayed full-screen. - * @param crossfadeDurationInMillis Duration of the crossfade in milliseconds. - */ - public DrawableSplashScreen( - @NonNull Drawable drawable, - @NonNull ImageView.ScaleType scaleType, - long crossfadeDurationInMillis) { - this.drawable = drawable; - this.scaleType = scaleType; - this.crossfadeDurationInMillis = crossfadeDurationInMillis; - } - - @Nullable - @Override - public View createSplashView(@NonNull Context context, @Nullable Bundle savedInstanceState) { - splashView = new DrawableSplashScreenView(context); - splashView.setSplashDrawable(drawable, scaleType); - return splashView; - } - - @Override - public void transitionToFlutter(@NonNull Runnable onTransitionComplete) { - if (splashView == null) { - onTransitionComplete.run(); - return; - } - - splashView - .animate() - .alpha(0.0f) - .setDuration(crossfadeDurationInMillis) - .setListener( - new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) {} - - @Override - public void onAnimationEnd(Animator animation) { - onTransitionComplete.run(); - } - - @Override - public void onAnimationCancel(Animator animation) { - onTransitionComplete.run(); - } - - @Override - public void onAnimationRepeat(Animator animation) {} - }); - } - - // Public for Android OS requirements. This View should not be used by external developers. - public static class DrawableSplashScreenView extends ImageView { - public DrawableSplashScreenView(@NonNull Context context) { - this(context, null, 0); - } - - public DrawableSplashScreenView(@NonNull Context context, @Nullable AttributeSet attrs) { - this(context, attrs, 0); - } - - public DrawableSplashScreenView( - @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - public void setSplashDrawable(@Nullable Drawable drawable) { - setSplashDrawable(drawable, ImageView.ScaleType.FIT_XY); - } - - public void setSplashDrawable( - @Nullable Drawable drawable, @NonNull ImageView.ScaleType scaleType) { - setScaleType(scaleType); - setImageDrawable(drawable); - } - } -} diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index abbedeb4ddd18..ee04ce1364c45 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -20,7 +20,6 @@ import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.HANDLE_DEEPLINKING_META_DATA_KEY; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.INITIAL_ROUTE_META_DATA_KEY; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.NORMAL_THEME_META_DATA_KEY; -import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.SPLASH_SCREEN_META_DATA_KEY; import android.app.Activity; import android.content.Context; @@ -28,10 +27,8 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.res.Resources; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; import android.view.View; @@ -42,7 +39,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import androidx.core.content.res.ResourcesCompat; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleRegistry; @@ -201,10 +197,6 @@ *

With themes defined, and AndroidManifest.xml updated, Flutter displays the specified launch * screen until the Android application is initialized. * - *

Flutter also requires initialization time. To specify a splash screen for Flutter - * initialization, subclass {@code FlutterActivity} and override {@link #provideSplashScreen()}. See - * {@link SplashScreen} for details on implementing a splash screen. - * *

Alternative Activity {@link FlutterFragmentActivity} is also available, which * is similar to {@code FlutterActivity} but it extends {@code FragmentActivity}. You should use * {@code FlutterActivity}, if possible, but if you need a {@code FragmentActivity} then you should @@ -756,41 +748,6 @@ private void switchLaunchThemeForNormalTheme() { } } - @Nullable - @Override - public SplashScreen provideSplashScreen() { - Drawable manifestSplashDrawable = getSplashScreenFromManifest(); - if (manifestSplashDrawable != null) { - return new DrawableSplashScreen(manifestSplashDrawable); - } else { - return null; - } - } - - /** - * Returns a {@link Drawable} to be used as a splash screen as requested by meta-data in the - * {@code AndroidManifest.xml} file, or null if no such splash screen is requested. - * - *

See {@link FlutterActivityLaunchConfigs#SPLASH_SCREEN_META_DATA_KEY} for the meta-data key - * to be used in a manifest file. - */ - @Nullable - private Drawable getSplashScreenFromManifest() { - try { - Bundle metaData = getMetaData(); - int splashScreenId = metaData != null ? metaData.getInt(SPLASH_SCREEN_META_DATA_KEY) : 0; - return splashScreenId != 0 - ? ResourcesCompat.getDrawable(getResources(), splashScreenId, getTheme()) - : null; - } catch (Resources.NotFoundException e) { - Log.e(TAG, "Splash screen not found. Ensure the drawable exists and that it's valid."); - throw e; - } catch (PackageManager.NameNotFoundException e) { - // This is never expected to happen. - return null; - } - } - /** * Sets this {@code Activity}'s {@code Window} background to be transparent, and hides the status * bar, if this {@code Activity}'s desired {@link BackgroundMode} is {@link diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java index 91dc8c52caaea..1e59b74e03bbb 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -30,7 +30,6 @@ import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener; import io.flutter.plugin.platform.PlatformPlugin; -import io.flutter.util.ViewUtils; import java.util.Arrays; import java.util.List; @@ -344,9 +343,7 @@ private FlutterEngineGroup.Options addEntrypointOptions(FlutterEngineGroup.Optio * with Android tools, such as "Displayed" timing printed with `am start`. * *

Note that it should only be set to true when {@code Host#getRenderMode()} is {@code - * RenderMode.surface}. This parameter is also ignored, disabling the delay should the legacy - * {@code Host#provideSplashScreen()} be non-null. See Android Splash Migration. + * RenderMode.surface}. * *

This method: * @@ -397,20 +394,6 @@ View onCreateView( flutterView.attachToFlutterEngine(flutterEngine); flutterView.setId(flutterViewId); - SplashScreen splashScreen = host.provideSplashScreen(); - - if (splashScreen != null) { - Log.w( - TAG, - "A splash screen was provided to Flutter, but this is deprecated. See" - + " flutter.dev/go/android-splash-migration for migration steps."); - FlutterSplashView flutterSplashView = new FlutterSplashView(host.getContext()); - flutterSplashView.setId(ViewUtils.generateViewId(FLUTTER_SPLASH_VIEW_FALLBACK_ID)); - flutterSplashView.displayFlutterViewWithSplash(flutterView, splashScreen); - - return flutterSplashView; - } - if (shouldDelayFirstAndroidViewDraw) { delayFirstAndroidViewDraw(flutterView); } @@ -952,8 +935,7 @@ private void ensureAlive() { * FlutterActivityAndFragmentDelegate}. */ /* package */ interface Host - extends SplashScreenProvider, - FlutterEngineProvider, + extends FlutterEngineProvider, FlutterEngineConfigurator, PlatformPlugin.PlatformPluginDelegate { /** @@ -1070,9 +1052,6 @@ private void ensureAlive() { */ ExclusiveAppComponent getExclusiveAppComponent(); - @Nullable - SplashScreen provideSplashScreen(); - /** * Returns the {@link io.flutter.embedding.engine.FlutterEngine} that should be rendered to a * {@link FlutterView}. diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityLaunchConfigs.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityLaunchConfigs.java index 9a14eb83d784e..3f775af65d38b 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityLaunchConfigs.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityLaunchConfigs.java @@ -13,8 +13,6 @@ public class FlutterActivityLaunchConfigs { /* package */ static final String DART_ENTRYPOINT_META_DATA_KEY = "io.flutter.Entrypoint"; /* package */ static final String DART_ENTRYPOINT_URI_META_DATA_KEY = "io.flutter.EntrypointUri"; /* package */ static final String INITIAL_ROUTE_META_DATA_KEY = "io.flutter.InitialRoute"; - /* package */ static final String SPLASH_SCREEN_META_DATA_KEY = - "io.flutter.embedding.android.SplashScreenDrawable"; /* package */ static final String NORMAL_THEME_META_DATA_KEY = "io.flutter.embedding.android.NormalTheme"; /* package */ static final String HANDLE_DEEPLINKING_META_DATA_KEY = diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java index 85ce3ade38fb0..b521c73882f0d 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java @@ -1447,18 +1447,6 @@ public TransparencyMode getTransparencyMode() { return TransparencyMode.valueOf(transparencyModeName); } - @Override - @Nullable - public SplashScreen provideSplashScreen() { - FragmentActivity parentActivity = getActivity(); - if (parentActivity instanceof SplashScreenProvider) { - SplashScreenProvider splashScreenProvider = (SplashScreenProvider) parentActivity; - return splashScreenProvider.provideSplashScreen(); - } - - return null; - } - /** * Hook for subclasses to return a {@link io.flutter.embedding.engine.FlutterEngine} with whatever * configuration is desired. diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java index 3b0f8f9820dcf..4f3ab481b9ee2 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java @@ -19,17 +19,14 @@ import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.HANDLE_DEEPLINKING_META_DATA_KEY; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.INITIAL_ROUTE_META_DATA_KEY; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.NORMAL_THEME_META_DATA_KEY; -import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.SPLASH_SCREEN_META_DATA_KEY; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.res.Resources; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; import android.view.View; @@ -40,7 +37,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import androidx.core.content.res.ResourcesCompat; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import io.flutter.Log; @@ -65,7 +61,7 @@ // are duplicated for readability purposes. Be sure to replicate any change in this class in // FlutterActivity, too. public class FlutterFragmentActivity extends FragmentActivity - implements SplashScreenProvider, FlutterEngineProvider, FlutterEngineConfigurator { + implements FlutterEngineProvider, FlutterEngineConfigurator { private static final String TAG = "FlutterFragmentActivity"; // FlutterFragment management. @@ -439,41 +435,6 @@ private void switchLaunchThemeForNormalTheme() { } } - @Nullable - @Override - public SplashScreen provideSplashScreen() { - Drawable manifestSplashDrawable = getSplashScreenFromManifest(); - if (manifestSplashDrawable != null) { - return new DrawableSplashScreen(manifestSplashDrawable); - } else { - return null; - } - } - - /** - * Returns a {@link Drawable} to be used as a splash screen as requested by meta-data in the - * {@code AndroidManifest.xml} file, or null if no such splash screen is requested. - * - *

See {@link FlutterActivityLaunchConfigs#SPLASH_SCREEN_META_DATA_KEY} for the meta-data key - * to be used in a manifest file. - */ - @Nullable - private Drawable getSplashScreenFromManifest() { - try { - Bundle metaData = getMetaData(); - int splashScreenId = metaData != null ? metaData.getInt(SPLASH_SCREEN_META_DATA_KEY) : 0; - return splashScreenId != 0 - ? ResourcesCompat.getDrawable(getResources(), splashScreenId, getTheme()) - : null; - } catch (Resources.NotFoundException e) { - Log.e(TAG, "Splash screen not found. Ensure the drawable exists and that it's valid."); - throw e; - } catch (PackageManager.NameNotFoundException e) { - // This is never expected to happen. - return null; - } - } - /** * Sets this {@code Activity}'s {@code Window} background to be transparent, and hides the status * bar, if this {@code Activity}'s desired {@link BackgroundMode} is {@link diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterSplashView.java b/shell/platform/android/io/flutter/embedding/android/FlutterSplashView.java deleted file mode 100644 index 1f6e8d49d277b..0000000000000 --- a/shell/platform/android/io/flutter/embedding/android/FlutterSplashView.java +++ /dev/null @@ -1,295 +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. - -package io.flutter.embedding.android; - -import android.content.Context; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.util.AttributeSet; -import android.view.View; -import android.widget.FrameLayout; -import androidx.annotation.Keep; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import io.flutter.Log; -import io.flutter.embedding.engine.FlutterEngine; -import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener; - -/** - * {@code View} that displays a {@link SplashScreen} until a given {@link FlutterView} renders its - * first frame. - */ -/* package */ final class FlutterSplashView extends FrameLayout { - private static String TAG = "FlutterSplashView"; - - @Nullable private SplashScreen splashScreen; - @Nullable private FlutterView flutterView; - @Nullable private View splashScreenView; - @VisibleForTesting @Nullable /* package */ Bundle splashScreenState; - @Nullable private String transitioningIsolateId; - @Nullable private String previousCompletedSplashIsolate; - - @NonNull - private final FlutterView.FlutterEngineAttachmentListener flutterEngineAttachmentListener = - new FlutterView.FlutterEngineAttachmentListener() { - @Override - public void onFlutterEngineAttachedToFlutterView(@NonNull FlutterEngine engine) { - flutterView.removeFlutterEngineAttachmentListener(this); - displayFlutterViewWithSplash(flutterView, splashScreen); - } - - @Override - public void onFlutterEngineDetachedFromFlutterView() {} - }; - - @NonNull - private final FlutterUiDisplayListener flutterUiDisplayListener = - new FlutterUiDisplayListener() { - @Override - public void onFlutterUiDisplayed() { - if (splashScreen != null) { - transitionToFlutter(); - } - } - - @Override - public void onFlutterUiNoLongerDisplayed() { - // no-op - } - }; - - @NonNull - private final Runnable onTransitionComplete = - new Runnable() { - @Override - public void run() { - removeView(splashScreenView); - previousCompletedSplashIsolate = transitioningIsolateId; - } - }; - - public FlutterSplashView(@NonNull Context context) { - this(context, null, 0); - } - - public FlutterSplashView(@NonNull Context context, @Nullable AttributeSet attrs) { - this(context, attrs, 0); - } - - public FlutterSplashView( - @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - setSaveEnabled(true); - } - - @Nullable - @Override - protected Parcelable onSaveInstanceState() { - Parcelable superState = super.onSaveInstanceState(); - SavedState savedState = new SavedState(superState); - savedState.previousCompletedSplashIsolate = previousCompletedSplashIsolate; - savedState.splashScreenState = - splashScreen != null ? splashScreen.saveSplashScreenState() : null; - return savedState; - } - - @Override - protected void onRestoreInstanceState(Parcelable state) { - if (!(state instanceof SavedState)) { - super.onRestoreInstanceState(state); - return; - } - SavedState savedState = (SavedState) state; - super.onRestoreInstanceState(savedState.getSuperState()); - previousCompletedSplashIsolate = savedState.previousCompletedSplashIsolate; - splashScreenState = savedState.splashScreenState; - } - - /** - * Displays the given {@code splashScreen} on top of the given {@code flutterView} until Flutter - * has rendered its first frame, then the {@code splashScreen} is transitioned away. - * - *

If no {@code splashScreen} is provided, this {@code FlutterSplashView} displays the given - * {@code flutterView} on its own. - */ - public void displayFlutterViewWithSplash( - @NonNull FlutterView flutterView, @Nullable SplashScreen splashScreen) { - // If we were displaying a previous FlutterView, remove it. - if (this.flutterView != null) { - this.flutterView.removeOnFirstFrameRenderedListener(flutterUiDisplayListener); - removeView(this.flutterView); - } - // If we were displaying a previous splash screen View, remove it. - if (splashScreenView != null) { - removeView(splashScreenView); - } - - // Display the new FlutterView. - this.flutterView = flutterView; - addView(flutterView); - - this.splashScreen = splashScreen; - - // Display the new splash screen, if needed. - if (splashScreen != null) { - if (isSplashScreenNeededNow()) { - Log.v(TAG, "Showing splash screen UI."); - // This is the typical case. A FlutterEngine is attached to the FlutterView and we're - // waiting for the first frame to render. Show a splash UI until that happens. - splashScreenView = splashScreen.createSplashView(getContext(), splashScreenState); - addView(this.splashScreenView); - flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener); - } else if (isSplashScreenTransitionNeededNow()) { - Log.v( - TAG, - "Showing an immediate splash transition to Flutter due to previously interrupted transition."); - splashScreenView = splashScreen.createSplashView(getContext(), splashScreenState); - addView(splashScreenView); - transitionToFlutter(); - } else if (!flutterView.isAttachedToFlutterEngine()) { - Log.v( - TAG, - "FlutterView is not yet attached to a FlutterEngine. Showing nothing until a FlutterEngine is attached."); - flutterView.addFlutterEngineAttachmentListener(flutterEngineAttachmentListener); - } - } - } - - /** - * Returns true if current conditions require a splash UI to be displayed. - * - *

This method does not evaluate whether a previously interrupted splash transition needs to - * resume. See {@link #isSplashScreenTransitionNeededNow()} to answer that question. - */ - private boolean isSplashScreenNeededNow() { - return flutterView != null - && flutterView.isAttachedToFlutterEngine() - && !flutterView.hasRenderedFirstFrame() - && !hasSplashCompleted(); - } - - /** - * Returns true if a previous splash transition was interrupted by recreation, e.g., an - * orientation change, and that previous transition should be resumed. - * - *

Not all splash screens are capable of remembering their transition progress. In those cases, - * this method will return false even if a previous visual transition was interrupted. - */ - private boolean isSplashScreenTransitionNeededNow() { - return flutterView != null - && flutterView.isAttachedToFlutterEngine() - && splashScreen != null - && splashScreen.doesSplashViewRememberItsTransition() - && wasPreviousSplashTransitionInterrupted(); - } - - /** - * Returns true if a splash screen was transitioning to a Flutter experience and was then - * interrupted, e.g., by an Android configuration change. Returns false otherwise. - * - *

Invoking this method expects that a {@code flutterView} exists and it is attached to a - * {@code FlutterEngine}. - */ - private boolean wasPreviousSplashTransitionInterrupted() { - if (flutterView == null) { - throw new IllegalStateException( - "Cannot determine if previous splash transition was " - + "interrupted when no FlutterView is set."); - } - if (!flutterView.isAttachedToFlutterEngine()) { - throw new IllegalStateException( - "Cannot determine if previous splash transition was " - + "interrupted when no FlutterEngine is attached to our FlutterView. This question " - + "depends on an isolate ID to differentiate Flutter experiences."); - } - return flutterView.hasRenderedFirstFrame() && !hasSplashCompleted(); - } - - /** - * Returns true if a splash UI for a specific Flutter experience has already completed. - * - *

A "specific Flutter experience" is defined as any experience with the same Dart isolate ID. - * The purpose of this distinction is to prevent a situation where a user gets past a splash UI, - * rotates the device (or otherwise triggers a recreation) and the splash screen reappears. - * - *

An isolate ID is deemed reasonable as a key for a completion event because a Dart isolate - * cannot be entered twice. Therefore, a single Dart isolate cannot return to an "un-rendered" - * state after having previously rendered content. - */ - private boolean hasSplashCompleted() { - if (flutterView == null) { - throw new IllegalStateException( - "Cannot determine if splash has completed when no FlutterView " + "is set."); - } - if (!flutterView.isAttachedToFlutterEngine()) { - throw new IllegalStateException( - "Cannot determine if splash has completed when no " - + "FlutterEngine is attached to our FlutterView. This question depends on an isolate ID " - + "to differentiate Flutter experiences."); - } - - // A null isolate ID on a non-null FlutterEngine indicates that the Dart isolate has not - // been initialized. Therefore, no frame has been rendered for this engine, which means - // no splash screen could have completed yet. - return flutterView.getAttachedFlutterEngine().getDartExecutor().getIsolateServiceId() != null - && flutterView - .getAttachedFlutterEngine() - .getDartExecutor() - .getIsolateServiceId() - .equals(previousCompletedSplashIsolate); - } - - /** - * Transitions a splash screen to the Flutter UI. - * - *

This method requires that our FlutterView be attached to an engine, and that engine have a - * Dart isolate ID. It also requires that a {@code splashScreen} exist. - */ - private void transitionToFlutter() { - transitioningIsolateId = - flutterView.getAttachedFlutterEngine().getDartExecutor().getIsolateServiceId(); - Log.v(TAG, "Transitioning splash screen to a Flutter UI. Isolate: " + transitioningIsolateId); - splashScreen.transitionToFlutter(onTransitionComplete); - } - - @Keep - public static class SavedState extends BaseSavedState { - public static Creator CREATOR = - new Creator() { - @Override - public SavedState createFromParcel(Parcel source) { - return new SavedState(source); - } - - @Override - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - - private String previousCompletedSplashIsolate; - private Bundle splashScreenState; - - SavedState(Parcelable superState) { - super(superState); - } - - SavedState(Parcel source) { - super(source); - previousCompletedSplashIsolate = source.readString(); - splashScreenState = source.readBundle(getClass().getClassLoader()); - } - - @Override - public void writeToParcel(Parcel out, int flags) { - super.writeToParcel(out, flags); - out.writeString(previousCompletedSplashIsolate); - out.writeBundle(splashScreenState); - } - } -} diff --git a/shell/platform/android/io/flutter/embedding/android/SplashScreen.java b/shell/platform/android/io/flutter/embedding/android/SplashScreen.java deleted file mode 100644 index c432d04581101..0000000000000 --- a/shell/platform/android/io/flutter/embedding/android/SplashScreen.java +++ /dev/null @@ -1,108 +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. - -package io.flutter.embedding.android; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.os.Bundle; -import android.view.View; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -/** - * Splash screen configuration for a given Flutter experience. - * - *

Implementations provide a visual representation of a splash screen in {@link - * #createSplashView(Context, Bundle)}, and implement a transition from the splash UI to Flutter's - * UI in {@link #transitionToFlutter(Runnable)}. - * - *

Please use the new Splash screen API available on Android S. On lower versions of Android, - * it's no longer necessary to display a splash screen to wait for the Flutter first frame. - * - * @deprecated - */ -@Deprecated -public interface SplashScreen { - /** - * Creates a {@code View} to be displayed as a splash screen before Flutter renders its first - * frame. - * - *

This method can be called at any time, and may be called multiple times depending on Android - * configuration changes that require recreation of a view hierarchy. Implementers that provide a - * stateful splash view, such as one with animations, should take care to migrate that animation - * state from the previously returned splash view to the newly created splash view. - * - * @param context The current context. e.g. The activity. - * @param savedInstanceState If the activity is being re-initialized after previously being shut - * down then this Bundle contains the data it most recently supplied in {@code - * onSaveInstanceState(Bundle)}. - * @return The splash screen view. - */ - @Nullable - View createSplashView(@NonNull Context context, @Nullable Bundle savedInstanceState); - - /** - * Invoked by Flutter when Flutter has rendered its first frame, and would like the {@code - * splashView} to disappear. - * - *

The provided {@code onTransitionComplete} callback must be invoked when the splash {@code - * View} has finished transitioning itself away. The splash {@code View} will be removed and - * destroyed when the callback is invoked. - * - * @param onTransitionComplete The callback after the transition has completed. - */ - void transitionToFlutter(@NonNull Runnable onTransitionComplete); - - /** - * Returns {@code true} if the splash {@code View} built by this {@code SplashScreen} remembers - * its transition progress across configuration changes by saving that progress to {@code View} - * state. Returns {@code false} otherwise. - * - *

The typical return value for this method is {@code false}. When the return value is {@code - * false}, the following can happen: - * - *

    - *
  1. Splash {@code View} begins transitioning to the Flutter UI. - *
  2. A configuration change occurs, like an orientation change, and the {@code Activity} is - * re-created, along with the {@code View} hierarchy. - *
  3. The remainder of the splash transition is skipped and the Flutter UI is displayed. - *
- * - * In the vast majority of cases, skipping a little bit of the splash transition should be - * acceptable. Most users will never experience such a situation, and those that do are unlikely - * to notice the visual artifact. However, a workaround is available for those developers who need - * it. - * - *

Returning {@code true} from this method will cause the given splash {@code View} to be - * displayed in the {@code View} hierarchy, even if Flutter has already rendered its first frame. - * It is then the responsibility of the splash {@code View} to remember its previous transition - * progress, restart any animations, and then trigger its completion callback when appropriate. It - * is also the responsibility of the splash {@code View} to immediately invoke the completion - * callback if it has already completed its transition. By meeting these requirements, and - * returning {@code true} from this method, the splash screen experience will be completely - * seamless, including configuration changes. - * - * @return True if the given splash {@code View} should be displayed in the {@code View} - * hierarchy. - */ - // We suppress NewApi because the CI linter thinks that "default" methods are unsupported. - @SuppressLint("NewApi") - default boolean doesSplashViewRememberItsTransition() { - return false; - } - - /** - * Returns whatever state is necessary to restore a splash {@code View} after destruction and - * recreation, e.g., orientation change. - * - * @return Bundle used to restore a splash screen state. - */ - // We suppress NewApi because the CI linter thinks that "default" methods are unsupported. - @SuppressLint("NewApi") - @Nullable - default Bundle saveSplashScreenState() { - return null; - } -} diff --git a/shell/platform/android/io/flutter/embedding/android/SplashScreenProvider.java b/shell/platform/android/io/flutter/embedding/android/SplashScreenProvider.java deleted file mode 100644 index f048c9f8c9eae..0000000000000 --- a/shell/platform/android/io/flutter/embedding/android/SplashScreenProvider.java +++ /dev/null @@ -1,28 +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. - -package io.flutter.embedding.android; - -import androidx.annotation.Nullable; - -/** - * Provides a {@link SplashScreen} to display while Flutter initializes and renders its first frame. - * - *

Flutter now automatically keeps the Android launch screen displayed until Flutter has drawn - * the first frame, and thus, there is no longer a need to provide an implementation of this - * interface. - * - * @deprecated - */ -@Deprecated -public interface SplashScreenProvider { - /** - * Provides a {@link SplashScreen} to display while Flutter initializes and renders its first - * frame. - * - * @return The splash screen. - */ - @Nullable - SplashScreen provideSplashScreen(); -} diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java index 8930a92f3eaf8..05e724dd3ecba 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java @@ -18,8 +18,6 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; -import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; import android.net.Uri; import android.view.View; import androidx.annotation.NonNull; @@ -1229,9 +1227,6 @@ public void flutterSurfaceViewVisibilityChangedWithFlutterView() { @Test public void itDoesNotDelayTheFirstDrawWhenRequestedAndWithAProvidedSplashScreen() { - when(mockHost.provideSplashScreen()) - .thenReturn(new DrawableSplashScreen(new ColorDrawable(Color.GRAY))); - // ---- Test setup ---- // Create the real object that we're testing. FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost); diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java index 132d2a2389360..9f9ecd6d63774 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java @@ -14,13 +14,11 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.robolectric.Shadows.shadowOf; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; -import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; @@ -432,19 +430,6 @@ public void itDoesNotRegisterPluginsTwiceWhenUsingACachedEngine() { assertEquals(1, registeredEngines.size()); } - @Test - public void itDoesNotCrashWhenSplashScreenMetadataIsNotDefined() { - Intent intent = FlutterActivity.createDefaultIntent(ctx); - ActivityController activityController = - Robolectric.buildActivity(FlutterActivity.class, intent); - FlutterActivity flutterActivity = activityController.get(); - - // We never supplied the metadata to the robolectric activity info so it doesn't exist. - SplashScreen splashScreen = flutterActivity.provideSplashScreen(); - // It should quietly return a null and not crash. - assertNull(splashScreen); - } - @Test public void itDoesNotReleaseEnginewhenDetachFromFlutterEngine() { FlutterActivityAndFragmentDelegate mockDelegate = @@ -499,80 +484,6 @@ public void itReleaseEngineWhenOnDestroy() { assertFalse(mockDelegate.isAttached()); } - @Test - @Config( - sdk = Build.VERSION_CODES.KITKAT, - shadows = {SplashShadowResources.class}) - public void itLoadsSplashScreenDrawable() throws PackageManager.NameNotFoundException { - Intent intent = FlutterActivity.createDefaultIntent(ctx); - ActivityController activityController = - Robolectric.buildActivity(FlutterActivity.class, intent); - FlutterActivity flutterActivity = activityController.get(); - - // Inject splash screen drawable resource id in the metadata. - PackageManager pm = ctx.getPackageManager(); - ActivityInfo activityInfo = - pm.getActivityInfo(flutterActivity.getComponentName(), PackageManager.GET_META_DATA); - activityInfo.metaData = new Bundle(); - activityInfo.metaData.putInt( - FlutterActivityLaunchConfigs.SPLASH_SCREEN_META_DATA_KEY, - SplashShadowResources.SPLASH_DRAWABLE_ID); - shadowOf(ctx.getPackageManager()).addOrUpdateActivity(activityInfo); - - // It should load the drawable. - SplashScreen splashScreen = flutterActivity.provideSplashScreen(); - assertNotNull(splashScreen); - } - - @Test - @Config( - sdk = Build.VERSION_CODES.LOLLIPOP, - shadows = {SplashShadowResources.class}) - @TargetApi(21) // Theme references in drawables requires API 21+ - public void itLoadsThemedSplashScreenDrawable() throws PackageManager.NameNotFoundException { - // A drawable with theme references can be parsed only if the app theme is supplied - // in getDrawable methods. This test verifies it by fetching a (fake) themed drawable. - // On failure, a Resource.NotFoundException will ocurr. - Intent intent = FlutterActivity.createDefaultIntent(ctx); - ActivityController activityController = - Robolectric.buildActivity(FlutterActivity.class, intent); - FlutterActivity flutterActivity = activityController.get(); - - // Inject themed splash screen drawable resource id in the metadata. - PackageManager pm = ctx.getPackageManager(); - ActivityInfo activityInfo = - pm.getActivityInfo(flutterActivity.getComponentName(), PackageManager.GET_META_DATA); - activityInfo.metaData = new Bundle(); - activityInfo.metaData.putInt( - FlutterActivityLaunchConfigs.SPLASH_SCREEN_META_DATA_KEY, - SplashShadowResources.THEMED_SPLASH_DRAWABLE_ID); - shadowOf(ctx.getPackageManager()).addOrUpdateActivity(activityInfo); - - // It should load the drawable. - SplashScreen splashScreen = flutterActivity.provideSplashScreen(); - assertNotNull(splashScreen); - } - - @Test - public void itWithMetadataWithoutSplashScreenResourceKeyDoesNotProvideSplashScreen() - throws PackageManager.NameNotFoundException { - Intent intent = FlutterActivity.createDefaultIntent(ctx); - ActivityController activityController = - Robolectric.buildActivity(FlutterActivity.class, intent); - FlutterActivity flutterActivity = activityController.get(); - - // Setup an empty metadata file. - PackageManager pm = ctx.getPackageManager(); - ActivityInfo activityInfo = - pm.getActivityInfo(flutterActivity.getComponentName(), PackageManager.GET_META_DATA); - activityInfo.metaData = new Bundle(); - shadowOf(ctx.getPackageManager()).addOrUpdateActivity(activityInfo); - - // It should not load the drawable. - SplashScreen splashScreen = flutterActivity.provideSplashScreen(); - assertNull(splashScreen); - } - @Test @Config(minSdk = Build.VERSION_CODES.JELLY_BEAN, maxSdk = Build.VERSION_CODES.P) public void fullyDrawn_beforeAndroidQ() { diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java index 137ab26d42272..d569c64c36203 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java @@ -350,12 +350,6 @@ public ExclusiveAppComponent getExclusiveAppComponent() { return null; } - @Nullable - @Override - public SplashScreen provideSplashScreen() { - return null; - } - @Nullable @Override public FlutterEngine provideFlutterEngine(@NonNull Context context) { diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java index 690a08728bc87..17960263e996a 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java @@ -4,19 +4,14 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; -import static org.robolectric.Shadows.shadowOf; -import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; -import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; -import android.os.Build; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; @@ -38,7 +33,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; -import org.robolectric.android.controller.ActivityController; import org.robolectric.annotation.Config; @Config(manifest = Config.NONE) @@ -256,93 +250,6 @@ public void itHandlesNewFragmentRecreationDuringRestoreWhenActivityIsRecreated() assertEquals(0, activity.numberOfEnginesCreated); } - @Test - public void itDoesNotCrashWhenSplashScreenMetadataIsNotDefined() { - Intent intent = FlutterFragmentActivity.createDefaultIntent(ctx); - ActivityController activityController = - Robolectric.buildActivity(FlutterFragmentActivity.class, intent); - FlutterFragmentActivity fragmentActivity = activityController.get(); - - // We never supplied the resource key to robolectric so it doesn't exist. - SplashScreen splashScreen = fragmentActivity.provideSplashScreen(); - // It should quietly return a null and not crash. - assertNull(splashScreen); - } - - @Test - @Config( - sdk = Build.VERSION_CODES.KITKAT, - shadows = {SplashShadowResources.class}) - public void itLoadsSplashScreenDrawable() throws PackageManager.NameNotFoundException { - Intent intent = FlutterFragmentActivity.createDefaultIntent(ctx); - ActivityController activityController = - Robolectric.buildActivity(FlutterFragmentActivity.class, intent); - FlutterFragmentActivity activity = activityController.get(); - - // Inject splash screen drawable resource id in the metadata - PackageManager pm = ctx.getPackageManager(); - ActivityInfo activityInfo = - pm.getActivityInfo(activity.getComponentName(), PackageManager.GET_META_DATA); - activityInfo.metaData = new Bundle(); - activityInfo.metaData.putInt( - FlutterActivityLaunchConfigs.SPLASH_SCREEN_META_DATA_KEY, - SplashShadowResources.SPLASH_DRAWABLE_ID); - shadowOf(ctx.getPackageManager()).addOrUpdateActivity(activityInfo); - - // It should load the drawable. - SplashScreen splashScreen = activity.provideSplashScreen(); - assertNotNull(splashScreen); - } - - @Test - @Config( - sdk = Build.VERSION_CODES.LOLLIPOP, - shadows = {SplashShadowResources.class}) - @TargetApi(21) // Theme references in drawables requires API 21+ - public void itLoadsThemedSplashScreenDrawable() throws PackageManager.NameNotFoundException { - // A drawable with theme references can be parsed only if the app theme is supplied - // in getDrawable methods. This test verifies it by fetching a (fake) themed drawable. - // On failure, a Resource.NotFoundException will ocurr. - Intent intent = FlutterFragmentActivity.createDefaultIntent(ctx); - ActivityController activityController = - Robolectric.buildActivity(FlutterFragmentActivity.class, intent); - FlutterFragmentActivity activity = activityController.get(); - - // Inject themed splash screen drawable resource id in the metadata. - PackageManager pm = ctx.getPackageManager(); - ActivityInfo activityInfo = - pm.getActivityInfo(activity.getComponentName(), PackageManager.GET_META_DATA); - activityInfo.metaData = new Bundle(); - activityInfo.metaData.putInt( - FlutterActivityLaunchConfigs.SPLASH_SCREEN_META_DATA_KEY, - SplashShadowResources.THEMED_SPLASH_DRAWABLE_ID); - shadowOf(ctx.getPackageManager()).addOrUpdateActivity(activityInfo); - - // It should load the drawable. - SplashScreen splashScreen = activity.provideSplashScreen(); - assertNotNull(splashScreen); - } - - @Test - public void itWithMetadataWithoutSplashScreenResourceKeyDoesNotProvideSplashScreen() - throws PackageManager.NameNotFoundException { - Intent intent = FlutterFragmentActivity.createDefaultIntent(ctx); - ActivityController activityController = - Robolectric.buildActivity(FlutterFragmentActivity.class, intent); - FlutterFragmentActivity activity = activityController.get(); - - // Setup an empty metadata file. - PackageManager pm = ctx.getPackageManager(); - ActivityInfo activityInfo = - pm.getActivityInfo(activity.getComponentName(), PackageManager.GET_META_DATA); - activityInfo.metaData = new Bundle(); - shadowOf(ctx.getPackageManager()).addOrUpdateActivity(activityInfo); - - // It should not load the drawable. - SplashScreen splashScreen = activity.provideSplashScreen(); - assertNull(splashScreen); - } - static class FlutterFragmentActivityWithProvidedEngine extends FlutterFragmentActivity { int numberOfEnginesCreated = 0; diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java deleted file mode 100644 index 5371e05b58bbf..0000000000000 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ /dev/null @@ -1,1104 +0,0 @@ -package io.flutter.embedding.android; - -import static junit.framework.TestCase.assertEquals; -import static junit.framework.TestCase.assertFalse; -import static junit.framework.TestCase.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.app.Activity; -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.Insets; -import android.graphics.Rect; -import android.graphics.Region; -import android.hardware.HardwareBuffer; -import android.media.Image; -import android.media.Image.Plane; -import android.media.ImageReader; -import android.os.Build; -import android.provider.Settings; -import android.view.DisplayCutout; -import android.view.Surface; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowInsets; -import android.view.WindowManager; -import android.widget.FrameLayout; -import androidx.core.util.Consumer; -import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.window.layout.FoldingFeature; -import androidx.window.layout.WindowLayoutInfo; -import io.flutter.embedding.engine.FlutterEngine; -import io.flutter.embedding.engine.FlutterJNI; -import io.flutter.embedding.engine.loader.FlutterLoader; -import io.flutter.embedding.engine.renderer.FlutterRenderer; -import io.flutter.embedding.engine.systemchannels.SettingsChannel; -import io.flutter.plugin.platform.PlatformViewsController; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.concurrent.atomic.AtomicReference; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.Spy; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import org.robolectric.Robolectric; -import org.robolectric.Shadows; -import org.robolectric.annotation.Config; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; -import org.robolectric.shadows.ShadowDisplay; - -@Config(manifest = Config.NONE) -@RunWith(AndroidJUnit4.class) -@TargetApi(30) -public class FlutterViewTest { - private final Context ctx = ApplicationProvider.getApplicationContext(); - @Mock FlutterJNI mockFlutterJni; - @Mock FlutterLoader mockFlutterLoader; - @Spy PlatformViewsController platformViewsController; - - @Before - public void setUp() { - MockitoAnnotations.openMocks(this); - when(mockFlutterJni.isAttached()).thenReturn(true); - } - - @Test - public void attachToFlutterEngine_alertsPlatformViews() { - FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController); - - flutterView.attachToFlutterEngine(flutterEngine); - - verify(platformViewsController, times(1)).attachToView(flutterView); - } - - @Test - public void flutterView_importantForAutofillDoesNotExcludeDescendants() { - FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); - - // Value should not exclude descendants because platform views are added as child views and - // can be eligible for autofill (e.g. a WebView). - assertEquals(View.IMPORTANT_FOR_AUTOFILL_YES, flutterView.getImportantForAutofill()); - } - - @Test - public void detachFromFlutterEngine_alertsPlatformViews() { - FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController); - - flutterView.attachToFlutterEngine(flutterEngine); - flutterView.detachFromFlutterEngine(); - - verify(platformViewsController, times(1)).detachFromView(); - } - - @Test - public void detachFromFlutterEngine_turnsOffA11y() { - FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); - when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); - - flutterView.attachToFlutterEngine(flutterEngine); - flutterView.detachFromFlutterEngine(); - - verify(flutterRenderer, times(1)).setSemanticsEnabled(false); - } - - @Test - public void detachFromFlutterEngine_revertImageView() { - FlutterView flutterView = new FlutterView(ctx); - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - - flutterView.attachToFlutterEngine(flutterEngine); - assertFalse(flutterView.renderSurface instanceof FlutterImageView); - - flutterView.convertToImageView(); - assertTrue(flutterView.renderSurface instanceof FlutterImageView); - - flutterView.detachFromFlutterEngine(); - assertFalse(flutterView.renderSurface instanceof FlutterImageView); - } - - @Test - public void detachFromFlutterEngine_removeImageView() { - FlutterView flutterView = new FlutterView(ctx); - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - - flutterView.attachToFlutterEngine(flutterEngine); - flutterView.convertToImageView(); - assertEquals(flutterView.getChildCount(), 2); - View view = flutterView.getChildAt(1); - assertTrue(view instanceof FlutterImageView); - - flutterView.detachFromFlutterEngine(); - assertEquals(flutterView.getChildCount(), 1); - view = flutterView.getChildAt(0); - assertFalse(view instanceof FlutterImageView); - } - - @Test - public void detachFromFlutterEngine_closesImageView() { - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - - FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); - when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); - - FlutterImageView imageViewMock = mock(FlutterImageView.class); - when(imageViewMock.getAttachedRenderer()).thenReturn(flutterRenderer); - - FlutterView flutterView = spy(new FlutterView(ctx)); - when(flutterView.createImageView()).thenReturn(imageViewMock); - - flutterView.attachToFlutterEngine(flutterEngine); - - assertFalse(flutterView.renderSurface == imageViewMock); - - flutterView.convertToImageView(); - assertTrue(flutterView.renderSurface == imageViewMock); - - flutterView.detachFromFlutterEngine(); - assertFalse(flutterView.renderSurface == imageViewMock); - verify(imageViewMock, times(1)).closeImageReader(); - } - - @Test - public void flutterImageView_revertImageViewAndAvoidNPE() { - FlutterView flutterView = new FlutterView(ctx); - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); - when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); - - flutterView.attachToFlutterEngine(flutterEngine); - flutterView.convertToImageView(); - assertTrue(flutterView.renderSurface instanceof FlutterImageView); - - // Register a `FlutterUiDisplayListener` callback. - // During callback execution it will invoke `flutterImageView.detachFromRenderer()`. - flutterView.revertImageView( - () -> { - // No-op - }); - assertFalse(flutterView.renderSurface instanceof FlutterImageView); - - flutterView.detachFromFlutterEngine(); - assertEquals(null, flutterView.getCurrentImageSurface()); - - // Invoke all registered `FlutterUiDisplayListener` callback - mockFlutterJni.onFirstFrame(); - } - - @Test - public void onConfigurationChanged_fizzlesWhenNullEngine() { - FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - - Configuration configuration = ctx.getResources().getConfiguration(); - // 1 invocation of channels. - flutterView.attachToFlutterEngine(flutterEngine); - flutterView.onConfigurationChanged(configuration); - flutterView.detachFromFlutterEngine(); - - // Should fizzle. - flutterView.onConfigurationChanged(configuration); - - verify(flutterEngine, times(1)).getLocalizationPlugin(); - verify(flutterEngine, times(2)).getSettingsChannel(); - } - - @Test - public void onConfigurationChanged_notifiesEngineOfDisplaySize() { - FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - - Configuration configuration = ctx.getResources().getConfiguration(); - - flutterView.attachToFlutterEngine(flutterEngine); - flutterView.onConfigurationChanged(configuration); - - verify(flutterEngine, times(1)) - .updateDisplayMetrics(any(Float.class), any(Float.class), any(Float.class)); - } - - // TODO(mattcarroll): turn this into an e2e test. GitHub #42990 - @Test - public void itSendsLightPlatformBrightnessToFlutter() { - // Setup test. - AtomicReference reportedBrightness = - new AtomicReference<>(); - - // FYI - The default brightness is LIGHT, which is why we don't need to configure it. - FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - - SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); - SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); - when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setNativeSpellCheckServiceDefined(any(Boolean.class))) - .thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setBrieflyShowPassword(any(Boolean.class))) - .thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setUse24HourFormat(any(Boolean.class))).thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setPlatformBrightness(any(SettingsChannel.PlatformBrightness.class))) - .thenAnswer( - new Answer() { - @Override - public SettingsChannel.MessageBuilder answer(InvocationOnMock invocation) - throws Throwable { - reportedBrightness.set( - (SettingsChannel.PlatformBrightness) invocation.getArguments()[0]); - return fakeMessageBuilder; - } - }); - when(fakeSettingsChannel.startMessage()).thenReturn(fakeMessageBuilder); - when(flutterEngine.getSettingsChannel()).thenReturn(fakeSettingsChannel); - - flutterView.attachToFlutterEngine(flutterEngine); - - // Execute behavior under test. - flutterView.sendUserSettingsToFlutter(); - - // Verify results. - assertEquals(SettingsChannel.PlatformBrightness.light, reportedBrightness.get()); - } - - // TODO(mattcarroll): turn this into an e2e test. GitHub #42990 - @Test - public void itSendsDarkPlatformBrightnessToFlutter() { - // Setup test. - AtomicReference reportedBrightness = - new AtomicReference<>(); - - Context spiedContext = spy(Robolectric.setupActivity(Activity.class)); - - Resources spiedResources = spy(spiedContext.getResources()); - when(spiedContext.getResources()).thenReturn(spiedResources); - - Configuration spiedConfiguration = spy(spiedResources.getConfiguration()); - spiedConfiguration.uiMode = - (spiedResources.getConfiguration().uiMode | Configuration.UI_MODE_NIGHT_YES) - & ~Configuration.UI_MODE_NIGHT_NO; - when(spiedResources.getConfiguration()).thenReturn(spiedConfiguration); - - FlutterView flutterView = new FlutterView(spiedContext); - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - - SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); - SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); - when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setNativeSpellCheckServiceDefined(any(Boolean.class))) - .thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setBrieflyShowPassword(any(Boolean.class))) - .thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setUse24HourFormat(any(Boolean.class))).thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setPlatformBrightness(any(SettingsChannel.PlatformBrightness.class))) - .thenAnswer( - new Answer() { - @Override - public SettingsChannel.MessageBuilder answer(InvocationOnMock invocation) - throws Throwable { - reportedBrightness.set( - (SettingsChannel.PlatformBrightness) invocation.getArguments()[0]); - return fakeMessageBuilder; - } - }); - when(fakeSettingsChannel.startMessage()).thenReturn(fakeMessageBuilder); - when(flutterEngine.getSettingsChannel()).thenReturn(fakeSettingsChannel); - - // Execute behavior under test. - flutterView.attachToFlutterEngine(flutterEngine); - flutterView.sendUserSettingsToFlutter(); - - // Verify results. - assertEquals(SettingsChannel.PlatformBrightness.dark, reportedBrightness.get()); - } - - @Test - public void itSendsTextShowPasswordToFrameworkOnAttach() { - // Setup test. - AtomicReference reportedShowPassword = new AtomicReference<>(); - - FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - Settings.System.putInt( - flutterView.getContext().getContentResolver(), Settings.System.TEXT_SHOW_PASSWORD, 1); - - SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); - SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); - when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setNativeSpellCheckServiceDefined(any(Boolean.class))) - .thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setPlatformBrightness(any(SettingsChannel.PlatformBrightness.class))) - .thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setUse24HourFormat(any(Boolean.class))).thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setBrieflyShowPassword(any(Boolean.class))) - .thenAnswer( - new Answer() { - @Override - public SettingsChannel.MessageBuilder answer(InvocationOnMock invocation) - throws Throwable { - reportedShowPassword.set((Boolean) invocation.getArguments()[0]); - return fakeMessageBuilder; - } - }); - when(fakeSettingsChannel.startMessage()).thenReturn(fakeMessageBuilder); - when(flutterEngine.getSettingsChannel()).thenReturn(fakeSettingsChannel); - - flutterView.attachToFlutterEngine(flutterEngine); - - // Verify results. - assertTrue(reportedShowPassword.get()); - } - - public void itSendsTextHidePasswordToFrameworkOnAttach() { - // Setup test. - AtomicReference reportedShowPassword = new AtomicReference<>(); - - FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - Settings.System.putInt( - flutterView.getContext().getContentResolver(), Settings.System.TEXT_SHOW_PASSWORD, 0); - - SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); - SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); - when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setPlatformBrightness(any(SettingsChannel.PlatformBrightness.class))) - .thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setUse24HourFormat(any(Boolean.class))).thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setBrieflyShowPassword(any(Boolean.class))) - .thenAnswer( - new Answer() { - @Override - public SettingsChannel.MessageBuilder answer(InvocationOnMock invocation) - throws Throwable { - reportedShowPassword.set((Boolean) invocation.getArguments()[0]); - return fakeMessageBuilder; - } - }); - when(fakeSettingsChannel.startMessage()).thenReturn(fakeMessageBuilder); - when(flutterEngine.getSettingsChannel()).thenReturn(fakeSettingsChannel); - - flutterView.attachToFlutterEngine(flutterEngine); - - // Verify results. - assertFalse(reportedShowPassword.get()); - } - - // This test uses the API 30+ Algorithm for window insets. The legacy algorithm is - // set to -1 values, so it is clear if the wrong algorithm is used. - @Test - @TargetApi(30) - @Config( - sdk = 30, - shadows = { - FlutterViewTest.ShadowFullscreenView.class, - FlutterViewTest.ShadowFullscreenViewGroup.class - }) - public void setPaddingTopToZeroForFullscreenMode() { - FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); - when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); - - // When we attach a new FlutterView to the engine without any system insets, the viewport - // metrics - // default to 0. - flutterView.attachToFlutterEngine(flutterEngine); - ArgumentCaptor viewportMetricsCaptor = - ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class); - verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); - assertEquals(0, viewportMetricsCaptor.getValue().viewPaddingTop); - - // Then we simulate the system applying a window inset. - WindowInsets windowInsets = - new WindowInsets.Builder() - .setInsets( - android.view.WindowInsets.Type.navigationBars() - | android.view.WindowInsets.Type.statusBars(), - Insets.of(100, 100, 100, 100)) - .build(); - flutterView.onApplyWindowInsets(windowInsets); - - // Verify. - verify(flutterRenderer, times(3)).setViewportMetrics(viewportMetricsCaptor.capture()); - validateViewportMetricPadding(viewportMetricsCaptor, 100, 100, 100, 100); - } - - // This test uses the pre-API 30 Algorithm for window insets. - @Test - @TargetApi(28) - @Config( - sdk = 28, - shadows = { - FlutterViewTest.ShadowFullscreenView.class, - FlutterViewTest.ShadowFullscreenViewGroup.class - }) - public void setPaddingTopToZeroForFullscreenModeLegacy() { - FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); - when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); - - // When we attach a new FlutterView to the engine without any system insets, the viewport - // metrics - // default to 0. - flutterView.attachToFlutterEngine(flutterEngine); - ArgumentCaptor viewportMetricsCaptor = - ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class); - verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); - assertEquals(0, viewportMetricsCaptor.getValue().viewPaddingTop); - - // Then we simulate the system applying a window inset. - WindowInsets windowInsets = mock(WindowInsets.class); - mockSystemWindowInsets(windowInsets, 100, 100, 100, 100); - flutterView.onApplyWindowInsets(windowInsets); - - // Verify. - verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); - validateViewportMetricPadding(viewportMetricsCaptor, 100, 0, 100, 0); - } - - // This test uses the API 30+ Algorithm for window insets. The legacy algorithm is - // set to -1 values, so it is clear if the wrong algorithm is used. - @Test - @TargetApi(30) - @Config(sdk = 30) - public void reportSystemInsetWhenNotFullscreen() { - // Without custom shadows, the default system ui visibility flags is 0. - FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); - assertEquals(0, flutterView.getSystemUiVisibility()); - - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); - when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); - - // When we attach a new FlutterView to the engine without any system insets, the viewport - // metrics - // default to 0. - flutterView.attachToFlutterEngine(flutterEngine); - ArgumentCaptor viewportMetricsCaptor = - ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class); - verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); - assertEquals(0, viewportMetricsCaptor.getValue().viewPaddingTop); - - // Then we simulate the system applying a window inset. - WindowInsets windowInsets = - new WindowInsets.Builder() - .setInsets( - android.view.WindowInsets.Type.navigationBars() - | android.view.WindowInsets.Type.statusBars(), - Insets.of(100, 100, 100, 100)) - .build(); - flutterView.onApplyWindowInsets(windowInsets); - - // Verify. - verify(flutterRenderer, times(3)).setViewportMetrics(viewportMetricsCaptor.capture()); - // Top padding is reported as-is. - validateViewportMetricPadding(viewportMetricsCaptor, 100, 100, 100, 100); - } - - // This test uses the pre-API 30 Algorithm for window insets. - @Test - @TargetApi(28) - @Config(sdk = 28) - public void reportSystemInsetWhenNotFullscreenLegacy() { - // Without custom shadows, the default system ui visibility flags is 0. - FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); - assertEquals(0, flutterView.getSystemUiVisibility()); - - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); - when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); - - // When we attach a new FlutterView to the engine without any system insets, the viewport - // metrics - // default to 0. - flutterView.attachToFlutterEngine(flutterEngine); - ArgumentCaptor viewportMetricsCaptor = - ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class); - verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); - assertEquals(0, viewportMetricsCaptor.getValue().viewPaddingTop); - - // Then we simulate the system applying a window inset. - WindowInsets windowInsets = mock(WindowInsets.class); - mockSystemWindowInsets(windowInsets, 100, 100, 100, 100); - flutterView.onApplyWindowInsets(windowInsets); - - // Verify. - verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); - // Top padding is reported as-is. - validateViewportMetricPadding(viewportMetricsCaptor, 100, 100, 100, 0); - } - - @Test - @Config(minSdk = 23, maxSdk = 29, qualifiers = "land") - public void systemInsetHandlesFullscreenNavbarRight() { - FlutterView flutterView = spy(new FlutterView(ctx)); - setExpectedDisplayRotation(Surface.ROTATION_90); - assertEquals(0, flutterView.getSystemUiVisibility()); - when(flutterView.getWindowSystemUiVisibility()) - .thenReturn(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); - when(flutterView.getContext()).thenReturn(ctx); - - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); - when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); - - // When we attach a new FlutterView to the engine without any system insets, - // the viewport metrics default to 0. - flutterView.attachToFlutterEngine(flutterEngine); - ArgumentCaptor viewportMetricsCaptor = - ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class); - verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); - assertEquals(0, viewportMetricsCaptor.getValue().viewPaddingTop); - - // Then we simulate the system applying a window inset. - WindowInsets windowInsets = mock(WindowInsets.class); - mockSystemWindowInsets(windowInsets, 100, 100, 100, 100); - mockSystemGestureInsetsIfNeed(windowInsets); - - flutterView.onApplyWindowInsets(windowInsets); - - verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); - // Top padding is removed due to full screen. - // Right padding is zero because the rotation is 90deg - // Bottom padding is removed due to hide navigation. - validateViewportMetricPadding(viewportMetricsCaptor, 100, 0, 0, 0); - } - - @Test - @Config(minSdk = 20, maxSdk = 22, qualifiers = "land") - public void systemInsetHandlesFullscreenNavbarRightBelowSDK23() { - FlutterView flutterView = spy(new FlutterView(ctx)); - setExpectedDisplayRotation(Surface.ROTATION_270); - assertEquals(0, flutterView.getSystemUiVisibility()); - when(flutterView.getWindowSystemUiVisibility()) - .thenReturn(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); - when(flutterView.getContext()).thenReturn(ctx); - - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); - when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); - - // When we attach a new FlutterView to the engine without any system insets, - // the viewport metrics default to 0. - flutterView.attachToFlutterEngine(flutterEngine); - ArgumentCaptor viewportMetricsCaptor = - ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class); - verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); - assertEquals(0, viewportMetricsCaptor.getValue().viewPaddingTop); - - // Then we simulate the system applying a window inset. - WindowInsets windowInsets = mock(WindowInsets.class); - mockSystemWindowInsets(windowInsets, 100, 100, 100, 100); - mockSystemGestureInsetsIfNeed(windowInsets); - - flutterView.onApplyWindowInsets(windowInsets); - - verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); - // Top padding is removed due to full screen. - // Right padding is zero because the rotation is 270deg under SDK 23 - // Bottom padding is removed due to hide navigation. - validateViewportMetricPadding(viewportMetricsCaptor, 100, 0, 0, 0); - } - - @Test - @Config(minSdk = 23, maxSdk = 29, qualifiers = "land") - public void systemInsetHandlesFullscreenNavbarLeft() { - FlutterView flutterView = spy(new FlutterView(ctx)); - setExpectedDisplayRotation(Surface.ROTATION_270); - assertEquals(0, flutterView.getSystemUiVisibility()); - when(flutterView.getWindowSystemUiVisibility()) - .thenReturn(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); - when(flutterView.getContext()).thenReturn(ctx); - - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); - when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); - - // When we attach a new FlutterView to the engine without any system insets, - // the viewport metrics default to 0. - flutterView.attachToFlutterEngine(flutterEngine); - ArgumentCaptor viewportMetricsCaptor = - ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class); - verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); - assertEquals(0, viewportMetricsCaptor.getValue().viewPaddingTop); - - // Then we simulate the system applying a window inset. - WindowInsets windowInsets = mock(WindowInsets.class); - mockSystemWindowInsets(windowInsets, 100, 100, 100, 100); - mockSystemGestureInsetsIfNeed(windowInsets); - - flutterView.onApplyWindowInsets(windowInsets); - - verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); - // Left padding is zero because the rotation is 270deg - // Top padding is removed due to full screen. - // Bottom padding is removed due to hide navigation. - validateViewportMetricPadding(viewportMetricsCaptor, 0, 0, 100, 0); - } - - // This test uses the API 30+ Algorithm for window insets. The legacy algorithm is - // set to -1 values, so it is clear if the wrong algorithm is used. - @Test - @TargetApi(30) - @Config(sdk = 30, qualifiers = "land") - public void systemInsetGetInsetsFullscreen() { - FlutterView flutterView = spy(new FlutterView(ctx)); - setExpectedDisplayRotation(Surface.ROTATION_270); - assertEquals(0, flutterView.getSystemUiVisibility()); - when(flutterView.getWindowSystemUiVisibility()) - .thenReturn(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); - when(flutterView.getContext()).thenReturn(ctx); - - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); - when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); - - // When we attach a new FlutterView to the engine without any system insets, - // the viewport metrics default to 0. - flutterView.attachToFlutterEngine(flutterEngine); - ArgumentCaptor viewportMetricsCaptor = - ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class); - verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); - assertEquals(0, viewportMetricsCaptor.getValue().viewPaddingTop); - - Insets insets = Insets.of(10, 20, 30, 40); - // Then we simulate the system applying a window inset. - WindowInsets windowInsets = mock(WindowInsets.class); - mockSystemWindowInsets(windowInsets, -1, -1, -1, -1); - when(windowInsets.getInsets(anyInt())).thenReturn(insets); - - flutterView.onApplyWindowInsets(windowInsets); - - verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); - validateViewportMetricPadding(viewportMetricsCaptor, 10, 20, 30, 40); - } - - // This test uses the pre-API 30 Algorithm for window insets. - @Test - @TargetApi(28) - @Config(sdk = 28, qualifiers = "land") - public void systemInsetGetInsetsFullscreenLegacy() { - FlutterView flutterView = spy(new FlutterView(ctx)); - setExpectedDisplayRotation(Surface.ROTATION_270); - assertEquals(0, flutterView.getSystemUiVisibility()); - when(flutterView.getWindowSystemUiVisibility()) - .thenReturn(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); - when(flutterView.getContext()).thenReturn(ctx); - - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); - when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); - - // When we attach a new FlutterView to the engine without any system insets, - // the viewport metrics default to 0. - flutterView.attachToFlutterEngine(flutterEngine); - ArgumentCaptor viewportMetricsCaptor = - ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class); - verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); - assertEquals(0, viewportMetricsCaptor.getValue().viewPaddingTop); - - // Then we simulate the system applying a window inset. - WindowInsets windowInsets = mock(WindowInsets.class); - mockSystemWindowInsets(windowInsets, 102, 100, 103, 101); - - flutterView.onApplyWindowInsets(windowInsets); - - verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); - // Left padding is zero because the rotation is 270deg - // Top padding is removed due to full screen. - // Bottom padding is removed due to hide navigation. - validateViewportMetricPadding(viewportMetricsCaptor, 0, 0, 103, 0); - } - - // This test uses the API 30+ Algorithm for window insets. The legacy algorithm is - // set to -1 values, so it is clear if the wrong algorithm is used. - @Test - @TargetApi(30) - @Config(sdk = 30, qualifiers = "land") - public void systemInsetDisplayCutoutSimple() { - FlutterView flutterView = spy(new FlutterView(ctx)); - assertEquals(0, flutterView.getSystemUiVisibility()); - when(flutterView.getWindowSystemUiVisibility()).thenReturn(0); - when(flutterView.getContext()).thenReturn(ctx); - - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); - when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); - - // When we attach a new FlutterView to the engine without any system insets, - // the viewport metrics default to 0. - flutterView.attachToFlutterEngine(flutterEngine); - ArgumentCaptor viewportMetricsCaptor = - ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class); - verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); - assertEquals(0, viewportMetricsCaptor.getValue().viewPaddingTop); - - Insets insets = Insets.of(100, 100, 100, 100); - Insets systemGestureInsets = Insets.of(110, 110, 110, 110); - // Then we simulate the system applying a window inset. - WindowInsets windowInsets = mock(WindowInsets.class); - DisplayCutout displayCutout = mock(DisplayCutout.class); - mockSystemWindowInsets(windowInsets, -1, -1, -1, -1); - when(windowInsets.getInsets(anyInt())).thenReturn(insets); - when(windowInsets.getSystemGestureInsets()).thenReturn(systemGestureInsets); - when(windowInsets.getDisplayCutout()).thenReturn(displayCutout); - - Insets waterfallInsets = Insets.of(200, 0, 200, 0); - when(displayCutout.getWaterfallInsets()).thenReturn(waterfallInsets); - when(displayCutout.getSafeInsetTop()).thenReturn(150); - when(displayCutout.getSafeInsetBottom()).thenReturn(150); - when(displayCutout.getSafeInsetLeft()).thenReturn(150); - when(displayCutout.getSafeInsetRight()).thenReturn(150); - - flutterView.onApplyWindowInsets(windowInsets); - - verify(flutterRenderer, times(2)).setViewportMetrics(viewportMetricsCaptor.capture()); - validateViewportMetricPadding(viewportMetricsCaptor, 200, 150, 200, 150); - - assertEquals(100, viewportMetricsCaptor.getValue().viewInsetTop); - } - - @Test - public void itRegistersAndUnregistersToWindowManager() { - Context context = Robolectric.setupActivity(Activity.class); - FlutterView flutterView = spy(new FlutterView(context)); - ShadowDisplay display = - Shadows.shadowOf( - ((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay()); - WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo = - mock(WindowInfoRepositoryCallbackAdapterWrapper.class); - // For reasoning behing using doReturn instead of when, read "Important gotcha" at - // https://www.javadoc.io/doc/org.mockito/mockito-core/1.10.19/org/mockito/Mockito.html#13 - doReturn(windowInfoRepo).when(flutterView).createWindowInfoRepo(); - - // When a new FlutterView is attached to the window - flutterView.onAttachedToWindow(); - - // Then the WindowManager callback is registered - verify(windowInfoRepo, times(1)).addWindowLayoutInfoListener(any(), any(), any()); - - // When the FlutterView is detached from the window - flutterView.onDetachedFromWindow(); - - // Then the WindowManager callback is unregistered - verify(windowInfoRepo, times(1)).removeWindowLayoutInfoListener(any()); - } - - @Test - public void itSendsHingeDisplayFeatureToFlutter() { - Context context = Robolectric.setupActivity(Activity.class); - FlutterView flutterView = spy(new FlutterView(context)); - ShadowDisplay display = - Shadows.shadowOf( - ((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay()); - when(flutterView.getContext()).thenReturn(context); - WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo = - mock(WindowInfoRepositoryCallbackAdapterWrapper.class); - doReturn(windowInfoRepo).when(flutterView).createWindowInfoRepo(); - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); - when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); - - FoldingFeature displayFeature = mock(FoldingFeature.class); - when(displayFeature.getBounds()).thenReturn(new Rect(0, 0, 100, 100)); - when(displayFeature.getOcclusionType()).thenReturn(FoldingFeature.OcclusionType.FULL); - when(displayFeature.getState()).thenReturn(FoldingFeature.State.FLAT); - - WindowLayoutInfo testWindowLayout = new WindowLayoutInfo(Arrays.asList(displayFeature)); - - // When FlutterView is attached to the engine and window, and a hinge display feature exists - flutterView.attachToFlutterEngine(flutterEngine); - ArgumentCaptor viewportMetricsCaptor = - ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class); - verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); - assertEquals(Arrays.asList(), viewportMetricsCaptor.getValue().displayFeatures); - flutterView.onAttachedToWindow(); - ArgumentCaptor> wmConsumerCaptor = - ArgumentCaptor.forClass((Class) Consumer.class); - verify(windowInfoRepo).addWindowLayoutInfoListener(any(), any(), wmConsumerCaptor.capture()); - Consumer wmConsumer = wmConsumerCaptor.getValue(); - wmConsumer.accept(testWindowLayout); - - // Then the Renderer receives the display feature - verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); - assertEquals( - FlutterRenderer.DisplayFeatureType.HINGE, - viewportMetricsCaptor.getValue().displayFeatures.get(0).type); - assertEquals( - FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, - viewportMetricsCaptor.getValue().displayFeatures.get(0).state); - assertEquals( - new Rect(0, 0, 100, 100), viewportMetricsCaptor.getValue().displayFeatures.get(0).bounds); - } - - @Test - public void flutterImageView_acquiresImageAndInvalidates() { - final ImageReader mockReader = mock(ImageReader.class); - when(mockReader.getMaxImages()).thenReturn(2); - - final FlutterImageView imageView = - spy(new FlutterImageView(ctx, mockReader, FlutterImageView.SurfaceKind.background)); - - final FlutterJNI jni = mock(FlutterJNI.class); - imageView.attachToRenderer(new FlutterRenderer(jni)); - - final Image mockImage = mock(Image.class); - when(mockReader.acquireLatestImage()).thenReturn(mockImage); - - assertTrue(imageView.acquireLatestImage()); - verify(mockReader, times(1)).acquireLatestImage(); - verify(imageView, times(1)).invalidate(); - } - - @Test - @SuppressLint("WrongCall") /*View#onDraw*/ - public void flutterImageView_acquiresImageClosesPreviousImageUnlessNoNewImage() { - final ImageReader mockReader = mock(ImageReader.class); - when(mockReader.getMaxImages()).thenReturn(3); - - final Image mockImage = mock(Image.class); - when(mockImage.getPlanes()).thenReturn(new Plane[0]); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - final HardwareBuffer mockHardwareBuffer = mock(HardwareBuffer.class); - when(mockHardwareBuffer.getUsage()).thenReturn(HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE); - when(mockImage.getHardwareBuffer()).thenReturn(mockHardwareBuffer); - } - // Mock no latest image on the second time - when(mockReader.acquireLatestImage()) - .thenReturn(mockImage) - .thenReturn(null) - .thenReturn(mockImage); - - final FlutterImageView imageView = - spy(new FlutterImageView(ctx, mockReader, FlutterImageView.SurfaceKind.background)); - - final FlutterJNI jni = mock(FlutterJNI.class); - imageView.attachToRenderer(new FlutterRenderer(jni)); - doNothing().when(imageView).invalidate(); - - assertTrue(imageView.acquireLatestImage()); // No previous, acquire latest image - assertFalse( - imageView.acquireLatestImage()); // Mock no image when acquire, don't close, and assertFalse - assertTrue(imageView.acquireLatestImage()); // Acquire latest image and close previous - assertTrue(imageView.acquireLatestImage()); // Acquire latest image and close previous - assertTrue(imageView.acquireLatestImage()); // Acquire latest image and close previous - verify(mockImage, times(3)).close(); // Close 3 times - - imageView.onDraw(mock(Canvas.class)); // Draw latest image - - assertTrue(imageView.acquireLatestImage()); // acquire latest image and close previous - - imageView.onDraw(mock(Canvas.class)); // Draw latest image - imageView.onDraw(mock(Canvas.class)); // Draw latest image - imageView.onDraw(mock(Canvas.class)); // Draw latest image - - verify(mockReader, times(6)).acquireLatestImage(); - } - - @Test - public void flutterImageView_detachFromRendererClosesPreviousImage() { - final ImageReader mockReader = mock(ImageReader.class); - when(mockReader.getMaxImages()).thenReturn(2); - - final Image mockImage = mock(Image.class); - when(mockReader.acquireLatestImage()).thenReturn(mockImage); - - final FlutterImageView imageView = - spy(new FlutterImageView(ctx, mockReader, FlutterImageView.SurfaceKind.background)); - - final FlutterJNI jni = mock(FlutterJNI.class); - imageView.attachToRenderer(new FlutterRenderer(jni)); - - doNothing().when(imageView).invalidate(); - imageView.acquireLatestImage(); - imageView.acquireLatestImage(); - verify(mockImage, times(1)).close(); - - imageView.detachFromRenderer(); - // There's an acquireLatestImage() in detachFromRenderer(), - // so it will be 2 times called close() inside detachFromRenderer() - verify(mockImage, times(3)).close(); - } - - @Test - public void flutterImageView_workaroundWithOnePixelWhenResizeWithZero() { - final ImageReader mockReader = mock(ImageReader.class); - when(mockReader.getMaxImages()).thenReturn(2); - - final FlutterImageView imageView = - spy(new FlutterImageView(ctx, mockReader, FlutterImageView.SurfaceKind.background)); - - final FlutterJNI jni = mock(FlutterJNI.class); - imageView.attachToRenderer(new FlutterRenderer(jni)); - - final Image mockImage = mock(Image.class); - when(mockReader.acquireLatestImage()).thenReturn(mockImage); - - final int incorrectWidth = 0; - final int incorrectHeight = -100; - imageView.resizeIfNeeded(incorrectWidth, incorrectHeight); - assertEquals(1, imageView.getImageReader().getWidth()); - assertEquals(1, imageView.getImageReader().getHeight()); - } - - @Test - public void flutterImageView_closesReader() { - final ImageReader mockReader = mock(ImageReader.class); - when(mockReader.getMaxImages()).thenReturn(1); - - final FlutterImageView imageView = - spy(new FlutterImageView(ctx, mockReader, FlutterImageView.SurfaceKind.background)); - - imageView.closeImageReader(); - verify(mockReader, times(1)).close(); - } - - @Test - public void flutterSurfaceView_GathersTransparentRegion() { - final Region mockRegion = mock(Region.class); - final FlutterSurfaceView surfaceView = new FlutterSurfaceView(ctx); - - surfaceView.setAlpha(0.0f); - assertFalse(surfaceView.gatherTransparentRegion(mockRegion)); - verify(mockRegion, times(0)).op(anyInt(), anyInt(), anyInt(), anyInt(), any()); - - surfaceView.setAlpha(1.0f); - assertTrue(surfaceView.gatherTransparentRegion(mockRegion)); - verify(mockRegion, times(1)).op(0, 0, 0, 0, Region.Op.DIFFERENCE); - } - - @Test - @SuppressLint("PrivateApi") - @Config(sdk = Build.VERSION_CODES.P) - public void findViewByAccessibilityIdTraversal_returnsRootViewOnAndroid28() throws Exception { - FlutterView flutterView = new FlutterView(ctx); - - Method getAccessibilityViewIdMethod = View.class.getDeclaredMethod("getAccessibilityViewId"); - Integer accessibilityViewId = (Integer) getAccessibilityViewIdMethod.invoke(flutterView); - - assertEquals(flutterView, flutterView.findViewByAccessibilityIdTraversal(accessibilityViewId)); - } - - @Test - @Config(sdk = Build.VERSION_CODES.P) - @SuppressLint("PrivateApi") - public void findViewByAccessibilityIdTraversal_returnsChildViewOnAndroid28() throws Exception { - FlutterView flutterView = new FlutterView(ctx); - FrameLayout childView1 = new FrameLayout(ctx); - flutterView.addView(childView1); - - FrameLayout childView2 = new FrameLayout(ctx); - childView1.addView(childView2); - - Method getAccessibilityViewIdMethod = View.class.getDeclaredMethod("getAccessibilityViewId"); - Integer accessibilityViewId = (Integer) getAccessibilityViewIdMethod.invoke(childView2); - - assertEquals(childView2, flutterView.findViewByAccessibilityIdTraversal(accessibilityViewId)); - } - - @Test - @Config(sdk = Build.VERSION_CODES.Q) - @SuppressLint("PrivateApi") - public void findViewByAccessibilityIdTraversal_returnsRootViewOnAndroid29() throws Exception { - FlutterView flutterView = new FlutterView(ctx); - - Method getAccessibilityViewIdMethod = View.class.getDeclaredMethod("getAccessibilityViewId"); - Integer accessibilityViewId = (Integer) getAccessibilityViewIdMethod.invoke(flutterView); - - assertEquals(null, flutterView.findViewByAccessibilityIdTraversal(accessibilityViewId)); - } - - @Test - public void flutterSplashView_itDoesNotCrashOnRestoreInstanceState() { - final FlutterSplashView splashView = new FlutterSplashView(ctx); - splashView.onRestoreInstanceState(View.BaseSavedState.EMPTY_STATE); - // It should not crash and "splashScreenState" should be null. - assertEquals(null, splashView.splashScreenState); - } - - public void ViewportMetrics_initializedPhysicalTouchSlop() { - FlutterView flutterView = new FlutterView(ctx); - FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); - FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); - when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); - - flutterView.attachToFlutterEngine(flutterEngine); - ArgumentCaptor viewportMetricsCaptor = - ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class); - verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); - - assertFalse(-1 == viewportMetricsCaptor.getValue().physicalTouchSlop); - } - - private void setExpectedDisplayRotation(int rotation) { - ShadowDisplay display = - Shadows.shadowOf( - ((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay()); - display.setRotation(rotation); - } - - private void validateViewportMetricPadding( - ArgumentCaptor viewportMetricsCaptor, - int left, - int top, - int right, - int bottom) { - assertEquals(left, viewportMetricsCaptor.getValue().viewPaddingLeft); - assertEquals(top, viewportMetricsCaptor.getValue().viewPaddingTop); - assertEquals(right, viewportMetricsCaptor.getValue().viewPaddingRight); - assertEquals(bottom, viewportMetricsCaptor.getValue().viewPaddingBottom); - } - - private void mockSystemWindowInsets( - WindowInsets windowInsets, int left, int top, int right, int bottom) { - when(windowInsets.getSystemWindowInsetLeft()).thenReturn(left); - when(windowInsets.getSystemWindowInsetTop()).thenReturn(top); - when(windowInsets.getSystemWindowInsetRight()).thenReturn(right); - when(windowInsets.getSystemWindowInsetBottom()).thenReturn(bottom); - } - - private void mockSystemGestureInsetsIfNeed(WindowInsets windowInsets) { - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { - when(windowInsets.getSystemGestureInsets()).thenReturn(Insets.NONE); - } - } - - /* - * A custom shadow that reports fullscreen flag for system UI visibility - */ - @Implements(View.class) - public static class ShadowFullscreenView { - @Implementation - public int getWindowSystemUiVisibility() { - return View.SYSTEM_UI_FLAG_FULLSCREEN; - } - } - - // ViewGroup is the first shadow in the type hierarchy for FlutterView. Shadows need to mimic - // production classes' view hierarchy. - @Implements(ViewGroup.class) - public static class ShadowFullscreenViewGroup extends ShadowFullscreenView {} -} diff --git a/tools/android_lint/project.xml b/tools/android_lint/project.xml index d686d8a20b7cb..c2a754adebd14 100644 --- a/tools/android_lint/project.xml +++ b/tools/android_lint/project.xml @@ -31,15 +31,12 @@ - - - @@ -50,7 +47,6 @@ - From 0261c4b83fdf40c18d7b890ec0e3dbf9c3cb55ba Mon Sep 17 00:00:00 2001 From: camsim99 Date: Wed, 26 Jul 2023 13:33:36 -0700 Subject: [PATCH 2/3] Remove impacted test --- .../FlutterActivityAndFragmentDelegateTest.java | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java index 05e724dd3ecba..3cb5d2dd6665f 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java @@ -1225,23 +1225,6 @@ public void flutterSurfaceViewVisibilityChangedWithFlutterView() { assertEquals(View.INVISIBLE, surfaceView.getVisibility()); } - @Test - public void itDoesNotDelayTheFirstDrawWhenRequestedAndWithAProvidedSplashScreen() { - // ---- Test setup ---- - // Create the real object that we're testing. - FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost); - - // We're testing lifecycle behaviors, which require/expect that certain methods have already - // been executed by the time they run. Therefore, we run those expected methods first. - delegate.onAttach(ctx); - - // --- Execute the behavior under test --- - boolean shouldDelayFirstAndroidViewDraw = true; - delegate.onCreateView(null, null, null, 0, shouldDelayFirstAndroidViewDraw); - - assertNull(delegate.activePreDrawListener); - } - @Test public void usesFlutterEngineGroup() { FlutterEngineGroup mockEngineGroup = mock(FlutterEngineGroup.class); From 411be2944269458ba7e0e9b960b02b2afa0f6478 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Wed, 26 Jul 2023 15:44:58 -0700 Subject: [PATCH 3/3] Remove ss references --- .../android/io/flutter/embedding/android/FlutterActivity.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index ee04ce1364c45..5ec47c5831a09 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -65,7 +65,6 @@ * *