[go: nahoru, domu]

Skip to content

Commit

Permalink
Android Embedding Refactor PR31: Integrate platform views with the ne…
Browse files Browse the repository at this point in the history
…w embedding and the plugin shim. (#9206)
  • Loading branch information
matthew-carroll committed Jul 2, 2019
1 parent 8ac7cbd commit 56a3f41
Show file tree
Hide file tree
Showing 10 changed files with 172 additions and 91 deletions.
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}
* 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(
@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

0 comments on commit 56a3f41

Please sign in to comment.