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.embedding.engine; 6 7 import android.content.Context; 8 import android.content.res.AssetManager; 9 import android.graphics.Bitmap; 10 import android.graphics.SurfaceTexture; 11 import android.os.Looper; 12 import android.support.annotation.NonNull; 13 import android.support.annotation.Nullable; 14 import android.support.annotation.UiThread; 15 import android.view.Surface; 16 import android.view.SurfaceHolder; 17 18 import java.nio.ByteBuffer; 19 import java.util.HashSet; 20 import java.util.Set; 21 22 import io.flutter.Log; 23 import io.flutter.embedding.engine.FlutterEngine.EngineLifecycleListener; 24 import io.flutter.embedding.engine.dart.PlatformMessageHandler; 25 import io.flutter.embedding.engine.renderer.FlutterRenderer; 26 import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener; 27 import io.flutter.plugin.common.StandardMessageCodec; 28 import io.flutter.view.AccessibilityBridge; 29 import io.flutter.view.FlutterCallbackInformation; 30 31 /** 32 * Interface between Flutter embedding's Java code and Flutter engine's C/C++ code. 33 * 34 * WARNING: THIS CLASS IS EXPERIMENTAL. DO NOT SHIP A DEPENDENCY ON THIS CODE. 35 * IF YOU USE IT, WE WILL BREAK YOU. 36 * 37 * Flutter's engine is built with C/C++. The Android Flutter embedding is responsible for 38 * coordinating Android OS events and app user interactions with the C/C++ engine. Such coordination 39 * requires messaging from an Android app in Java code to the C/C++ engine code. This 40 * communication requires a JNI (Java Native Interface) API to cross the Java/native boundary. 41 * 42 * The entirety of Flutter's JNI API is codified in {@code FlutterJNI}. There are multiple reasons 43 * that all such calls are centralized in one class. First, JNI calls are inherently static and 44 * contain no Java implementation, therefore there is little reason to associate calls with different 45 * classes. Second, every JNI call must be registered in C/C++ code and this registration becomes 46 * more complicated with every additional Java class that contains JNI calls. Third, most Android 47 * developers are not familiar with native development or JNI intricacies, therefore it is in the 48 * interest of future maintenance to reduce the API surface that includes JNI declarations. Thus, 49 * all Flutter JNI calls are centralized in {@code FlutterJNI}. 50 * 51 * Despite the fact that individual JNI calls are inherently static, there is state that exists 52 * within {@code FlutterJNI}. Most calls within {@code FlutterJNI} correspond to a specific 53 * "platform view", of which there may be many. Therefore, each {@code FlutterJNI} instance holds 54 * onto a "native platform view ID" after {@link #attachToNative(boolean)}, which is shared with 55 * the native C/C++ engine code. That ID is passed to every platform-view-specific native method. 56 * ID management is handled within {@code FlutterJNI} so that developers don't have to hold onto 57 * that ID. 58 * 59 * To connect part of an Android app to Flutter's C/C++ engine, instantiate a {@code FlutterJNI} and 60 * then attach it to the native side: 61 * 62 * {@code 63 * // Instantiate FlutterJNI and attach to the native side. 64 * FlutterJNI flutterJNI = new FlutterJNI(); 65 * flutterJNI.attachToNative(); 66 * 67 * // Use FlutterJNI as desired. 68 * flutterJNI.dispatchPointerDataPacket(...); 69 * 70 * // Destroy the connection to the native side and cleanup. 71 * flutterJNI.detachFromNativeAndReleaseResources(); 72 * } 73 * 74 * To provide a visual, interactive surface for Flutter rendering and touch events, register a 75 * {@link FlutterRenderer.RenderSurface} with {@link #setRenderSurface(FlutterRenderer.RenderSurface)} 76 * 77 * To receive callbacks for certain events that occur on the native side, register listeners: 78 * 79 * <ol> 80 * <li>{@link #addEngineLifecycleListener(EngineLifecycleListener)}</li> 81 * <li>{@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}</li> 82 * </ol> 83 * 84 * To facilitate platform messages between Java and Dart running in Flutter, register a handler: 85 * 86 * {@link #setPlatformMessageHandler(PlatformMessageHandler)} 87 * 88 * To invoke a native method that is not associated with a platform view, invoke it statically: 89 * 90 * {@code 91 * bool enabled = FlutterJNI.nativeGetIsSoftwareRenderingEnabled(); 92 * } 93 */ 94 public class FlutterJNI { 95 private static final String TAG = "FlutterJNI"; 96 97 @Nullable 98 private static AsyncWaitForVsyncDelegate asyncWaitForVsyncDelegate; 99 // This should also be updated by FlutterView when it is attached to a Display. 100 // The initial value of 0.0 indicates unknown refresh rate. 101 private static float refreshRateFPS = 0.0f; 102 103 // This is set from native code via JNI. 104 @Nullable 105 private static String observatoryUri; 106 107 // TODO(mattcarroll): add javadocs nativeInit( @onNull Context context, @NonNull String[] args, @Nullable String bundlePath, @NonNull String appStoragePath, @NonNull String engineCachesPath )108 public static native void nativeInit( 109 @NonNull Context context, 110 @NonNull String[] args, 111 @Nullable String bundlePath, 112 @NonNull String appStoragePath, 113 @NonNull String engineCachesPath 114 ); 115 116 // TODO(mattcarroll): add javadocs nativeRecordStartTimestamp(long initTimeMillis)117 public static native void nativeRecordStartTimestamp(long initTimeMillis); 118 119 // TODO(mattcarroll): add javadocs 120 @UiThread nativeGetIsSoftwareRenderingEnabled()121 public static native boolean nativeGetIsSoftwareRenderingEnabled(); 122 123 @Nullable 124 // TODO(mattcarroll): add javadocs getObservatoryUri()125 public static String getObservatoryUri() { 126 return observatoryUri; 127 } 128 setRefreshRateFPS(float refreshRateFPS)129 public static void setRefreshRateFPS(float refreshRateFPS) { 130 FlutterJNI.refreshRateFPS = refreshRateFPS; 131 } 132 133 // TODO(mattcarroll): add javadocs setAsyncWaitForVsyncDelegate(@ullable AsyncWaitForVsyncDelegate delegate)134 public static void setAsyncWaitForVsyncDelegate(@Nullable AsyncWaitForVsyncDelegate delegate) { 135 asyncWaitForVsyncDelegate = delegate; 136 } 137 138 // TODO(mattcarroll): add javadocs 139 // Called by native. asyncWaitForVsync(final long cookie)140 private static void asyncWaitForVsync(final long cookie) { 141 if (asyncWaitForVsyncDelegate != null) { 142 asyncWaitForVsyncDelegate.asyncWaitForVsync(cookie); 143 } else { 144 throw new IllegalStateException("An AsyncWaitForVsyncDelegate must be registered with FlutterJNI before asyncWaitForVsync() is invoked."); 145 } 146 } 147 148 // TODO(mattcarroll): add javadocs nativeOnVsync(long frameTimeNanos, long frameTargetTimeNanos, long cookie)149 public static native void nativeOnVsync(long frameTimeNanos, long frameTargetTimeNanos, long cookie); 150 151 // TODO(mattcarroll): add javadocs 152 @NonNull nativeLookupCallbackInformation(long handle)153 public static native FlutterCallbackInformation nativeLookupCallbackInformation(long handle); 154 155 @Nullable 156 private Long nativePlatformViewId; 157 @Nullable 158 private FlutterRenderer.RenderSurface renderSurface; 159 @Nullable 160 private AccessibilityDelegate accessibilityDelegate; 161 @Nullable 162 private PlatformMessageHandler platformMessageHandler; 163 @NonNull 164 private final Set<EngineLifecycleListener> engineLifecycleListeners = new HashSet<>(); 165 @NonNull 166 private final Set<OnFirstFrameRenderedListener> firstFrameListeners = new HashSet<>(); 167 @NonNull 168 private final Looper mainLooper; // cached to avoid synchronization on repeat access. 169 FlutterJNI()170 public FlutterJNI() { 171 // We cache the main looper so that we can ensure calls are made on the main thread 172 // without consistently paying the synchronization cost of getMainLooper(). 173 mainLooper = Looper.getMainLooper(); 174 } 175 176 //------ Start Native Attach/Detach Support ---- 177 /** 178 * Returns true if this instance of {@code FlutterJNI} is connected to Flutter's native 179 * engine via a Java Native Interface (JNI). 180 */ isAttached()181 public boolean isAttached() { 182 return nativePlatformViewId != null; 183 } 184 185 /** 186 * Attaches this {@code FlutterJNI} instance to Flutter's native engine, which allows 187 * for communication between Android code and Flutter's platform agnostic engine. 188 * <p> 189 * This method must not be invoked if {@code FlutterJNI} is already attached to native. 190 */ 191 @UiThread attachToNative(boolean isBackgroundView)192 public void attachToNative(boolean isBackgroundView) { 193 ensureRunningOnMainThread(); 194 ensureNotAttachedToNative(); 195 nativePlatformViewId = nativeAttach(this, isBackgroundView); 196 } 197 nativeAttach(@onNull FlutterJNI flutterJNI, boolean isBackgroundView)198 private native long nativeAttach(@NonNull FlutterJNI flutterJNI, boolean isBackgroundView); 199 200 /** 201 * Detaches this {@code FlutterJNI} instance from Flutter's native engine, which precludes 202 * any further communication between Android code and Flutter's platform agnostic engine. 203 * <p> 204 * This method must not be invoked if {@code FlutterJNI} is not already attached to native. 205 * <p> 206 * Invoking this method will result in the release of all native-side resources that were 207 * setup during {@link #attachToNative(boolean)}, or accumulated thereafter. 208 * <p> 209 * It is permissable to re-attach this instance to native after detaching it from native. 210 */ 211 @UiThread detachFromNativeAndReleaseResources()212 public void detachFromNativeAndReleaseResources() { 213 ensureRunningOnMainThread(); 214 ensureAttachedToNative(); 215 nativeDestroy(nativePlatformViewId); 216 nativePlatformViewId = null; 217 } 218 nativeDestroy(long nativePlatformViewId)219 private native void nativeDestroy(long nativePlatformViewId); 220 ensureNotAttachedToNative()221 private void ensureNotAttachedToNative() { 222 if (nativePlatformViewId != null) { 223 throw new RuntimeException("Cannot execute operation because FlutterJNI is attached to native."); 224 } 225 } 226 ensureAttachedToNative()227 private void ensureAttachedToNative() { 228 if (nativePlatformViewId == null) { 229 throw new RuntimeException("Cannot execute operation because FlutterJNI is not attached to native."); 230 } 231 } 232 //------ End Native Attach/Detach Support ---- 233 234 //----- Start Render Surface Support ----- 235 /** 236 * Sets the {@link FlutterRenderer.RenderSurface} delegate for the attached Flutter context. 237 * <p> 238 * Flutter expects a user interface to exist on the platform side (Android), and that interface 239 * is expected to offer some capabilities that Flutter depends upon. The {@link FlutterRenderer.RenderSurface} 240 * interface represents those expectations. 241 * <p> 242 * If an app includes a user interface that renders a Flutter UI then a {@link FlutterRenderer.RenderSurface} 243 * should be set (this is the typical Flutter scenario). If no UI is being rendered, such as a 244 * Flutter app that is running Dart code in the background, then no registration may be necessary. 245 * <p> 246 * If no {@link FlutterRenderer.RenderSurface} is registered, then related messages coming from 247 * Flutter will be dropped (ignored). 248 */ 249 @UiThread setRenderSurface(@ullable FlutterRenderer.RenderSurface renderSurface)250 public void setRenderSurface(@Nullable FlutterRenderer.RenderSurface renderSurface) { 251 ensureRunningOnMainThread(); 252 this.renderSurface = renderSurface; 253 } 254 255 /** 256 * Adds a {@link OnFirstFrameRenderedListener}, which receives a callback when Flutter's 257 * engine notifies {@code FlutterJNI} that the first frame of a Flutter UI has been rendered 258 * to the {@link Surface} that was provided to Flutter. 259 */ 260 @UiThread addOnFirstFrameRenderedListener(@onNull OnFirstFrameRenderedListener listener)261 public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) { 262 ensureRunningOnMainThread(); 263 firstFrameListeners.add(listener); 264 } 265 266 /** 267 * Removes a {@link OnFirstFrameRenderedListener} that was added with 268 * {@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}. 269 */ 270 @UiThread removeOnFirstFrameRenderedListener(@onNull OnFirstFrameRenderedListener listener)271 public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) { 272 ensureRunningOnMainThread(); 273 firstFrameListeners.remove(listener); 274 } 275 276 // Called by native to notify first Flutter frame rendered. 277 @SuppressWarnings("unused") 278 @UiThread onFirstFrame()279 private void onFirstFrame() { 280 ensureRunningOnMainThread(); 281 if (renderSurface != null) { 282 renderSurface.onFirstFrameRendered(); 283 } 284 // TODO(mattcarroll): log dropped messages when in debug mode (https://github.com/flutter/flutter/issues/25391) 285 286 for (OnFirstFrameRenderedListener listener : firstFrameListeners) { 287 listener.onFirstFrameRendered(); 288 } 289 } 290 291 /** 292 * Call this method when a {@link Surface} has been created onto which you would like Flutter 293 * to paint. 294 * <p> 295 * See {@link android.view.SurfaceHolder.Callback#surfaceCreated(SurfaceHolder)} for an example 296 * of where this call might originate. 297 */ 298 @UiThread onSurfaceCreated(@onNull Surface surface)299 public void onSurfaceCreated(@NonNull Surface surface) { 300 ensureRunningOnMainThread(); 301 ensureAttachedToNative(); 302 nativeSurfaceCreated(nativePlatformViewId, surface); 303 } 304 nativeSurfaceCreated(long nativePlatformViewId, @NonNull Surface surface)305 private native void nativeSurfaceCreated(long nativePlatformViewId, @NonNull Surface surface); 306 307 /** 308 * Call this method when the {@link Surface} changes that was previously registered with 309 * {@link #onSurfaceCreated(Surface)}. 310 * <p> 311 * See {@link android.view.SurfaceHolder.Callback#surfaceChanged(SurfaceHolder, int, int, int)} 312 * for an example of where this call might originate. 313 */ 314 @UiThread onSurfaceChanged(int width, int height)315 public void onSurfaceChanged(int width, int height) { 316 ensureRunningOnMainThread(); 317 ensureAttachedToNative(); 318 nativeSurfaceChanged(nativePlatformViewId, width, height); 319 } 320 nativeSurfaceChanged(long nativePlatformViewId, int width, int height)321 private native void nativeSurfaceChanged(long nativePlatformViewId, int width, int height); 322 323 /** 324 * Call this method when the {@link Surface} is destroyed that was previously registered with 325 * {@link #onSurfaceCreated(Surface)}. 326 * <p> 327 * See {@link android.view.SurfaceHolder.Callback#surfaceDestroyed(SurfaceHolder)} for an example 328 * of where this call might originate. 329 */ 330 @UiThread onSurfaceDestroyed()331 public void onSurfaceDestroyed() { 332 ensureRunningOnMainThread(); 333 ensureAttachedToNative(); 334 nativeSurfaceDestroyed(nativePlatformViewId); 335 } 336 nativeSurfaceDestroyed(long nativePlatformViewId)337 private native void nativeSurfaceDestroyed(long nativePlatformViewId); 338 339 /** 340 * Call this method to notify Flutter of the current device viewport metrics that are 341 * applies to the Flutter UI that is being rendered. 342 * <p> 343 * This method should be invoked with initial values upon attaching to native. Then, 344 * it should be invoked any time those metrics change while {@code FlutterJNI} is 345 * attached to native. 346 */ 347 @UiThread setViewportMetrics( float devicePixelRatio, int physicalWidth, int physicalHeight, int physicalPaddingTop, int physicalPaddingRight, int physicalPaddingBottom, int physicalPaddingLeft, int physicalViewInsetTop, int physicalViewInsetRight, int physicalViewInsetBottom, int physicalViewInsetLeft, int systemGestureInsetTop, int systemGestureInsetRight, int systemGestureInsetBottom, int systemGestureInsetLeft )348 public void setViewportMetrics( 349 float devicePixelRatio, 350 int physicalWidth, 351 int physicalHeight, 352 int physicalPaddingTop, 353 int physicalPaddingRight, 354 int physicalPaddingBottom, 355 int physicalPaddingLeft, 356 int physicalViewInsetTop, 357 int physicalViewInsetRight, 358 int physicalViewInsetBottom, 359 int physicalViewInsetLeft, 360 int systemGestureInsetTop, 361 int systemGestureInsetRight, 362 int systemGestureInsetBottom, 363 int systemGestureInsetLeft 364 ) { 365 ensureRunningOnMainThread(); 366 ensureAttachedToNative(); 367 nativeSetViewportMetrics( 368 nativePlatformViewId, 369 devicePixelRatio, 370 physicalWidth, 371 physicalHeight, 372 physicalPaddingTop, 373 physicalPaddingRight, 374 physicalPaddingBottom, 375 physicalPaddingLeft, 376 physicalViewInsetTop, 377 physicalViewInsetRight, 378 physicalViewInsetBottom, 379 physicalViewInsetLeft, 380 systemGestureInsetTop, 381 systemGestureInsetRight, 382 systemGestureInsetBottom, 383 systemGestureInsetLeft 384 ); 385 } 386 nativeSetViewportMetrics( long nativePlatformViewId, float devicePixelRatio, int physicalWidth, int physicalHeight, int physicalPaddingTop, int physicalPaddingRight, int physicalPaddingBottom, int physicalPaddingLeft, int physicalViewInsetTop, int physicalViewInsetRight, int physicalViewInsetBottom, int physicalViewInsetLeft, int systemGestureInsetTop, int systemGestureInsetRight, int systemGestureInsetBottom, int systemGestureInsetLeft )387 private native void nativeSetViewportMetrics( 388 long nativePlatformViewId, 389 float devicePixelRatio, 390 int physicalWidth, 391 int physicalHeight, 392 int physicalPaddingTop, 393 int physicalPaddingRight, 394 int physicalPaddingBottom, 395 int physicalPaddingLeft, 396 int physicalViewInsetTop, 397 int physicalViewInsetRight, 398 int physicalViewInsetBottom, 399 int physicalViewInsetLeft, 400 int systemGestureInsetTop, 401 int systemGestureInsetRight, 402 int systemGestureInsetBottom, 403 int systemGestureInsetLeft 404 ); 405 //----- End Render Surface Support ----- 406 407 //------ Start Touch Interaction Support --- 408 /** 409 * Sends a packet of pointer data to Flutter's engine. 410 */ 411 @UiThread dispatchPointerDataPacket(@onNull ByteBuffer buffer, int position)412 public void dispatchPointerDataPacket(@NonNull ByteBuffer buffer, int position) { 413 ensureRunningOnMainThread(); 414 ensureAttachedToNative(); 415 nativeDispatchPointerDataPacket(nativePlatformViewId, buffer, position); 416 } 417 nativeDispatchPointerDataPacket(long nativePlatformViewId, @NonNull ByteBuffer buffer, int position)418 private native void nativeDispatchPointerDataPacket(long nativePlatformViewId, 419 @NonNull ByteBuffer buffer, 420 int position); 421 //------ End Touch Interaction Support --- 422 423 //------ Start Accessibility Support ----- 424 /** 425 * Sets the {@link AccessibilityDelegate} for the attached Flutter context. 426 * 427 * The {@link AccessibilityDelegate} is responsible for maintaining an Android-side cache of 428 * Flutter's semantics tree and custom accessibility actions. This cache should be hooked up 429 * to Android's accessibility system. 430 * 431 * See {@link AccessibilityBridge} for an example of an {@link AccessibilityDelegate} and the 432 * surrounding responsibilities. 433 */ 434 // TODO(mattcarroll): move AccessibilityDelegate definition into FlutterJNI. FlutterJNI should be the basis of dependencies, not the other way round. 435 @UiThread setAccessibilityDelegate(@ullable AccessibilityDelegate accessibilityDelegate)436 public void setAccessibilityDelegate(@Nullable AccessibilityDelegate accessibilityDelegate) { 437 ensureRunningOnMainThread(); 438 this.accessibilityDelegate = accessibilityDelegate; 439 } 440 441 /** 442 * Invoked by native to send semantics tree updates from Flutter to Android. 443 * 444 * The {@code buffer} and {@code strings} form a communication protocol that is implemented here: 445 * https://github.com/flutter/engine/blob/master/shell/platform/android/platform_view_android.cc#L207 446 */ 447 @SuppressWarnings("unused") 448 @UiThread updateSemantics(@onNull ByteBuffer buffer, @NonNull String[] strings)449 private void updateSemantics(@NonNull ByteBuffer buffer, @NonNull String[] strings) { 450 ensureRunningOnMainThread(); 451 if (accessibilityDelegate != null) { 452 accessibilityDelegate.updateSemantics(buffer, strings); 453 } 454 // TODO(mattcarroll): log dropped messages when in debug mode (https://github.com/flutter/flutter/issues/25391) 455 } 456 457 /** 458 * Invoked by native to send new custom accessibility events from Flutter to Android. 459 * 460 * The {@code buffer} and {@code strings} form a communication protocol that is implemented here: 461 * https://github.com/flutter/engine/blob/master/shell/platform/android/platform_view_android.cc#L207 462 * 463 * // TODO(cbracken): expand these docs to include more actionable information. 464 */ 465 @SuppressWarnings("unused") 466 @UiThread updateCustomAccessibilityActions(@onNull ByteBuffer buffer, @NonNull String[] strings)467 private void updateCustomAccessibilityActions(@NonNull ByteBuffer buffer, @NonNull String[] strings) { 468 ensureRunningOnMainThread(); 469 if (accessibilityDelegate != null) { 470 accessibilityDelegate.updateCustomAccessibilityActions(buffer, strings); 471 } 472 // TODO(mattcarroll): log dropped messages when in debug mode (https://github.com/flutter/flutter/issues/25391) 473 } 474 475 /** 476 * Sends a semantics action to Flutter's engine, without any additional arguments. 477 */ dispatchSemanticsAction(int id, @NonNull AccessibilityBridge.Action action)478 public void dispatchSemanticsAction(int id, @NonNull AccessibilityBridge.Action action) { 479 dispatchSemanticsAction(id, action, null); 480 } 481 482 /** 483 * Sends a semantics action to Flutter's engine, with additional arguments. 484 */ dispatchSemanticsAction(int id, @NonNull AccessibilityBridge.Action action, @Nullable Object args)485 public void dispatchSemanticsAction(int id, @NonNull AccessibilityBridge.Action action, @Nullable Object args) { 486 ensureAttachedToNative(); 487 488 ByteBuffer encodedArgs = null; 489 int position = 0; 490 if (args != null) { 491 encodedArgs = StandardMessageCodec.INSTANCE.encodeMessage(args); 492 position = encodedArgs.position(); 493 } 494 dispatchSemanticsAction(id, action.value, encodedArgs, position); 495 } 496 497 /** 498 * Sends a semantics action to Flutter's engine, given arguments that are already encoded for 499 * the engine. 500 * <p> 501 * To send a semantics action that has not already been encoded, see 502 * {@link #dispatchSemanticsAction(int, AccessibilityBridge.Action)} and 503 * {@link #dispatchSemanticsAction(int, AccessibilityBridge.Action, Object)}. 504 */ 505 @UiThread dispatchSemanticsAction(int id, int action, @Nullable ByteBuffer args, int argsPosition)506 public void dispatchSemanticsAction(int id, int action, @Nullable ByteBuffer args, int argsPosition) { 507 ensureRunningOnMainThread(); 508 ensureAttachedToNative(); 509 nativeDispatchSemanticsAction(nativePlatformViewId, id, action, args, argsPosition); 510 } 511 nativeDispatchSemanticsAction( long nativePlatformViewId, int id, int action, @Nullable ByteBuffer args, int argsPosition )512 private native void nativeDispatchSemanticsAction( 513 long nativePlatformViewId, 514 int id, 515 int action, 516 @Nullable ByteBuffer args, 517 int argsPosition 518 ); 519 520 /** 521 * Instructs Flutter to enable/disable its semantics tree, which is used by Flutter to support 522 * accessibility and related behaviors. 523 */ 524 @UiThread setSemanticsEnabled(boolean enabled)525 public void setSemanticsEnabled(boolean enabled) { 526 ensureRunningOnMainThread(); 527 ensureAttachedToNative(); 528 nativeSetSemanticsEnabled(nativePlatformViewId, enabled); 529 } 530 nativeSetSemanticsEnabled(long nativePlatformViewId, boolean enabled)531 private native void nativeSetSemanticsEnabled(long nativePlatformViewId, boolean enabled); 532 533 // TODO(mattcarroll): figure out what flags are supported and add javadoc about when/why/where to use this. 534 @UiThread setAccessibilityFeatures(int flags)535 public void setAccessibilityFeatures(int flags) { 536 ensureRunningOnMainThread(); 537 ensureAttachedToNative(); 538 nativeSetAccessibilityFeatures(nativePlatformViewId, flags); 539 } 540 nativeSetAccessibilityFeatures(long nativePlatformViewId, int flags)541 private native void nativeSetAccessibilityFeatures(long nativePlatformViewId, int flags); 542 //------ End Accessibility Support ---- 543 544 //------ Start Texture Registration Support ----- 545 /** 546 * Gives control of a {@link SurfaceTexture} to Flutter so that Flutter can display that 547 * texture within Flutter's UI. 548 */ 549 @UiThread registerTexture(long textureId, @NonNull SurfaceTexture surfaceTexture)550 public void registerTexture(long textureId, @NonNull SurfaceTexture surfaceTexture) { 551 ensureRunningOnMainThread(); 552 ensureAttachedToNative(); 553 nativeRegisterTexture(nativePlatformViewId, textureId, surfaceTexture); 554 } 555 nativeRegisterTexture(long nativePlatformViewId, long textureId, @NonNull SurfaceTexture surfaceTexture)556 private native void nativeRegisterTexture(long nativePlatformViewId, long textureId, @NonNull SurfaceTexture surfaceTexture); 557 558 /** 559 * Call this method to inform Flutter that a texture previously registered with 560 * {@link #registerTexture(long, SurfaceTexture)} has a new frame available. 561 * <p> 562 * Invoking this method instructs Flutter to update its presentation of the given texture so that 563 * the new frame is displayed. 564 */ 565 @UiThread markTextureFrameAvailable(long textureId)566 public void markTextureFrameAvailable(long textureId) { 567 ensureRunningOnMainThread(); 568 ensureAttachedToNative(); 569 nativeMarkTextureFrameAvailable(nativePlatformViewId, textureId); 570 } 571 nativeMarkTextureFrameAvailable(long nativePlatformViewId, long textureId)572 private native void nativeMarkTextureFrameAvailable(long nativePlatformViewId, long textureId); 573 574 /** 575 * Unregisters a texture that was registered with {@link #registerTexture(long, SurfaceTexture)}. 576 */ 577 @UiThread unregisterTexture(long textureId)578 public void unregisterTexture(long textureId) { 579 ensureRunningOnMainThread(); 580 ensureAttachedToNative(); 581 nativeUnregisterTexture(nativePlatformViewId, textureId); 582 } 583 nativeUnregisterTexture(long nativePlatformViewId, long textureId)584 private native void nativeUnregisterTexture(long nativePlatformViewId, long textureId); 585 //------ Start Texture Registration Support ----- 586 587 //------ Start Dart Execution Support ------- 588 /** 589 * Executes a Dart entrypoint. 590 * <p> 591 * This can only be done once per JNI attachment because a Dart isolate can only be 592 * entered once. 593 */ 594 @UiThread runBundleAndSnapshotFromLibrary( @onNull String bundlePath, @Nullable String entrypointFunctionName, @Nullable String pathToEntrypointFunction, @NonNull AssetManager assetManager )595 public void runBundleAndSnapshotFromLibrary( 596 @NonNull String bundlePath, 597 @Nullable String entrypointFunctionName, 598 @Nullable String pathToEntrypointFunction, 599 @NonNull AssetManager assetManager 600 ) { 601 ensureRunningOnMainThread(); 602 ensureAttachedToNative(); 603 nativeRunBundleAndSnapshotFromLibrary( 604 nativePlatformViewId, 605 bundlePath, 606 entrypointFunctionName, 607 pathToEntrypointFunction, 608 assetManager 609 ); 610 } 611 nativeRunBundleAndSnapshotFromLibrary( long nativePlatformViewId, @NonNull String bundlePath, @Nullable String entrypointFunctionName, @Nullable String pathToEntrypointFunction, @NonNull AssetManager manager )612 private native void nativeRunBundleAndSnapshotFromLibrary( 613 long nativePlatformViewId, 614 @NonNull String bundlePath, 615 @Nullable String entrypointFunctionName, 616 @Nullable String pathToEntrypointFunction, 617 @NonNull AssetManager manager 618 ); 619 //------ End Dart Execution Support ------- 620 621 //--------- Start Platform Message Support ------ 622 /** 623 * Sets the handler for all platform messages that come from the attached platform view to Java. 624 * <p> 625 * Communication between a specific Flutter context (Dart) and the host platform (Java) is 626 * accomplished by passing messages. Messages can be sent from Java to Dart with the corresponding 627 * {@code FlutterJNI} methods: 628 * <ul> 629 * <li>{@link #dispatchPlatformMessage(String, ByteBuffer, int, int)}</li> 630 * <li>{@link #dispatchEmptyPlatformMessage(String, int)}</li> 631 * </ul> 632 * <p> 633 * {@code FlutterJNI} is also the recipient of all platform messages sent from its attached 634 * Flutter context. {@code FlutterJNI} does not know what to do with these messages, so a handler 635 * is exposed to allow these messages to be processed in whatever manner is desired: 636 * <p> 637 * {@code setPlatformMessageHandler(PlatformMessageHandler)} 638 * <p> 639 * If a message is received but no {@link PlatformMessageHandler} is registered, that message will 640 * be dropped (ignored). Therefore, when using {@code FlutterJNI} to integrate a Flutter context 641 * in an app, a {@link PlatformMessageHandler} must be registered for 2-way Java/Dart communication 642 * to operate correctly. Moreover, the handler must be implemented such that fundamental platform 643 * messages are handled as expected. See {@link FlutterNativeView} for an example implementation. 644 */ 645 @UiThread setPlatformMessageHandler(@ullable PlatformMessageHandler platformMessageHandler)646 public void setPlatformMessageHandler(@Nullable PlatformMessageHandler platformMessageHandler) { 647 ensureRunningOnMainThread(); 648 this.platformMessageHandler = platformMessageHandler; 649 } 650 651 // Called by native. 652 // TODO(mattcarroll): determine if message is nonull or nullable 653 @SuppressWarnings("unused") handlePlatformMessage(@onNull final String channel, byte[] message, final int replyId)654 private void handlePlatformMessage(@NonNull final String channel, byte[] message, final int replyId) { 655 if (platformMessageHandler != null) { 656 platformMessageHandler.handleMessageFromDart(channel, message, replyId); 657 } 658 // TODO(mattcarroll): log dropped messages when in debug mode (https://github.com/flutter/flutter/issues/25391) 659 } 660 661 // Called by native to respond to a platform message that we sent. 662 // TODO(mattcarroll): determine if reply is nonull or nullable 663 @SuppressWarnings("unused") handlePlatformMessageResponse(int replyId, byte[] reply)664 private void handlePlatformMessageResponse(int replyId, byte[] reply) { 665 if (platformMessageHandler != null) { 666 platformMessageHandler.handlePlatformMessageResponse(replyId, reply); 667 } 668 // TODO(mattcarroll): log dropped messages when in debug mode (https://github.com/flutter/flutter/issues/25391) 669 } 670 671 /** 672 * Sends an empty reply (identified by {@code responseId}) from Android to Flutter over the given 673 * {@code channel}. 674 */ 675 @UiThread dispatchEmptyPlatformMessage(@onNull String channel, int responseId)676 public void dispatchEmptyPlatformMessage(@NonNull String channel, int responseId) { 677 ensureRunningOnMainThread(); 678 if (isAttached()) { 679 nativeDispatchEmptyPlatformMessage(nativePlatformViewId, channel, responseId); 680 } else { 681 Log.w(TAG, "Tried to send a platform message to Flutter, but FlutterJNI was detached from native C++. Could not send. Channel: " + channel + ". Response ID: " + responseId); 682 } 683 } 684 685 // Send an empty platform message to Dart. nativeDispatchEmptyPlatformMessage( long nativePlatformViewId, @NonNull String channel, int responseId )686 private native void nativeDispatchEmptyPlatformMessage( 687 long nativePlatformViewId, 688 @NonNull String channel, 689 int responseId 690 ); 691 692 /** 693 * Sends a reply {@code message} from Android to Flutter over the given {@code channel}. 694 */ 695 @UiThread dispatchPlatformMessage(@onNull String channel, @Nullable ByteBuffer message, int position, int responseId)696 public void dispatchPlatformMessage(@NonNull String channel, @Nullable ByteBuffer message, int position, int responseId) { 697 ensureRunningOnMainThread(); 698 if (isAttached()) { 699 nativeDispatchPlatformMessage( 700 nativePlatformViewId, 701 channel, 702 message, 703 position, 704 responseId 705 ); 706 } else { 707 Log.w(TAG, "Tried to send a platform message to Flutter, but FlutterJNI was detached from native C++. Could not send. Channel: " + channel + ". Response ID: " + responseId); 708 } 709 } 710 711 // Send a data-carrying platform message to Dart. nativeDispatchPlatformMessage( long nativePlatformViewId, @NonNull String channel, @Nullable ByteBuffer message, int position, int responseId )712 private native void nativeDispatchPlatformMessage( 713 long nativePlatformViewId, 714 @NonNull String channel, 715 @Nullable ByteBuffer message, 716 int position, 717 int responseId 718 ); 719 720 // TODO(mattcarroll): differentiate between channel responses and platform responses. 721 @UiThread invokePlatformMessageEmptyResponseCallback(int responseId)722 public void invokePlatformMessageEmptyResponseCallback(int responseId) { 723 ensureRunningOnMainThread(); 724 if (isAttached()) { 725 nativeInvokePlatformMessageEmptyResponseCallback(nativePlatformViewId, responseId); 726 } else { 727 Log.w(TAG, "Tried to send a platform message response, but FlutterJNI was detached from native C++. Could not send. Response ID: " + responseId); 728 } 729 } 730 731 // Send an empty response to a platform message received from Dart. nativeInvokePlatformMessageEmptyResponseCallback( long nativePlatformViewId, int responseId )732 private native void nativeInvokePlatformMessageEmptyResponseCallback( 733 long nativePlatformViewId, 734 int responseId 735 ); 736 737 // TODO(mattcarroll): differentiate between channel responses and platform responses. 738 @UiThread invokePlatformMessageResponseCallback(int responseId, @Nullable ByteBuffer message, int position)739 public void invokePlatformMessageResponseCallback(int responseId, @Nullable ByteBuffer message, int position) { 740 ensureRunningOnMainThread(); 741 if (isAttached()) { 742 nativeInvokePlatformMessageResponseCallback( 743 nativePlatformViewId, 744 responseId, 745 message, 746 position 747 ); 748 } else { 749 Log.w(TAG, "Tried to send a platform message response, but FlutterJNI was detached from native C++. Could not send. Response ID: " + responseId); 750 } 751 } 752 753 // Send a data-carrying response to a platform message received from Dart. nativeInvokePlatformMessageResponseCallback( long nativePlatformViewId, int responseId, @Nullable ByteBuffer message, int position )754 private native void nativeInvokePlatformMessageResponseCallback( 755 long nativePlatformViewId, 756 int responseId, 757 @Nullable ByteBuffer message, 758 int position 759 ); 760 //------- End Platform Message Support ---- 761 762 //----- Start Engine Lifecycle Support ---- 763 /** 764 * Adds the given {@code engineLifecycleListener} to be notified of Flutter engine lifecycle 765 * events, e.g., {@link EngineLifecycleListener#onPreEngineRestart()}. 766 */ 767 @UiThread addEngineLifecycleListener(@onNull EngineLifecycleListener engineLifecycleListener)768 public void addEngineLifecycleListener(@NonNull EngineLifecycleListener engineLifecycleListener) { 769 ensureRunningOnMainThread(); 770 engineLifecycleListeners.add(engineLifecycleListener); 771 } 772 773 /** 774 * Removes the given {@code engineLifecycleListener}, which was previously added using 775 * {@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}. 776 */ 777 @UiThread removeEngineLifecycleListener(@onNull EngineLifecycleListener engineLifecycleListener)778 public void removeEngineLifecycleListener(@NonNull EngineLifecycleListener engineLifecycleListener) { 779 ensureRunningOnMainThread(); 780 engineLifecycleListeners.remove(engineLifecycleListener); 781 } 782 783 // Called by native. 784 @SuppressWarnings("unused") onPreEngineRestart()785 private void onPreEngineRestart() { 786 for (EngineLifecycleListener listener : engineLifecycleListeners) { 787 listener.onPreEngineRestart(); 788 } 789 } 790 //----- End Engine Lifecycle Support ---- 791 792 // TODO(mattcarroll): determine if this is nonull or nullable 793 @UiThread getBitmap()794 public Bitmap getBitmap() { 795 ensureRunningOnMainThread(); 796 ensureAttachedToNative(); 797 return nativeGetBitmap(nativePlatformViewId); 798 } 799 800 // TODO(mattcarroll): determine if this is nonull or nullable nativeGetBitmap(long nativePlatformViewId)801 private native Bitmap nativeGetBitmap(long nativePlatformViewId); 802 ensureRunningOnMainThread()803 private void ensureRunningOnMainThread() { 804 if (Looper.myLooper() != mainLooper) { 805 throw new RuntimeException( 806 "Methods marked with @UiThread must be executed on the main thread. Current thread: " 807 + Thread.currentThread().getName() 808 ); 809 } 810 } 811 812 /** 813 * Delegate responsible for creating and updating Android-side caches of Flutter's semantics 814 * tree and custom accessibility actions. 815 * 816 * {@link AccessibilityBridge} is an example of an {@code AccessibilityDelegate}. 817 */ 818 public interface AccessibilityDelegate { 819 /** 820 * Sends new custom accessibility actions from Flutter to Android. 821 * 822 * Implementers are expected to maintain an Android-side cache of custom accessibility actions. 823 * This method provides new actions to add to that cache. 824 */ updateCustomAccessibilityActions(@onNull ByteBuffer buffer, @NonNull String[] strings)825 void updateCustomAccessibilityActions(@NonNull ByteBuffer buffer, @NonNull String[] strings); 826 827 /** 828 * Sends new {@code SemanticsNode} information from Flutter to Android. 829 * 830 * Implementers are expected to maintain an Android-side cache of Flutter's semantics tree. 831 * This method provides updates from Flutter for the Android-side semantics tree cache. 832 */ updateSemantics(@onNull ByteBuffer buffer, @NonNull String[] strings)833 void updateSemantics(@NonNull ByteBuffer buffer, @NonNull String[] strings); 834 } 835 836 public interface AsyncWaitForVsyncDelegate { asyncWaitForVsync(final long cookie)837 void asyncWaitForVsync(final long cookie); 838 } 839 } 840