Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.util.TypedValue;
Expand Down Expand Up @@ -66,7 +67,10 @@
* {@code Fragment}.
*/
// TODO(mattcarroll): explain each call forwarded to Fragment (first requires resolution of PluginRegistry API).
public class FlutterActivity extends FragmentActivity implements OnFirstFrameRenderedListener {
public class FlutterActivity extends FragmentActivity
implements FlutterFragment.FlutterEngineProvider,
FlutterFragment.FlutterEngineConfigurator,
OnFirstFrameRenderedListener {
private static final String TAG = "FlutterActivity";

// Meta-data arguments, processed from manifest XML.
Expand Down Expand Up @@ -365,6 +369,27 @@ protected boolean shouldAttachEngineToActivity() {
return true;
}

/**
* Hook for subclasses to easily provide a custom {@code FlutterEngine}.
*/
@Nullable
@Override
public FlutterEngine provideFlutterEngine(@NonNull Context context) {
// No-op. Hook for subclasses.
return null;
}

/**
* Hook for subclasses to easily configure a {@code FlutterEngine}, e.g., register
* plugins.
* <p>
* This method is called after {@link #provideFlutterEngine(Context)}.
*/
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
// No-op. Hook for subclasses.
}

@Override
public void onPostResume() {
super.onPostResume();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;

import android.app.Activity;
import android.arch.lifecycle.Lifecycle;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
Expand Down Expand Up @@ -359,8 +361,13 @@ public void onAttach(@NonNull Context context) {
// sync with the Activity. We use the Fragment's Lifecycle because it is possible that the
// attached Activity is not a LifecycleOwner.
Log.d(TAG, "Attaching FlutterEngine to the Activity that owns this Fragment.");
flutterEngine.getActivityControlSurface().attachToActivity(getActivity(), getLifecycle());
flutterEngine.getActivityControlSurface().attachToActivity(
getActivity(),
getLifecycle()
);
}

configureFlutterEngine(flutterEngine);
}

private void initializeFlutter(@NonNull Context context) {
Expand Down Expand Up @@ -401,11 +408,11 @@ private void setupFlutterEngine() {
// Defer to the Activity that owns us to provide a FlutterEngine.
Log.d(TAG, "Deferring to attached Activity to provide a FlutterEngine.");
FlutterEngineProvider flutterEngineProvider = (FlutterEngineProvider) attachedActivity;
flutterEngine = flutterEngineProvider.getFlutterEngine(getContext());
flutterEngine = flutterEngineProvider.provideFlutterEngine(getContext());
if (flutterEngine != null) {
isFlutterEngineFromActivity = true;
return;
}
return;
}

// Neither our subclass, nor our owning Activity wanted to provide a custom FlutterEngine.
Expand Down Expand Up @@ -434,11 +441,34 @@ protected FlutterEngine createFlutterEngine(@NonNull Context context) {
return null;
}

/**
* Configures a {@link FlutterEngine} after its creation.
* <p>
* This method is called after the given {@link FlutterEngine} has been attached to the
* owning {@code FragmentActivity}. See
* {@link io.flutter.embedding.engine.plugins.activity.ActivityControlSurface#attachToActivity(Activity, Lifecycle)}.
* <p>
* It is possible that the owning {@code FragmentActivity} opted not to connect itself as
* an {@link io.flutter.embedding.engine.plugins.activity.ActivityControlSurface}. In that
* case, any configuration, e.g., plugins, must not expect or depend upon an available
* {@code Activity} at the time that this method is invoked.
* <p>
* The default behavior of this method is to defer to the owning {@code FragmentActivity}
* as a {@link FlutterEngineConfigurator}. Subclasses can override this method if the
* subclass needs to override the {@code FragmentActivity}'s behavior, or add to it.
*/
protected void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
FragmentActivity attachedActivity = getActivity();
if (attachedActivity instanceof FlutterEngineConfigurator) {
((FlutterEngineConfigurator) attachedActivity).configureFlutterEngine(flutterEngine);
}
}

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
Log.v(TAG, "Creating FlutterView.");
flutterView = new FlutterView(getContext(), getRenderMode(), getTransparencyMode());
flutterView = new FlutterView(getActivity(), getRenderMode(), getTransparencyMode());
flutterView.addOnFirstFrameRenderedListener(onFirstFrameRenderedListener);
return flutterView;
}
Expand Down Expand Up @@ -548,10 +578,6 @@ public void run() {
Log.v(TAG, "Attaching FlutterEngine to FlutterView.");
flutterView.attachToFlutterEngine(flutterEngine);

// TODO(mattcarroll): the following call should exist here, but the plugin system needs to be revamped.
// The existing attach() method does not know how to handle this kind of FlutterView.
//flutterEngine.getPlugins().attach(this, getActivity());

doInitialFlutterViewRun();
}
});
Expand Down Expand Up @@ -788,7 +814,7 @@ protected void onFirstFrameRendered() {}
* {@link FlutterActivity}s and/or {@code FlutterFragments}.
* <p>
* If the {@link FragmentActivity} that owns this {@code FlutterFragment} implements
* {@code FlutterEngineProvider}, that {@link FlutterActivity} will be given an opportunity
* {@code FlutterEngineProvider}, that {@link FragmentActivity} will be given an opportunity
* to provide a {@link FlutterEngine} instead of the {@code FlutterFragment} creating a
* new one. The {@link FragmentActivity} can provide an existing, pre-warmed {@link FlutterEngine},
* if desired.
Expand All @@ -804,6 +830,27 @@ public interface FlutterEngineProvider {
* to provide its own {@code FlutterEngine} instance.
*/
@Nullable
FlutterEngine getFlutterEngine(@NonNull Context context);
FlutterEngine provideFlutterEngine(@NonNull Context context);
}

/**
* Configures a {@link FlutterEngine} after it is created, e.g., adds plugins.
* <p>
* This interface may be applied to a {@link FragmentActivity} that owns a {@code FlutterFragment}.
*/
public interface FlutterEngineConfigurator {
/**
* Configures the given {@link FlutterEngine}.
* <p>
* This method is called after the given {@link FlutterEngine} has been attached to the
* owning {@code FragmentActivity}. See
* {@link io.flutter.embedding.engine.plugins.activity.ActivityControlSurface#attachToActivity(Activity, Lifecycle)}.
* <p>
* It is possible that the owning {@code FragmentActivity} opted not to connect itself as
* an {@link io.flutter.embedding.engine.plugins.activity.ActivityControlSurface}. In that
* case, any configuration, e.g., plugins, must not expect or depend upon an available
* {@code Activity} at the time that this method is invoked.
*/
void configureFlutterEngine(@NonNull FlutterEngine flutterEngine);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowInsets;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeProvider;
Expand All @@ -36,6 +37,7 @@
import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
import io.flutter.plugin.editing.TextInputPlugin;
import io.flutter.plugin.platform.PlatformViewsController;
import io.flutter.view.AccessibilityBridge;

/**
Expand Down Expand Up @@ -117,6 +119,8 @@ public void onFirstFrameRendered() {
* <li>{@link #renderMode} defaults to {@link RenderMode#surface}.</li>
* <li>{@link #transparencyMode} defaults to {@link TransparencyMode#opaque}.</li>
* </ul>
* {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the signature be changed to actually take an Activity throughout?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the approach we were talking about last week was that we're keeping the PlatformViewController to have an ApplicationContext constructor but just that we're adding new APIs to it, something like onAttachToActivity. And you can only do presentation.show after you onAttachToActivity. But then FlutterView isn't involved in any of this. It's just between the fragment, which does flutterPluginRegistry.getPlatformViewsController.onAttachActivity(activity), and the PlatformViewsController.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mklim I tried that but compilation failed because apparently Android View subclasses need to declare exact replicas of superclass constructors (probably due to XML inflation concerns). So I backed out that change and left a comment.

@xster the reason this was a problem at all is because we can't use an application context. If you ever attempt to show() a dialog/presentation with an application context, the app will blow up. That's why the previous commit that used the application context had to be reverted, which sparked the conversation with the Android team, etc. So now we're doing what we have to do to give PlatformViewsController an Activity.

* to be compatible with {@link PlatformViewsController}.
*/
public FlutterView(@NonNull Context context) {
this(context, null, null, null);
Expand All @@ -127,6 +131,9 @@ public FlutterView(@NonNull Context context) {
* and allows selection of a {@link #renderMode}.
* <p>
* {@link #transparencyMode} defaults to {@link TransparencyMode#opaque}.
* <p>
* {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
* to be compatible with {@link PlatformViewsController}.
*/
public FlutterView(@NonNull Context context, @NonNull RenderMode renderMode) {
this(context, null, renderMode, null);
Expand All @@ -135,6 +142,9 @@ public FlutterView(@NonNull Context context, @NonNull RenderMode renderMode) {
/**
* Constructs a {@code FlutterView} programmatically, without any XML attributes,
* assumes the use of {@link RenderMode#surface}, and allows selection of a {@link #transparencyMode}.
* <p>
* {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
* to be compatible with {@link PlatformViewsController}.
*/
public FlutterView(@NonNull Context context, @NonNull TransparencyMode transparencyMode) {
this(context, null, RenderMode.surface, transparencyMode);
Expand All @@ -143,16 +153,21 @@ public FlutterView(@NonNull Context context, @NonNull TransparencyMode transpare
/**
* Constructs a {@code FlutterView} programmatically, without any XML attributes, and allows
* a selection of {@link #renderMode} and {@link #transparencyMode}.
* <p>
* {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
* to be compatible with {@link PlatformViewsController}.
*/
public FlutterView(@NonNull Context context, @NonNull RenderMode renderMode, @NonNull TransparencyMode transparencyMode) {
this(context, null, renderMode, transparencyMode);
}

/**
* Constructs a {@code FlutterSurfaceView} in an XML-inflation-compliant manner.
*
* // TODO(mattcarroll): expose renderMode in XML when build system supports R.attr
* <p>
* {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
* to be compatible with {@link PlatformViewsController}.
*/
// TODO(mattcarroll): expose renderMode in XML when build system supports R.attr
public FlutterView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, null, null);
}
Expand Down Expand Up @@ -367,6 +382,21 @@ public InputConnection onCreateInputConnection(@NonNull EditorInfo outAttrs) {
return textInputPlugin.createInputConnection(this, outAttrs);
}

/**
* Allows a {@code View} that is not currently the input connection target to invoke commands on
* the {@link android.view.inputmethod.InputMethodManager}, which is otherwise disallowed.
* <p>
* Returns true to allow non-input-connection-targets to invoke methods on
* {@code InputMethodManager}, or false to exclusively allow the input connection target to invoke
* such methods.
*/
@Override
public boolean checkInputConnectionProxy(View view) {
return flutterEngine != null
? flutterEngine.getPlatformViewsController().checkInputConnectionProxy(view)
: super.checkInputConnectionProxy(view);
}

/**
* Invoked when key is released.
*
Expand Down Expand Up @@ -511,7 +541,9 @@ private void resetWillNotDraw(boolean isAccessibilityEnabled, boolean isTouchExp
* See {@link #detachFromFlutterEngine()} for information on how to detach from a
* {@link FlutterEngine}.
*/
public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
public void attachToFlutterEngine(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole thing looks a lot cleaner now. Thanks!

@NonNull FlutterEngine flutterEngine
) {
Log.d(TAG, "Attaching to a FlutterEngine: " + flutterEngine);
if (isAttachedToFlutterEngine()) {
if (flutterEngine == this.flutterEngine) {
Expand All @@ -537,7 +569,7 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
textInputPlugin = new TextInputPlugin(
this,
this.flutterEngine.getDartExecutor(),
null
this.flutterEngine.getPlatformViewsController()
);
androidKeyProcessor = new AndroidKeyProcessor(
this.flutterEngine.getKeyEventChannel(),
Expand All @@ -549,16 +581,18 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
flutterEngine.getAccessibilityChannel(),
(AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE),
getContext().getContentResolver(),
// TODO(mattcaroll): plumb the platform views controller to the accessibility bridge.
// https://github.com/flutter/flutter/issues/29618
null
this.flutterEngine.getPlatformViewsController()
);
accessibilityBridge.setOnAccessibilityChangeListener(onAccessibilityChangeListener);
resetWillNotDraw(
accessibilityBridge.isAccessibilityEnabled(),
accessibilityBridge.isTouchExplorationEnabled()
);

// Connect AccessibilityBridge to the PlatformViewsController within the FlutterEngine.
// This allows platform Views to hook into Flutter's overall accessibility system.
this.flutterEngine.getPlatformViewsController().attachAccessibilityBridge(accessibilityBridge);

// Inform the Android framework that it should retrieve a new InputConnection
// now that an engine is attached.
// TODO(mattcarroll): once this is proven to work, move this line ot TextInputPlugin
Expand Down Expand Up @@ -597,6 +631,9 @@ public void detachFromFlutterEngine() {
listener.onFlutterEngineDetachedFromFlutterView();
}

// Disconnect the FlutterEngine's PlatformViewsController from the AccessibilityBridge.
flutterEngine.getPlatformViewsController().detachAccessibiltyBridge();

// Disconnect and clean up the AccessibilityBridge.
accessibilityBridge.release();
accessibilityBridge = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import io.flutter.embedding.engine.systemchannels.SettingsChannel;
import io.flutter.embedding.engine.systemchannels.SystemChannel;
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
import io.flutter.plugin.platform.PlatformViewsController;

/**
* A single Flutter execution environment.
Expand Down Expand Up @@ -88,6 +89,11 @@ public class FlutterEngine implements LifecycleOwner {
@NonNull
private final TextInputChannel textInputChannel;

// Platform Views.
@NonNull
private final PlatformViewsController platformViewsController;

// Engine Lifecycle.
@NonNull
private final Set<EngineLifecycleListener> engineLifecycleListeners = new HashSet<>();
@NonNull
Expand Down Expand Up @@ -138,6 +144,8 @@ public FlutterEngine(@NonNull Context context) {
systemChannel = new SystemChannel(dartExecutor);
textInputChannel = new TextInputChannel(dartExecutor);

platformViewsController = new PlatformViewsController();

androidLifecycle = new FlutterEngineAndroidLifecycle(this);
this.pluginRegistry = new FlutterEnginePluginRegistry(
context.getApplicationContext(),
Expand Down Expand Up @@ -300,6 +308,15 @@ public PluginRegistry getPlugins() {
return pluginRegistry;
}

/**
* {@code PlatformViewsController}, which controls all platform views running within
* this {@code FlutterEngine}.
*/
@NonNull
public PlatformViewsController getPlatformViewsController() {
return platformViewsController;
}

@NonNull
public ActivityControlSurface getActivityControlSurface() {
return pluginRegistry;
Expand Down
Loading