diff --git a/shell/platform/android/io/flutter/embedding/engine/android/FlutterSurfaceView.java b/shell/platform/android/io/flutter/embedding/engine/android/FlutterSurfaceView.java index edbe8645d32d116dca494d648dc937f3f38819c0..dc52f9e6d46f9f92cb7b3520866ec9dfaf1abb38 100644 --- a/shell/platform/android/io/flutter/embedding/engine/android/FlutterSurfaceView.java +++ b/shell/platform/android/io/flutter/embedding/engine/android/FlutterSurfaceView.java @@ -56,6 +56,7 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + Log.d(TAG, "SurfaceHolder.Callback.surfaceChanged()"); if (isAttachedToFlutterRenderer) { changeSurfaceSize(width, height); } @@ -97,13 +98,17 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R * Invoked by the owner of this {@code FlutterSurfaceView} when it wants to begin rendering * a Flutter UI to this {@code FlutterSurfaceView}. * - * If an Android {@link android.view.Surface} is available, this method will begin rendering - * {@link FlutterRenderer}'s Flutter UI to this {@code FlutterSurfaceView}. + * If an Android {@link android.view.Surface} is available, this method will give that + * {@link android.view.Surface} to the given {@link FlutterRenderer} to begin rendering + * Flutter's UI to this {@code FlutterSurfaceView}. * * If no Android {@link android.view.Surface} is available yet, this {@code FlutterSurfaceView} - * will wait until a {@link android.view.Surface} becomes available and then begin rendering. + * will wait until a {@link android.view.Surface} becomes available and then give that + * {@link android.view.Surface} to the given {@link FlutterRenderer} to begin rendering + * Flutter's UI to this {@code FlutterSurfaceView}. */ public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) { + Log.d(TAG, "attachToRenderer"); if (this.flutterRenderer != null) { this.flutterRenderer.detachFromRenderSurface(); } @@ -114,6 +119,7 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R // If we're already attached to an Android window then we're now attached to both a renderer // and the Android window. We can begin rendering now. if (isSurfaceAvailableForRendering) { + Log.d(TAG, "Surface is available for rendering. Connecting."); connectSurfaceToRenderer(); } } @@ -179,5 +185,6 @@ public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.R @Override public void onFirstFrameRendered() { // TODO(mattcarroll): decide where this method should live and what it needs to do. + Log.d(TAG, "onFirstFrameRendered()"); } } diff --git a/shell/platform/android/io/flutter/embedding/engine/android/FlutterTextureView.java b/shell/platform/android/io/flutter/embedding/engine/android/FlutterTextureView.java index 8ea3fee2d6327ef93a34f97b814b90d4f1faf5d1..c12c2fa4274fcde6604eedaa144a74f8a9448964 100644 --- a/shell/platform/android/io/flutter/embedding/engine/android/FlutterTextureView.java +++ b/shell/platform/android/io/flutter/embedding/engine/android/FlutterTextureView.java @@ -111,11 +111,14 @@ public class FlutterTextureView extends TextureView implements FlutterRenderer.R * Invoked by the owner of this {@code FlutterTextureView} when it wants to begin rendering * a Flutter UI to this {@code FlutterTextureView}. * - * If an Android {@link SurfaceTexture} is available, this method will begin rendering - * {@link FlutterRenderer}'s Flutter UI to this {@code FlutterTextureView}. + * If an Android {@link SurfaceTexture} is available, this method will give that + * {@link SurfaceTexture} to the given {@link FlutterRenderer} to begin rendering + * Flutter's UI to this {@code FlutterTextureView}. * - * If no Android {@link SurfaceTexture} is available yet, this {@code FlutterSurfaceView} - * will wait until a {@link SurfaceTexture} becomes available and then begin rendering. + * If no Android {@link SurfaceTexture} is available yet, this {@code FlutterTextureView} + * will wait until a {@link SurfaceTexture} becomes available and then give that + * {@link SurfaceTexture} to the given {@link FlutterRenderer} to begin rendering + * Flutter's UI to this {@code FlutterTextureView}. */ public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) { if (this.flutterRenderer != null) { @@ -193,5 +196,6 @@ public class FlutterTextureView extends TextureView implements FlutterRenderer.R @Override public void onFirstFrameRendered() { // TODO(mattcarroll): decide where this method should live and what it needs to do. + Log.d(TAG, "onFirstFrameRendered()"); } } 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 fad69bac0ed9726eb03a762d61f9bef11ab7c11e..aee6a5a95547ce8b3114a19400f02f31637939b0 100644 --- a/shell/platform/android/io/flutter/embedding/engine/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/engine/android/FlutterView.java @@ -17,10 +17,9 @@ import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.WindowInsets; +import android.view.WindowManager; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; -import android.view.inputmethod.InputMethod; -import android.view.inputmethod.InputMethodManager; import android.widget.FrameLayout; import java.util.ArrayList; @@ -30,6 +29,7 @@ import java.util.Locale; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.plugin.editing.TextInputPlugin; +import io.flutter.view.VsyncWaiter; /** * Displays a Flutter UI on an Android device. @@ -77,6 +77,9 @@ public class FlutterView extends FrameLayout { @Nullable private AndroidKeyProcessor androidKeyProcessor; + // Directly implemented View behavior that communicates with Flutter. + private final FlutterRenderer.ViewportMetrics viewportMetrics = new FlutterRenderer.ViewportMetrics(); + /** * Constructs a {@code FlutterSurfaceView} programmatically, without any XML attributes. * @@ -158,8 +161,10 @@ public class FlutterView extends FrameLayout { */ @Override protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { - // TODO(mattcarroll): hookup to viewport metrics. super.onSizeChanged(width, height, oldWidth, oldHeight); + viewportMetrics.width = width; + viewportMetrics.height = height; + sendViewportMetricsToFlutter(); } /** @@ -174,8 +179,22 @@ public class FlutterView extends FrameLayout { */ @Override public final WindowInsets onApplyWindowInsets(WindowInsets insets) { - // TODO(mattcarroll): hookup to Flutter metrics. - return insets; + WindowInsets newInsets = super.onApplyWindowInsets(insets); + + // Status bar (top) and left/right system insets should partially obscure the content (padding). + viewportMetrics.paddingTop = insets.getSystemWindowInsetTop(); + viewportMetrics.paddingRight = insets.getSystemWindowInsetRight(); + viewportMetrics.paddingBottom = 0; + viewportMetrics.paddingLeft = insets.getSystemWindowInsetLeft(); + + // Bottom system inset (keyboard) should adjust scrollable bottom edge (inset). + viewportMetrics.viewInsetTop = 0; + viewportMetrics.viewInsetRight = 0; + viewportMetrics.viewInsetBottom = insets.getSystemWindowInsetBottom(); + viewportMetrics.viewInsetLeft = 0; + sendViewportMetricsToFlutter(); + + return newInsets; } /** @@ -188,8 +207,23 @@ public class FlutterView extends FrameLayout { @Override @SuppressWarnings("deprecation") protected boolean fitSystemWindows(Rect insets) { - // TODO(mattcarroll): hookup to Flutter metrics. - return super.fitSystemWindows(insets); + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { + // Status bar, left/right system insets partially obscure content (padding). + viewportMetrics.paddingTop = insets.top; + viewportMetrics.paddingRight = insets.right; + viewportMetrics.paddingBottom = 0; + viewportMetrics.paddingLeft = insets.left; + + // Bottom system inset (keyboard) should adjust scrollable bottom edge (inset). + viewportMetrics.viewInsetTop = 0; + viewportMetrics.viewInsetRight = 0; + viewportMetrics.viewInsetBottom = insets.bottom; + viewportMetrics.viewInsetLeft = 0; + sendViewportMetricsToFlutter(); + return true; + } else { + return super.fitSystemWindows(insets); + } } //------- End: Process View configuration that Flutter cares about. -------- @@ -312,13 +346,16 @@ public class FlutterView extends FrameLayout { * {@link FlutterEngine}. */ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) { + Log.d(TAG, "attachToFlutterEngine()"); if (isAttachedToFlutterEngine()) { if (flutterEngine == this.flutterEngine) { // We are already attached to this FlutterEngine + Log.d(TAG, "Already attached to this engine. Doing nothing."); return; } // Detach from a previous FlutterEngine so we can attach to this new one. + Log.d(TAG, "Currently attached to a different engine. Detaching."); detachFromFlutterEngine(); } @@ -346,6 +383,7 @@ public class FlutterView extends FrameLayout { // Push View and Context related information from Android to Flutter. sendUserSettingsToFlutter(); sendLocalesToFlutter(getResources().getConfiguration()); + sendViewportMetricsToFlutter(); } /** @@ -359,7 +397,9 @@ public class FlutterView extends FrameLayout { * {@link FlutterEngine}. */ public void detachFromFlutterEngine() { + Log.d(TAG, "detachFromFlutterEngine()"); if (!isAttachedToFlutterEngine()) { + Log.d(TAG, "Not attached to an engine. Doing nothing."); return; } Log.d(TAG, "Detaching from Flutter Engine"); @@ -422,6 +462,18 @@ public class FlutterView extends FrameLayout { .send(); } + // TODO(mattcarroll): consider introducing a system channel for this communication instead of JNI + private void sendViewportMetricsToFlutter() { + Log.d(TAG, "sendViewportMetricsToFlutter()"); + if (!isAttachedToFlutterEngine()) { + Log.w(TAG, "Tried to send viewport metrics from Android to Flutter but this FlutterView was not attached to a FlutterEngine."); + return; + } + + viewportMetrics.devicePixelRatio = getResources().getDisplayMetrics().density; + flutterEngine.getRenderer().setViewportMetrics(viewportMetrics); + } + /** * Render modes for a {@link FlutterView}. */ diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java index 996ddb4952ed0c5aaf145f3abbc7d16a75b3e1a4..35dacea6d467d5aecb4c1673f733ec66613ddf54 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -29,7 +29,7 @@ import io.flutter.view.TextureRegistry; * code via JNI. The corresponding {@link RenderSurface} is used as a delegate to carry out * certain actions on behalf of this {@code FlutterRenderer} within an Android view hierarchy. * - * {@link FlutterView} is an implementation of a {@link RenderSurface}. + * {@link io.flutter.embedding.engine.android.FlutterView} is an implementation of a {@link RenderSurface}. */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public class FlutterRenderer implements TextureRegistry { @@ -49,14 +49,16 @@ public class FlutterRenderer implements TextureRegistry { } this.renderSurface = renderSurface; + this.renderSurface.attachToRenderer(this); this.flutterJNI.setRenderSurface(renderSurface); } public void detachFromRenderSurface() { // TODO(mattcarroll): determine desired behavior if we're asked to detach without first being attached if (this.renderSurface != null) { - surfaceDestroyed(); + this.renderSurface.detachFromRenderer(); this.renderSurface = null; + surfaceDestroyed(); this.flutterJNI.setRenderSurface(null); } } @@ -157,29 +159,19 @@ public class FlutterRenderer implements TextureRegistry { } // TODO(mattcarroll): describe the native behavior that this invokes - public void setViewportMetrics(float devicePixelRatio, - int physicalWidth, - int physicalHeight, - int physicalPaddingTop, - int physicalPaddingRight, - int physicalPaddingBottom, - int physicalPaddingLeft, - int physicalViewInsetTop, - int physicalViewInsetRight, - int physicalViewInsetBottom, - int physicalViewInsetLeft) { + public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) { flutterJNI.setViewportMetrics( - devicePixelRatio, - physicalWidth, - physicalHeight, - physicalPaddingTop, - physicalPaddingRight, - physicalPaddingBottom, - physicalPaddingLeft, - physicalViewInsetTop, - physicalViewInsetRight, - physicalViewInsetBottom, - physicalViewInsetLeft + viewportMetrics.devicePixelRatio, + viewportMetrics.width, + viewportMetrics.height, + viewportMetrics.paddingTop, + viewportMetrics.paddingRight, + viewportMetrics.paddingBottom, + viewportMetrics.paddingLeft, + viewportMetrics.viewInsetTop, + viewportMetrics.viewInsetRight, + viewportMetrics.viewInsetBottom, + viewportMetrics.viewInsetLeft ); } @@ -281,4 +273,24 @@ public class FlutterRenderer implements TextureRegistry { */ void onFirstFrameRendered(); } + + /** + * Mutable data structure that holds all viewport metrics properties that Flutter cares about. + * + * All distance measurements, e.g., width, height, padding, viewInsets, are measured in device + * pixels, not logical pixels. + */ + public static final class ViewportMetrics { + public float devicePixelRatio = 1.0f; + public int width = 0; + public int height = 0; + public int paddingTop = 0; + public int paddingRight = 0; + public int paddingBottom = 0; + public int paddingLeft = 0; + public int viewInsetTop = 0; + public int viewInsetRight = 0; + public int viewInsetBottom = 0; + public int viewInsetLeft = 0; + } }