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 edbe8645d32d1..dc52f9e6d46f9 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 void surfaceCreated(SurfaceHolder holder) { @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 @@ private void init() { * 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 void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) { // 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 void updateSemantics(ByteBuffer buffer, String[] strings) { @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 8ea3fee2d6327..c12c2fa4274fc 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 @@ private void init() { * 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 void updateSemantics(ByteBuffer buffer, String[] strings) { @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 fad69bac0ed97..aee6a5a95547c 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.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 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 @@ protected void onConfigurationChanged(Configuration newConfig) { */ @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 @@ protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) */ @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 final WindowInsets onApplyWindowInsets(WindowInsets insets) { @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 boolean onHoverEvent(MotionEvent event) { * {@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 void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) { // Push View and Context related information from Android to Flutter. sendUserSettingsToFlutter(); sendLocalesToFlutter(getResources().getConfiguration()); + sendViewportMetricsToFlutter(); } /** @@ -359,7 +397,9 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) { * {@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 @@ private void sendUserSettingsToFlutter() { .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 996ddb4952ed0..35dacea6d467d 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 @@ * 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 void attachToRenderSurface(@NonNull RenderSurface renderSurface) { } 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 void surfaceDestroyed() { } // 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 interface RenderSurface { */ 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; + } }