• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
5 package io.flutter.view;
6 
7 import android.annotation.TargetApi;
8 import android.annotation.SuppressLint;
9 import android.app.Activity;
10 import android.content.Context;
11 import android.content.ContextWrapper;
12 import android.content.res.Configuration;
13 import android.graphics.Bitmap;
14 import android.graphics.Insets;
15 import android.graphics.PixelFormat;
16 import android.graphics.Rect;
17 import android.graphics.SurfaceTexture;
18 import android.os.Build;
19 import android.os.Handler;
20 import android.os.LocaleList;
21 import android.support.annotation.NonNull;
22 import android.support.annotation.RequiresApi;
23 import android.support.annotation.UiThread;
24 import android.text.format.DateFormat;
25 import android.util.AttributeSet;
26 import android.util.Log;
27 import android.view.KeyEvent;
28 import android.view.MotionEvent;
29 import android.view.Surface;
30 import android.view.SurfaceHolder;
31 import android.view.SurfaceView;
32 import android.view.View;
33 import android.view.WindowInsets;
34 import android.view.accessibility.AccessibilityManager;
35 import android.view.accessibility.AccessibilityNodeProvider;
36 import android.view.inputmethod.EditorInfo;
37 import android.view.inputmethod.InputConnection;
38 import android.view.inputmethod.InputMethodManager;
39 
40 import java.nio.ByteBuffer;
41 import java.nio.ByteOrder;
42 import java.util.ArrayList;
43 import java.util.List;
44 import java.util.Locale;
45 import java.util.concurrent.atomic.AtomicLong;
46 
47 import io.flutter.app.FlutterPluginRegistry;
48 import io.flutter.embedding.android.AndroidKeyProcessor;
49 import io.flutter.embedding.android.AndroidTouchProcessor;
50 import io.flutter.embedding.engine.FlutterJNI;
51 import io.flutter.embedding.engine.dart.DartExecutor;
52 import io.flutter.embedding.engine.renderer.FlutterRenderer;
53 import io.flutter.embedding.engine.systemchannels.AccessibilityChannel;
54 import io.flutter.embedding.engine.systemchannels.KeyEventChannel;
55 import io.flutter.embedding.engine.systemchannels.LifecycleChannel;
56 import io.flutter.embedding.engine.systemchannels.LocalizationChannel;
57 import io.flutter.embedding.engine.systemchannels.NavigationChannel;
58 import io.flutter.embedding.engine.systemchannels.PlatformChannel;
59 import io.flutter.embedding.engine.systemchannels.SettingsChannel;
60 import io.flutter.embedding.engine.systemchannels.SystemChannel;
61 import io.flutter.plugin.common.ActivityLifecycleListener;
62 import io.flutter.plugin.common.BinaryMessenger;
63 import io.flutter.plugin.editing.TextInputPlugin;
64 import io.flutter.plugin.platform.PlatformPlugin;
65 import io.flutter.plugin.platform.PlatformViewsController;
66 
67 /**
68  * An Android view containing a Flutter app.
69  */
70 public class FlutterView extends SurfaceView implements BinaryMessenger, TextureRegistry {
71     /**
72      * Interface for those objects that maintain and expose a reference to a
73      * {@code FlutterView} (such as a full-screen Flutter activity).
74      *
75      * <p>
76      * This indirection is provided to support applications that use an activity
77      * other than {@link io.flutter.app.FlutterActivity} (e.g. Android v4 support
78      * library's {@code FragmentActivity}). It allows Flutter plugins to deal in
79      * this interface and not require that the activity be a subclass of
80      * {@code FlutterActivity}.
81      * </p>
82      */
83     public interface Provider {
84         /**
85          * Returns a reference to the Flutter view maintained by this object. This may
86          * be {@code null}.
87          */
getFlutterView()88         FlutterView getFlutterView();
89     }
90 
91     private static final String TAG = "FlutterView";
92 
93     static final class ViewportMetrics {
94         float devicePixelRatio = 1.0f;
95         int physicalWidth = 0;
96         int physicalHeight = 0;
97         int physicalPaddingTop = 0;
98         int physicalPaddingRight = 0;
99         int physicalPaddingBottom = 0;
100         int physicalPaddingLeft = 0;
101         int physicalViewInsetTop = 0;
102         int physicalViewInsetRight = 0;
103         int physicalViewInsetBottom = 0;
104         int physicalViewInsetLeft = 0;
105         int systemGestureInsetTop = 0;
106         int systemGestureInsetRight = 0;
107         int systemGestureInsetBottom = 0;
108         int systemGestureInsetLeft = 0;
109     }
110 
111     private final DartExecutor dartExecutor;
112     private final FlutterRenderer flutterRenderer;
113     private final NavigationChannel navigationChannel;
114     private final KeyEventChannel keyEventChannel;
115     private final LifecycleChannel lifecycleChannel;
116     private final LocalizationChannel localizationChannel;
117     private final PlatformChannel platformChannel;
118     private final SettingsChannel settingsChannel;
119     private final SystemChannel systemChannel;
120     private final InputMethodManager mImm;
121     private final TextInputPlugin mTextInputPlugin;
122     private final AndroidKeyProcessor androidKeyProcessor;
123     private final AndroidTouchProcessor androidTouchProcessor;
124     private AccessibilityBridge mAccessibilityNodeProvider;
125     private final SurfaceHolder.Callback mSurfaceCallback;
126     private final ViewportMetrics mMetrics;
127     private final List<ActivityLifecycleListener> mActivityLifecycleListeners;
128     private final List<FirstFrameListener> mFirstFrameListeners;
129     private final AtomicLong nextTextureId = new AtomicLong(0L);
130     private FlutterNativeView mNativeView;
131     private boolean mIsSoftwareRenderingEnabled = false; // using the software renderer or not
132     private boolean didRenderFirstFrame = false;
133 
134     private final AccessibilityBridge.OnAccessibilityChangeListener onAccessibilityChangeListener = new AccessibilityBridge.OnAccessibilityChangeListener() {
135         @Override
136         public void onAccessibilityChanged(boolean isAccessibilityEnabled, boolean isTouchExplorationEnabled) {
137             resetWillNotDraw(isAccessibilityEnabled, isTouchExplorationEnabled);
138         }
139     };
140 
FlutterView(Context context)141     public FlutterView(Context context) {
142         this(context, null);
143     }
144 
FlutterView(Context context, AttributeSet attrs)145     public FlutterView(Context context, AttributeSet attrs) {
146         this(context, attrs, null);
147     }
148 
FlutterView(Context context, AttributeSet attrs, FlutterNativeView nativeView)149     public FlutterView(Context context, AttributeSet attrs, FlutterNativeView nativeView) {
150         super(context, attrs);
151 
152         Activity activity = getActivity(getContext());
153         if (activity == null) {
154             throw new IllegalArgumentException("Bad context");
155         }
156 
157         if (nativeView == null) {
158             mNativeView = new FlutterNativeView(activity.getApplicationContext());
159         } else {
160             mNativeView = nativeView;
161         }
162 
163         dartExecutor = mNativeView.getDartExecutor();
164         flutterRenderer = new FlutterRenderer(mNativeView.getFlutterJNI());
165         mIsSoftwareRenderingEnabled = FlutterJNI.nativeGetIsSoftwareRenderingEnabled();
166         mMetrics = new ViewportMetrics();
167         mMetrics.devicePixelRatio = context.getResources().getDisplayMetrics().density;
168         setFocusable(true);
169         setFocusableInTouchMode(true);
170 
171         mNativeView.attachViewAndActivity(this, activity);
172 
173         mSurfaceCallback = new SurfaceHolder.Callback() {
174             @Override
175             public void surfaceCreated(SurfaceHolder holder) {
176                 assertAttached();
177                 mNativeView.getFlutterJNI().onSurfaceCreated(holder.getSurface());
178             }
179 
180             @Override
181             public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
182                 assertAttached();
183                 mNativeView.getFlutterJNI().onSurfaceChanged(width, height);
184             }
185 
186             @Override
187             public void surfaceDestroyed(SurfaceHolder holder) {
188                 assertAttached();
189                 mNativeView.getFlutterJNI().onSurfaceDestroyed();
190             }
191         };
192         getHolder().addCallback(mSurfaceCallback);
193 
194         mActivityLifecycleListeners = new ArrayList<>();
195         mFirstFrameListeners = new ArrayList<>();
196 
197         // Create all platform channels
198         navigationChannel = new NavigationChannel(dartExecutor);
199         keyEventChannel = new KeyEventChannel(dartExecutor);
200         lifecycleChannel = new LifecycleChannel(dartExecutor);
201         localizationChannel = new LocalizationChannel(dartExecutor);
202         platformChannel = new PlatformChannel(dartExecutor);
203         systemChannel = new SystemChannel(dartExecutor);
204         settingsChannel = new SettingsChannel(dartExecutor);
205 
206         // Create and setup plugins
207         PlatformPlugin platformPlugin = new PlatformPlugin(activity, platformChannel);
208         addActivityLifecycleListener(new ActivityLifecycleListener() {
209             @Override
210             public void onPostResume() {
211                 platformPlugin.updateSystemUiOverlays();
212             }
213         });
214         mImm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
215         PlatformViewsController platformViewsController = mNativeView.getPluginRegistry().getPlatformViewsController();
216         mTextInputPlugin = new TextInputPlugin(this, dartExecutor, platformViewsController);
217         androidKeyProcessor = new AndroidKeyProcessor(keyEventChannel, mTextInputPlugin);
218         androidTouchProcessor = new AndroidTouchProcessor(flutterRenderer);
219         mNativeView.getPluginRegistry().getPlatformViewsController().attachTextInputPlugin(mTextInputPlugin);
220 
221         // Send initial platform information to Dart
222         sendLocalesToDart(getResources().getConfiguration());
223         sendUserPlatformSettingsToDart();
224     }
225 
getActivity(Context context)226     private static Activity getActivity(Context context) {
227         if (context == null) {
228             return null;
229         }
230         if (context instanceof Activity) {
231             return (Activity) context;
232         }
233         if (context instanceof ContextWrapper) {
234             // Recurse up chain of base contexts until we find an Activity.
235             return getActivity(((ContextWrapper) context).getBaseContext());
236         }
237         return null;
238     }
239 
240     @NonNull
getDartExecutor()241     public DartExecutor getDartExecutor() {
242         return dartExecutor;
243     }
244 
245     @Override
onKeyUp(int keyCode, KeyEvent event)246     public boolean onKeyUp(int keyCode, KeyEvent event) {
247         if (!isAttached()) {
248             return super.onKeyUp(keyCode, event);
249         }
250         androidKeyProcessor.onKeyUp(event);
251         return super.onKeyUp(keyCode, event);
252     }
253 
254     @Override
onKeyDown(int keyCode, KeyEvent event)255     public boolean onKeyDown(int keyCode, KeyEvent event) {
256         if (!isAttached()) {
257             return super.onKeyDown(keyCode, event);
258         }
259         androidKeyProcessor.onKeyDown(event);
260         return super.onKeyDown(keyCode, event);
261     }
262 
getFlutterNativeView()263     public FlutterNativeView getFlutterNativeView() {
264         return mNativeView;
265     }
266 
getPluginRegistry()267     public FlutterPluginRegistry getPluginRegistry() {
268         return mNativeView.getPluginRegistry();
269     }
270 
getLookupKeyForAsset(String asset)271     public String getLookupKeyForAsset(String asset) {
272         return FlutterMain.getLookupKeyForAsset(asset);
273     }
274 
getLookupKeyForAsset(String asset, String packageName)275     public String getLookupKeyForAsset(String asset, String packageName) {
276         return FlutterMain.getLookupKeyForAsset(asset, packageName);
277     }
278 
addActivityLifecycleListener(ActivityLifecycleListener listener)279     public void addActivityLifecycleListener(ActivityLifecycleListener listener) {
280         mActivityLifecycleListeners.add(listener);
281     }
282 
onStart()283     public void onStart() {
284         lifecycleChannel.appIsInactive();
285     }
286 
onPause()287     public void onPause() {
288         lifecycleChannel.appIsInactive();
289     }
290 
onPostResume()291     public void onPostResume() {
292         for (ActivityLifecycleListener listener : mActivityLifecycleListeners) {
293             listener.onPostResume();
294         }
295         lifecycleChannel.appIsResumed();
296     }
297 
onStop()298     public void onStop() {
299         lifecycleChannel.appIsPaused();
300     }
301 
onMemoryPressure()302     public void onMemoryPressure() {
303         systemChannel.sendMemoryPressureWarning();
304     }
305 
306     /**
307      * Returns true if the Flutter experience associated with this {@code FlutterView} has
308      * rendered its first frame, or false otherwise.
309      */
hasRenderedFirstFrame()310     public boolean hasRenderedFirstFrame() {
311         return didRenderFirstFrame;
312     }
313 
314     /**
315      * Provide a listener that will be called once when the FlutterView renders its
316      * first frame to the underlaying SurfaceView.
317      */
addFirstFrameListener(FirstFrameListener listener)318     public void addFirstFrameListener(FirstFrameListener listener) {
319         mFirstFrameListeners.add(listener);
320     }
321 
322     /**
323      * Remove an existing first frame listener.
324      */
removeFirstFrameListener(FirstFrameListener listener)325     public void removeFirstFrameListener(FirstFrameListener listener) {
326         mFirstFrameListeners.remove(listener);
327     }
328 
329     /**
330      * Updates this to support rendering as a transparent {@link SurfaceView}.
331      *
332      * Sets it on top of its window. The background color still needs to be
333      * controlled from within the Flutter UI itself.
334      *
335      * @deprecated This breaks accessibility highlighting. See https://github.com/flutter/flutter/issues/37025.
336      */
337     @Deprecated
enableTransparentBackground()338     public void enableTransparentBackground() {
339         Log.w(TAG, "Warning: FlutterView is set on top of the window. Accessibility highlights will not be visible in the Flutter UI. https://github.com/flutter/flutter/issues/37025");
340         setZOrderOnTop(true);
341         getHolder().setFormat(PixelFormat.TRANSPARENT);
342     }
343 
344     /**
345      * Reverts this back to the {@link SurfaceView} defaults, at the back of its
346      * window and opaque.
347      */
disableTransparentBackground()348     public void disableTransparentBackground() {
349         setZOrderOnTop(false);
350         getHolder().setFormat(PixelFormat.OPAQUE);
351     }
352 
setInitialRoute(String route)353     public void setInitialRoute(String route) {
354         navigationChannel.setInitialRoute(route);
355     }
356 
pushRoute(String route)357     public void pushRoute(String route) {
358         navigationChannel.pushRoute(route);
359     }
360 
popRoute()361     public void popRoute() {
362         navigationChannel.popRoute();
363     }
364 
sendUserPlatformSettingsToDart()365     private void sendUserPlatformSettingsToDart() {
366         // Lookup the current brightness of the Android OS.
367         boolean isNightModeOn = (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
368         SettingsChannel.PlatformBrightness brightness = isNightModeOn
369             ? SettingsChannel.PlatformBrightness.dark
370             : SettingsChannel.PlatformBrightness.light;
371 
372         settingsChannel
373             .startMessage()
374             .setTextScaleFactor(getResources().getConfiguration().fontScale)
375             .setUse24HourFormat(DateFormat.is24HourFormat(getContext()))
376             .setPlatformBrightness(brightness)
377             .send();
378     }
379 
380     @SuppressWarnings("deprecation")
sendLocalesToDart(Configuration config)381     private void sendLocalesToDart(Configuration config) {
382         List<Locale> locales = new ArrayList<>();
383         if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
384             LocaleList localeList = config.getLocales();
385             int localeCount = localeList.size();
386             for (int index = 0; index < localeCount; ++index) {
387                 Locale locale = localeList.get(index);
388                 locales.add(locale);
389             }
390         } else {
391             locales.add(config.locale);
392         }
393         localizationChannel.sendLocales(locales);
394     }
395 
396     @Override
onConfigurationChanged(Configuration newConfig)397     protected void onConfigurationChanged(Configuration newConfig) {
398         super.onConfigurationChanged(newConfig);
399         sendLocalesToDart(newConfig);
400         sendUserPlatformSettingsToDart();
401     }
402 
getDevicePixelRatio()403     float getDevicePixelRatio() {
404         return mMetrics.devicePixelRatio;
405     }
406 
detach()407     public FlutterNativeView detach() {
408         if (!isAttached())
409             return null;
410         getHolder().removeCallback(mSurfaceCallback);
411         mNativeView.detachFromFlutterView();
412 
413         FlutterNativeView view = mNativeView;
414         mNativeView = null;
415         return view;
416     }
417 
destroy()418     public void destroy() {
419         if (!isAttached())
420             return;
421 
422         getHolder().removeCallback(mSurfaceCallback);
423 
424         mNativeView.destroy();
425         mNativeView = null;
426     }
427 
428     @Override
onCreateInputConnection(EditorInfo outAttrs)429     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
430         return mTextInputPlugin.createInputConnection(this, outAttrs);
431     }
432 
433     @Override
checkInputConnectionProxy(View view)434     public boolean checkInputConnectionProxy(View view) {
435         return mNativeView.getPluginRegistry().getPlatformViewsController().checkInputConnectionProxy(view);
436     }
437 
438     @Override
onTouchEvent(MotionEvent event)439     public boolean onTouchEvent(MotionEvent event) {
440         if (!isAttached()) {
441             return super.onTouchEvent(event);
442         }
443 
444         // TODO(abarth): This version check might not be effective in some
445         // versions of Android that statically compile code and will be upset
446         // at the lack of |requestUnbufferedDispatch|. Instead, we should factor
447         // version-dependent code into separate classes for each supported
448         // version and dispatch dynamically.
449         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
450             requestUnbufferedDispatch(event);
451         }
452 
453         return androidTouchProcessor.onTouchEvent(event);
454     }
455 
456     @Override
onHoverEvent(MotionEvent event)457     public boolean onHoverEvent(MotionEvent event) {
458         if (!isAttached()) {
459             return super.onHoverEvent(event);
460         }
461 
462         boolean handled = mAccessibilityNodeProvider.onAccessibilityHoverEvent(event);
463         if (!handled) {
464             // TODO(ianh): Expose hover events to the platform,
465             // implementing ADD, REMOVE, etc.
466         }
467         return handled;
468     }
469 
470     /**
471      * Invoked by Android when a generic motion event occurs, e.g., joystick movement, mouse hover,
472      * track pad touches, scroll wheel movements, etc.
473      *
474      * Flutter handles all of its own gesture detection and processing, therefore this
475      * method forwards all {@link MotionEvent} data from Android to Flutter.
476      */
477     @Override
onGenericMotionEvent(MotionEvent event)478     public boolean onGenericMotionEvent(MotionEvent event) {
479         boolean handled = isAttached() && androidTouchProcessor.onGenericMotionEvent(event);
480         return handled ? true : super.onGenericMotionEvent(event);
481     }
482 
483     @Override
onSizeChanged(int width, int height, int oldWidth, int oldHeight)484     protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
485         mMetrics.physicalWidth = width;
486         mMetrics.physicalHeight = height;
487         updateViewportMetrics();
488         super.onSizeChanged(width, height, oldWidth, oldHeight);
489     }
490 
491     // TODO(garyq): Add support for notch cutout API
492     // Decide if we want to zero the padding of the sides. When in Landscape orientation,
493     // android may decide to place the software navigation bars on the side. When the nav
494     // bar is hidden, the reported insets should be removed to prevent extra useless space
495     // on the sides.
496     enum ZeroSides { NONE, LEFT, RIGHT, BOTH }
calculateShouldZeroSides()497     ZeroSides calculateShouldZeroSides() {
498         // We get both orientation and rotation because rotation is all 4
499         // rotations relative to default rotation while orientation is portrait
500         // or landscape. By combining both, we can obtain a more precise measure
501         // of the rotation.
502         Activity activity = (Activity)getContext();
503         int orientation = activity.getResources().getConfiguration().orientation;
504         int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
505 
506         if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
507             if (rotation == Surface.ROTATION_90) {
508                 return ZeroSides.RIGHT;
509             }
510             else if (rotation == Surface.ROTATION_270) {
511                 // In android API >= 23, the nav bar always appears on the "bottom" (USB) side.
512                 return Build.VERSION.SDK_INT >= 23 ? ZeroSides.LEFT : ZeroSides.RIGHT;
513             }
514             // Ambiguous orientation due to landscape left/right default. Zero both sides.
515             else if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
516                 return ZeroSides.BOTH;
517             }
518         }
519         // Square orientation deprecated in API 16, we will not check for it and return false
520         // to be safe and not remove any unique padding for the devices that do use it.
521         return ZeroSides.NONE;
522     }
523 
524     // TODO(garyq): Use clean ways to detect keyboard instead of heuristics if possible
525     // TODO(garyq): The keyboard detection may interact strangely with
526     //   https://github.com/flutter/flutter/issues/22061
527 
528     // Uses inset heights and screen heights as a heuristic to determine if the insets should
529     // be padded. When the on-screen keyboard is detected, we want to include the full inset
530     // but when the inset is just the hidden nav bar, we want to provide a zero inset so the space
531     // can be used.
532     @TargetApi(20)
533     @RequiresApi(20)
calculateBottomKeyboardInset(WindowInsets insets)534     int calculateBottomKeyboardInset(WindowInsets insets) {
535         int screenHeight = getRootView().getHeight();
536         // Magic number due to this being a heuristic. This should be replaced, but we have not
537         // found a clean way to do it yet (Sept. 2018)
538         final double keyboardHeightRatioHeuristic = 0.18;
539         if (insets.getSystemWindowInsetBottom() < screenHeight * keyboardHeightRatioHeuristic) {
540             // Is not a keyboard, so return zero as inset.
541             return 0;
542         }
543         else {
544             // Is a keyboard, so return the full inset.
545             return insets.getSystemWindowInsetBottom();
546         }
547     }
548 
549     // This callback is not present in API < 20, which means lower API devices will see
550     // the wider than expected padding when the status and navigation bars are hidden.
551     // The annotations to suppress "InlinedApi" and "NewApi" lints prevent lint warnings
552     // caused by usage of Android Q APIs. These calls are safe because they are
553     // guarded.
554     @Override
555     @TargetApi(20)
556     @RequiresApi(20)
557     @SuppressLint({"InlinedApi", "NewApi"})
onApplyWindowInsets(WindowInsets insets)558     public final WindowInsets onApplyWindowInsets(WindowInsets insets) {
559         boolean statusBarHidden =
560             (SYSTEM_UI_FLAG_FULLSCREEN & getWindowSystemUiVisibility()) != 0;
561         boolean navigationBarHidden =
562             (SYSTEM_UI_FLAG_HIDE_NAVIGATION & getWindowSystemUiVisibility()) != 0;
563 
564         // We zero the left and/or right sides to prevent the padding the
565         // navigation bar would have caused.
566         ZeroSides zeroSides = ZeroSides.NONE;
567         if (navigationBarHidden) {
568             zeroSides = calculateShouldZeroSides();
569         }
570 
571         // The padding on top should be removed when the statusbar is hidden.
572         mMetrics.physicalPaddingTop = statusBarHidden ? 0 : insets.getSystemWindowInsetTop();
573         mMetrics.physicalPaddingRight =
574             zeroSides == ZeroSides.RIGHT || zeroSides == ZeroSides.BOTH ? 0 : insets.getSystemWindowInsetRight();
575         mMetrics.physicalPaddingBottom = 0;
576         mMetrics.physicalPaddingLeft =
577             zeroSides == ZeroSides.LEFT || zeroSides == ZeroSides.BOTH ? 0 : insets.getSystemWindowInsetLeft();
578 
579         // Bottom system inset (keyboard) should adjust scrollable bottom edge (inset).
580         mMetrics.physicalViewInsetTop = 0;
581         mMetrics.physicalViewInsetRight = 0;
582         // We perform hidden navbar and keyboard handling if the navbar is set to hidden. Otherwise,
583         // the navbar padding should always be provided.
584         mMetrics.physicalViewInsetBottom =
585             navigationBarHidden ? calculateBottomKeyboardInset(insets) : insets.getSystemWindowInsetBottom();
586         mMetrics.physicalViewInsetLeft = 0;
587 
588         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
589             Insets systemGestureInsets = insets.getSystemGestureInsets();
590             mMetrics.systemGestureInsetTop = systemGestureInsets.top;
591             mMetrics.systemGestureInsetRight = systemGestureInsets.right;
592             mMetrics.systemGestureInsetBottom = systemGestureInsets.bottom;
593             mMetrics.systemGestureInsetLeft = systemGestureInsets.left;
594         }
595         updateViewportMetrics();
596         return super.onApplyWindowInsets(insets);
597     }
598 
599     @Override
600     @SuppressWarnings("deprecation")
fitSystemWindows(Rect insets)601     protected boolean fitSystemWindows(Rect insets) {
602         if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
603             // Status bar, left/right system insets partially obscure content (padding).
604             mMetrics.physicalPaddingTop = insets.top;
605             mMetrics.physicalPaddingRight = insets.right;
606             mMetrics.physicalPaddingBottom = 0;
607             mMetrics.physicalPaddingLeft = insets.left;
608 
609             // Bottom system inset (keyboard) should adjust scrollable bottom edge (inset).
610             mMetrics.physicalViewInsetTop = 0;
611             mMetrics.physicalViewInsetRight = 0;
612             mMetrics.physicalViewInsetBottom = insets.bottom;
613             mMetrics.physicalViewInsetLeft = 0;
614             updateViewportMetrics();
615             return true;
616         } else {
617             return super.fitSystemWindows(insets);
618         }
619     }
620 
isAttached()621     private boolean isAttached() {
622         return mNativeView != null && mNativeView.isAttached();
623     }
624 
assertAttached()625     void assertAttached() {
626         if (!isAttached())
627             throw new AssertionError("Platform view is not attached");
628     }
629 
preRun()630     private void preRun() {
631         resetAccessibilityTree();
632     }
633 
resetAccessibilityTree()634     void resetAccessibilityTree() {
635         if (mAccessibilityNodeProvider != null) {
636             mAccessibilityNodeProvider.reset();
637         }
638     }
639 
postRun()640     private void postRun() {
641     }
642 
runFromBundle(FlutterRunArguments args)643     public void runFromBundle(FlutterRunArguments args) {
644       assertAttached();
645       preRun();
646       mNativeView.runFromBundle(args);
647       postRun();
648     }
649 
650     /**
651      * Return the most recent frame as a bitmap.
652      *
653      * @return A bitmap.
654      */
getBitmap()655     public Bitmap getBitmap() {
656         assertAttached();
657         return mNativeView.getFlutterJNI().getBitmap();
658     }
659 
updateViewportMetrics()660     private void updateViewportMetrics() {
661         if (!isAttached())
662             return;
663         mNativeView.getFlutterJNI().setViewportMetrics(
664             mMetrics.devicePixelRatio,
665             mMetrics.physicalWidth,
666             mMetrics.physicalHeight,
667             mMetrics.physicalPaddingTop,
668             mMetrics.physicalPaddingRight,
669             mMetrics.physicalPaddingBottom,
670             mMetrics.physicalPaddingLeft,
671             mMetrics.physicalViewInsetTop,
672             mMetrics.physicalViewInsetRight,
673             mMetrics.physicalViewInsetBottom,
674             mMetrics.physicalViewInsetLeft,
675             mMetrics.systemGestureInsetTop,
676             mMetrics.systemGestureInsetRight,
677             mMetrics.systemGestureInsetBottom,
678             mMetrics.systemGestureInsetLeft
679         );
680     }
681 
682     // Called by native to update the semantics/accessibility tree.
updateSemantics(ByteBuffer buffer, String[] strings)683     public void updateSemantics(ByteBuffer buffer, String[] strings) {
684         try {
685             if (mAccessibilityNodeProvider != null) {
686                 buffer.order(ByteOrder.LITTLE_ENDIAN);
687                 mAccessibilityNodeProvider.updateSemantics(buffer, strings);
688             }
689         } catch (Exception ex) {
690             Log.e(TAG, "Uncaught exception while updating semantics", ex);
691         }
692     }
693 
updateCustomAccessibilityActions(ByteBuffer buffer, String[] strings)694     public void updateCustomAccessibilityActions(ByteBuffer buffer, String[] strings) {
695         try {
696             if (mAccessibilityNodeProvider != null) {
697                 buffer.order(ByteOrder.LITTLE_ENDIAN);
698                 mAccessibilityNodeProvider.updateCustomAccessibilityActions(buffer, strings);
699             }
700         } catch (Exception ex) {
701             Log.e(TAG, "Uncaught exception while updating local context actions", ex);
702         }
703     }
704 
705     // Called by FlutterNativeView to notify first Flutter frame rendered.
onFirstFrame()706     public void onFirstFrame() {
707         didRenderFirstFrame = true;
708 
709         // Allow listeners to remove themselves when they are called.
710         List<FirstFrameListener> listeners = new ArrayList<>(mFirstFrameListeners);
711         for (FirstFrameListener listener : listeners) {
712             listener.onFirstFrame();
713         }
714     }
715 
716     @Override
onAttachedToWindow()717     protected void onAttachedToWindow() {
718         super.onAttachedToWindow();
719 
720         PlatformViewsController platformViewsController = getPluginRegistry().getPlatformViewsController();
721         mAccessibilityNodeProvider = new AccessibilityBridge(
722             this,
723             new AccessibilityChannel(dartExecutor, getFlutterNativeView().getFlutterJNI()),
724             (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE),
725             getContext().getContentResolver(),
726             platformViewsController
727         );
728         mAccessibilityNodeProvider.setOnAccessibilityChangeListener(onAccessibilityChangeListener);
729 
730         resetWillNotDraw(
731             mAccessibilityNodeProvider.isAccessibilityEnabled(),
732             mAccessibilityNodeProvider.isTouchExplorationEnabled()
733         );
734     }
735 
736     @Override
onDetachedFromWindow()737     protected void onDetachedFromWindow() {
738         super.onDetachedFromWindow();
739 
740         mAccessibilityNodeProvider.release();
741         mAccessibilityNodeProvider = null;
742     }
743 
744     // TODO(mattcarroll): Confer with Ian as to why we need this method. Delete if possible, otherwise add comments.
resetWillNotDraw(boolean isAccessibilityEnabled, boolean isTouchExplorationEnabled)745     private void resetWillNotDraw(boolean isAccessibilityEnabled, boolean isTouchExplorationEnabled) {
746         if (!mIsSoftwareRenderingEnabled) {
747             setWillNotDraw(!(isAccessibilityEnabled || isTouchExplorationEnabled));
748         } else {
749             setWillNotDraw(false);
750         }
751     }
752 
753     @Override
getAccessibilityNodeProvider()754     public AccessibilityNodeProvider getAccessibilityNodeProvider() {
755         if (mAccessibilityNodeProvider != null && mAccessibilityNodeProvider.isAccessibilityEnabled()) {
756             return mAccessibilityNodeProvider;
757         } else {
758             // TODO(goderbauer): when a11y is off this should return a one-off snapshot of
759             // the a11y
760             // tree.
761             return null;
762         }
763     }
764 
765     @Override
766     @UiThread
send(String channel, ByteBuffer message)767     public void send(String channel, ByteBuffer message) {
768         send(channel, message, null);
769     }
770 
771     @Override
772     @UiThread
send(String channel, ByteBuffer message, BinaryReply callback)773     public void send(String channel, ByteBuffer message, BinaryReply callback) {
774         if (!isAttached()) {
775             Log.d(TAG, "FlutterView.send called on a detached view, channel=" + channel);
776             return;
777         }
778         mNativeView.send(channel, message, callback);
779     }
780 
781     @Override
782     @UiThread
setMessageHandler(String channel, BinaryMessageHandler handler)783     public void setMessageHandler(String channel, BinaryMessageHandler handler) {
784         mNativeView.setMessageHandler(channel, handler);
785     }
786 
787     /**
788      * Listener will be called on the Android UI thread once when Flutter renders
789      * the first frame.
790      */
791     public interface FirstFrameListener {
onFirstFrame()792         void onFirstFrame();
793     }
794 
795     @Override
createSurfaceTexture()796     public TextureRegistry.SurfaceTextureEntry createSurfaceTexture() {
797         final SurfaceTexture surfaceTexture = new SurfaceTexture(0);
798         surfaceTexture.detachFromGLContext();
799         final SurfaceTextureRegistryEntry entry = new SurfaceTextureRegistryEntry(nextTextureId.getAndIncrement(),
800                 surfaceTexture);
801         mNativeView.getFlutterJNI().registerTexture(entry.id(), surfaceTexture);
802         return entry;
803     }
804 
805     final class SurfaceTextureRegistryEntry implements TextureRegistry.SurfaceTextureEntry {
806         private final long id;
807         private final SurfaceTexture surfaceTexture;
808         private boolean released;
809 
SurfaceTextureRegistryEntry(long id, SurfaceTexture surfaceTexture)810         SurfaceTextureRegistryEntry(long id, SurfaceTexture surfaceTexture) {
811             this.id = id;
812             this.surfaceTexture = surfaceTexture;
813 
814             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
815                 // The callback relies on being executed on the UI thread (unsynchronised read of mNativeView
816                 // and also the engine code check for platform thread in Shell::OnPlatformViewMarkTextureFrameAvailable),
817                 // so we explicitly pass a Handler for the current thread.
818                 this.surfaceTexture.setOnFrameAvailableListener(onFrameListener, new Handler());
819             } else {
820                 // Android documentation states that the listener can be called on an arbitrary thread.
821                 // But in practice, versions of Android that predate the newer API will call the listener
822                 // on the thread where the SurfaceTexture was constructed.
823                 this.surfaceTexture.setOnFrameAvailableListener(onFrameListener);
824             }
825         }
826 
827         private SurfaceTexture.OnFrameAvailableListener onFrameListener = new SurfaceTexture.OnFrameAvailableListener() {
828             @Override
829             public void onFrameAvailable(SurfaceTexture texture) {
830                 if (released || mNativeView == null) {
831                     // Even though we make sure to unregister the callback before releasing, as of Android O
832                     // SurfaceTexture has a data race when accessing the callback, so the callback may
833                     // still be called by a stale reference after released==true and mNativeView==null.
834                     return;
835                 }
836                 mNativeView.getFlutterJNI().markTextureFrameAvailable(SurfaceTextureRegistryEntry.this.id);
837             }
838         };
839 
840         @Override
surfaceTexture()841         public SurfaceTexture surfaceTexture() {
842             return surfaceTexture;
843         }
844 
845         @Override
id()846         public long id() {
847             return id;
848         }
849 
850         @Override
release()851         public void release() {
852             if (released) {
853                 return;
854             }
855             released = true;
856 
857             // The ordering of the next 3 calls is important:
858             // First we remove the frame listener, then we release the SurfaceTexture, and only after we unregister
859             // the texture which actually deletes the GL texture.
860 
861             // Otherwise onFrameAvailableListener might be called after mNativeView==null
862             // (https://github.com/flutter/flutter/issues/20951). See also the check in onFrameAvailable.
863             surfaceTexture.setOnFrameAvailableListener(null);
864             surfaceTexture.release();
865             mNativeView.getFlutterJNI().unregisterTexture(id);
866         }
867     }
868 }
869