AccessibilityBridge.java 29.4 KB
Newer Older
H
Hixie 已提交
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;
H
Hixie 已提交
6 7 8

import android.graphics.Rect;
import android.opengl.Matrix;
H
Hixie 已提交
9
import android.os.Bundle;
10
import android.util.Log;
H
Hixie 已提交
11
import android.view.View;
12
import android.view.accessibility.AccessibilityEvent;
H
Hixie 已提交
13 14
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeProvider;
15
import io.flutter.plugin.common.BasicMessageChannel;
16
import io.flutter.plugin.common.StandardMessageCodec;
H
Hixie 已提交
17

18
import java.nio.ByteBuffer;
H
Hixie 已提交
19
import java.util.ArrayList;
20
import java.util.Arrays;
21 22
import java.util.Collections;
import java.util.Comparator;
H
Hixie 已提交
23
import java.util.HashMap;
24
import java.util.HashSet;
25
import java.util.Iterator;
H
Hixie 已提交
26 27
import java.util.List;
import java.util.Map;
28
import java.util.Set;
H
Hixie 已提交
29

30
class AccessibilityBridge extends AccessibilityNodeProvider implements BasicMessageChannel.MessageHandler<Object> {
31 32 33
    private static final String TAG = "FlutterView";

    private Map<Integer, SemanticsObject> mObjects;
34
    private final FlutterView mOwner;
35
    private boolean mAccessibilityEnabled = false;
36 37
    private SemanticsObject mA11yFocusedObject;
    private SemanticsObject mInputFocusedObject;
38 39
    private SemanticsObject mHoveredObject;

40 41
    private final BasicMessageChannel<Object> mFlutterAccessibilityChannel;

42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
    enum Action {
        TAP(1 << 0),
        LONG_PRESS(1 << 1),
        SCROLL_LEFT(1 << 2),
        SCROLL_RIGHT(1 << 3),
        SCROLL_UP(1 << 4),
        SCROLL_DOWN(1 << 5),
        INCREASE(1 << 6),
        DECREASE(1 << 7),
        SHOW_ON_SCREEN(1 << 8),
        MOVE_CURSOR_FORWARD_BY_CHARACTER(1 << 9),
        MOVE_CURSOR_BACKWARD_BY_CHARACTER(1 << 10);

        Action(int value) {
            this.value = value;
        }

        final int value;
    }

    enum Flag {
        HAS_CHECKED_STATE(1 << 0),
        IS_CHECKED(1 << 1),
        IS_SELECTED(1 << 2),
        IS_BUTTON(1 << 3),
        IS_TEXT_FIELD(1 << 4),
68 69
        IS_FOCUSED(1 << 5),
        IS_DISABLED(1 << 6);
70 71 72 73 74 75 76

        Flag(int value) {
            this.value = value;
        }

        final int value;
    }
77 78

    AccessibilityBridge(FlutterView owner) {
H
Hixie 已提交
79 80
        assert owner != null;
        mOwner = owner;
81
        mObjects = new HashMap<Integer, SemanticsObject>();
82
        mFlutterAccessibilityChannel = new BasicMessageChannel<>(owner, "flutter/accessibility",
83
            StandardMessageCodec.INSTANCE);
H
Hixie 已提交
84 85
    }

86
    void setAccessibilityEnabled(boolean accessibilityEnabled) {
87
        mAccessibilityEnabled = accessibilityEnabled;
88 89 90 91 92
        if (accessibilityEnabled) {
            mFlutterAccessibilityChannel.setMessageHandler(this);
        } else {
            mFlutterAccessibilityChannel.setMessageHandler(null);
        }
H
Hixie 已提交
93 94
    }

H
Hixie 已提交
95
    @Override
96
    @SuppressWarnings("deprecation")
H
Hixie 已提交
97 98 99 100
    public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
        if (virtualViewId == View.NO_ID) {
            AccessibilityNodeInfo result = AccessibilityNodeInfo.obtain(mOwner);
            mOwner.onInitializeAccessibilityNodeInfo(result);
101
            if (mObjects.containsKey(0))
H
Hixie 已提交
102 103 104 105
                result.addChild(mOwner, 0);
            return result;
        }

106
        SemanticsObject object = mObjects.get(virtualViewId);
107
        if (object == null)
H
Hixie 已提交
108 109 110 111
            return null;

        AccessibilityNodeInfo result = AccessibilityNodeInfo.obtain(mOwner, virtualViewId);
        result.setPackageName(mOwner.getContext().getPackageName());
112
        result.setClassName("Flutter"); // TODO(goderbauer): Set proper class names
H
Hixie 已提交
113
        result.setSource(mOwner, virtualViewId);
114
        result.setFocusable(object.isFocusable());
115 116
        if (mInputFocusedObject != null)
            result.setFocused(mInputFocusedObject.id == virtualViewId);
117

118 119
        if (mA11yFocusedObject != null)
            result.setAccessibilityFocused(mA11yFocusedObject.id == virtualViewId);
H
Hixie 已提交
120

121
        if (object.hasFlag(Flag.IS_TEXT_FIELD)) {
122
            result.setClassName("android.widget.EditText");
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
            result.setEditable(true);

            // Cursor movements
            int granularities = 0;
            if (object.hasAction(Action.MOVE_CURSOR_FORWARD_BY_CHARACTER)) {
                result.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
                granularities |= AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER;
            }
            if (object.hasAction(Action.MOVE_CURSOR_BACKWARD_BY_CHARACTER)) {
                result.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
                granularities |= AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER;
            }
            result.setMovementGranularities(granularities);
        }

        if (object.hasFlag(Flag.IS_BUTTON)) {
          result.setClassName("android.widget.Button");
        }
141

142 143 144
        if (object.parent != null) {
            assert object.id > 0;
            result.setParent(mOwner, object.parent.id);
H
Hixie 已提交
145
        } else {
146
            assert object.id == 0;
H
Hixie 已提交
147 148 149
            result.setParent(mOwner);
        }

150 151 152
        Rect bounds = object.getGlobalRect();
        if (object.parent != null) {
            Rect parentBounds = object.parent.getGlobalRect();
H
Hixie 已提交
153 154 155 156 157 158 159 160
            Rect boundsInParent = new Rect(bounds);
            boundsInParent.offset(-parentBounds.left, -parentBounds.top);
            result.setBoundsInParent(boundsInParent);
        } else {
            result.setBoundsInParent(bounds);
        }
        result.setBoundsInScreen(bounds);
        result.setVisibleToUser(true);
161
        result.setEnabled(!object.hasFlag(Flag.IS_DISABLED));
H
Hixie 已提交
162

163
        if (object.hasAction(Action.TAP)) {
164
            result.addAction(AccessibilityNodeInfo.ACTION_CLICK);
H
Hixie 已提交
165 166
            result.setClickable(true);
        }
167
        if (object.hasAction(Action.LONG_PRESS)) {
168
            result.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
H
Hixie 已提交
169 170
            result.setLongClickable(true);
        }
171 172
        if (object.hasAction(Action.SCROLL_LEFT) || object.hasAction(Action.SCROLL_UP)
                || object.hasAction(Action.SCROLL_RIGHT) || object.hasAction(Action.SCROLL_DOWN)) {
H
Hixie 已提交
173
            result.setScrollable(true);
174 175 176
            // This tells Android's a11y to send scroll events when reaching the end of
            // the visible viewport of a scrollable.
            result.setClassName("android.widget.ScrollView");
177 178 179
            // TODO(ianh): Once we're on SDK v23+, call addAction to
            // expose AccessibilityAction.ACTION_SCROLL_LEFT, _RIGHT,
            // _UP, and _DOWN when appropriate.
180
            if (object.hasAction(Action.SCROLL_LEFT) || object.hasAction(Action.SCROLL_UP)) {
181 182
                result.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
            }
183
            if (object.hasAction(Action.SCROLL_RIGHT) || object.hasAction(Action.SCROLL_DOWN)) {
184 185
                result.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
            }
H
Hixie 已提交
186
        }
187
        if (object.hasAction(Action.INCREASE) || object.hasAction(Action.DECREASE)) {
188
            result.setClassName("android.widget.SeekBar");
189
            if (object.hasAction(Action.INCREASE)) {
190 191
                result.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
            }
192
            if (object.hasAction(Action.DECREASE)) {
193 194 195
                result.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
            }
        }
H
Hixie 已提交
196

197 198 199
        result.setCheckable(object.hasFlag(Flag.HAS_CHECKED_STATE));
        result.setChecked(object.hasFlag(Flag.IS_CHECKED));
        result.setSelected(object.hasFlag(Flag.IS_SELECTED));
200
        result.setText(object.getValueLabelHint());
H
Hixie 已提交
201

H
Hixie 已提交
202
        // Accessibility Focus
203
        if (mA11yFocusedObject != null && mA11yFocusedObject.id == virtualViewId) {
204
            result.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
H
Hixie 已提交
205
        } else {
206
            result.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
H
Hixie 已提交
207 208
        }

209
        if (object.children != null) {
210 211 212 213 214 215 216 217 218 219
            List<SemanticsObject> childrenInTraversalOrder =
                new ArrayList<SemanticsObject>(object.children);
            Collections.sort(childrenInTraversalOrder, new Comparator<SemanticsObject>() {
                public int compare(SemanticsObject a, SemanticsObject b) {
                    final int top = Integer.compare(a.globalRect.top, b.globalRect.top);
                    // TODO(goderbauer): sort right-to-left in rtl environments.
                    return top == 0 ? Integer.compare(a.globalRect.left, b.globalRect.left) : top;
                }
            });
            for (SemanticsObject child : childrenInTraversalOrder) {
220 221
                result.addChild(mOwner, child.id);
            }
H
Hixie 已提交
222 223 224 225 226
        }

        return result;
    }

H
Hixie 已提交
227 228
    @Override
    public boolean performAction(int virtualViewId, int action, Bundle arguments) {
229
        SemanticsObject object = mObjects.get(virtualViewId);
230
        if (object == null) {
H
Hixie 已提交
231
            return false;
232
        }
H
Hixie 已提交
233 234
        switch (action) {
            case AccessibilityNodeInfo.ACTION_CLICK: {
235
                mOwner.dispatchSemanticsAction(virtualViewId, Action.TAP);
H
Hixie 已提交
236 237 238
                return true;
            }
            case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
239
                mOwner.dispatchSemanticsAction(virtualViewId, Action.LONG_PRESS);
H
Hixie 已提交
240 241
                return true;
            }
242
            case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
243 244 245
                if (object.hasAction(Action.SCROLL_UP)) {
                    mOwner.dispatchSemanticsAction(virtualViewId, Action.SCROLL_UP);
                } else if (object.hasAction(Action.SCROLL_LEFT)) {
246
                    // TODO(ianh): bidi support using textDirection
247 248
                    mOwner.dispatchSemanticsAction(virtualViewId, Action.SCROLL_LEFT);
                } else if (object.hasAction(Action.INCREASE)) {
249 250 251
                    object.value = object.increasedValue;
                    // Event causes Android to read out the updated value.
                    sendAccessibilityEvent(virtualViewId, AccessibilityEvent.TYPE_VIEW_SELECTED);
252
                    mOwner.dispatchSemanticsAction(virtualViewId, Action.INCREASE);
H
Hixie 已提交
253 254 255 256 257
                } else {
                    return false;
                }
                return true;
            }
258
            case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
259 260 261
                if (object.hasAction(Action.SCROLL_DOWN)) {
                    mOwner.dispatchSemanticsAction(virtualViewId, Action.SCROLL_DOWN);
                } else if (object.hasAction(Action.SCROLL_RIGHT)) {
262
                    // TODO(ianh): bidi support using textDirection
263 264
                    mOwner.dispatchSemanticsAction(virtualViewId, Action.SCROLL_RIGHT);
                } else if (object.hasAction(Action.DECREASE)) {
265 266 267
                    object.value = object.decreasedValue;
                    // Event causes Android to read out the updated value.
                    sendAccessibilityEvent(virtualViewId, AccessibilityEvent.TYPE_VIEW_SELECTED);
268
                    mOwner.dispatchSemanticsAction(virtualViewId, Action.DECREASE);
H
Hixie 已提交
269 270 271 272 273
                } else {
                    return false;
                }
                return true;
            }
274 275 276 277 278 279
            case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
                return performCursorMoveAction(object, virtualViewId, arguments, false);
            }
            case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: {
                return performCursorMoveAction(object, virtualViewId, arguments, true);
            }
H
Hixie 已提交
280 281
            case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
                sendAccessibilityEvent(virtualViewId, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
282
                mA11yFocusedObject = null;
H
Hixie 已提交
283 284 285 286
                return true;
            }
            case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
                sendAccessibilityEvent(virtualViewId, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
287

288
                if (mA11yFocusedObject == null) {
H
Hixie 已提交
289 290 291 292 293
                    // When Android focuses a node, it doesn't invalidate the view.
                    // (It does when it sends ACTION_CLEAR_ACCESSIBILITY_FOCUS, so
                    // we only have to worry about this when the focused node is null.)
                    mOwner.invalidate();
                }
294
                mA11yFocusedObject = object;
295

296
                if (object.hasAction(Action.INCREASE) || object.hasAction(Action.DECREASE)) {
297 298 299 300
                    // SeekBars only announce themselves after this event.
                    sendAccessibilityEvent(virtualViewId, AccessibilityEvent.TYPE_VIEW_SELECTED);
                }

H
Hixie 已提交
301 302
                return true;
            }
303 304 305
            // TODO(goderbauer): Use ACTION_SHOW_ON_SCREEN from Android Support Library after
            //     https://github.com/flutter/flutter/issues/11099 is resolved.
            case 16908342: { // ACTION_SHOW_ON_SCREEN, added in API level 23
306
                mOwner.dispatchSemanticsAction(virtualViewId, Action.SHOW_ON_SCREEN);
307 308
                return true;
            }
H
Hixie 已提交
309 310 311 312
        }
        return false;
    }

313 314 315
    boolean performCursorMoveAction(SemanticsObject object, int virtualViewId, Bundle arguments, boolean forward) {
        final int granularity = arguments.getInt(
            AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
316 317
        final boolean extendSelection = arguments.getBoolean(
            AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN);
318 319 320
        switch (granularity) {
            case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER: {
                if (forward && object.hasAction(Action.MOVE_CURSOR_FORWARD_BY_CHARACTER)) {
321
                    mOwner.dispatchSemanticsAction(virtualViewId, Action.MOVE_CURSOR_FORWARD_BY_CHARACTER, extendSelection);
322 323 324
                    return true;
                }
                if (!forward && object.hasAction(Action.MOVE_CURSOR_BACKWARD_BY_CHARACTER)) {
325
                    mOwner.dispatchSemanticsAction(virtualViewId, Action.MOVE_CURSOR_BACKWARD_BY_CHARACTER, extendSelection);
326 327 328 329 330 331 332 333
                    return true;
                }
            }
            // TODO(goderbauer): support other granularities.
        }
        return false;
    }

334
    // TODO(ianh): implement findAccessibilityNodeInfosByText()
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350

    @Override
    public AccessibilityNodeInfo findFocus(int focus) {
        switch (focus) {
            case AccessibilityNodeInfo.FOCUS_INPUT: {
                if (mInputFocusedObject != null)
                    return createAccessibilityNodeInfo(mInputFocusedObject.id);
            }
            case AccessibilityNodeInfo.FOCUS_ACCESSIBILITY: {
                if (mA11yFocusedObject != null)
                    return createAccessibilityNodeInfo(mA11yFocusedObject.id);
            }
        }
        return null;
    }

351

352 353
    private SemanticsObject getRootObject() {
      assert mObjects.containsKey(0);
354 355 356
      return mObjects.get(0);
    }

357 358 359 360 361 362 363 364 365 366
    private SemanticsObject getOrCreateObject(int id) {
      SemanticsObject object = mObjects.get(id);
      if (object == null) {
          object = new SemanticsObject();
          object.id = id;
          mObjects.put(id, object);
      }
      return object;
    }

367
    void handleTouchExplorationExit() {
368 369 370
        if (mHoveredObject != null) {
            sendAccessibilityEvent(mHoveredObject.id, AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
            mHoveredObject = null;
371 372 373
        }
    }

374
    void handleTouchExploration(float x, float y) {
375
        if (mObjects.isEmpty()) {
376
            return;
377
        }
378
        SemanticsObject newObject = getRootObject().hitTest(new float[]{ x, y, 0, 1 });
379
        if (newObject != mHoveredObject) {
H
Hixie 已提交
380
            // sending ENTER before EXIT is how Android wants it
381 382
            if (newObject != null) {
                sendAccessibilityEvent(newObject.id, AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
383
            }
384 385
            if (mHoveredObject != null) {
                sendAccessibilityEvent(mHoveredObject.id, AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
386
            }
387
            mHoveredObject = newObject;
388 389 390
        }
    }

391 392 393 394
    void updateSemantics(ByteBuffer buffer, String[] strings) {
        ArrayList<Integer> updated = new ArrayList<Integer>();
        while (buffer.hasRemaining()) {
            int id = buffer.getInt();
395 396 397 398 399
            SemanticsObject object = getOrCreateObject(id);
            object.updateWith(buffer, strings);
            if (object.hasFlag(Flag.IS_FOCUSED)) {
                mInputFocusedObject = object;
            }
400
            updated.add(id);
401
        }
402

403 404 405 406 407 408
        Set<SemanticsObject> visitedObjects = new HashSet<SemanticsObject>();
        SemanticsObject rootObject = getRootObject();
        if (rootObject != null) {
          final float[] identity = new float[16];
          Matrix.setIdentityM(identity, 0);
          rootObject.updateRecursively(identity, visitedObjects, false);
409
        }
410 411 412 413

        Iterator<Map.Entry<Integer, SemanticsObject>> it = mObjects.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<Integer, SemanticsObject> entry = it.next();
A
Adam Barth 已提交
414 415 416
            SemanticsObject object = entry.getValue();
            if (!visitedObjects.contains(object)) {
                willRemoveSemanticsObject(object);
417
                it.remove();
418 419
            }
        }
420 421 422

        for (Integer id : updated) {
            sendAccessibilityEvent(id, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
423
        }
424 425
    }

426 427 428 429 430 431 432 433
    private AccessibilityEvent obtainAccessibilityEvent(int virtualViewId, int eventType) {
        assert virtualViewId != 0;
        AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
        event.setPackageName(mOwner.getContext().getPackageName());
        event.setSource(mOwner, virtualViewId);
        return event;
    }

434
    private void sendAccessibilityEvent(int virtualViewId, int eventType) {
435
        if (!mAccessibilityEnabled) {
H
Hixie 已提交
436 437
            return;
        }
438 439 440
        if (virtualViewId == 0) {
            mOwner.sendAccessibilityEvent(eventType);
        } else {
441 442 443 444 445 446 447
            sendAccessibilityEvent(obtainAccessibilityEvent(virtualViewId, eventType));
        }
    }

    private void sendAccessibilityEvent(AccessibilityEvent event) {
        if (!mAccessibilityEnabled) {
            return;
H
Hixie 已提交
448
        }
449
        mOwner.getParent().requestSendAccessibilityEvent(mOwner, event);
H
Hixie 已提交
450 451
    }

452 453 454
    // Message Handler for [mFlutterAccessibilityChannel].
    public void onMessage(Object message, BasicMessageChannel.Reply<Object> reply) {
        @SuppressWarnings("unchecked")
455 456
        final HashMap<String, Object> annotatedEvent = (HashMap<String, Object>)message;
        final String type = (String)annotatedEvent.get("type");
457 458
        @SuppressWarnings("unchecked")
        final HashMap<String, Object> data = (HashMap<String, Object>)annotatedEvent.get("data");
459 460 461

        switch (type) {
            case "scroll":
462
                final int nodeId = (int)annotatedEvent.get("nodeId");
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477
                AccessibilityEvent event =
                    obtainAccessibilityEvent(nodeId, AccessibilityEvent.TYPE_VIEW_SCROLLED);
                char axis = ((String)data.get("axis")).charAt(0);
                double minPosition = (double)data.get("minScrollExtent");
                double maxPosition = (double)data.get("maxScrollExtent") - minPosition;
                double position = (double)data.get("pixels") - minPosition;
                if (axis == 'v') {
                    event.setScrollY((int)position);
                    event.setMaxScrollY((int)maxPosition);
                } else {
                    assert axis == 'h';
                    event.setScrollX((int)position);
                    event.setMaxScrollX((int)maxPosition);
                }
                sendAccessibilityEvent(event);
478
                break;
479 480 481
            case "announce":
                mOwner.announceForAccessibility((String) data.get("message"));
                break;
482 483 484
            default:
                assert false;
        }
485 486
    }

487
    private void willRemoveSemanticsObject(SemanticsObject object) {
488 489 490
        assert mObjects.containsKey(object.id);
        assert mObjects.get(object.id) == object;
        object.parent = null;
491 492 493 494 495 496
        if (mA11yFocusedObject == object) {
            sendAccessibilityEvent(mA11yFocusedObject.id, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
            mA11yFocusedObject = null;
        }
        if (mInputFocusedObject == object) {
            mInputFocusedObject = null;
H
Hixie 已提交
497
        }
498 499
        if (mHoveredObject == object) {
            mHoveredObject = null;
500
        }
H
Hixie 已提交
501 502
    }

503
    void reset() {
504
        mObjects.clear();
505 506 507
        if (mA11yFocusedObject != null)
            sendAccessibilityEvent(mA11yFocusedObject.id, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
        mA11yFocusedObject = null;
508
        mHoveredObject = null;
509
        sendAccessibilityEvent(0, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
H
Hixie 已提交
510 511
    }

512 513 514 515 516 517 518 519 520 521 522 523 524 525
    private enum TextDirection {
        UNKNOWN, LTR, RTL;

        public static TextDirection fromInt(int value) {
            switch (value) {
                case 1:
                    return RTL;
                case 2:
                    return LTR;
            }
            return UNKNOWN;
        }
    }

526 527
    private class SemanticsObject {
        SemanticsObject() { }
528

529
        int id = -1;
530 531

        int flags;
A
Adam Barth 已提交
532
        int actions;
533
        String label;
534
        String value;
535 536
        String increasedValue;
        String decreasedValue;
537
        String hint;
538
        TextDirection textDirection;
H
Hixie 已提交
539 540 541

        private float left;
        private float top;
542 543 544
        private float right;
        private float bottom;
        private float[] transform;
H
Hixie 已提交
545

546
        SemanticsObject parent;
547
        List<SemanticsObject> children;  // In inverse hit test order (i.e. paint order).
H
Hixie 已提交
548

549 550
        private boolean inverseTransformDirty = true;
        private float[] inverseTransform;
H
Hixie 已提交
551

552 553 554
        private boolean globalGeometryDirty = true;
        private float[] globalTransform;
        private Rect globalRect;
H
Hixie 已提交
555

556 557 558 559 560 561 562 563
        boolean hasAction(Action action) {
            return (actions & action.value) != 0;
        }

        boolean hasFlag(Flag flag) {
            return (flags & flag.value) != 0;
        }

A
Adam Barth 已提交
564 565 566 567 568 569 570 571 572 573 574 575
        void log(String indent) {
          Log.i(TAG, indent + "SemanticsObject id=" + id + " label=" + label + " actions=" +  actions + " flags=" + flags + "\n" +
                     indent + "  +-- rect.ltrb=(" + left + ", " + top + ", " + right + ", " + bottom + ")\n" +
                     indent + "  +-- transform=" + Arrays.toString(transform) + "\n");
          if (children != null) {
              String childIndent = indent + "  ";
              for (SemanticsObject child : children) {
                  child.log(childIndent);
              }
          }
        }

576 577
        void updateWith(ByteBuffer buffer, String[] strings) {
            flags = buffer.getInt();
A
Adam Barth 已提交
578
            actions = buffer.getInt();
H
Hixie 已提交
579

580 581 582 583 584 585
            int stringIndex = buffer.getInt();
            label = stringIndex == -1 ? null : strings[stringIndex];

            stringIndex = buffer.getInt();
            value = stringIndex == -1 ? null : strings[stringIndex];

586 587 588 589 590 591
            stringIndex = buffer.getInt();
            increasedValue = stringIndex == -1 ? null : strings[stringIndex];

            stringIndex = buffer.getInt();
            decreasedValue = stringIndex == -1 ? null : strings[stringIndex];

592 593
            stringIndex = buffer.getInt();
            hint = stringIndex == -1 ? null : strings[stringIndex];
594

595 596
            textDirection = TextDirection.fromInt(buffer.getInt());

597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620
            left = buffer.getFloat();
            top = buffer.getFloat();
            right = buffer.getFloat();
            bottom = buffer.getFloat();

            if (transform == null)
                transform = new float[16];
            for (int i = 0; i < 16; ++i)
                transform[i] = buffer.getFloat();
            inverseTransformDirty = true;
            globalGeometryDirty = true;

            final int childCount = buffer.getInt();
            if (childCount == 0) {
                children = null;
            } else {
                if (children == null)
                    children = new ArrayList<SemanticsObject>(childCount);
                else
                    children.clear();

                for (int i = 0; i < childCount; ++i) {
                    SemanticsObject child = getOrCreateObject(buffer.getInt());
                    child.parent = this;
A
Adam Barth 已提交
621
                    children.add(child);
622 623
                }
            }
H
Hixie 已提交
624 625
        }

626 627 628 629 630 631 632 633
        private void ensureInverseTransform() {
            if (!inverseTransformDirty)
                return;
            inverseTransformDirty = false;
            if (inverseTransform == null)
                inverseTransform = new float[16];
            if (!Matrix.invertM(inverseTransform, 0, transform, 0))
                Arrays.fill(inverseTransform, 0);
H
Hixie 已提交
634 635
        }

636
        Rect getGlobalRect() {
637
            assert !globalGeometryDirty;
H
Hixie 已提交
638 639
            return globalRect;
        }
640

641 642 643 644 645
        SemanticsObject hitTest(float[] point) {
            final float w = point[3];
            final float x = point[0] / w;
            final float y = point[1] / w;
            if (x < left || x >= right || y < top || y >= bottom)
646
                return null;
647
            if (children != null) {
648 649 650 651 652 653
                final float[] transformedPoint = new float[4];
                for (int i = children.size() - 1; i >= 0; i -= 1) {
                    final SemanticsObject child = children.get(i);
                    child.ensureInverseTransform();
                    Matrix.multiplyMV(transformedPoint, 0, child.inverseTransform, 0, point, 0);
                    final SemanticsObject result = child.hitTest(transformedPoint);
654 655 656
                    if (result != null) {
                        return result;
                    }
657 658 659 660
                }
            }
            return this;
        }
H
Hixie 已提交
661

662 663
        // TODO(goderbauer): This should be decided by the framework once we have more information
        //     about focusability there.
664
        boolean isFocusable() {
665 666
            int scrollableActions = Action.SCROLL_RIGHT.value | Action.SCROLL_LEFT.value
                    | Action.SCROLL_UP.value | Action.SCROLL_DOWN.value;
667 668 669 670 671
            return (actions & ~scrollableActions) != 0
                || flags != 0
                || (label != null && !label.isEmpty())
                || (value != null && !value.isEmpty())
                || (hint != null && !hint.isEmpty());
672 673
        }

674 675
        void updateRecursively(float[] ancestorTransform, Set<SemanticsObject> visitedObjects, boolean forceUpdate) {
            visitedObjects.add(this);
H
Hixie 已提交
676

677 678 679 680 681 682
            if (globalGeometryDirty)
                forceUpdate = true;

            if (forceUpdate) {
                if (globalTransform == null)
                    globalTransform = new float[16];
I
Ian Hickson 已提交
683
                Matrix.multiplyMM(globalTransform, 0, ancestorTransform, 0, transform, 0);
684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704

                final float[] sample = new float[4];
                sample[2] = 0;
                sample[3] = 1;

                final float[] point1 = new float[4];
                final float[] point2 = new float[4];
                final float[] point3 = new float[4];
                final float[] point4 = new float[4];

                sample[0] = left;
                sample[1] = top;
                transformPoint(point1, globalTransform, sample);

                sample[0] = right;
                sample[1] = top;
                transformPoint(point2, globalTransform, sample);

                sample[0] = right;
                sample[1] = bottom;
                transformPoint(point3, globalTransform, sample);
H
Hixie 已提交
705

706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748
                sample[0] = left;
                sample[1] = bottom;
                transformPoint(point4, globalTransform, sample);

                if (globalRect == null)
                    globalRect = new Rect();

                globalRect.set(
                    Math.round(min(point1[0], point2[0], point3[0], point4[0])),
                    Math.round(min(point1[1], point2[1], point3[1], point4[1])),
                    Math.round(max(point1[0], point2[0], point3[0], point4[0])),
                    Math.round(max(point1[1], point2[1], point3[1], point4[1]))
                );

                globalGeometryDirty = false;
            }

            assert globalTransform != null;
            assert globalRect != null;

            if (children != null) {
                for (int i = 0; i < children.size(); ++i) {
                    children.get(i).updateRecursively(globalTransform, visitedObjects, forceUpdate);
                }
            }
        }

        private void transformPoint(float[] result, float[] transform, float[] point) {
            Matrix.multiplyMV(result, 0, transform, 0, point, 0);
            final float w = result[3];
            result[0] /= w;
            result[1] /= w;
            result[2] /= w;
            result[3] = 0;
        }

        private float min(float a, float b, float c, float d) {
            return Math.min(a, Math.min(b, Math.min(c, d)));
        }

        private float max(float a, float b, float c, float d) {
            return Math.max(a, Math.max(b, Math.max(c, d)));
        }
749 750 751 752 753 754 755 756 757 758 759 760 761

        private String getValueLabelHint() {
            StringBuilder sb = new StringBuilder();
            String[] array = { value, label, hint };
            for (String word: array) {
                if (word != null && (word = word.trim()).length() > 0) {
                    if (sb.length() > 0)
                        sb.append(", ");
                    sb.append(word);
                }
            }
            return sb.length() > 0 ? sb.toString() : null;
        }
762
    }
H
Hixie 已提交
763
}