FlutterView.java 29.1 KB
Newer Older
1 2 3 4
// Copyright 2013 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.

5
package io.flutter.view;
6

7
import android.app.Activity;
8
import android.content.BroadcastReceiver;
9
import android.content.Context;
10 11 12
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
13
import android.content.res.Configuration;
H
Hixie 已提交
14
import android.opengl.Matrix;
15
import android.graphics.Bitmap;
H
Hixie 已提交
16
import android.graphics.Rect;
A
Adam Barth 已提交
17
import android.os.Build;
18
import android.util.AttributeSet;
19
import android.util.Log;
20
import android.util.TypedValue;
21
import android.view.KeyEvent;
22
import android.view.MotionEvent;
23 24 25 26
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
H
Hixie 已提交
27 28 29 30
import android.view.WindowInsets;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeProvider;
31 32
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
33
import org.json.JSONArray;
34 35
import org.json.JSONException;
import org.json.JSONObject;
36

37
import org.chromium.base.CalledByNative;
38
import org.chromium.base.JNINamespace;
39
import org.chromium.mojo.bindings.Interface.Binding;
40 41
import org.chromium.mojo.bindings.InterfaceRequest;
import org.chromium.mojo.system.Core;
42
import org.chromium.mojo.system.MessagePipeHandle;
H
Hixie 已提交
43
import org.chromium.mojo.system.MojoException;
44
import org.chromium.mojo.system.Pair;
H
Hixie 已提交
45
import org.chromium.mojo.system.impl.CoreImpl;
46
import org.chromium.mojom.sky.SkyEngine;
A
Adam Barth 已提交
47
import org.chromium.mojom.sky.ViewportMetrics;
48

49 50
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
51
import java.nio.charset.StandardCharsets;
52
import java.util.ArrayList;
H
Hixie 已提交
53
import java.util.HashMap;
54
import java.util.List;
55
import java.util.Locale;
H
Hixie 已提交
56
import java.util.Map;
57

A
Adam Barth 已提交
58
import io.flutter.plugin.common.ActivityLifecycleListener;
59
import io.flutter.plugin.editing.TextInputPlugin;
A
Adam Barth 已提交
60 61
import io.flutter.plugin.platform.PlatformPlugin;

62
/**
63
 * An Android view containing a Flutter app.
64
 */
65
@JNINamespace("shell")
66
public class FlutterView extends SurfaceView
67
  implements AccessibilityManager.AccessibilityStateChangeListener {
68
    private static final String TAG = "FlutterView";
69

70 71
    private static final String ACTION_DISCOVER = "io.flutter.view.DISCOVER";

A
Adam Barth 已提交
72
    private long mNativePlatformView;
73 74
    private TextInputPlugin mTextInputPlugin;

75
    private SkyEngine.Proxy mSkyEngine;
76 77
    private HashMap<String, OnMessageListener> mOnMessageListeners;
    private HashMap<String, OnMessageListenerAsync> mAsyncOnMessageListeners;
78
    private final SurfaceHolder.Callback mSurfaceCallback;
79
    private final ViewportMetrics mMetrics;
H
Hixie 已提交
80
    private final AccessibilityManager mAccessibilityManager;
81
    private BroadcastReceiver discoveryReceiver;
82
    private List<ActivityLifecycleListener> mActivityLifecycleListeners;
83

84
    public FlutterView(Context context) {
85 86 87
        this(context, null);
    }

88
    public FlutterView(Context context, AttributeSet attrs) {
89
        super(context, attrs);
90

91 92
        mMetrics = new ViewportMetrics();
        mMetrics.devicePixelRatio = context.getResources().getDisplayMetrics().density;
93 94 95
        setFocusable(true);
        setFocusableInTouchMode(true);

96
        attach();
A
Adam Barth 已提交
97
        assert mNativePlatformView != 0;
98

99 100 101 102 103 104 105 106
        int color = 0xFF000000;
        TypedValue typedValue = new TypedValue();
        context.getTheme().resolveAttribute(android.R.attr.colorBackground, typedValue, true);
        if (typedValue.type >= TypedValue.TYPE_FIRST_COLOR_INT && typedValue.type <= TypedValue.TYPE_LAST_COLOR_INT)
          color = typedValue.data;
        // TODO(abarth): Consider letting the developer override this color.
        final int backgroundColor = color;

107 108
        mSurfaceCallback = new SurfaceHolder.Callback() {
            @Override
109 110
            public void surfaceCreated(SurfaceHolder holder) {
                assert mNativePlatformView != 0;
111
                nativeSurfaceCreated(mNativePlatformView, holder.getSurface(), backgroundColor);
112 113 114
            }

            @Override
115
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
A
Adam Barth 已提交
116
                assert mNativePlatformView != 0;
117
                nativeSurfaceChanged(mNativePlatformView, width, height);
118 119 120 121
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
A
Adam Barth 已提交
122 123
                assert mNativePlatformView != 0;
                nativeSurfaceDestroyed(mNativePlatformView);
124 125 126
            }
        };
        getHolder().addCallback(mSurfaceCallback);
127

128 129
        Core core = CoreImpl.getInstance();

H
Hixie 已提交
130
        mAccessibilityManager = (AccessibilityManager)getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
131

132 133
        mOnMessageListeners = new HashMap<String, OnMessageListener>();
        mAsyncOnMessageListeners = new HashMap<String, OnMessageListenerAsync>();
134
        mActivityLifecycleListeners = new ArrayList<ActivityLifecycleListener>();
135

136
        setLocale(getResources().getConfiguration().locale);
137

138 139 140 141
        // Configure the platform plugin.
        PlatformPlugin platformPlugin = new PlatformPlugin((Activity)getContext());
        addOnMessageListener("flutter/platform", platformPlugin);
        addActivityLifecycleListener(platformPlugin);
142 143
        mTextInputPlugin = new TextInputPlugin((Activity)getContext());
        addOnMessageListener("flutter/textinput", mTextInputPlugin);
A
Adam Barth 已提交
144

145 146 147 148
        if ((context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
            discoveryReceiver = new DiscoveryReceiver();
            context.registerReceiver(discoveryReceiver, new IntentFilter(ACTION_DISCOVER));
        }
149 150
    }

151 152
    private void encodeKeyEvent(KeyEvent event, JSONObject message) throws JSONException {
        message.put("flags", event.getFlags());
153 154
        message.put("codePoint", event.getUnicodeChar());
        message.put("keyCode", event.getKeyCode());
155 156 157 158
        message.put("scanCode", event.getScanCode());
        message.put("metaState", event.getMetaState());
    }

159 160
    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
161 162 163 164 165
        try {
            JSONObject message = new JSONObject();
            message.put("type", "keyup");
            message.put("keymap", "android");
            encodeKeyEvent(event, message);
166
            sendPlatformMessage("flutter/keyevent", message.toString(), null);
167 168 169
        } catch (JSONException e) {
            Log.e(TAG, "Failed to serialize key event", e);
        }
170
        return super.onKeyUp(keyCode, event);
171 172 173 174
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
175 176 177 178 179
        try {
            JSONObject message = new JSONObject();
            message.put("type", "keydown");
            message.put("keymap", "android");
            encodeKeyEvent(event, message);
180
            sendPlatformMessage("flutter/keyevent", message.toString(), null);
181 182 183
        } catch (JSONException e) {
            Log.e(TAG, "Failed to serialize key event", e);
        }
184
        return super.onKeyDown(keyCode, event);
185 186
    }

187 188 189 190
    public void addActivityLifecycleListener(ActivityLifecycleListener listener) {
        mActivityLifecycleListeners.add(listener);
    }

191
    public void onPause() {
192
        sendPlatformMessage("flutter/lifecycle", "AppLifecycleState.paused", null);
193 194
    }

195
    public void onPostResume() {
196 197 198
        for (ActivityLifecycleListener listener : mActivityLifecycleListeners)
            listener.onPostResume();

199
        sendPlatformMessage("flutter/lifecycle", "AppLifecycleState.resumed", null);
200 201 202
    }

    public void pushRoute(String route) {
203 204 205 206 207 208 209 210 211 212
        try {
            final JSONArray args = new JSONArray();
            args.put(0, route);
            final JSONObject message = new JSONObject();
            message.put("method", "pushRoute");
            message.put("args", args);
            sendPlatformMessage("flutter/navigation", message.toString(), null);
        } catch (JSONException e) {
            Log.e(TAG, "Unexpected JSONException pushing route", e);
        }
213 214 215
    }

    public void popRoute() {
216 217 218 219 220 221 222 223
        try {
            final JSONObject message = new JSONObject();
            message.put("method", "popRoute");
            message.put("args", new JSONArray());
            sendPlatformMessage("flutter/navigation", message.toString(), null);
        } catch (JSONException e) {
            Log.e(TAG, "Unexpected JSONException pushing route", e);
        }
224 225 226
    }

    private void setLocale(Locale locale) {
227 228 229 230 231 232 233 234 235 236 237
        try {
            final JSONArray args = new JSONArray();
            args.put(0, locale.getLanguage());
            args.put(1, locale.getCountry());
            final JSONObject message = new JSONObject();
            message.put("method", "setLocale");
            message.put("args", args);
            sendPlatformMessage("flutter/localization", message.toString(), null);
        } catch (JSONException e) {
            Log.e(TAG, "Unexpected JSONException pushing route", e);
        }
238 239 240 241 242 243 244 245
    }

    @Override
    protected void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        setLocale(newConfig.locale);
    }

H
Hixie 已提交
246 247 248 249
    float getDevicePixelRatio() {
        return mMetrics.devicePixelRatio;
    }

250
    public void destroy() {
251 252 253 254
        if (discoveryReceiver != null) {
            getContext().unregisterReceiver(discoveryReceiver);
        }

255
        getHolder().removeCallback(mSurfaceCallback);
A
Adam Barth 已提交
256 257
        nativeDetach(mNativePlatformView);
        mNativePlatformView = 0;
258 259

        mSkyEngine.close();
260 261
    }

262 263
    @Override
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
A
Adam Barth 已提交
264
        return mTextInputPlugin.createInputConnection(this, outAttrs);
265 266
    }

267
    // Must match the PointerChange enum in pointer.dart.
268 269 270 271 272 273
    private static final int kPointerChangeCancel = 0;
    private static final int kPointerChangeAdd = 1;
    private static final int kPointerChangeRemove = 2;
    private static final int kPointerChangeDown = 3;
    private static final int kPointerChangeMove = 4;
    private static final int kPointerChangeUp = 5;
274 275 276 277 278 279 280 281

    // Must match the PointerDeviceKind enum in pointer.dart.
    private static final int kPointerDeviceKindTouch = 0;
    private static final int kPointerDeviceKindMouse = 1;
    private static final int kPointerDeviceKindStylus = 2;
    private static final int kPointerDeviceKindInvertedStylus = 3;

    private int getPointerChangeForAction(int maskedAction) {
282
        // Primary pointer:
283
        if (maskedAction == MotionEvent.ACTION_DOWN) {
284
            return kPointerChangeDown;
285 286
        }
        if (maskedAction == MotionEvent.ACTION_UP) {
287
            return kPointerChangeUp;
288
        }
289
        // Secondary pointer:
290
        if (maskedAction == MotionEvent.ACTION_POINTER_DOWN) {
291
            return kPointerChangeDown;
292 293
        }
        if (maskedAction == MotionEvent.ACTION_POINTER_UP) {
294
            return kPointerChangeUp;
295
        }
296
        // All pointers:
297
        if (maskedAction == MotionEvent.ACTION_MOVE) {
298
            return kPointerChangeMove;
299 300
        }
        if (maskedAction == MotionEvent.ACTION_CANCEL) {
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
            return kPointerChangeCancel;
        }
        return -1;
    }

    private int getPointerDeviceTypeForToolType(int toolType) {
        switch (toolType) {
            case MotionEvent.TOOL_TYPE_FINGER:
                return kPointerDeviceKindTouch;
            case MotionEvent.TOOL_TYPE_STYLUS:
                return kPointerDeviceKindStylus;
            case MotionEvent.TOOL_TYPE_MOUSE:
                return kPointerDeviceKindMouse;
            default:
                // MotionEvent.TOOL_TYPE_UNKNOWN will reach here.
                return -1;
317
        }
318 319
    }

320
    private void addPointerForIndex(MotionEvent event, int pointerIndex,
321 322 323
                                    ByteBuffer packet) {
        int pointerChange = getPointerChangeForAction(event.getActionMasked());
        if (pointerChange == -1) {
324 325 326
            return;
        }

327 328 329 330
        int pointerKind = event.getToolType(pointerIndex);
        if (pointerKind == -1) {
            return;
        }
331

332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
        long timeStamp = event.getEventTime() * 1000; // Convert from milliseconds to microseconds.

        packet.putLong(timeStamp); // time_stamp
        packet.putLong(event.getPointerId(pointerIndex)); // pointer
        packet.putLong(pointerChange); // change
        packet.putLong(pointerKind); // kind
        packet.putDouble(event.getX(pointerIndex)); // physical_x
        packet.putDouble(event.getY(pointerIndex)); // physical_y

        if (pointerKind == kPointerDeviceKindMouse) {
          packet.putLong(event.getButtonState() & 0x1F); // buttons
        } else if (pointerKind == kPointerDeviceKindStylus) {
          packet.putLong((event.getButtonState() >> 4) & 0xF); // buttons
        } else {
          packet.putLong(0); // buttons
        }
348

349
        packet.putLong(0); // obscured
350

351 352
        // TODO(eseidel): Could get the calibrated range if necessary:
        // event.getDevice().getMotionRange(MotionEvent.AXIS_PRESSURE)
353 354 355
        packet.putDouble(event.getPressure(pointerIndex)); // presure
        packet.putDouble(0.0); // pressure_min
        packet.putDouble(1.0); // pressure_max
356

357 358 359 360 361 362 363 364 365 366
        if (pointerKind == kPointerDeviceKindStylus) {
          packet.putDouble(event.getAxisValue(MotionEvent.AXIS_DISTANCE, pointerIndex)); // distance
          packet.putDouble(0.0); // distance_max
        } else {
          packet.putDouble(0.0); // distance
          packet.putDouble(0.0); // distance_max
        }

        packet.putDouble(event.getToolMajor(pointerIndex)); // radius_major
        packet.putDouble(event.getToolMinor(pointerIndex)); // radius_minor
367

368 369
        packet.putDouble(0.0); // radius_min
        packet.putDouble(0.0); // radius_max
370

371
        packet.putDouble(event.getAxisValue(MotionEvent.AXIS_ORIENTATION, pointerIndex)); // orientation
372

373 374 375 376 377
        if (pointerKind == kPointerDeviceKindStylus) {
          packet.putDouble(event.getAxisValue(MotionEvent.AXIS_TILT, pointerIndex)); // tilt
        } else {
          packet.putDouble(0.0); // tilt
        }
378 379 380 381
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
A
Adam Barth 已提交
382 383 384 385 386 387 388 389
        // TODO(abarth): This version check might not be effective in some
        // versions of Android that statically compile code and will be upset
        // at the lack of |requestUnbufferedDispatch|. Instead, we should factor
        // version-dependent code into separate classes for each supported
        // version and dispatch dynamically.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            requestUnbufferedDispatch(event);
        }
390

391 392 393 394 395 396 397 398
        // These values must match the unpacking code in hooks.dart.
        final int kPointerDataFieldCount = 19;
        final int kBytePerField = 8;

        int pointerCount = event.getPointerCount();

        ByteBuffer packet = ByteBuffer.allocateDirect(pointerCount * kPointerDataFieldCount * kBytePerField);
        packet.order(ByteOrder.LITTLE_ENDIAN);
399

400 401 402 403 404 405 406
        int maskedAction = event.getActionMasked();
        // ACTION_UP, ACTION_POINTER_UP, ACTION_DOWN, and ACTION_POINTER_DOWN
        // only apply to a single pointer, other events apply to all pointers.
        if (maskedAction == MotionEvent.ACTION_UP
                || maskedAction == MotionEvent.ACTION_POINTER_UP
                || maskedAction == MotionEvent.ACTION_DOWN
                || maskedAction == MotionEvent.ACTION_POINTER_DOWN) {
407
            addPointerForIndex(event, event.getActionIndex(), packet);
408 409 410 411
        } else {
            // ACTION_MOVE may not actually mean all pointers have moved
            // but it's the responsibility of a later part of the system to
            // ignore 0-deltas if desired.
412 413
            for (int p = 0; p < pointerCount; p++) {
                addPointerForIndex(event, p, packet);
414 415
            }
        }
416

417 418
        assert packet.position() % (kPointerDataFieldCount * kBytePerField) == 0;
        nativeDispatchPointerDataPacket(mNativePlatformView, packet, packet.position());
419 420 421
        return true;
    }

422 423 424 425 426 427 428 429 430 431
    @Override
    public boolean onHoverEvent(MotionEvent event) {
        boolean handled = handleAccessibilityHoverEvent(event);
        if (!handled) {
            // TODO(ianh): Expose hover events to the platform,
            // implementing ADD, REMOVE, etc.
        }
        return handled;
    }

432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449
    @Override
    protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
        mMetrics.physicalWidth = width;
        mMetrics.physicalHeight = height;
        mSkyEngine.onViewportMetricsChanged(mMetrics);
        super.onSizeChanged(width, height, oldWidth, oldHeight);
    }

    @Override
    public final WindowInsets onApplyWindowInsets(WindowInsets insets) {
        mMetrics.physicalPaddingTop = insets.getSystemWindowInsetTop();
        mMetrics.physicalPaddingRight = insets.getSystemWindowInsetRight();
        mMetrics.physicalPaddingBottom = insets.getSystemWindowInsetBottom();
        mMetrics.physicalPaddingLeft = insets.getSystemWindowInsetLeft();
        mSkyEngine.onViewportMetricsChanged(mMetrics);
        return super.onApplyWindowInsets(insets);
    }

450 451
    private void attach() {
        Core core = CoreImpl.getInstance();
452
        Pair<SkyEngine.Proxy, InterfaceRequest<SkyEngine>> engine =
453
                SkyEngine.MANAGER.getInterfaceRequest(core);
454
        mSkyEngine = engine.first;
455 456
        mNativePlatformView =
            nativeAttach(engine.second.passHandle().releaseNativeHandle(), this);
457
    }
458

459
    private void preRun() {
H
Hixie 已提交
460
        resetAccessibilityTree();
461 462 463 464 465 466 467 468
    }

    private void postRun() {
        Core core = CoreImpl.getInstance();
    }

    public void runFromBundle(String bundlePath, String snapshotPath) {
        preRun();
H
Hixie 已提交
469

470 471
        if (FlutterMain.isRunningPrecompiledCode()) {
            mSkyEngine.runFromPrecompiledSnapshot(bundlePath);
472
        } else {
473 474 475 476 477 478
            String scriptUri = "file://" + bundlePath;
            if (snapshotPath != null) {
                mSkyEngine.runFromBundleAndSnapshot(scriptUri, bundlePath, snapshotPath);
            } else {
                mSkyEngine.runFromBundle(scriptUri, bundlePath);
            }
479
        }
480

481 482 483
        postRun();
    }

484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509
    public void runFromSource(final String main,
                              final String packages,
                              final String assetsDirectory) {
        Runnable runnable = new Runnable() {
            public void run() {
                preRun();
                mSkyEngine.runFromFile(main,
                                       packages,
                                       assetsDirectory);
                postRun();
                synchronized (this) {
                    notify();
                }
            }
        };

        try {
            synchronized (runnable) {
                // Post to the Android UI thread and wait for the response.
                post(runnable);
                runnable.wait();
            }
        } catch (InterruptedException e) {
            Log.e(TAG, "Thread got interrupted waiting for " +
                       "RunFromSourceRunnable to finish", e);
        }
510 511
    }

512 513 514 515 516
    /** Return the most recent frame as a bitmap. */
    public Bitmap getBitmap() {
        return nativeGetBitmap(mNativePlatformView);
    }

517 518
    private static native long nativeAttach(int skyEngineHandle,
                                            FlutterView view);
519
    private static native int nativeGetObservatoryPort();
520 521
    private static native void nativeDetach(long nativePlatformViewAndroid);
    private static native void nativeSurfaceCreated(long nativePlatformViewAndroid,
522 523 524 525 526
                                                    Surface surface,
                                                    int backgroundColor);
    private static native void nativeSurfaceChanged(long nativePlatformViewAndroid,
                                                    int width,
                                                    int height);
527
    private static native void nativeSurfaceDestroyed(long nativePlatformViewAndroid);
528
    private static native Bitmap nativeGetBitmap(long nativePlatformViewAndroid);
H
Hixie 已提交
529

530
    // Send a platform message to Dart.
531
    private static native void nativeDispatchPlatformMessage(long nativePlatformViewAndroid, String channel, String message, int responseId);
532
    private static native void nativeDispatchPointerDataPacket(long nativePlatformViewAndroid, ByteBuffer buffer, int position);
533 534
    private static native void nativeDispatchSemanticsAction(long nativePlatformViewAndroid, int id, int action);
    private static native void nativeSetSemanticsEnabled(long nativePlatformViewAndroid, boolean enabled);
535

536 537 538 539
    // Send a response to a platform message received from Dart.
    private static native void nativeInvokePlatformMessageResponseCallback(long nativePlatformViewAndroid, int responseId, String message);

    // Called by native to send us a platform message.
540
    @CalledByNative
541 542
    private void handlePlatformMessage(String channel, String message, final int responseId) {
        OnMessageListener listener = mOnMessageListeners.get(channel);
543 544 545 546 547
        if (listener != null) {
            nativeInvokePlatformMessageResponseCallback(mNativePlatformView, responseId, listener.onMessage(this, message));
            return;
        }

548
        OnMessageListenerAsync asyncListener = mAsyncOnMessageListeners.get(channel);
549 550
        if (asyncListener != null) {
            asyncListener.onMessage(this, message, new MessageResponse() {
551 552 553 554
                @Override
                public void send(String response) {
                    nativeInvokePlatformMessageResponseCallback(mNativePlatformView, responseId, response);
                }
555 556 557 558 559 560
            });
            return;
        }

        nativeInvokePlatformMessageResponseCallback(mNativePlatformView, responseId, null);
    }
H
Hixie 已提交
561

562 563 564 565 566 567 568 569 570 571 572
    private int mNextResponseId = 1;
    private final Map<Integer, MessageReplyCallback> mPendingResponses = new HashMap<Integer, MessageReplyCallback>();

    // Called by native to respond to a platform message that we sent.
    @CalledByNative
    private void handlePlatformMessageResponse(int responseId, String response) {
        MessageReplyCallback callback = mPendingResponses.remove(responseId);
        if (callback != null)
            callback.onReply(response);
    }

573 574
    @CalledByNative
    private void updateSemantics(ByteBuffer buffer, String[] strings) {
A
Adam Barth 已提交
575 576
        if (mAccessibilityNodeProvider != null) {
            buffer.order(ByteOrder.LITTLE_ENDIAN);
577
            mAccessibilityNodeProvider.updateSemantics(buffer, strings);
A
Adam Barth 已提交
578
        }
579 580
    }

H
Hixie 已提交
581 582
    // ACCESSIBILITY

H
Hixie 已提交
583
    private boolean mAccessibilityEnabled = false;
584
    private boolean mTouchExplorationEnabled = false;
585
    private TouchExplorationListener mTouchExplorationListener;
586

587 588 589 590
    protected void dispatchSemanticsAction(int id, int action) {
        nativeDispatchSemanticsAction(mNativePlatformView, id, action);
    }

H
Hixie 已提交
591 592 593
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
H
Hixie 已提交
594
        mAccessibilityEnabled = mAccessibilityManager.isEnabled();
595
        mTouchExplorationEnabled = mAccessibilityManager.isTouchExplorationEnabled();
H
Hixie 已提交
596
        if (mAccessibilityEnabled || mTouchExplorationEnabled)
H
Hixie 已提交
597
          ensureAccessibilityEnabled();
H
Hixie 已提交
598
        resetWillNotDraw();
H
Hixie 已提交
599
        mAccessibilityManager.addAccessibilityStateChangeListener(this);
600 601 602 603 604
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            if (mTouchExplorationListener == null)
                mTouchExplorationListener = new TouchExplorationListener();
            mAccessibilityManager.addTouchExplorationStateChangeListener(mTouchExplorationListener);
        }
H
Hixie 已提交
605 606
    }

H
Hixie 已提交
607 608 609 610
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mAccessibilityManager.removeAccessibilityStateChangeListener(this);
611 612
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
            mAccessibilityManager.removeTouchExplorationStateChangeListener(mTouchExplorationListener);
H
Hixie 已提交
613 614 615 616 617 618
    }

    private void resetWillNotDraw() {
        setWillNotDraw(!(mAccessibilityEnabled || mTouchExplorationEnabled));
    }

H
Hixie 已提交
619 620
    @Override
    public void onAccessibilityStateChanged(boolean enabled) {
H
Hixie 已提交
621 622
        if (enabled) {
            mAccessibilityEnabled = true;
H
Hixie 已提交
623
            ensureAccessibilityEnabled();
H
Hixie 已提交
624 625 626 627 628 629
        } else {
            mAccessibilityEnabled = false;
        }
        if (mAccessibilityNodeProvider != null) {
            mAccessibilityNodeProvider.setAccessibilityEnabled(mAccessibilityEnabled);
        }
H
Hixie 已提交
630
        resetWillNotDraw();
H
Hixie 已提交
631 632
    }

633 634 635 636 637 638 639 640 641 642 643 644
    class TouchExplorationListener
      implements AccessibilityManager.TouchExplorationStateChangeListener {
        @Override
        public void onTouchExplorationStateChanged(boolean enabled) {
            if (enabled) {
                mTouchExplorationEnabled = true;
                ensureAccessibilityEnabled();
            } else {
                mTouchExplorationEnabled = false;
                if (mAccessibilityNodeProvider != null) {
                    mAccessibilityNodeProvider.handleTouchExplorationExit();
                }
645
            }
646
            resetWillNotDraw();
647
        }
H
Hixie 已提交
648 649
    }

H
Hixie 已提交
650 651 652 653 654 655
    @Override
    public AccessibilityNodeProvider getAccessibilityNodeProvider() {
        ensureAccessibilityEnabled();
        return mAccessibilityNodeProvider;
    }

656
    private AccessibilityBridge mAccessibilityNodeProvider;
H
Hixie 已提交
657 658 659

    void ensureAccessibilityEnabled() {
        if (mAccessibilityNodeProvider == null) {
660 661
            mAccessibilityNodeProvider = new AccessibilityBridge(this);
            nativeSetSemanticsEnabled(mNativePlatformView, true);
H
Hixie 已提交
662 663 664
        }
    }

H
Hixie 已提交
665 666
    void resetAccessibilityTree() {
        if (mAccessibilityNodeProvider != null) {
667
            mAccessibilityNodeProvider.reset();
H
Hixie 已提交
668
        }
H
Hixie 已提交
669 670
    }

671 672 673
    private boolean handleAccessibilityHoverEvent(MotionEvent event) {
        if (!mTouchExplorationEnabled)
            return false;
H
Hixie 已提交
674 675 676 677
        if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER ||
                   event.getAction() == MotionEvent.ACTION_HOVER_MOVE) {
            mAccessibilityNodeProvider.handleTouchExploration(event.getX(), event.getY());
        } else if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
678 679
            mAccessibilityNodeProvider.handleTouchExplorationExit();
        } else {
H
Hixie 已提交
680 681
            Log.d("flutter", "unexpected accessibility hover event: " + event);
            return false;
682 683 684
        }
        return true;
    }
H
Hixie 已提交
685

686 687 688 689 690
    /**
     * Send a message to the Flutter application. The Flutter application can
     * register a platform message handler that will receive these messages with
     * the PlatformMessages object.
     */
691
    public void sendPlatformMessage(String channel, String message, MessageReplyCallback callback) {
692 693 694 695 696
        int responseId = 0;
        if (callback != null) {
            responseId = mNextResponseId++;
            mPendingResponses.put(responseId, callback);
        }
697
        nativeDispatchPlatformMessage(mNativePlatformView, channel, message, responseId);
698 699
    }

700 701 702 703
    /**
     * Send a message to the Flutter application.  The Flutter Dart code can register a
     * host message handler that will receive these messages.
     */
704 705
    public void sendToFlutter(String channel, String message, MessageReplyCallback callback) {
        sendPlatformMessage(channel, message, callback);
706 707
    }

708 709
    public void sendToFlutter(String channel, String message) {
        sendToFlutter(channel, message, null);
710 711 712 713 714 715 716 717 718 719 720
    }

    /** Callback invoked when the app replies to a message sent with sendToFlutter. */
    public interface MessageReplyCallback {
        void onReply(String reply);
    }

    /**
     * Register a callback to be invoked when the Flutter application sends a message
     * to its host.
     */
721 722
    public void addOnMessageListener(String channel, OnMessageListener listener) {
        mOnMessageListeners.put(channel, listener);
723 724 725 726 727 728
    }

    /**
     * Register a callback to be invoked when the Flutter application sends a message
     * to its host.  The reply to the message can be provided asynchronously.
     */
729 730
    public void addOnMessageListenerAsync(String channel, OnMessageListenerAsync listener) {
        mAsyncOnMessageListeners.put(channel, listener);
731 732 733 734 735 736 737
    }

    public interface OnMessageListener {
        /**
         * Called when a message is received from the Flutter app.
         * @return the reply to the message (can be null)
         */
738
        String onMessage(FlutterView view, String message);
739 740 741 742 743 744 745
    };

    public interface OnMessageListenerAsync {
        /**
         * Called when a message is received from the Flutter app.
         * @param response Used to send a reply back to the app.
         */
746
        void onMessage(FlutterView view, String message, MessageResponse response);
747 748 749 750 751 752
    }

    public interface MessageResponse {
        void send(String reply);
    }

753 754 755 756 757 758 759 760 761 762 763 764
    /** Broadcast receiver used to discover active Flutter instances. */
    private class DiscoveryReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            JSONObject discover = new JSONObject();
            try {
                discover.put("id", getContext().getPackageName());
                discover.put("observatoryPort", nativeGetObservatoryPort());
                Log.i(TAG, "DISCOVER: " + discover);
            } catch (JSONException e) {}
        }
    }
765
}