Android Embedding PR 17: Clarify AccessibilityBridge and move logic out of FlutterView. (flutter/engine#8061)

This commit is contained in:
Matt Carroll
2019-03-08 18:09:04 -08:00
committed by GitHub
parent d29f5a6f87
commit af6deb0357
5 changed files with 1106 additions and 637 deletions

View File

@@ -20,6 +20,8 @@ import io.flutter.embedding.engine.dart.PlatformMessageHandler;
import io.flutter.embedding.engine.FlutterEngine.EngineLifecycleListener;
import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
import io.flutter.plugin.common.StandardMessageCodec;
import io.flutter.view.AccessibilityBridge;
/**
* Interface between Flutter embedding's Java code and Flutter engine's C/C++ code.
@@ -323,6 +325,22 @@ public class FlutterJNI {
ByteBuffer buffer,
int position);
public void dispatchSemanticsAction(int id, @NonNull AccessibilityBridge.Action action) {
dispatchSemanticsAction(id, action, null);
}
public void dispatchSemanticsAction(int id, @NonNull AccessibilityBridge.Action action, @Nullable Object args) {
ensureAttachedToNative();
ByteBuffer encodedArgs = null;
int position = 0;
if (args != null) {
encodedArgs = StandardMessageCodec.INSTANCE.encodeMessage(args);
position = encodedArgs.position();
}
dispatchSemanticsAction(id, action.value, encodedArgs, position);
}
@UiThread
public void dispatchSemanticsAction(int id, int action, ByteBuffer args, int argsPosition) {
ensureAttachedToNative();

View File

@@ -103,17 +103,17 @@ public class AccessibilityChannel {
void announce(@NonNull String message);
/**
* The user has tapped on the artifact with the given {@code nodeId}.
* The user has tapped on the widget with the given {@code nodeId}.
*/
void onTap(int nodeId);
/**
* The user has long pressed on the artifact with the given {@code nodeId}.
* The user has long pressed on the widget with the given {@code nodeId}.
*/
void onLongPress(int nodeId);
/**
* The user has opened a popup window, menu, dialog, etc.
* The user has opened a tooltip.
*/
void onTooltip(@NonNull String message);
}

View File

@@ -8,12 +8,10 @@ import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.LocaleList;
@@ -52,8 +50,7 @@ import java.util.concurrent.atomic.AtomicLong;
/**
* An Android view containing a Flutter app.
*/
public class FlutterView extends SurfaceView
implements BinaryMessenger, TextureRegistry, AccessibilityManager.AccessibilityStateChangeListener {
public class FlutterView extends SurfaceView implements BinaryMessenger, TextureRegistry {
/**
* Interface for those objects that maintain and expose a reference to a
* {@code FlutterView} (such as a full-screen Flutter activity).
@@ -91,7 +88,6 @@ public class FlutterView extends SurfaceView
}
private final DartExecutor dartExecutor;
private final AccessibilityChannel accessibilityChannel;
private final NavigationChannel navigationChannel;
private final KeyEventChannel keyEventChannel;
private final LifecycleChannel lifecycleChannel;
@@ -105,14 +101,19 @@ public class FlutterView extends SurfaceView
private AccessibilityBridge mAccessibilityNodeProvider;
private final SurfaceHolder.Callback mSurfaceCallback;
private final ViewportMetrics mMetrics;
private final AccessibilityManager mAccessibilityManager;
private final List<ActivityLifecycleListener> mActivityLifecycleListeners;
private final List<FirstFrameListener> mFirstFrameListeners;
private final AtomicLong nextTextureId = new AtomicLong(0L);
private FlutterNativeView mNativeView;
private final AnimationScaleObserver mAnimationScaleObserver;
private boolean mIsSoftwareRenderingEnabled = false; // using the software renderer or not
private final AccessibilityBridge.OnAccessibilityChangeListener onAccessibilityChangeListener = new AccessibilityBridge.OnAccessibilityChangeListener() {
@Override
public void onAccessibilityChanged(boolean isAccessibilityEnabled, boolean isTouchExplorationEnabled) {
resetWillNotDraw(isAccessibilityEnabled, isTouchExplorationEnabled);
}
};
public FlutterView(Context context) {
this(context, null);
}
@@ -133,7 +134,6 @@ public class FlutterView extends SurfaceView
dartExecutor = mNativeView.getDartExecutor();
mIsSoftwareRenderingEnabled = FlutterJNI.nativeGetIsSoftwareRenderingEnabled();
mAnimationScaleObserver = new AnimationScaleObserver(new Handler());
mMetrics = new ViewportMetrics();
mMetrics.devicePixelRatio = context.getResources().getDisplayMetrics().density;
setFocusable(true);
@@ -162,13 +162,10 @@ public class FlutterView extends SurfaceView
};
getHolder().addCallback(mSurfaceCallback);
mAccessibilityManager = (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
mActivityLifecycleListeners = new ArrayList<>();
mFirstFrameListeners = new ArrayList<>();
// Create all platform channels
accessibilityChannel = new AccessibilityChannel(dartExecutor);
navigationChannel = new NavigationChannel(dartExecutor);
keyEventChannel = new KeyEventChannel(dartExecutor);
lifecycleChannel = new LifecycleChannel(dartExecutor);
@@ -236,7 +233,6 @@ public class FlutterView extends SurfaceView
}
public void onPostResume() {
updateAccessibilityFeatures();
for (ActivityLifecycleListener listener : mActivityLifecycleListeners) {
listener.onPostResume();
}
@@ -573,7 +569,7 @@ public class FlutterView extends SurfaceView
return false;
}
boolean handled = handleAccessibilityHoverEvent(event);
boolean handled = mAccessibilityNodeProvider.onAccessibilityHoverEvent(event);
if (!handled) {
// TODO(ianh): Expose hover events to the platform,
// implementing ADD, REMOVE, etc.
@@ -746,6 +742,12 @@ public class FlutterView extends SurfaceView
resetAccessibilityTree();
}
void resetAccessibilityTree() {
if (mAccessibilityNodeProvider != null) {
mAccessibilityNodeProvider.reset();
}
}
private void postRun() {
}
@@ -844,206 +846,52 @@ public class FlutterView extends SurfaceView
}
}
// ACCESSIBILITY
private boolean mAccessibilityEnabled = false;
private boolean mTouchExplorationEnabled = false;
private int mAccessibilityFeatureFlags = 0;
private TouchExplorationListener mTouchExplorationListener;
protected void dispatchSemanticsAction(int id, AccessibilityBridge.Action action) {
dispatchSemanticsAction(id, action, null);
}
protected void dispatchSemanticsAction(int id, AccessibilityBridge.Action action, Object args) {
if (!isAttached())
return;
ByteBuffer encodedArgs = null;
int position = 0;
if (args != null) {
encodedArgs = StandardMessageCodec.INSTANCE.encodeMessage(args);
position = encodedArgs.position();
}
mNativeView.getFlutterJNI().dispatchSemanticsAction(id, action.value, encodedArgs, position);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mAccessibilityEnabled = mAccessibilityManager.isEnabled();
mTouchExplorationEnabled = mAccessibilityManager.isTouchExplorationEnabled();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
Uri transitionUri = Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE);
getContext().getContentResolver().registerContentObserver(transitionUri, false, mAnimationScaleObserver);
}
if (mAccessibilityEnabled || mTouchExplorationEnabled) {
ensureAccessibilityEnabled();
}
if (mTouchExplorationEnabled) {
mAccessibilityFeatureFlags |= AccessibilityFeature.ACCESSIBLE_NAVIGATION.value;
} else {
mAccessibilityFeatureFlags &= ~AccessibilityFeature.ACCESSIBLE_NAVIGATION.value;
}
// Apply additional accessibility settings
updateAccessibilityFeatures();
resetWillNotDraw();
mAccessibilityManager.addAccessibilityStateChangeListener(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (mTouchExplorationListener == null) {
mTouchExplorationListener = new TouchExplorationListener();
}
mAccessibilityManager.addTouchExplorationStateChangeListener(mTouchExplorationListener);
}
}
mAccessibilityNodeProvider = new AccessibilityBridge(
this,
getFlutterNativeView().getFlutterJNI(),
new AccessibilityChannel(dartExecutor),
(AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE),
getContext().getContentResolver()
);
mAccessibilityNodeProvider.setOnAccessibilityChangeListener(onAccessibilityChangeListener);
private void updateAccessibilityFeatures() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
String transitionAnimationScale = Settings.Global.getString(getContext().getContentResolver(),
Settings.Global.TRANSITION_ANIMATION_SCALE);
if (transitionAnimationScale != null && transitionAnimationScale.equals("0")) {
mAccessibilityFeatureFlags |= AccessibilityFeature.DISABLE_ANIMATIONS.value;
} else {
mAccessibilityFeatureFlags &= ~AccessibilityFeature.DISABLE_ANIMATIONS.value;
}
}
mNativeView.getFlutterJNI().setAccessibilityFeatures(mAccessibilityFeatureFlags);
resetWillNotDraw(
mAccessibilityNodeProvider.isAccessibilityEnabled(),
mAccessibilityNodeProvider.isTouchExplorationEnabled()
);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
getContext().getContentResolver().unregisterContentObserver(mAnimationScaleObserver);
mAccessibilityManager.removeAccessibilityStateChangeListener(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mAccessibilityManager.removeTouchExplorationStateChangeListener(mTouchExplorationListener);
}
mAccessibilityNodeProvider.release();
mAccessibilityNodeProvider = null;
}
private void resetWillNotDraw() {
// TODO(mattcarroll): Confer with Ian as to why we need this method. Delete if possible, otherwise add comments.
private void resetWillNotDraw(boolean isAccessibilityEnabled, boolean isTouchExplorationEnabled) {
if (!mIsSoftwareRenderingEnabled) {
setWillNotDraw(!(mAccessibilityEnabled || mTouchExplorationEnabled));
setWillNotDraw(!(isAccessibilityEnabled || isTouchExplorationEnabled));
} else {
setWillNotDraw(false);
}
}
@Override
public void onAccessibilityStateChanged(boolean enabled) {
if (enabled) {
ensureAccessibilityEnabled();
} else {
mAccessibilityEnabled = false;
if (mAccessibilityNodeProvider != null) {
mAccessibilityNodeProvider.setAccessibilityEnabled(false);
}
mNativeView.getFlutterJNI().setSemanticsEnabled(false);
}
resetWillNotDraw();
}
/// Must match the enum defined in window.dart.
private enum AccessibilityFeature {
ACCESSIBLE_NAVIGATION(1 << 0),
INVERT_COLORS(1 << 1), // NOT SUPPORTED
DISABLE_ANIMATIONS(1 << 2);
AccessibilityFeature(int value) {
this.value = value;
}
final int value;
}
// Listens to the global TRANSITION_ANIMATION_SCALE property and notifies us so
// that we can disable animations in Flutter.
private class AnimationScaleObserver extends ContentObserver {
public AnimationScaleObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
this.onChange(selfChange, null);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
String value = Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1 ? null
: Settings.Global.getString(getContext().getContentResolver(),
Settings.Global.TRANSITION_ANIMATION_SCALE);
if (value != null && value.equals("0")) {
mAccessibilityFeatureFlags |= AccessibilityFeature.DISABLE_ANIMATIONS.value;
} else {
mAccessibilityFeatureFlags &= ~AccessibilityFeature.DISABLE_ANIMATIONS.value;
}
mNativeView.getFlutterJNI().setAccessibilityFeatures(mAccessibilityFeatureFlags);
}
}
// This is guarded at instantiation time.
@TargetApi(19)
@RequiresApi(19)
class TouchExplorationListener implements AccessibilityManager.TouchExplorationStateChangeListener {
@Override
public void onTouchExplorationStateChanged(boolean enabled) {
if (enabled) {
mTouchExplorationEnabled = true;
ensureAccessibilityEnabled();
mAccessibilityFeatureFlags |= AccessibilityFeature.ACCESSIBLE_NAVIGATION.value;
mNativeView.getFlutterJNI().setAccessibilityFeatures(mAccessibilityFeatureFlags);
} else {
mTouchExplorationEnabled = false;
if (mAccessibilityNodeProvider != null) {
mAccessibilityNodeProvider.handleTouchExplorationExit();
}
mAccessibilityFeatureFlags &= ~AccessibilityFeature.ACCESSIBLE_NAVIGATION.value;
mNativeView.getFlutterJNI().setAccessibilityFeatures(mAccessibilityFeatureFlags);
}
resetWillNotDraw();
}
}
@Override
public AccessibilityNodeProvider getAccessibilityNodeProvider() {
if (mAccessibilityEnabled)
if (mAccessibilityNodeProvider.isAccessibilityEnabled()) {
return mAccessibilityNodeProvider;
// TODO(goderbauer): when a11y is off this should return a one-off snapshot of
// the a11y
// tree.
return null;
}
void ensureAccessibilityEnabled() {
if (!isAttached())
return;
mAccessibilityEnabled = true;
if (mAccessibilityNodeProvider == null) {
mAccessibilityNodeProvider = new AccessibilityBridge(this, accessibilityChannel);
}
mNativeView.getFlutterJNI().setSemanticsEnabled(true);
mAccessibilityNodeProvider.setAccessibilityEnabled(true);
}
void resetAccessibilityTree() {
if (mAccessibilityNodeProvider != null) {
mAccessibilityNodeProvider.reset();
}
}
private boolean handleAccessibilityHoverEvent(MotionEvent event) {
if (!mTouchExplorationEnabled) {
return false;
}
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) {
mAccessibilityNodeProvider.handleTouchExplorationExit();
} else {
Log.d("flutter", "unexpected accessibility hover event: " + event);
return false;
// TODO(goderbauer): when a11y is off this should return a one-off snapshot of
// the a11y
// tree.
return null;
}
return true;
}
@Override

View File

@@ -4,22 +4,22 @@
<issue
id="Assert"
message="Assertions are unreliable in Dalvik and unimplemented in ART. Use `BuildConfig.DEBUG` conditional checks instead."
errorLine1=" assert object.id > ROOT_NODE_ID;"
errorLine1=" assert semanticsNode.id > ROOT_NODE_ID;"
errorLine2=" ~~~~~~">
<location
file="../../../flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java"
line="273"
line="537"
column="13"/>
</issue>
<issue
id="Assert"
message="Assertions are unreliable in Dalvik and unimplemented in ART. Use `BuildConfig.DEBUG` conditional checks instead."
errorLine1=" assert object.id == ROOT_NODE_ID;"
errorLine1=" assert semanticsNode.id == ROOT_NODE_ID;"
errorLine2=" ~~~~~~">
<location
file="../../../flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java"
line="276"
line="540"
column="13"/>
</issue>
@@ -30,18 +30,18 @@
errorLine2=" ~~~~~~">
<location
file="../../../flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java"
line="378"
line="649"
column="9"/>
</issue>
<issue
id="Assert"
message="Assertions are unreliable in Dalvik and unimplemented in ART. Use `BuildConfig.DEBUG` conditional checks instead."
errorLine1=" assert objects.containsKey(0);"
errorLine1=" assert flutterSemanticsTree.containsKey(0);"
errorLine2=" ~~~~~~">
<location
file="../../../flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java"
line="644"
line="984"
column="9"/>
</issue>
@@ -52,7 +52,7 @@
errorLine2=" ~~~~~~">
<location
file="../../../flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java"
line="824"
line="1240"
column="21"/>
</issue>
@@ -63,7 +63,7 @@
errorLine2=" ~~~~~~">
<location
file="../../../flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java"
line="825"
line="1241"
column="21"/>
</issue>
@@ -74,29 +74,29 @@
errorLine2=" ~~~~~~">
<location
file="../../../flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java"
line="919"
line="1393"
column="9"/>
</issue>
<issue
id="Assert"
message="Assertions are unreliable in Dalvik and unimplemented in ART. Use `BuildConfig.DEBUG` conditional checks instead."
errorLine1=" assert objects.containsKey(object.id);"
errorLine1=" assert flutterSemanticsTree.containsKey(semanticsNodeToBeRemoved.id);"
errorLine2=" ~~~~~~">
<location
file="../../../flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java"
line="953"
line="1405"
column="9"/>
</issue>
<issue
id="Assert"
message="Assertions are unreliable in Dalvik and unimplemented in ART. Use `BuildConfig.DEBUG` conditional checks instead."
errorLine1=" assert objects.get(object.id) == object;"
errorLine1=" assert flutterSemanticsTree.get(semanticsNodeToBeRemoved.id) == semanticsNodeToBeRemoved;"
errorLine2=" ~~~~~~">
<location
file="../../../flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java"
line="954"
line="1406"
column="9"/>
</issue>
@@ -107,7 +107,7 @@
errorLine2=" ~~~~~~">
<location
file="../../../flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java"
line="1098"
line="1701"
column="13"/>
</issue>
@@ -118,7 +118,7 @@
errorLine2=" ~~~~~~">
<location
file="../../../flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java"
line="1227"
line="1830"
column="25"/>
</issue>
@@ -129,7 +129,7 @@
errorLine2=" ~~~~~~">
<location
file="../../../flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java"
line="1249"
line="1852"
column="13"/>
</issue>
@@ -305,7 +305,7 @@
errorLine2=" ~~~~~~~~~~~">
<location
file="../../../flutter/shell/platform/android/io/flutter/view/ResourceExtractor.java"
line="47"
line="48"
column="19"/>
</issue>
@@ -327,7 +327,7 @@
errorLine2=" ^">
<location
file="../../../flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java"
line="1115"
line="1718"
column="13"/>
</issue>
@@ -360,7 +360,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="../../../flutter/shell/platform/android/io/flutter/view/ResourceExtractor.java"
line="200"
line="201"
column="17"/>
</issue>
@@ -371,7 +371,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="../../../flutter/shell/platform/android/io/flutter/view/ResourceExtractor.java"
line="327"
line="328"
column="17"/>
</issue>
@@ -432,24 +432,24 @@
<issue
id="UseSparseArrays"
message="Use `new SparseArray&lt;SemanticsObject>(...)` instead for better performance"
errorLine1=" objects = new HashMap&lt;>();"
errorLine2=" ~~~~~~~~~~~~~~~">
message="Use `new SparseArray&lt;SemanticsNode>(...)` instead for better performance"
errorLine1=" private final Map&lt;Integer, SemanticsNode> flutterSemanticsTree = new HashMap&lt;>();"
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="../../../flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java"
line="142"
column="19"/>
line="135"
column="70"/>
</issue>
<issue
id="UseSparseArrays"
message="Use `new SparseArray&lt;CustomAccessibilityAction>(...)` instead for better performance"
errorLine1=" customAccessibilityActions = new HashMap&lt;>();"
errorLine2=" ~~~~~~~~~~~~~~~">
errorLine1=" private final Map&lt;Integer, CustomAccessibilityAction> customAccessibilityActions = new HashMap&lt;>();"
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="../../../flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java"
line="143"
column="38"/>
line="160"
column="88"/>
</issue>
<issue
@@ -492,7 +492,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="../../../flutter/shell/platform/android/io/flutter/view/FlutterView.java"
line="511"
line="508"
column="20"/>
</issue>