• 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.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