From 474424b10fbe8c3b55016bd6f1a56ed9e52d18b3 Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Thu, 28 Feb 2019 17:34:36 -0800 Subject: [PATCH 1/4] Android Embedding PR 14: Almost done with FlutterFragment. --- .../engine/android/FlutterFragment.java | 83 +++++++++++++++++++ .../embedding/engine/android/FlutterView.java | 22 +++++ 2 files changed, 105 insertions(+) diff --git a/shell/platform/android/io/flutter/embedding/engine/android/FlutterFragment.java b/shell/platform/android/io/flutter/embedding/engine/android/FlutterFragment.java index e4042bbaaa139..19bd99beb5d72 100644 --- a/shell/platform/android/io/flutter/embedding/engine/android/FlutterFragment.java +++ b/shell/platform/android/io/flutter/embedding/engine/android/FlutterFragment.java @@ -19,6 +19,7 @@ import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.embedding.engine.dart.DartExecutor; +import io.flutter.plugin.platform.PlatformPlugin; import io.flutter.view.FlutterMain; import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; @@ -160,6 +161,8 @@ protected static Bundle createArgsBundle(@Nullable String dartEntrypoint, private FlutterEngine flutterEngine; @Nullable private FlutterView flutterView; + @Nullable + private PlatformPlugin platformPlugin; public FlutterFragment() { // Ensure that we at least have an empty Bundle of arguments so that we don't @@ -181,11 +184,30 @@ public FlutterEngine getFlutterEngine() { public void onAttach(Context context) { super.onAttach(context); + initializeFlutter(getContextCompat()); + // When "retain instance" is true, the FlutterEngine will survive configuration // changes. Therefore, we create a new one only if one does not already exist. if (flutterEngine == null) { createFlutterEngine(); } + + // Regardless of whether or not a FlutterEngine already existed, the PlatformPlugin + // is bound to a specific Activity. Therefore, it needs to be created and configured + // every time this Fragment attaches to a new Activity. + // TODO(mattcarroll): the PlatformPlugin needs to be reimagined because it implicitly takes + // control of the entire window. This is unacceptable for non-fullscreen + // use-cases. + platformPlugin = new PlatformPlugin(getActivity(), flutterEngine.getPlatformChannel()); + } + + private void initializeFlutter(@NonNull Context context) { + String[] flutterShellArgsArray = getArguments().getStringArray(ARG_FLUTTER_INITIALIZATION_ARGS); + FlutterShellArgs flutterShellArgs = new FlutterShellArgs( + flutterShellArgsArray != null ? flutterShellArgsArray : new String[] {} + ); + + FlutterMain.ensureInitializationComplete(context.getApplicationContext(), flutterShellArgs.toArray()); } /** @@ -308,6 +330,67 @@ protected String getDartEntrypointFunctionName() { public void onPostResume() { Log.d(TAG, "onPostResume()"); flutterEngine.getLifecycleChannel().appIsResumed(); + + // TODO(mattcarroll): find a better way to handle the update of UI overlays than calling through + // to platformPlugin. We're implicitly entangling the Window, Activity, Fragment, + // and engine all with this one call. + platformPlugin.onPostResume(); + + // TODO(mattcarroll): consider a more abstract way to invoke this behavior. It is very strange for + // a Fragment to have a seemingly random View responsibility, but this is what + // existed in the original embedding and I don't have a good alternative yet. + flutterView.updateAccessibilityFeatures(); + } + + @Override + public void onPause() { + super.onPause(); + Log.d(TAG, "onPause()"); + flutterEngine.getLifecycleChannel().appIsInactive(); + } + + @Override + public void onStop() { + super.onStop(); + Log.d(TAG, "onStop()"); + flutterEngine.getLifecycleChannel().appIsPaused(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + Log.d(TAG, "onDestroyView()"); + flutterView.detachFromFlutterEngine(); + } + + @Override + public void onDetach() { + super.onDetach(); + Log.d(TAG, "onDetach()"); + + // Null out the platformPlugin to avoid a possible retain cycle between the plugin, this Fragment, + // and this Fragment's Activity. + platformPlugin = null; + + // Destroy our FlutterEngine if we're not set to retain it. + if (!retainFlutterIsolateAfterFragmentDestruction()) { + flutterEngine.destroy(); + flutterEngine = null; + } + } + + /** + * Returns true if the {@link FlutterEngine} within this {@code FlutterFragment} should outlive + * the {@code FlutterFragment}, itself. + * + * Defaults to false. This method can be overridden in subclasses to retain the + * {@link FlutterEngine}. + */ + // TODO(mattcarroll): consider a dynamic determination of this preference based on whether the + // engine was created automatically, or if the engine was provided manually. + // Manually provided engines should probably not be destroyed. + protected boolean retainFlutterIsolateAfterFragmentDestruction() { + return false; } /** diff --git a/shell/platform/android/io/flutter/embedding/engine/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/engine/android/FlutterView.java index aee6a5a95547c..4a1d39683b82e 100644 --- a/shell/platform/android/io/flutter/embedding/engine/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/engine/android/FlutterView.java @@ -9,6 +9,7 @@ import android.graphics.Rect; import android.os.Build; import android.os.LocaleList; +import android.provider.Settings; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.format.DateFormat; @@ -334,6 +335,27 @@ public boolean onHoverEvent(MotionEvent event) { } //-------- End: Process UI I/O that Flutter cares about. --------- + //-------- Start: Accessibility ------- + public void updateAccessibilityFeatures() { + // TODO(mattcarroll): get the following accessibility code working. This method is added now to + // avoid forgetting its invocation in FlutterFragment, but I want to think + // more deeply about how much accessibility logic should truly live in + // FlutterView vs AccessibilityBridge. +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { +// String transitionAnimationScale = Settings.Global.getString(getContext().getContentResolver(), +// Settings.Global.TRANSITION_ANIMATION_SCALE); +// if (transitionAnimationScale != null && transitionAnimationScale.equals("0")) { +// accessibilityFeatureFlags ^= AccessibilityFeature.DISABLE_ANIMATIONS.value; +// } else { +// accessibilityFeatureFlags &= ~AccessibilityFeature.DISABLE_ANIMATIONS.value; +// } +// } else { +// // TODO(mattcarroll): we need to do something here for API 16 +// } +// flutterEngine.getRenderer().setAccessibilityFeatures(accessibilityFeatureFlags); + } + //-------- End: Accessibility --------- + /** * Connects this {@code FlutterView} to the given {@link FlutterEngine}. * From 31170254b5f5137a6e06cd1000891fc105870062 Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Thu, 28 Feb 2019 19:53:18 -0800 Subject: [PATCH 2/4] Added a null check. --- .../engine/android/FlutterFragment.java | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/android/FlutterFragment.java b/shell/platform/android/io/flutter/embedding/engine/android/FlutterFragment.java index 19bd99beb5d72..47defdf13367a 100644 --- a/shell/platform/android/io/flutter/embedding/engine/android/FlutterFragment.java +++ b/shell/platform/android/io/flutter/embedding/engine/android/FlutterFragment.java @@ -329,17 +329,21 @@ protected String getDartEntrypointFunctionName() { // TODO(mattcarroll): determine why this can't be in onResume(). Comment reason, or move if possible. public void onPostResume() { Log.d(TAG, "onPostResume()"); - flutterEngine.getLifecycleChannel().appIsResumed(); + if (flutterEngine != null) { + flutterEngine.getLifecycleChannel().appIsResumed(); - // TODO(mattcarroll): find a better way to handle the update of UI overlays than calling through - // to platformPlugin. We're implicitly entangling the Window, Activity, Fragment, - // and engine all with this one call. - platformPlugin.onPostResume(); + // TODO(mattcarroll): find a better way to handle the update of UI overlays than calling through + // to platformPlugin. We're implicitly entangling the Window, Activity, Fragment, + // and engine all with this one call. + platformPlugin.onPostResume(); - // TODO(mattcarroll): consider a more abstract way to invoke this behavior. It is very strange for - // a Fragment to have a seemingly random View responsibility, but this is what - // existed in the original embedding and I don't have a good alternative yet. - flutterView.updateAccessibilityFeatures(); + // TODO(mattcarroll): consider a more abstract way to invoke this behavior. It is very strange for + // a Fragment to have a seemingly random View responsibility, but this is what + // existed in the original embedding and I don't have a good alternative yet. + flutterView.updateAccessibilityFeatures(); + } else { + Log.w(TAG, "onPostResume() invoked before FlutterFragment was attached to an Activity."); + } } @Override From 89f6744a6a3fc30cbafc0e5dc872c090ab779b6b Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Thu, 28 Feb 2019 19:57:51 -0800 Subject: [PATCH 3/4] PR Updates. --- .../io/flutter/embedding/engine/android/FlutterView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/engine/android/FlutterView.java index 4a1d39683b82e..2eafc318d18dd 100644 --- a/shell/platform/android/io/flutter/embedding/engine/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/engine/android/FlutterView.java @@ -345,7 +345,7 @@ public void updateAccessibilityFeatures() { // String transitionAnimationScale = Settings.Global.getString(getContext().getContentResolver(), // Settings.Global.TRANSITION_ANIMATION_SCALE); // if (transitionAnimationScale != null && transitionAnimationScale.equals("0")) { -// accessibilityFeatureFlags ^= AccessibilityFeature.DISABLE_ANIMATIONS.value; +// accessibilityFeatureFlags != AccessibilityFeature.DISABLE_ANIMATIONS.value; // } else { // accessibilityFeatureFlags &= ~AccessibilityFeature.DISABLE_ANIMATIONS.value; // } From 819da7b0e9bfd295f56960b619117df8147edd92 Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Mon, 4 Mar 2019 17:17:14 -0800 Subject: [PATCH 4/4] PR Updates. --- .../embedding/engine/android/FlutterView.java | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/engine/android/FlutterView.java index 2eafc318d18dd..c7d86cbf2e6ab 100644 --- a/shell/platform/android/io/flutter/embedding/engine/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/engine/android/FlutterView.java @@ -336,23 +336,11 @@ public boolean onHoverEvent(MotionEvent event) { //-------- End: Process UI I/O that Flutter cares about. --------- //-------- Start: Accessibility ------- + /** + * No-op. Placeholder so that the containing Fragment can call through, but not yet implemented. + */ public void updateAccessibilityFeatures() { - // TODO(mattcarroll): get the following accessibility code working. This method is added now to - // avoid forgetting its invocation in FlutterFragment, but I want to think - // more deeply about how much accessibility logic should truly live in - // FlutterView vs AccessibilityBridge. -// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { -// String transitionAnimationScale = Settings.Global.getString(getContext().getContentResolver(), -// Settings.Global.TRANSITION_ANIMATION_SCALE); -// if (transitionAnimationScale != null && transitionAnimationScale.equals("0")) { -// accessibilityFeatureFlags != AccessibilityFeature.DISABLE_ANIMATIONS.value; -// } else { -// accessibilityFeatureFlags &= ~AccessibilityFeature.DISABLE_ANIMATIONS.value; -// } -// } else { -// // TODO(mattcarroll): we need to do something here for API 16 -// } -// flutterEngine.getRenderer().setAccessibilityFeatures(accessibilityFeatureFlags); + // TODO(mattcarroll): bring in accessibility code from old FlutterView. } //-------- End: Accessibility ---------