Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
FlutterView.java
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package io.flutter.view;
6
7import static io.flutter.Build.API_LEVELS;
8
9import android.annotation.SuppressLint;
10import android.annotation.TargetApi;
11import android.app.Activity;
12import android.content.Context;
13import android.content.res.Configuration;
14import android.graphics.Bitmap;
15import android.graphics.Insets;
16import android.graphics.PixelFormat;
17import android.graphics.SurfaceTexture;
18import android.os.Build;
19import android.os.Handler;
20import android.text.format.DateFormat;
21import android.util.AttributeSet;
22import android.util.SparseArray;
23import android.view.DisplayCutout;
24import android.view.KeyEvent;
25import android.view.MotionEvent;
26import android.view.PointerIcon;
27import android.view.Surface;
28import android.view.SurfaceHolder;
29import android.view.SurfaceView;
30import android.view.View;
31import android.view.ViewConfiguration;
32import android.view.ViewStructure;
33import android.view.WindowInsets;
34import android.view.WindowManager;
35import android.view.accessibility.AccessibilityManager;
36import android.view.accessibility.AccessibilityNodeProvider;
37import android.view.autofill.AutofillValue;
38import android.view.inputmethod.EditorInfo;
39import android.view.inputmethod.InputConnection;
40import android.view.inputmethod.InputMethodManager;
41import android.window.BackEvent;
42import androidx.annotation.NonNull;
43import androidx.annotation.RequiresApi;
44import androidx.annotation.UiThread;
45import io.flutter.Log;
46import io.flutter.app.FlutterPluginRegistry;
47import io.flutter.embedding.android.AndroidTouchProcessor;
48import io.flutter.embedding.android.KeyboardManager;
49import io.flutter.embedding.engine.dart.DartExecutor;
50import io.flutter.embedding.engine.renderer.FlutterRenderer;
51import io.flutter.embedding.engine.renderer.SurfaceTextureWrapper;
52import io.flutter.embedding.engine.systemchannels.AccessibilityChannel;
53import io.flutter.embedding.engine.systemchannels.BackGestureChannel;
54import io.flutter.embedding.engine.systemchannels.LifecycleChannel;
55import io.flutter.embedding.engine.systemchannels.LocalizationChannel;
56import io.flutter.embedding.engine.systemchannels.MouseCursorChannel;
57import io.flutter.embedding.engine.systemchannels.NavigationChannel;
58import io.flutter.embedding.engine.systemchannels.PlatformChannel;
59import io.flutter.embedding.engine.systemchannels.SettingsChannel;
60import io.flutter.embedding.engine.systemchannels.SystemChannel;
61import io.flutter.embedding.engine.systemchannels.TextInputChannel;
62import io.flutter.plugin.common.ActivityLifecycleListener;
63import io.flutter.plugin.common.BinaryMessenger;
64import io.flutter.plugin.editing.TextInputPlugin;
65import io.flutter.plugin.localization.LocalizationPlugin;
66import io.flutter.plugin.mouse.MouseCursorPlugin;
67import io.flutter.plugin.platform.PlatformPlugin;
68import io.flutter.plugin.platform.PlatformViewsController;
69import io.flutter.util.ViewUtils;
70import java.nio.ByteBuffer;
71import java.util.ArrayList;
72import java.util.List;
73import java.util.concurrent.atomic.AtomicLong;
74
75/**
76 * Deprecated Android view containing a Flutter app.
77 *
78 * @deprecated {@link io.flutter.embedding.android.FlutterView} is the new API that now replaces
79 * this class. See https://flutter.dev/go/android-project-migration for more migration details.
80 */
81@Deprecated
82public class FlutterView extends SurfaceView
83 implements BinaryMessenger,
85 MouseCursorPlugin.MouseCursorViewDelegate,
86 KeyboardManager.ViewDelegate {
87 /**
88 * Interface for those objects that maintain and expose a reference to a {@code FlutterView} (such
89 * as a full-screen Flutter activity).
90 *
91 * <p>This indirection is provided to support applications that use an activity other than {@link
92 * io.flutter.app.FlutterActivity} (e.g. Android v4 support library's {@code FragmentActivity}).
93 * It allows Flutter plugins to deal in this interface and not require that the activity be a
94 * subclass of {@code FlutterActivity}.
95 */
96 public interface Provider {
97 /**
98 * Returns a reference to the Flutter view maintained by this object. This may be {@code null}.
99 *
100 * @return a reference to the Flutter view maintained by this object.
101 */
103 }
104
105 private static final String TAG = "FlutterView";
106
125
126 private final DartExecutor dartExecutor;
127 private final FlutterRenderer flutterRenderer;
128 private final NavigationChannel navigationChannel;
129 private final BackGestureChannel backGestureChannel;
130 private final LifecycleChannel lifecycleChannel;
131 private final LocalizationChannel localizationChannel;
132 private final PlatformChannel platformChannel;
133 private final SettingsChannel settingsChannel;
134 private final SystemChannel systemChannel;
135 private final InputMethodManager mImm;
136 private final TextInputPlugin mTextInputPlugin;
137 private final LocalizationPlugin mLocalizationPlugin;
138 private final MouseCursorPlugin mMouseCursorPlugin;
139 private final KeyboardManager mKeyboardManager;
140 private final AndroidTouchProcessor androidTouchProcessor;
141 private AccessibilityBridge mAccessibilityNodeProvider;
142 private final SurfaceHolder.Callback mSurfaceCallback;
143 private final ViewportMetrics mMetrics;
144 private final List<ActivityLifecycleListener> mActivityLifecycleListeners;
145 private final List<FirstFrameListener> mFirstFrameListeners;
146 private final AtomicLong nextTextureId = new AtomicLong(0L);
147 private FlutterNativeView mNativeView;
148 private boolean mIsSoftwareRenderingEnabled = false; // using the software renderer or not
149 private boolean didRenderFirstFrame = false;
150
151 private final AccessibilityBridge.OnAccessibilityChangeListener onAccessibilityChangeListener =
152 new AccessibilityBridge.OnAccessibilityChangeListener() {
153 @Override
154 public void onAccessibilityChanged(
155 boolean isAccessibilityEnabled, boolean isTouchExplorationEnabled) {
156 resetWillNotDraw(isAccessibilityEnabled, isTouchExplorationEnabled);
157 }
158 };
159
160 public FlutterView(Context context) {
161 this(context, null);
162 }
163
164 public FlutterView(Context context, AttributeSet attrs) {
165 this(context, attrs, null);
166 }
167
168 public FlutterView(Context context, AttributeSet attrs, FlutterNativeView nativeView) {
169 super(context, attrs);
170
171 Activity activity = ViewUtils.getActivity(getContext());
172 if (activity == null) {
173 throw new IllegalArgumentException("Bad context");
174 }
175
176 if (nativeView == null) {
177 mNativeView = new FlutterNativeView(activity.getApplicationContext());
178 } else {
179 mNativeView = nativeView;
180 }
181
182 dartExecutor = mNativeView.getDartExecutor();
183 flutterRenderer = new FlutterRenderer(mNativeView.getFlutterJNI());
184 mIsSoftwareRenderingEnabled = mNativeView.getFlutterJNI().getIsSoftwareRenderingEnabled();
185 mMetrics = new ViewportMetrics();
186 mMetrics.devicePixelRatio = context.getResources().getDisplayMetrics().density;
187 mMetrics.physicalTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
188 setFocusable(true);
189 setFocusableInTouchMode(true);
190
191 mNativeView.attachViewAndActivity(this, activity);
192
193 mSurfaceCallback =
194 new SurfaceHolder.Callback() {
195 @Override
196 public void surfaceCreated(SurfaceHolder holder) {
198 mNativeView.getFlutterJNI().onSurfaceCreated(holder.getSurface());
199 }
200
201 @Override
202 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
204 mNativeView.getFlutterJNI().onSurfaceChanged(width, height);
205 }
206
207 @Override
208 public void surfaceDestroyed(SurfaceHolder holder) {
210 mNativeView.getFlutterJNI().onSurfaceDestroyed();
211 }
212 };
213 getHolder().addCallback(mSurfaceCallback);
214
215 mActivityLifecycleListeners = new ArrayList<>();
216 mFirstFrameListeners = new ArrayList<>();
217
218 // Create all platform channels
219 navigationChannel = new NavigationChannel(dartExecutor);
220 backGestureChannel = new BackGestureChannel(dartExecutor);
221 lifecycleChannel = new LifecycleChannel(dartExecutor);
222 localizationChannel = new LocalizationChannel(dartExecutor);
223 platformChannel = new PlatformChannel(dartExecutor);
224 systemChannel = new SystemChannel(dartExecutor);
225 settingsChannel = new SettingsChannel(dartExecutor);
226
227 // Create and set up plugins
228 PlatformPlugin platformPlugin = new PlatformPlugin(activity, platformChannel);
230 new ActivityLifecycleListener() {
231 @Override
232 public void onPostResume() {
233 platformPlugin.updateSystemUiOverlays();
234 }
235 });
236 mImm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
237 PlatformViewsController platformViewsController =
238 mNativeView.getPluginRegistry().getPlatformViewsController();
239 mTextInputPlugin =
240 new TextInputPlugin(this, new TextInputChannel(dartExecutor), platformViewsController);
241 mKeyboardManager = new KeyboardManager(this);
242
243 if (Build.VERSION.SDK_INT >= API_LEVELS.API_24) {
244 mMouseCursorPlugin = new MouseCursorPlugin(this, new MouseCursorChannel(dartExecutor));
245 } else {
246 mMouseCursorPlugin = null;
247 }
248 mLocalizationPlugin = new LocalizationPlugin(context, localizationChannel);
249 androidTouchProcessor =
250 new AndroidTouchProcessor(flutterRenderer, /*trackMotionEvents=*/ false);
251 platformViewsController.attachToFlutterRenderer(flutterRenderer);
252 mNativeView
254 .getPlatformViewsController()
255 .attachTextInputPlugin(mTextInputPlugin);
256 mNativeView.getFlutterJNI().setLocalizationPlugin(mLocalizationPlugin);
257
258 // Send initial platform information to Dart
259 mLocalizationPlugin.sendLocalesToFlutter(getResources().getConfiguration());
260 sendUserPlatformSettingsToDart();
261 }
262
263 @NonNull
264 public DartExecutor getDartExecutor() {
265 return dartExecutor;
266 }
267
268 @Override
269 public boolean dispatchKeyEvent(KeyEvent event) {
270 Log.e(TAG, "dispatchKeyEvent: " + event.toString());
271 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
272 // Tell Android to start tracking this event.
273 getKeyDispatcherState().startTracking(event, this);
274 } else if (event.getAction() == KeyEvent.ACTION_UP) {
275 // Stop tracking the event.
276 getKeyDispatcherState().handleUpEvent(event);
277 }
278 // If the key processor doesn't handle it, then send it on to the
279 // superclass. The key processor will typically handle all events except
280 // those where it has re-dispatched the event after receiving a reply from
281 // the framework that the framework did not handle it.
282 return (isAttached() && mKeyboardManager.handleEvent(event)) || super.dispatchKeyEvent(event);
283 }
284
286 return mNativeView;
287 }
288
289 public FlutterPluginRegistry getPluginRegistry() {
290 return mNativeView.getPluginRegistry();
291 }
292
293 public String getLookupKeyForAsset(String asset) {
294 return FlutterMain.getLookupKeyForAsset(asset);
295 }
296
297 public String getLookupKeyForAsset(String asset, String packageName) {
298 return FlutterMain.getLookupKeyForAsset(asset, packageName);
299 }
300
301 public void addActivityLifecycleListener(ActivityLifecycleListener listener) {
302 mActivityLifecycleListeners.add(listener);
303 }
304
305 public void onStart() {
306 lifecycleChannel.appIsInactive();
307 }
308
309 public void onPause() {
310 lifecycleChannel.appIsInactive();
311 }
312
313 public void onPostResume() {
314 for (ActivityLifecycleListener listener : mActivityLifecycleListeners) {
315 listener.onPostResume();
316 }
317 lifecycleChannel.appIsResumed();
318 }
319
320 public void onStop() {
321 lifecycleChannel.appIsPaused();
322 }
323
324 public void onMemoryPressure() {
325 mNativeView.getFlutterJNI().notifyLowMemoryWarning();
326 systemChannel.sendMemoryPressureWarning();
327 }
328
329 /**
330 * Returns true if the Flutter experience associated with this {@code FlutterView} has rendered
331 * its first frame, or false otherwise.
332 */
333 public boolean hasRenderedFirstFrame() {
334 return didRenderFirstFrame;
335 }
336
337 /**
338 * Provide a listener that will be called once when the FlutterView renders its first frame to the
339 * underlaying SurfaceView.
340 */
342 mFirstFrameListeners.add(listener);
343 }
344
345 /** Remove an existing first frame listener. */
347 mFirstFrameListeners.remove(listener);
348 }
349
350 @Override
352
353 @Override
355
356 /**
357 * Reverts this back to the {@link SurfaceView} defaults, at the back of its window and opaque.
358 */
360 setZOrderOnTop(false);
361 getHolder().setFormat(PixelFormat.OPAQUE);
362 }
363
364 public void setInitialRoute(String route) {
365 navigationChannel.setInitialRoute(route);
366 }
367
368 public void pushRoute(String route) {
369 navigationChannel.pushRoute(route);
370 }
371
372 public void popRoute() {
373 navigationChannel.popRoute();
374 }
375
376 @TargetApi(API_LEVELS.API_34)
377 @RequiresApi(API_LEVELS.API_34)
378 public void startBackGesture(@NonNull BackEvent backEvent) {
379 backGestureChannel.startBackGesture(backEvent);
380 }
381
382 @TargetApi(API_LEVELS.API_34)
383 @RequiresApi(API_LEVELS.API_34)
384 public void updateBackGestureProgress(@NonNull BackEvent backEvent) {
385 backGestureChannel.updateBackGestureProgress(backEvent);
386 }
387
388 @TargetApi(API_LEVELS.API_34)
389 @RequiresApi(API_LEVELS.API_34)
390 public void commitBackGesture() {
391 backGestureChannel.commitBackGesture();
392 }
393
394 @TargetApi(API_LEVELS.API_34)
395 @RequiresApi(API_LEVELS.API_34)
396 public void cancelBackGesture() {
397 backGestureChannel.cancelBackGesture();
398 }
399
400 private void sendUserPlatformSettingsToDart() {
401 // Lookup the current brightness of the Android OS.
402 boolean isNightModeOn =
403 (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
404 == Configuration.UI_MODE_NIGHT_YES;
405 SettingsChannel.PlatformBrightness brightness =
406 isNightModeOn
407 ? SettingsChannel.PlatformBrightness.dark
408 : SettingsChannel.PlatformBrightness.light;
409
410 settingsChannel
411 .startMessage()
412 .setTextScaleFactor(getResources().getConfiguration().fontScale)
413 .setUse24HourFormat(DateFormat.is24HourFormat(getContext()))
414 .setPlatformBrightness(brightness)
415 .send();
416 }
417
418 @Override
419 protected void onConfigurationChanged(Configuration newConfig) {
420 super.onConfigurationChanged(newConfig);
421 mLocalizationPlugin.sendLocalesToFlutter(newConfig);
422 sendUserPlatformSettingsToDart();
423 }
424
426 return mMetrics.devicePixelRatio;
427 }
428
430 if (!isAttached()) return null;
431 getHolder().removeCallback(mSurfaceCallback);
432 mNativeView.detachFromFlutterView();
433
434 FlutterNativeView view = mNativeView;
435 mNativeView = null;
436 return view;
437 }
438
439 public void destroy() {
440 if (!isAttached()) return;
441
442 getHolder().removeCallback(mSurfaceCallback);
443 releaseAccessibilityNodeProvider();
444
445 mNativeView.destroy();
446 mNativeView = null;
447 }
448
449 @Override
450 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
451 return mTextInputPlugin.createInputConnection(this, mKeyboardManager, outAttrs);
452 }
453
454 @Override
455 public boolean checkInputConnectionProxy(View view) {
456 return mNativeView
458 .getPlatformViewsController()
459 .checkInputConnectionProxy(view);
460 }
461
462 @Override
463 public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) {
464 super.onProvideAutofillVirtualStructure(structure, flags);
465 mTextInputPlugin.onProvideAutofillVirtualStructure(structure, flags);
466 }
467
468 @Override
469 public void autofill(SparseArray<AutofillValue> values) {
470 mTextInputPlugin.autofill(values);
471 }
472
473 @Override
474 public boolean onTouchEvent(MotionEvent event) {
475 if (!isAttached()) {
476 return super.onTouchEvent(event);
477 }
478
479 requestUnbufferedDispatch(event);
480
481 return androidTouchProcessor.onTouchEvent(event);
482 }
483
484 @Override
485 public boolean onHoverEvent(MotionEvent event) {
486 if (!isAttached()) {
487 return super.onHoverEvent(event);
488 }
489
490 boolean handled = mAccessibilityNodeProvider.onAccessibilityHoverEvent(event);
491 if (!handled) {
492 // TODO(ianh): Expose hover events to the platform,
493 // implementing ADD, REMOVE, etc.
494 }
495 return handled;
496 }
497
498 /**
499 * Invoked by Android when a generic motion event occurs, e.g., joystick movement, mouse hover,
500 * track pad touches, scroll wheel movements, etc.
501 *
502 * <p>Flutter handles all of its own gesture detection and processing, therefore this method
503 * forwards all {@link MotionEvent} data from Android to Flutter.
504 */
505 @Override
506 public boolean onGenericMotionEvent(MotionEvent event) {
507 boolean handled =
508 isAttached() && androidTouchProcessor.onGenericMotionEvent(event, getContext());
509 return handled ? true : super.onGenericMotionEvent(event);
510 }
511
512 @Override
513 protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
514 mMetrics.physicalWidth = width;
515 mMetrics.physicalHeight = height;
516 updateViewportMetrics();
517 super.onSizeChanged(width, height, oldWidth, oldHeight);
518 }
519
520 // TODO(garyq): Add support for notch cutout API
521 // Decide if we want to zero the padding of the sides. When in Landscape orientation,
522 // android may decide to place the software navigation bars on the side. When the nav
523 // bar is hidden, the reported insets should be removed to prevent extra useless space
524 // on the sides.
525 private enum ZeroSides {
526 NONE,
527 LEFT,
528 RIGHT,
529 BOTH
530 }
531
532 private ZeroSides calculateShouldZeroSides() {
533 // We get both orientation and rotation because rotation is all 4
534 // rotations relative to default rotation while orientation is portrait
535 // or landscape. By combining both, we can obtain a more precise measure
536 // of the rotation.
537 Context context = getContext();
538 int orientation = context.getResources().getConfiguration().orientation;
539 int rotation =
540 ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE))
541 .getDefaultDisplay()
542 .getRotation();
543
544 if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
545 if (rotation == Surface.ROTATION_90) {
546 return ZeroSides.RIGHT;
547 } else if (rotation == Surface.ROTATION_270) {
548 // In android API >= 23, the nav bar always appears on the "bottom" (USB) side.
549 return Build.VERSION.SDK_INT >= API_LEVELS.API_23 ? ZeroSides.LEFT : ZeroSides.RIGHT;
550 }
551 // Ambiguous orientation due to landscape left/right default. Zero both sides.
552 else if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
553 return ZeroSides.BOTH;
554 }
555 }
556 // Square orientation deprecated in API 16, we will not check for it and return false
557 // to be safe and not remove any unique padding for the devices that do use it.
558 return ZeroSides.NONE;
559 }
560
561 // TODO(garyq): Use new Android R getInsets API
562 // TODO(garyq): The keyboard detection may interact strangely with
563 // https://github.com/flutter/flutter/issues/22061
564
565 // Uses inset heights and screen heights as a heuristic to determine if the insets should
566 // be padded. When the on-screen keyboard is detected, we want to include the full inset
567 // but when the inset is just the hidden nav bar, we want to provide a zero inset so the space
568 // can be used.
569
570 private int guessBottomKeyboardInset(WindowInsets insets) {
571 int screenHeight = getRootView().getHeight();
572 // Magic number due to this being a heuristic. This should be replaced, but we have not
573 // found a clean way to do it yet (Sept. 2018)
574 final double keyboardHeightRatioHeuristic = 0.18;
575 if (insets.getSystemWindowInsetBottom() < screenHeight * keyboardHeightRatioHeuristic) {
576 // Is not a keyboard, so return zero as inset.
577 return 0;
578 } else {
579 // Is a keyboard, so return the full inset.
580 return insets.getSystemWindowInsetBottom();
581 }
582 }
583
584 // This callback is not present in API < 20, which means lower API devices will see
585 // the wider than expected padding when the status and navigation bars are hidden.
586 // The annotations to suppress "InlinedApi" and "NewApi" lints prevent lint warnings
587 // caused by usage of Android Q APIs. These calls are safe because they are
588 // guarded.
589 @Override
590 @SuppressLint({"InlinedApi", "NewApi"})
591 public final WindowInsets onApplyWindowInsets(WindowInsets insets) {
592 // getSystemGestureInsets() was introduced in API 29 and immediately deprecated in 30.
593 if (Build.VERSION.SDK_INT == API_LEVELS.API_29) {
594 Insets systemGestureInsets = insets.getSystemGestureInsets();
595 mMetrics.systemGestureInsetTop = systemGestureInsets.top;
596 mMetrics.systemGestureInsetRight = systemGestureInsets.right;
597 mMetrics.systemGestureInsetBottom = systemGestureInsets.bottom;
598 mMetrics.systemGestureInsetLeft = systemGestureInsets.left;
599 }
600
601 boolean statusBarVisible = (SYSTEM_UI_FLAG_FULLSCREEN & getWindowSystemUiVisibility()) == 0;
602 boolean navigationBarVisible =
603 (SYSTEM_UI_FLAG_HIDE_NAVIGATION & getWindowSystemUiVisibility()) == 0;
604
605 if (Build.VERSION.SDK_INT >= API_LEVELS.API_30) {
606 int mask = 0;
607 if (navigationBarVisible) {
608 mask = mask | android.view.WindowInsets.Type.navigationBars();
609 }
610 if (statusBarVisible) {
611 mask = mask | android.view.WindowInsets.Type.statusBars();
612 }
613 Insets uiInsets = insets.getInsets(mask);
614 mMetrics.physicalViewPaddingTop = uiInsets.top;
615 mMetrics.physicalViewPaddingRight = uiInsets.right;
616 mMetrics.physicalViewPaddingBottom = uiInsets.bottom;
617 mMetrics.physicalViewPaddingLeft = uiInsets.left;
618
619 Insets imeInsets = insets.getInsets(android.view.WindowInsets.Type.ime());
620 mMetrics.physicalViewInsetTop = imeInsets.top;
621 mMetrics.physicalViewInsetRight = imeInsets.right;
622 mMetrics.physicalViewInsetBottom = imeInsets.bottom; // Typically, only bottom is non-zero
623 mMetrics.physicalViewInsetLeft = imeInsets.left;
624
625 Insets systemGestureInsets =
626 insets.getInsets(android.view.WindowInsets.Type.systemGestures());
627 mMetrics.systemGestureInsetTop = systemGestureInsets.top;
628 mMetrics.systemGestureInsetRight = systemGestureInsets.right;
629 mMetrics.systemGestureInsetBottom = systemGestureInsets.bottom;
630 mMetrics.systemGestureInsetLeft = systemGestureInsets.left;
631
632 // TODO(garyq): Expose the full rects of the display cutout.
633
634 // Take the max of the display cutout insets and existing padding to merge them
635 DisplayCutout cutout = insets.getDisplayCutout();
636 if (cutout != null) {
637 Insets waterfallInsets = cutout.getWaterfallInsets();
638 mMetrics.physicalViewPaddingTop =
639 Math.max(
640 Math.max(mMetrics.physicalViewPaddingTop, waterfallInsets.top),
641 cutout.getSafeInsetTop());
642 mMetrics.physicalViewPaddingRight =
643 Math.max(
644 Math.max(mMetrics.physicalViewPaddingRight, waterfallInsets.right),
645 cutout.getSafeInsetRight());
646 mMetrics.physicalViewPaddingBottom =
647 Math.max(
648 Math.max(mMetrics.physicalViewPaddingBottom, waterfallInsets.bottom),
649 cutout.getSafeInsetBottom());
650 mMetrics.physicalViewPaddingLeft =
651 Math.max(
652 Math.max(mMetrics.physicalViewPaddingLeft, waterfallInsets.left),
653 cutout.getSafeInsetLeft());
654 }
655 } else {
656 // We zero the left and/or right sides to prevent the padding the
657 // navigation bar would have caused.
658 ZeroSides zeroSides = ZeroSides.NONE;
659 if (!navigationBarVisible) {
660 zeroSides = calculateShouldZeroSides();
661 }
662
663 // Status bar (top), navigation bar (bottom) and left/right system insets should
664 // partially obscure the content (padding).
665 mMetrics.physicalViewPaddingTop = statusBarVisible ? insets.getSystemWindowInsetTop() : 0;
666 mMetrics.physicalViewPaddingRight =
667 zeroSides == ZeroSides.RIGHT || zeroSides == ZeroSides.BOTH
668 ? 0
669 : insets.getSystemWindowInsetRight();
670 mMetrics.physicalViewPaddingBottom =
671 navigationBarVisible && guessBottomKeyboardInset(insets) == 0
672 ? insets.getSystemWindowInsetBottom()
673 : 0;
674 mMetrics.physicalViewPaddingLeft =
675 zeroSides == ZeroSides.LEFT || zeroSides == ZeroSides.BOTH
676 ? 0
677 : insets.getSystemWindowInsetLeft();
678
679 // Bottom system inset (keyboard) should adjust scrollable bottom edge (inset).
680 mMetrics.physicalViewInsetTop = 0;
681 mMetrics.physicalViewInsetRight = 0;
682 mMetrics.physicalViewInsetBottom = guessBottomKeyboardInset(insets);
683 mMetrics.physicalViewInsetLeft = 0;
684 }
685
686 updateViewportMetrics();
687 return super.onApplyWindowInsets(insets);
688 }
689
690 private boolean isAttached() {
691 return mNativeView != null && mNativeView.isAttached();
692 }
693
695 if (!isAttached()) throw new AssertionError("Platform view is not attached");
696 }
697
698 private void preRun() {
700 }
701
703 if (mAccessibilityNodeProvider != null) {
704 mAccessibilityNodeProvider.reset();
705 }
706 }
707
708 private void postRun() {}
709
712 preRun();
713 mNativeView.runFromBundle(args);
714 postRun();
715 }
716
717 /**
718 * Return the most recent frame as a bitmap.
719 *
720 * @return A bitmap.
721 */
722 public Bitmap getBitmap() {
724 return mNativeView.getFlutterJNI().getBitmap();
725 }
726
727 private void updateViewportMetrics() {
728 if (!isAttached()) return;
729 mNativeView
731 .setViewportMetrics(
732 mMetrics.devicePixelRatio,
733 mMetrics.physicalWidth,
734 mMetrics.physicalHeight,
735 mMetrics.physicalViewPaddingTop,
739 mMetrics.physicalViewInsetTop,
740 mMetrics.physicalViewInsetRight,
742 mMetrics.physicalViewInsetLeft,
743 mMetrics.systemGestureInsetTop,
746 mMetrics.systemGestureInsetLeft,
747 mMetrics.physicalTouchSlop,
748 new int[0],
749 new int[0],
750 new int[0]);
751 }
752
753 // Called by FlutterNativeView to notify first Flutter frame rendered.
754 public void onFirstFrame() {
755 didRenderFirstFrame = true;
756
757 // Allow listeners to remove themselves when they are called.
758 List<FirstFrameListener> listeners = new ArrayList<>(mFirstFrameListeners);
759 for (FirstFrameListener listener : listeners) {
760 listener.onFirstFrame();
761 }
762 }
763
764 @Override
765 protected void onAttachedToWindow() {
766 super.onAttachedToWindow();
767
768 PlatformViewsController platformViewsController =
769 getPluginRegistry().getPlatformViewsController();
770 mAccessibilityNodeProvider =
772 this,
773 new AccessibilityChannel(dartExecutor, getFlutterNativeView().getFlutterJNI()),
774 (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE),
775 getContext().getContentResolver(),
776 platformViewsController);
777 mAccessibilityNodeProvider.setOnAccessibilityChangeListener(onAccessibilityChangeListener);
778
779 resetWillNotDraw(
780 mAccessibilityNodeProvider.isAccessibilityEnabled(),
781 mAccessibilityNodeProvider.isTouchExplorationEnabled());
782 }
783
784 @Override
785 protected void onDetachedFromWindow() {
786 super.onDetachedFromWindow();
787 releaseAccessibilityNodeProvider();
788 }
789
790 // TODO(mattcarroll): Confer with Ian as to why we need this method. Delete if possible, otherwise
791 // add comments.
792 private void resetWillNotDraw(boolean isAccessibilityEnabled, boolean isTouchExplorationEnabled) {
793 if (!mIsSoftwareRenderingEnabled) {
794 setWillNotDraw(!(isAccessibilityEnabled || isTouchExplorationEnabled));
795 } else {
796 setWillNotDraw(false);
797 }
798 }
799
800 @Override
801 public AccessibilityNodeProvider getAccessibilityNodeProvider() {
802 if (mAccessibilityNodeProvider != null && mAccessibilityNodeProvider.isAccessibilityEnabled()) {
803 return mAccessibilityNodeProvider;
804 } else {
805 // TODO(goderbauer): when a11y is off this should return a one-off snapshot of
806 // the a11y
807 // tree.
808 return null;
809 }
810 }
811
812 private void releaseAccessibilityNodeProvider() {
813 if (mAccessibilityNodeProvider != null) {
814 mAccessibilityNodeProvider.release();
815 mAccessibilityNodeProvider = null;
816 }
817 }
818
819 // -------- Start: Mouse -------
820
821 @Override
822 @TargetApi(API_LEVELS.API_24)
823 @RequiresApi(API_LEVELS.API_24)
824 @NonNull
825 public PointerIcon getSystemPointerIcon(int type) {
826 return PointerIcon.getSystemIcon(getContext(), type);
827 }
828
829 // -------- End: Mouse -------
830
831 // -------- Start: Keyboard -------
832
833 @Override
834 public BinaryMessenger getBinaryMessenger() {
835 return this;
836 }
837
838 @Override
839 public boolean onTextInputKeyEvent(@NonNull KeyEvent keyEvent) {
840 return mTextInputPlugin.handleKeyEvent(keyEvent);
841 }
842
843 @Override
844 public void redispatch(@NonNull KeyEvent keyEvent) {
845 getRootView().dispatchKeyEvent(keyEvent);
846 }
847
848 // -------- End: Keyboard -------
849
850 @Override
851 @UiThread
852 public TaskQueue makeBackgroundTaskQueue(TaskQueueOptions options) {
853 return null;
854 }
855
856 @Override
857 @UiThread
858 public void send(String channel, ByteBuffer message) {
859 send(channel, message, null);
860 }
861
862 @Override
863 @UiThread
864 public void send(String channel, ByteBuffer message, BinaryReply callback) {
865 if (!isAttached()) {
866 Log.d(TAG, "FlutterView.send called on a detached view, channel=" + channel);
867 return;
868 }
869 mNativeView.send(channel, message, callback);
870 }
871
872 @Override
873 @UiThread
874 public void setMessageHandler(@NonNull String channel, @NonNull BinaryMessageHandler handler) {
875 mNativeView.setMessageHandler(channel, handler);
876 }
877
878 @Override
879 @UiThread
880 public void setMessageHandler(
881 @NonNull String channel,
882 @NonNull BinaryMessageHandler handler,
883 @NonNull TaskQueue taskQueue) {
884 mNativeView.setMessageHandler(channel, handler, taskQueue);
885 }
886
887 /** Listener will be called on the Android UI thread once when Flutter renders the first frame. */
888 public interface FirstFrameListener {
890 }
891
892 @Override
893 @NonNull
894 public TextureRegistry.SurfaceTextureEntry createSurfaceTexture() {
895 final SurfaceTexture surfaceTexture = new SurfaceTexture(0);
896 return registerSurfaceTexture(surfaceTexture);
897 }
898
899 @Override
900 @NonNull
902 throw new UnsupportedOperationException("Image textures are not supported in this mode.");
903 }
904
905 @Override
907 throw new UnsupportedOperationException(
908 "SurfaceProducer textures are not supported in this mode.");
909 }
910
911 @Override
912 @NonNull
913 public TextureRegistry.SurfaceTextureEntry registerSurfaceTexture(
914 @NonNull SurfaceTexture surfaceTexture) {
915 surfaceTexture.detachFromGLContext();
916 final SurfaceTextureRegistryEntry entry =
917 new SurfaceTextureRegistryEntry(nextTextureId.getAndIncrement(), surfaceTexture);
918 mNativeView.getFlutterJNI().registerTexture(entry.id(), entry.textureWrapper());
919 return entry;
920 }
921
923 private final long id;
924 private final SurfaceTextureWrapper textureWrapper;
925 private boolean released;
926
928 this.id = id;
929 this.textureWrapper = new SurfaceTextureWrapper(surfaceTexture);
930
931 // The callback relies on being executed on the UI thread (unsynchronised read of
932 // mNativeView
933 // and also the engine code check for platform thread in
934 // Shell::OnPlatformViewMarkTextureFrameAvailable),
935 // so we explicitly pass a Handler for the current thread.
936 this.surfaceTexture().setOnFrameAvailableListener(onFrameListener, new Handler());
937 }
938
939 private SurfaceTexture.OnFrameAvailableListener onFrameListener =
940 new SurfaceTexture.OnFrameAvailableListener() {
941 @Override
942 public void onFrameAvailable(SurfaceTexture texture) {
943 if (released || mNativeView == null) {
944 // Even though we make sure to unregister the callback before releasing, as of Android
945 // O
946 // SurfaceTexture has a data race when accessing the callback, so the callback may
947 // still be called by a stale reference after released==true and mNativeView==null.
948 return;
949 }
950
951 mNativeView
953 .markTextureFrameAvailable(SurfaceTextureRegistryEntry.this.id);
954 }
955 };
956
957 public SurfaceTextureWrapper textureWrapper() {
958 return textureWrapper;
959 }
960
961 @NonNull
962 @Override
963 public SurfaceTexture surfaceTexture() {
964 return textureWrapper.surfaceTexture();
965 }
966
967 @Override
968 public long id() {
969 return id;
970 }
971
972 @Override
973 public void release() {
974 if (released) {
975 return;
976 }
977 released = true;
978
979 // The ordering of the next 3 calls is important:
980 // First we remove the frame listener, then we release the SurfaceTexture, and only after we
981 // unregister
982 // the texture which actually deletes the GL texture.
983
984 // Otherwise onFrameAvailableListener might be called after mNativeView==null
985 // (https://github.com/flutter/flutter/issues/20951). See also the check in onFrameAvailable.
986 surfaceTexture().setOnFrameAvailableListener(null);
987 textureWrapper.release();
988 mNativeView.getFlutterJNI().unregisterTexture(id);
989 }
990 }
991}
const char * options
void add(sk_sp< SkIDChangeListener > listener) SK_EXCLUDES(fMutex)
static void d(@NonNull String tag, @NonNull String message)
Definition Log.java:64
static void e(@NonNull String tag, @NonNull String message)
Definition Log.java:84
static String getLookupKeyForAsset(@NonNull String asset)
void setMessageHandler(String channel, BinaryMessageHandler handler)
void send(String channel, ByteBuffer message)
FlutterPluginRegistry getPluginRegistry()
void attachViewAndActivity(FlutterView flutterView, Activity activity)
void runFromBundle(FlutterRunArguments args)
SurfaceTextureRegistryEntry(long id, SurfaceTexture surfaceTexture)
void addFirstFrameListener(FirstFrameListener listener)
boolean onHoverEvent(MotionEvent event)
void setInitialRoute(String route)
void onSizeChanged(int width, int height, int oldWidth, int oldHeight)
void removeFirstFrameListener(FirstFrameListener listener)
PointerIcon getSystemPointerIcon(int type)
FlutterNativeView getFlutterNativeView()
String getLookupKeyForAsset(String asset)
void setMessageHandler( @NonNull String channel, @NonNull BinaryMessageHandler handler, @NonNull TaskQueue taskQueue)
void onConfigurationChanged(Configuration newConfig)
void redispatch(@NonNull KeyEvent keyEvent)
final WindowInsets onApplyWindowInsets(WindowInsets insets)
void updateBackGestureProgress(@NonNull BackEvent backEvent)
void setMessageHandler(@NonNull String channel, @NonNull BinaryMessageHandler handler)
FlutterView(Context context, AttributeSet attrs)
void pushRoute(String route)
ImageTextureEntry createImageTexture()
boolean dispatchKeyEvent(KeyEvent event)
boolean checkInputConnectionProxy(View view)
void send(String channel, ByteBuffer message)
FlutterNativeView detach()
String getLookupKeyForAsset(String asset, String packageName)
boolean onGenericMotionEvent(MotionEvent event)
BinaryMessenger getBinaryMessenger()
void runFromBundle(FlutterRunArguments args)
TaskQueue makeBackgroundTaskQueue(TaskQueueOptions options)
TextureRegistry.SurfaceTextureEntry registerSurfaceTexture( @NonNull SurfaceTexture surfaceTexture)
InputConnection onCreateInputConnection(EditorInfo outAttrs)
SurfaceProducer createSurfaceProducer()
void addActivityLifecycleListener(ActivityLifecycleListener listener)
void send(String channel, ByteBuffer message, BinaryReply callback)
void onProvideAutofillVirtualStructure(ViewStructure structure, int flags)
void startBackGesture(@NonNull BackEvent backEvent)
TextureRegistry.SurfaceTextureEntry createSurfaceTexture()
boolean onTextInputKeyEvent(@NonNull KeyEvent keyEvent)
AccessibilityNodeProvider getAccessibilityNodeProvider()
FlutterView(Context context, AttributeSet attrs, FlutterNativeView nativeView)
boolean onTouchEvent(MotionEvent event)
FlutterPluginRegistry getPluginRegistry()
void autofill(SparseArray< AutofillValue > values)
FlutterSemanticsFlag flags
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
FlKeyEvent * event
uint32_t uint32_t * format
Win32Message message
FlTexture * texture
#define TAG()
int32_t height
int32_t width
const uintptr_t id