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