Initial support for more finely-grained a11y features on Window (flutter/engine#5901)

This commit is contained in:
Jonah Williams
2018-07-31 18:18:19 -07:00
committed by GitHub
parent a369115f3e
commit 3a3f6ca0ee
18 changed files with 304 additions and 190 deletions

View File

@@ -70,9 +70,12 @@ void _updateSemanticsEnabled(bool enabled) {
_invoke(window.onSemanticsEnabledChanged, window._onSemanticsEnabledChangedZone);
}
void _updateAssistiveTechnologyEnabled(bool enabled) {
window._assistiveTechnologyEnabled = enabled;
_invoke(window.onAssistiveTechnologyEnabled, window._onAssistiveTechnologyEnabledZone);
void _updateAccessibilityFeatures(int values) {
final AccessibilityFeatures newFeatures = new AccessibilityFeatures._(values);
if (newFeatures == window._accessibilityFeatures)
return;
window._accessibilityFeatures = newFeatures;
_invoke(window.onAccessibilityFeaturesChanged, window._onAccessibilityFlagsChangedZone);
}
void _dispatchPlatformMessage(String name, ByteData data, int responseId) {

View File

@@ -649,31 +649,6 @@ class Window {
_onSemanticsEnabledChangedZone = Zone.current;
}
/// Whether the user is using assitive technologies to interact with the
/// application.
///
/// This includes screen readers such as TalkBack on Android and VoiceOVer
/// on iOS, as well as hardware switches, and more.
///
/// The [onAssistiveTechnologyEnabled] callback is called whenever this value
/// changes.
bool get assistiveTechnologyEnabled => _assistiveTechnologyEnabled;
bool _assistiveTechnologyEnabled = false;
/// A callback that is invoked when the value of [assistiveTechnologyEnabled]
/// changes.
///
/// The framework invokes this callback in the same zone in which the callback
/// was set.
VoidCallback get onAssistiveTechnologyEnabled => _onAssistiveTechnologyEnabled;
VoidCallback _onAssistiveTechnologyEnabled;
Zone _onAssistiveTechnologyEnabledZone;
set onAssistiveTechnologyEnabled(VoidCallback callback) {
_onAssistiveTechnologyEnabled = callback;
_onAssistiveTechnologyEnabledZone = Zone.current;
}
/// A callback that is invoked whenever the user requests an action to be
/// performed.
///
@@ -690,6 +665,22 @@ class Window {
_onSemanticsActionZone = Zone.current;
}
/// Additional accessibility features that may be enabled by the platform.
AccessibilityFeatures get accessibilityFeatures => _accessibilityFeatures;
AccessibilityFeatures _accessibilityFeatures;
/// A callback that is invoked when the value of [accessibilityFlags] changes.
///
/// The framework invokes this callback in the same zone in which the
/// callback was set.
VoidCallback get onAccessibilityFeaturesChanged => _onAccessibilityFeaturesChanged;
VoidCallback _onAccessibilityFeaturesChanged;
Zone _onAccessibilityFlagsChangedZone;
set onAccessibilityFeaturesChanged(VoidCallback callback) {
_onAccessibilityFeaturesChanged = callback;
_onAccessibilityFlagsChangedZone = Zone.current;
}
/// Change the retained semantics data about this window.
///
/// If [semanticsEnabled] is true, the user has requested that this funciton
@@ -760,6 +751,57 @@ class Window {
}
}
/// Additional accessibility features that may be enabled by the platform.
///
/// It is not possible to enable these settings from Flutter, instead they are
/// used by the platform to indicate that additional accessibility features are
/// enabled.
class AccessibilityFeatures {
const AccessibilityFeatures._(this._index);
static const int _kAccessibleNavigation = 1 << 0;
static const int _kInvertColorsIndex = 1 << 1;
static const int _kDisableAnimationsIndex = 1 << 2;
// A bitfield which represents each enabled feature.
final int _index;
/// Whether there is a running accessibility service which is changing the
/// interaction model of the device.
///
/// For example, TalkBack on Android and VoiceOver on iOS enable this flag.
bool get accessibleNavigation => _kAccessibleNavigation & _index != 0;
/// The platform is inverting the colors of the application.
bool get invertColors => _kInvertColorsIndex & _index != 0;
/// The platform is requesting that animations be disabled or simplified.
bool get disableAnimations => _kDisableAnimationsIndex & _index != 0;
@override
String toString() {
final List<String> features = <String>[];
if (accessibleNavigation)
features.add('accessibleNavigation');
if (invertColors)
features.add('invertColors');
if (disableAnimations)
features.add('disableAnimations');
return 'AccessibilityFeatures$features';
}
@override
bool operator ==(dynamic other) {
if (other.runtimeType != runtimeType)
return false;
final AccessibilityFeatures typedOther = other;
return _index == typedOther._index;
}
@override
int get hashCode => _index.hashCode;
}
/// The [Window] singleton. This object exposes the size of the display, the
/// core scheduler API, the input event callback, the graphics drawing API, and
/// other such core services.

View File

@@ -198,14 +198,14 @@ void Window::UpdateSemanticsEnabled(bool enabled) {
{ToDart(enabled)});
}
void Window::UpdateAssistiveTechnologyEnabled(bool enabled) {
void Window::UpdateAccessibilityFeatures(int32_t values) {
std::shared_ptr<tonic::DartState> dart_state = library_.dart_state().lock();
if (!dart_state)
return;
tonic::DartState::Scope scope(dart_state);
DartInvokeField(library_.value(), "_updateAssistiveTechnologyEnabled",
{ToDart(enabled)});
DartInvokeField(library_.value(), "_updateAccessibilityFeatures",
{ToDart(values)});
}
void Window::DispatchPlatformMessage(fml::RefPtr<PlatformMessage> message) {

View File

@@ -25,6 +25,13 @@ class Scene;
Dart_Handle ToByteData(const std::vector<uint8_t>& buffer);
// Must match the AccessibilityFeatureFlag enum in window.dart.
enum class AccessibilityFeatureFlag : int32_t {
kAccessibleNavigation = 1 << 0,
kInvertColors = 1 << 1,
kDisableAnimations = 1 << 2,
};
class WindowClient {
public:
virtual std::string DefaultRouteName() = 0;
@@ -54,7 +61,7 @@ class Window final {
const std::string& country_code);
void UpdateUserSettingsData(const std::string& data);
void UpdateSemanticsEnabled(bool enabled);
void UpdateAssistiveTechnologyEnabled(bool enabled);
void UpdateAccessibilityFeatures(int32_t flags);
void DispatchPlatformMessage(fml::RefPtr<PlatformMessage> message);
void DispatchPointerDataPacket(const PointerDataPacket& packet);
void DispatchSemanticsAction(int32_t id,

View File

@@ -19,7 +19,7 @@ dart:ui,::,_getScheduleMicrotaskClosure
dart:ui,::,_setupHooks
dart:ui,::,_updateLocale
dart:ui,::,_updateSemanticsEnabled
dart:ui,::,_updateAssistiveTechnologyEnabled
dart:ui,::,_updateAccessibilityFeatures
dart:ui,::,_updateUserSettingsData
dart:ui,::,_updateWindowMetrics
dart:ui,_ImageInfo,get:width

View File

@@ -126,8 +126,7 @@ bool RuntimeController::FlushRuntimeStateToIsolate() {
return SetViewportMetrics(window_data_.viewport_metrics) &&
SetLocale(window_data_.language_code, window_data_.country_code) &&
SetSemanticsEnabled(window_data_.semantics_enabled) &&
SetAssistiveTechnologyEnabled(
window_data_.assistive_technology_enabled);
SetAccessibilityFeatures(window_data_.accessibility_feature_flags_);
}
bool RuntimeController::SetViewportMetrics(const ViewportMetrics& metrics) {
@@ -175,11 +174,11 @@ bool RuntimeController::SetSemanticsEnabled(bool enabled) {
return false;
}
bool RuntimeController::SetAssistiveTechnologyEnabled(bool enabled) {
window_data_.assistive_technology_enabled = enabled;
bool RuntimeController::SetAccessibilityFeatures(int32_t flags) {
window_data_.accessibility_feature_flags_ = flags;
if (auto window = GetWindowIfAvailable()) {
window->UpdateAssistiveTechnologyEnabled(
window_data_.assistive_technology_enabled);
window->UpdateAccessibilityFeatures(
window_data_.accessibility_feature_flags_);
return true;
}

View File

@@ -47,7 +47,7 @@ class RuntimeController final : public WindowClient {
bool SetSemanticsEnabled(bool enabled);
bool SetAssistiveTechnologyEnabled(bool enabled);
bool SetAccessibilityFeatures(int32_t flags);
bool BeginFrame(fml::TimePoint frame_time);
@@ -83,6 +83,7 @@ class RuntimeController final : public WindowClient {
std::string user_settings_data = "{}";
bool semantics_enabled = false;
bool assistive_technology_enabled = false;
int32_t accessibility_feature_flags_ = 0;
};
RuntimeDelegate& client_;

View File

@@ -345,8 +345,8 @@ void Engine::SetSemanticsEnabled(bool enabled) {
runtime_controller_->SetSemanticsEnabled(enabled);
}
void Engine::SetAssistiveTechnologyEnabled(bool enabled) {
runtime_controller_->SetAssistiveTechnologyEnabled(enabled);
void Engine::SetAccessibilityFeatures(int32_t flags) {
runtime_controller_->SetAccessibilityFeatures(flags);
}
void Engine::StopAnimator() {

View File

@@ -99,7 +99,7 @@ class Engine final : public blink::RuntimeDelegate {
void SetSemanticsEnabled(bool enabled);
void SetAssistiveTechnologyEnabled(bool enabled);
void SetAccessibilityFeatures(int32_t flags);
void ScheduleFrame(bool regenerate_layer_tree = true) override;

View File

@@ -52,8 +52,8 @@ void PlatformView::SetSemanticsEnabled(bool enabled) {
delegate_.OnPlatformViewSetSemanticsEnabled(*this, enabled);
}
void PlatformView::SetAssistiveTechnologyEnabled(bool enabled) {
delegate_.OnPlatformViewSetAssistiveTechnologyEnabled(*this, enabled);
void PlatformView::SetAccessibilityFeatures(int32_t flags) {
delegate_.OnPlatformViewSetAccessibilityFeatures(*this, flags);
}
void PlatformView::SetViewportMetrics(const blink::ViewportMetrics& metrics) {

View File

@@ -58,9 +58,9 @@ class PlatformView {
virtual void OnPlatformViewSetSemanticsEnabled(const PlatformView& view,
bool enabled) = 0;
virtual void OnPlatformViewSetAssistiveTechnologyEnabled(
virtual void OnPlatformViewSetAccessibilityFeatures(
const PlatformView& view,
bool enabled) = 0;
int32_t flags) = 0;
virtual void OnPlatformViewRegisterTexture(
const PlatformView& view,
@@ -88,7 +88,7 @@ class PlatformView {
virtual void SetSemanticsEnabled(bool enabled);
virtual void SetAssistiveTechnologyEnabled(bool enabled);
virtual void SetAccessibilityFeatures(int32_t flags);
void SetViewportMetrics(const blink::ViewportMetrics& metrics);

View File

@@ -562,17 +562,17 @@ void Shell::OnPlatformViewSetSemanticsEnabled(const PlatformView& view,
});
}
void Shell::OnPlatformViewSetAssistiveTechnologyEnabled(
const PlatformView& view,
bool enabled) {
// |shell::PlatformView::Delegate|
void Shell::OnPlatformViewSetAccessibilityFeatures(const PlatformView& view,
int32_t flags) {
FML_DCHECK(is_setup_);
FML_DCHECK(&view == platform_view_.get());
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
task_runners_.GetUITaskRunner()->PostTask(
[engine = engine_->GetWeakPtr(), enabled] {
[engine = engine_->GetWeakPtr(), flags] {
if (engine) {
engine->SetAssistiveTechnologyEnabled(enabled);
engine->SetAccessibilityFeatures(flags);
}
});
}

View File

@@ -147,9 +147,9 @@ class Shell final : public PlatformView::Delegate,
void OnPlatformViewSetSemanticsEnabled(const PlatformView& view,
bool enabled) override;
// |shell::PlatformView::Delegate|
void OnPlatformViewSetAssistiveTechnologyEnabled(const PlatformView& view,
bool enabled) override;
// |shell:PlatformView::Delegate|
void OnPlatformViewSetAccessibilityFeatures(const PlatformView& view,
int32_t flags) override;
// |shell::PlatformView::Delegate|
void OnPlatformViewRegisterTexture(

View File

@@ -11,6 +11,10 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.provider.Settings;
import android.net.Uri;
import android.os.Handler;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
@@ -41,23 +45,24 @@ 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, AccessibilityManager.AccessibilityStateChangeListener {
/**
* Interface for those objects that maintain and expose a reference to a
* {@code FlutterView} (such as a full-screen Flutter activity).
*
* <p>This indirection is provided to support applications that use an
* activity other than {@link io.flutter.app.FlutterActivity} (e.g. Android
* v4 support library's {@code FragmentActivity}). It allows Flutter plugins
* to deal in this interface and not require that the activity be a subclass
* of {@code FlutterActivity}.</p>
* <p>
* This indirection is provided to support applications that use an activity
* other than {@link io.flutter.app.FlutterActivity} (e.g. Android v4 support
* library's {@code FragmentActivity}). It allows Flutter plugins to deal in
* this interface and not require that the activity be a subclass of
* {@code FlutterActivity}.
* </p>
*/
public interface Provider {
/**
* Returns a reference to the Flutter view maintained by this object.
* This may be {@code null}.
* Returns a reference to the Flutter view maintained by this object. This may
* be {@code null}.
*/
FlutterView getFlutterView();
}
@@ -96,6 +101,7 @@ public class FlutterView
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 InputConnection mLastInputConnection;
@@ -111,7 +117,7 @@ public class FlutterView
super(context, attrs);
mIsSoftwareRenderingEnabled = nativeGetIsSoftwareRenderingEnabled();
mAnimationScaleObserver = new AnimationScaleObserver(new Handler());
mMetrics = new ViewportMetrics();
mMetrics.devicePixelRatio = context.getResources().getDisplayMetrics().density;
setFocusable(true);
@@ -128,8 +134,7 @@ public class FlutterView
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) {
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.
@@ -156,29 +161,21 @@ public class FlutterView
};
getHolder().addCallback(mSurfaceCallback);
mAccessibilityManager =
(AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
mAccessibilityManager = (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
mActivityLifecycleListeners = new ArrayList<>();
mFirstFrameListeners = new ArrayList<>();
// Configure the platform plugins and flutter channels.
mFlutterLocalizationChannel =
new MethodChannel(this, "flutter/localization", JSONMethodCodec.INSTANCE);
mFlutterNavigationChannel =
new MethodChannel(this, "flutter/navigation", JSONMethodCodec.INSTANCE);
mFlutterKeyEventChannel =
new BasicMessageChannel<>(this, "flutter/keyevent", JSONMessageCodec.INSTANCE);
mFlutterLifecycleChannel =
new BasicMessageChannel<>(this, "flutter/lifecycle", StringCodec.INSTANCE);
mFlutterSystemChannel =
new BasicMessageChannel<>(this, "flutter/system", JSONMessageCodec.INSTANCE);
mFlutterSettingsChannel =
new BasicMessageChannel<>(this, "flutter/settings", JSONMessageCodec.INSTANCE);
mFlutterLocalizationChannel = new MethodChannel(this, "flutter/localization", JSONMethodCodec.INSTANCE);
mFlutterNavigationChannel = new MethodChannel(this, "flutter/navigation", JSONMethodCodec.INSTANCE);
mFlutterKeyEventChannel = new BasicMessageChannel<>(this, "flutter/keyevent", JSONMessageCodec.INSTANCE);
mFlutterLifecycleChannel = new BasicMessageChannel<>(this, "flutter/lifecycle", StringCodec.INSTANCE);
mFlutterSystemChannel = new BasicMessageChannel<>(this, "flutter/system", JSONMessageCodec.INSTANCE);
mFlutterSettingsChannel = new BasicMessageChannel<>(this, "flutter/settings", JSONMessageCodec.INSTANCE);
PlatformPlugin platformPlugin = new PlatformPlugin(activity);
MethodChannel flutterPlatformChannel =
new MethodChannel(this, "flutter/platform", JSONMethodCodec.INSTANCE);
MethodChannel flutterPlatformChannel = new MethodChannel(this, "flutter/platform", JSONMethodCodec.INSTANCE);
flutterPlatformChannel.setMethodCallHandler(platformPlugin);
addActivityLifecycleListener(platformPlugin);
mImm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
@@ -266,6 +263,7 @@ public class FlutterView
}
public void onPostResume() {
updateAccessibilityFeatures();
for (ActivityLifecycleListener listener : mActivityLifecycleListeners) {
listener.onPostResume();
}
@@ -283,8 +281,8 @@ public class FlutterView
}
/**
* Provide a listener that will be called once when the FlutterView renders its first frame
* to the underlaying SurfaceView.
* Provide a listener that will be called once when the FlutterView renders its
* first frame to the underlaying SurfaceView.
*/
public void addFirstFrameListener(FirstFrameListener listener) {
mFirstFrameListeners.add(listener);
@@ -317,8 +315,7 @@ public class FlutterView
}
private void setLocale(Locale locale) {
mFlutterLocalizationChannel.invokeMethod(
"setLocale", Arrays.asList(locale.getLanguage(), locale.getCountry()));
mFlutterLocalizationChannel.invokeMethod("setLocale", Arrays.asList(locale.getLanguage(), locale.getCountry()));
}
@Override
@@ -333,7 +330,8 @@ public class FlutterView
}
public FlutterNativeView detach() {
if (!isAttached()) return null;
if (!isAttached())
return null;
if (mDiscoveryReceiver != null) {
getContext().unregisterReceiver(mDiscoveryReceiver);
}
@@ -346,7 +344,8 @@ public class FlutterView
}
public void destroy() {
if (!isAttached()) return;
if (!isAttached())
return;
if (mDiscoveryReceiver != null) {
getContext().unregisterReceiver(mDiscoveryReceiver);
@@ -411,15 +410,15 @@ public class FlutterView
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;
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;
}
}
@@ -460,8 +459,7 @@ public class FlutterView
packet.putDouble(1.0); // pressure_max
if (pointerKind == kPointerDeviceKindStylus) {
packet.putDouble(
event.getAxisValue(MotionEvent.AXIS_DISTANCE, pointerIndex)); // distance
packet.putDouble(event.getAxisValue(MotionEvent.AXIS_DISTANCE, pointerIndex)); // distance
packet.putDouble(0.0); // distance_max
} else {
packet.putDouble(0.0); // distance
@@ -474,8 +472,7 @@ public class FlutterView
packet.putDouble(0.0); // radius_min
packet.putDouble(0.0); // radius_max
packet.putDouble(
event.getAxisValue(MotionEvent.AXIS_ORIENTATION, pointerIndex)); // orientation
packet.putDouble(event.getAxisValue(MotionEvent.AXIS_ORIENTATION, pointerIndex)); // orientation
if (pointerKind == kPointerDeviceKindStylus) {
packet.putDouble(event.getAxisValue(MotionEvent.AXIS_TILT, pointerIndex)); // tilt
@@ -505,16 +502,14 @@ public class FlutterView
int pointerCount = event.getPointerCount();
ByteBuffer packet =
ByteBuffer.allocateDirect(pointerCount * kPointerDataFieldCount * kBytePerField);
ByteBuffer packet = ByteBuffer.allocateDirect(pointerCount * kPointerDataFieldCount * kBytePerField);
packet.order(ByteOrder.LITTLE_ENDIAN);
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) {
|| maskedAction == MotionEvent.ACTION_DOWN || maskedAction == MotionEvent.ACTION_POINTER_DOWN) {
addPointerForIndex(event, event.getActionIndex(), packet);
} else {
// ACTION_MOVE may not actually mean all pointers have moved
@@ -596,14 +591,16 @@ public class FlutterView
}
void assertAttached() {
if (!isAttached()) throw new AssertionError("Platform view is not attached");
if (!isAttached())
throw new AssertionError("Platform view is not attached");
}
private void preRun() {
resetAccessibilityTree();
}
private void postRun() {}
private void postRun() {
}
public void runFromBundle(String bundlePath, String snapshotOverride) {
runFromBundle(bundlePath, snapshotOverride, "main", false);
@@ -631,53 +628,46 @@ public class FlutterView
return nativeGetBitmap(mNativeView.get());
}
private static native void nativeSurfaceCreated(
long nativePlatformViewAndroid, Surface surface, int backgroundColor);
private static native void nativeSurfaceCreated(long nativePlatformViewAndroid, Surface surface,
int backgroundColor);
private static native void nativeSurfaceChanged(
long nativePlatformViewAndroid, int width, int height);
private static native void nativeSurfaceChanged(long nativePlatformViewAndroid, int width, int height);
private static native void nativeSurfaceDestroyed(long nativePlatformViewAndroid);
private static native void nativeSetViewportMetrics(long nativePlatformViewAndroid,
float devicePixelRatio, int physicalWidth, int physicalHeight, int physicalPaddingTop,
int physicalPaddingRight, int physicalPaddingBottom, int physicalPaddingLeft,
int physicalViewInsetTop, int physicalViewInsetRight, int physicalViewInsetBottom,
int physicalViewInsetLeft);
private static native void nativeSetViewportMetrics(long nativePlatformViewAndroid, float devicePixelRatio,
int physicalWidth, int physicalHeight, int physicalPaddingTop, int physicalPaddingRight,
int physicalPaddingBottom, int physicalPaddingLeft, int physicalViewInsetTop, int physicalViewInsetRight,
int physicalViewInsetBottom, int physicalViewInsetLeft);
private static native Bitmap nativeGetBitmap(long nativePlatformViewAndroid);
private static native void nativeDispatchPointerDataPacket(
long nativePlatformViewAndroid, ByteBuffer buffer, int position);
private static native void nativeDispatchPointerDataPacket(long nativePlatformViewAndroid, ByteBuffer buffer,
int position);
private static native void nativeDispatchSemanticsAction(
long nativePlatformViewAndroid, int id, int action, ByteBuffer args, int argsPosition);
private static native void nativeDispatchSemanticsAction(long nativePlatformViewAndroid, int id, int action,
ByteBuffer args, int argsPosition);
private static native void nativeSetSemanticsEnabled(
long nativePlatformViewAndroid, boolean enabled);
private static native void nativeSetSemanticsEnabled(long nativePlatformViewAndroid, boolean enabled);
private static native void nativeSetAssistiveTechnologyEnabled(
long nativePlatformViewAndroid, boolean enabled);
private static native void nativeSetAccessibilityFeatures(long nativePlatformViewAndroid, int flags);
private static native boolean nativeGetIsSoftwareRenderingEnabled();
private static native void nativeRegisterTexture(
long nativePlatformViewAndroid, long textureId, SurfaceTexture surfaceTexture);
private static native void nativeRegisterTexture(long nativePlatformViewAndroid, long textureId,
SurfaceTexture surfaceTexture);
private static native void nativeMarkTextureFrameAvailable(
long nativePlatformViewAndroid, long textureId);
private static native void nativeMarkTextureFrameAvailable(long nativePlatformViewAndroid, long textureId);
private static native void nativeUnregisterTexture(
long nativePlatformViewAndroid, long textureId);
private static native void nativeUnregisterTexture(long nativePlatformViewAndroid, long textureId);
private void updateViewportMetrics() {
if (!isAttached()) return;
nativeSetViewportMetrics(mNativeView.get(), mMetrics.devicePixelRatio,
mMetrics.physicalWidth, mMetrics.physicalHeight, mMetrics.physicalPaddingTop,
mMetrics.physicalPaddingRight, mMetrics.physicalPaddingBottom,
mMetrics.physicalPaddingLeft, mMetrics.physicalViewInsetTop,
mMetrics.physicalViewInsetRight, mMetrics.physicalViewInsetBottom,
mMetrics.physicalViewInsetLeft);
if (!isAttached())
return;
nativeSetViewportMetrics(mNativeView.get(), mMetrics.devicePixelRatio, mMetrics.physicalWidth,
mMetrics.physicalHeight, mMetrics.physicalPaddingTop, mMetrics.physicalPaddingRight,
mMetrics.physicalPaddingBottom, mMetrics.physicalPaddingLeft, mMetrics.physicalViewInsetTop,
mMetrics.physicalViewInsetRight, mMetrics.physicalViewInsetBottom, mMetrics.physicalViewInsetLeft);
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
float fps = wm.getDefaultDisplay().getRefreshRate();
@@ -720,6 +710,7 @@ public class FlutterView
private boolean mAccessibilityEnabled = false;
private boolean mTouchExplorationEnabled = false;
private int mAccessibilityFeatureFlags = 0;
private TouchExplorationListener mTouchExplorationListener;
protected void dispatchSemanticsAction(int id, AccessibilityBridge.Action action) {
@@ -727,7 +718,8 @@ public class FlutterView
}
protected void dispatchSemanticsAction(int id, AccessibilityBridge.Action action, Object args) {
if (!isAttached()) return;
if (!isAttached())
return;
ByteBuffer encodedArgs = null;
int position = 0;
if (args != null) {
@@ -742,12 +734,19 @@ public class FlutterView
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) {
nativeSetAssistiveTechnologyEnabled(mNativeView.get(), true);
mAccessibilityFeatureFlags ^= AccessibilityFeature.ACCESSIBLE_NAVIGATION.value;
}
// Apply additional accessibility settings
updateAccessibilityFeatures();
resetWillNotDraw();
mAccessibilityManager.addAccessibilityStateChangeListener(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
@@ -758,13 +757,26 @@ public class FlutterView
}
}
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;
}
}
nativeSetAccessibilityFeatures(mNativeView.get(), mAccessibilityFeatureFlags);
}
@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);
mAccessibilityManager.removeTouchExplorationStateChangeListener(mTouchExplorationListener);
}
}
@@ -790,20 +802,59 @@ public class FlutterView
resetWillNotDraw();
}
class TouchExplorationListener
implements AccessibilityManager.TouchExplorationStateChangeListener {
/// 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 = Settings.Global.getString(getContext().getContentResolver(),
Settings.Global.TRANSITION_ANIMATION_SCALE);
if (value == "0") {
mAccessibilityFeatureFlags ^= AccessibilityFeature.DISABLE_ANIMATIONS.value;
} else {
mAccessibilityFeatureFlags &= ~AccessibilityFeature.DISABLE_ANIMATIONS.value;
}
nativeSetAccessibilityFeatures(mNativeView.get(), mAccessibilityFeatureFlags);
}
}
class TouchExplorationListener implements AccessibilityManager.TouchExplorationStateChangeListener {
@Override
public void onTouchExplorationStateChanged(boolean enabled) {
if (enabled) {
mTouchExplorationEnabled = true;
ensureAccessibilityEnabled();
nativeSetAssistiveTechnologyEnabled(mNativeView.get(), true);
mAccessibilityFeatureFlags ^= AccessibilityFeature.ACCESSIBLE_NAVIGATION.value;
nativeSetAccessibilityFeatures(mNativeView.get(), mAccessibilityFeatureFlags);
} else {
mTouchExplorationEnabled = false;
if (mAccessibilityNodeProvider != null) {
mAccessibilityNodeProvider.handleTouchExplorationExit();
}
nativeSetAssistiveTechnologyEnabled(mNativeView.get(), false);
mAccessibilityFeatureFlags &= ~AccessibilityFeature.ACCESSIBLE_NAVIGATION.value;
nativeSetAccessibilityFeatures(mNativeView.get(), mAccessibilityFeatureFlags);
}
resetWillNotDraw();
}
@@ -811,8 +862,10 @@ public class FlutterView
@Override
public AccessibilityNodeProvider getAccessibilityNodeProvider() {
if (mAccessibilityEnabled) return mAccessibilityNodeProvider;
// TODO(goderbauer): when a11y is off this should return a one-off snapshot of the a11y
if (mAccessibilityEnabled)
return mAccessibilityNodeProvider;
// TODO(goderbauer): when a11y is off this should return a one-off snapshot of
// the a11y
// tree.
return null;
}
@@ -820,7 +873,8 @@ public class FlutterView
private AccessibilityBridge mAccessibilityNodeProvider;
void ensureAccessibilityEnabled() {
if (!isAttached()) return;
if (!isAttached())
return;
mAccessibilityEnabled = true;
if (mAccessibilityNodeProvider == null) {
mAccessibilityNodeProvider = new AccessibilityBridge(this);
@@ -839,8 +893,7 @@ public class FlutterView
if (!mTouchExplorationEnabled) {
return false;
}
if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER
|| event.getAction() == MotionEvent.ACTION_HOVER_MOVE) {
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();
@@ -873,9 +926,9 @@ public class FlutterView
/**
* Broadcast receiver used to discover active Flutter instances.
*
* This is used by the `flutter` tool to find the observatory ports
* for all the active Flutter views. We dump the data to the logs
* and the tool scrapes the log lines for the data.
* This is used by the `flutter` tool to find the observatory ports for all the
* active Flutter views. We dump the data to the logs and the tool scrapes the
* log lines for the data.
*/
private class DiscoveryReceiver extends BroadcastReceiver {
@Override
@@ -893,16 +946,19 @@ public class FlutterView
}
/**
* Listener will be called on the Android UI thread once when Flutter renders the first frame.
* Listener will be called on the Android UI thread once when Flutter renders
* the first frame.
*/
public interface FirstFrameListener { void onFirstFrame(); }
public interface FirstFrameListener {
void onFirstFrame();
}
@Override
public TextureRegistry.SurfaceTextureEntry createSurfaceTexture() {
final SurfaceTexture surfaceTexture = new SurfaceTexture(0);
surfaceTexture.detachFromGLContext();
final SurfaceTextureRegistryEntry entry =
new SurfaceTextureRegistryEntry(nextTextureId.getAndIncrement(), surfaceTexture);
final SurfaceTextureRegistryEntry entry = new SurfaceTextureRegistryEntry(nextTextureId.getAndIncrement(),
surfaceTexture);
nativeRegisterTexture(mNativeView.get(), entry.id(), surfaceTexture);
return entry;
}
@@ -915,14 +971,12 @@ public class FlutterView
SurfaceTextureRegistryEntry(long id, SurfaceTexture surfaceTexture) {
this.id = id;
this.surfaceTexture = surfaceTexture;
this.surfaceTexture.setOnFrameAvailableListener(
new SurfaceTexture.OnFrameAvailableListener() {
@Override
public void onFrameAvailable(SurfaceTexture texture) {
nativeMarkTextureFrameAvailable(
mNativeView.get(), SurfaceTextureRegistryEntry.this.id);
}
});
this.surfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
@Override
public void onFrameAvailable(SurfaceTexture texture) {
nativeMarkTextureFrameAvailable(mNativeView.get(), SurfaceTextureRegistryEntry.this.id);
}
});
}
@Override

View File

@@ -443,12 +443,11 @@ static void SetSemanticsEnabled(JNIEnv* env,
ANDROID_SHELL_HOLDER->GetPlatformView()->SetSemanticsEnabled(enabled);
}
static void SetAssistiveTechnologyEnabled(JNIEnv* env,
jobject jcaller,
jlong shell_holder,
jboolean enabled) {
ANDROID_SHELL_HOLDER->GetPlatformView()->SetAssistiveTechnologyEnabled(
enabled);
static void SetAccessibilityFeatures(JNIEnv* env,
jobject jcaller,
jlong shell_holder,
jint flags) {
ANDROID_SHELL_HOLDER->GetPlatformView()->SetAccessibilityFeatures(flags);
}
static jboolean GetIsSoftwareRendering(JNIEnv* env, jobject jcaller) {
@@ -623,10 +622,9 @@ bool PlatformViewAndroid::Register(JNIEnv* env) {
.fnPtr = reinterpret_cast<void*>(&shell::SetSemanticsEnabled),
},
{
.name = "nativeSetAssistiveTechnologyEnabled",
.signature = "(JZ)V",
.fnPtr =
reinterpret_cast<void*>(&shell::SetAssistiveTechnologyEnabled),
.name = "nativeSetAccessibilityFeatures",
.signature = "(JI)V",
.fnPtr = reinterpret_cast<void*>(&shell::SetAccessibilityFeatures),
},
{
.name = "nativeGetIsSoftwareRenderingEnabled",

View File

@@ -64,7 +64,6 @@
nibName:(NSString*)nibNameOrNil
bundle:(NSBundle*)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
if (projectOrNil == nil)
_dartProject.reset([[FlutterDartProject alloc] init]);
@@ -282,6 +281,16 @@
name:UIAccessibilitySpeakScreenStatusDidChangeNotification
object:nil];
[center addObserver:self
selector:@selector(onAccessibilityStatusChanged:)
name:UIAccessibilityInvertColorsStatusDidChangeNotification
object:nil];
[center addObserver:self
selector:@selector(onAccessibilityStatusChanged:)
name:UIAccessibilityReduceMotionStatusDidChangeNotification
object:nil];
[center addObserver:self
selector:@selector(onMemoryWarning:)
name:UIApplicationDidReceiveMemoryWarningNotification
@@ -793,16 +802,23 @@ static inline blink::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* to
- (void)onAccessibilityStatusChanged:(NSNotification*)notification {
auto platformView = _shell->GetPlatformView();
int32_t flags = 0;
if (UIAccessibilityIsInvertColorsEnabled())
flags ^= static_cast<int32_t>(blink::AccessibilityFeatureFlag::kInvertColors);
if (UIAccessibilityIsReduceMotionEnabled())
flags ^= static_cast<int32_t>(blink::AccessibilityFeatureFlag::kDisableAnimations);
#if TARGET_OS_SIMULATOR
// There doesn't appear to be any way to determine whether the accessibility
// inspector is enabled on the simulator. We conservatively always turn on the
// accessibility bridge in the simulator, but never assistive technology.
platformView->SetSemanticsEnabled(true);
platformView->SetAssistiveTechnologyEnabled(false);
platformView->SetAccessibilityFeatures(flags);
#else
bool enabled = UIAccessibilityIsVoiceOverRunning() || UIAccessibilityIsSwitchControlRunning();
if (UIAccessibilityIsVoiceOverRunning() || UIAccessibilityIsSwitchControlRunning())
flags ^= static_cast<int32_t>(blink::AccessibilityFeatureFlag::kAccessibleNavigation);
platformView->SetSemanticsEnabled(enabled || UIAccessibilityIsSpeakScreenEnabled());
platformView->SetAssistiveTechnologyEnabled(enabled);
platformView->SetAccessibilityFeatures(flags);
#endif
}

View File

@@ -58,7 +58,7 @@ class PlatformViewIOS final : public HeadlessPlatformViewIOS {
void SetSemanticsEnabled(bool enabled) override;
// |shell::PlatformView|
void SetAssistiveTechnologyEnabled(bool enabled) override;
void SetAccessibilityFeatures(int32_t flags) override;
// |shell::PlatformView|
void UpdateSemantics(

View File

@@ -68,14 +68,8 @@ void PlatformViewIOS::SetSemanticsEnabled(bool enabled) {
}
// |shell:PlatformView|
void PlatformViewIOS::SetAssistiveTechnologyEnabled(bool enabled) {
if (enabled && !accessibility_bridge_) {
accessibility_bridge_ = std::make_unique<AccessibilityBridge>(owner_view_, this);
}
// Note: since the accessibility bridge is needed for both semantics and
// assistive technologies, but you cannot have the latter without the
// former, we only destroy the bridge in SetSemanticsEnabled and not here.
PlatformView::SetAssistiveTechnologyEnabled(enabled);
void PlatformViewIOS::SetAccessibilityFeatures(int32_t flags) {
PlatformView::SetAccessibilityFeatures(flags);
}
// |shell::PlatformView|