Chooses Flutter's initial route.
@@ -167,7 +162,7 @@
* FlutterView}. Using a {@link FlutterView} requires forwarding some calls from an {@code
* Activity}, as well as forwarding lifecycle calls from an {@code Activity} or a {@code Fragment}.
*
- * Launch Screen and Splash Screen
+ *
Launch Screen
*
*
{@code FlutterActivity} supports the display of an Android "launch screen", which is displayed
* while the Android application loads. It is only applicable if {@code FlutterActivity} is the
@@ -201,10 +196,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 +747,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:
- *
- *
- * - Splash {@code View} begins transitioning to the Flutter UI.
- *
- A configuration change occurs, like an orientation change, and the {@code Activity} is
- * re-created, along with the {@code View} hierarchy.
- *
- 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..3cb5d2dd6665f 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;
@@ -1227,26 +1225,6 @@ public void flutterSurfaceViewVisibilityChangedWithFlutterView() {
assertEquals(View.INVISIBLE, surfaceView.getVisibility());
}
- @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);
-
- // 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);
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 @@
-