Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SingleV
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java
FILE: ../../../flutter/shell/platform/android/io/flutter/util/PathUtils.java
FILE: ../../../flutter/shell/platform/android/io/flutter/util/Preconditions.java
FILE: ../../../flutter/shell/platform/android/io/flutter/util/Predicate.java
FILE: ../../../flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java
FILE: ../../../flutter/shell/platform/android/io/flutter/view/FlutterCallbackInformation.java
FILE: ../../../flutter/shell/platform/android/io/flutter/view/FlutterMain.java
Expand Down
1 change: 1 addition & 0 deletions shell/platform/android/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ java_library("flutter_shell_java") {
"io/flutter/plugin/platform/VirtualDisplayController.java",
"io/flutter/util/PathUtils.java",
"io/flutter/util/Preconditions.java",
"io/flutter/util/Predicate.java",
"io/flutter/view/AccessibilityBridge.java",
"io/flutter/view/FlutterCallbackInformation.java",
"io/flutter/view/FlutterMain.java",
Expand Down
11 changes: 11 additions & 0 deletions shell/platform/android/io/flutter/util/Predicate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright 2013 The Flutter 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.util;

// TODO(dnfield): remove this if/when we can use appcompat to support it.
// java.util.function.Predicate isn't available until API24
public interface Predicate<T> {
public abstract boolean test(T t);
}
66 changes: 64 additions & 2 deletions shell/platform/android/io/flutter/view/AccessibilityBridge.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import android.view.accessibility.AccessibilityNodeProvider;
import io.flutter.plugin.common.BasicMessageChannel;
import io.flutter.plugin.common.StandardMessageCodec;
import io.flutter.util.Predicate;

import java.nio.ByteBuffer;
import java.util.*;
Expand Down Expand Up @@ -125,6 +126,36 @@ void setAccessibilityEnabled(boolean accessibilityEnabled) {
}
}

private boolean shouldSetCollectionInfo(SemanticsObject object) {
// TODO(dnfield): make these lambdas when Java 1.8 support lands.
Predicate<SemanticsObject> parentMatcher = new Predicate<SemanticsObject>() {
@Override
public boolean test(SemanticsObject o) {
return o == mA11yFocusedObject;
}
};

Predicate<SemanticsObject> flagMatcher = new Predicate<SemanticsObject>() {
@Override
public boolean test(SemanticsObject o) {
return o.hasFlag(Flag.HAS_IMPLICIT_SCROLLING);
}
};

// TalkBack expects a number of rows and/or columns greater than 0 to announce
// in list and out of list. For an infinite or growing list, you have to
// specify something > 0 to get "in list" announcements.
// TalkBack will also only track one list at a time, so we only want to set this
// for a list that contians the current a11y focused object - otherwise, if there
// are two lists or nested lists, we may end up with announcements for only the last
// one that is currently availalbe in the semantics tree. However, we also want
// to set it if we're exiting a list to a non-list, so that we can get the "out of list"
// announcement when A11y focus moves out of a list and not into another list.
return object.scrollChildren > 0
&& (hasSemanticsObjectAncestor(mA11yFocusedObject, parentMatcher)
|| !hasSemanticsObjectAncestor(mA11yFocusedObject, flagMatcher));
}

@Override
@SuppressWarnings("deprecation")
public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
Expand Down Expand Up @@ -266,14 +297,29 @@ public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
if (object.hasAction(Action.SCROLL_LEFT) || object.hasAction(Action.SCROLL_UP)
|| object.hasAction(Action.SCROLL_RIGHT) || object.hasAction(Action.SCROLL_DOWN)) {
result.setScrollable(true);

// This tells Android's a11y to send scroll events when reaching the end of
// the visible viewport of a scrollable, unless the node itself does not
// allow implicit scrolling - then we leave the className as view.View.
//
// We should prefer setCollectionInfo to the class names, as this way we get "In List"
// and "Out of list" announcements. But we don't always know the counts, so we
// can fallback to the generic scroll view class names.
// TODO(dnfield): We should add semantics properties for rows and columns in 2 dimensional lists, e.g.
// GridView. Right now, we're only supporting ListViews and only if they have scroll children.
if (object.hasFlag(Flag.HAS_IMPLICIT_SCROLLING)) {
if (object.hasAction(Action.SCROLL_LEFT) || object.hasAction(Action.SCROLL_RIGHT)) {
result.setClassName("android.widget.HorizontalScrollView");
if (shouldSetCollectionInfo(object)) {
result.setCollectionInfo(AccessibilityNodeInfo.CollectionInfo.obtain(0, object.scrollChildren, false));
} else {
result.setClassName("android.widget.HorizontalScrollView");
}
} else {
result.setClassName("android.widget.ScrollView");
if (shouldSetCollectionInfo(object)) {
result.setCollectionInfo(AccessibilityNodeInfo.CollectionInfo.obtain(object.scrollChildren, 0, false));
} else {
result.setClassName("android.widget.ScrollView");
}
}
}
// TODO(ianh): Once we're on SDK v23+, call addAction to
Expand Down Expand Up @@ -960,6 +1006,11 @@ boolean isStandardAction() {
/// Value is derived from ACTION_TYPE_MASK in AccessibilityNodeInfo.java
static int firstResourceId = 267386881;


static boolean hasSemanticsObjectAncestor(SemanticsObject target, Predicate<SemanticsObject> tester) {
return target != null && target.getAncestor(tester) != null;
}

private class SemanticsObject {
SemanticsObject() {}

Expand Down Expand Up @@ -1012,6 +1063,17 @@ private class SemanticsObject {
private float[] globalTransform;
private Rect globalRect;

SemanticsObject getAncestor(Predicate<SemanticsObject> tester) {
SemanticsObject nextAncestor = parent;
while (nextAncestor != null) {
if (tester.test(nextAncestor)) {
return nextAncestor;
}
nextAncestor = nextAncestor.parent;
}
return null;
}

boolean hasAction(Action action) {
return (actions & action.value) != 0;
}
Expand Down