// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.view; import android.util.Log; import io.flutter.plugin.common.*; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicBoolean; import java.util.HashMap; import java.util.Map; class FlutterNativeView implements BinaryMessenger { private static final String TAG = "FlutterNativeView"; private final Map mMessageHandlers; private int mNextReplyId = 1; private final Map mPendingReplies = new HashMap<>(); private long mNativePlatformView; private FlutterView mFlutterView; FlutterNativeView(FlutterView flutterView) { mFlutterView = flutterView; attach(this); assertAttached(); mMessageHandlers = new HashMap<>(); } FlutterNativeView() { this(null); } public void destroy() { mFlutterView = null; nativeDestroy(mNativePlatformView); mNativePlatformView = 0; } public boolean isAttached() { return mNativePlatformView != 0; } public long get() { return mNativePlatformView; } public void assertAttached() { if (!isAttached()) throw new AssertionError("Platform view is not attached"); } public void runFromBundle(String bundlePath, String snapshotOverride, String entrypoint, boolean reuseRuntimeController) { assertAttached(); nativeRunBundleAndSnapshot(mNativePlatformView, bundlePath, snapshotOverride, entrypoint, reuseRuntimeController); } public void runFromSource(final String assetsDirectory, final String main, final String packages) { assertAttached(); nativeRunBundleAndSource(mNativePlatformView, assetsDirectory, main, packages); } public static String getObservatoryUri() { return nativeGetObservatoryUri(); } @Override public void send(String channel, ByteBuffer message) { send(channel, message, null); } @Override public void send(String channel, ByteBuffer message, BinaryReply callback) { if (!isAttached()) { Log.d(TAG, "FlutterView.send called on a detached view, channel=" + channel); return; } int replyId = 0; if (callback != null) { replyId = mNextReplyId++; mPendingReplies.put(replyId, callback); } if (message == null) { nativeDispatchEmptyPlatformMessage(mNativePlatformView, channel, replyId); } else { nativeDispatchPlatformMessage(mNativePlatformView, channel, message, message.position(), replyId); } } @Override public void setMessageHandler(String channel, BinaryMessageHandler handler) { if (handler == null) { mMessageHandlers.remove(channel); } else { mMessageHandlers.put(channel, handler); } } private void attach(FlutterNativeView view) { mNativePlatformView = nativeAttach(view); } // Called by native to send us a platform message. private void handlePlatformMessage(final String channel, byte[] message, final int replyId) { assertAttached(); BinaryMessageHandler handler = mMessageHandlers.get(channel); if (handler != null) { try { final ByteBuffer buffer = (message == null ? null : ByteBuffer.wrap(message)); handler.onMessage(buffer, new BinaryReply() { private final AtomicBoolean done = new AtomicBoolean(false); @Override public void reply(ByteBuffer reply) { if (!isAttached()) { Log.d(TAG, "handlePlatformMessage replying to a detached view, channel=" + channel); return; } if (done.getAndSet(true)) { throw new IllegalStateException("Reply already submitted"); } if (reply == null) { nativeInvokePlatformMessageEmptyResponseCallback(mNativePlatformView, replyId); } else { nativeInvokePlatformMessageResponseCallback(mNativePlatformView, replyId, reply, reply.position()); } } }); } catch (Exception ex) { Log.e(TAG, "Uncaught exception in binary message listener", ex); nativeInvokePlatformMessageEmptyResponseCallback(mNativePlatformView, replyId); } return; } nativeInvokePlatformMessageEmptyResponseCallback(mNativePlatformView, replyId); } // Called by native to respond to a platform message that we sent. private void handlePlatformMessageResponse(int replyId, byte[] reply) { BinaryReply callback = mPendingReplies.remove(replyId); if (callback != null) { try { callback.reply(reply == null ? null : ByteBuffer.wrap(reply)); } catch (Exception ex) { Log.e(TAG, "Uncaught exception in binary message reply handler", ex); } } } // Called by native to update the semantics/accessibility tree. private void updateSemantics(ByteBuffer buffer, String[] strings) { if (mFlutterView == null) return; mFlutterView.updateSemantics(buffer, strings); } // Called by native to notify first Flutter frame rendered. private void onFirstFrame() { if (mFlutterView == null) return; mFlutterView.onFirstFrame(); } private static native long nativeAttach(FlutterNativeView view); private static native void nativeDestroy(long nativePlatformViewAndroid); private static native void nativeRunBundleAndSnapshot(long nativePlatformViewAndroid, String bundlePath, String snapshotOverride, String entrypoint, boolean reuseRuntimeController); private static native void nativeRunBundleAndSource(long nativePlatformViewAndroid, String bundlePath, String main, String packages); private static native String nativeGetObservatoryUri(); // Send an empty platform message to Dart. private static native void nativeDispatchEmptyPlatformMessage(long nativePlatformViewAndroid, String channel, int responseId); // Send a data-carrying platform message to Dart. private static native void nativeDispatchPlatformMessage(long nativePlatformViewAndroid, String channel, ByteBuffer message, int position, int responseId); // Send an empty response to a platform message received from Dart. private static native void nativeInvokePlatformMessageEmptyResponseCallback( long nativePlatformViewAndroid, int responseId); // Send a data-carrying response to a platform message received from Dart. private static native void nativeInvokePlatformMessageResponseCallback( long nativePlatformViewAndroid, int responseId, ByteBuffer message, int position); }