From 3a3f6ca0ee9a77af1d994aa95830656ca6440186 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Tue, 31 Jul 2018 18:18:19 -0700 Subject: [PATCH] Initial support for more finely-grained a11y features on Window (flutter/engine#5901) --- engine/src/flutter/lib/ui/hooks.dart | 9 +- engine/src/flutter/lib/ui/window.dart | 92 ++++-- engine/src/flutter/lib/ui/window/window.cc | 6 +- engine/src/flutter/lib/ui/window/window.h | 9 +- .../flutter/runtime/dart_vm_entry_points.txt | 2 +- .../src/flutter/runtime/runtime_controller.cc | 11 +- .../src/flutter/runtime/runtime_controller.h | 3 +- engine/src/flutter/shell/common/engine.cc | 4 +- engine/src/flutter/shell/common/engine.h | 2 +- .../src/flutter/shell/common/platform_view.cc | 4 +- .../src/flutter/shell/common/platform_view.h | 6 +- engine/src/flutter/shell/common/shell.cc | 10 +- engine/src/flutter/shell/common/shell.h | 6 +- .../android/io/flutter/view/FlutterView.java | 278 +++++++++++------- .../android/platform_view_android_jni.cc | 18 +- .../framework/Source/FlutterViewController.mm | 22 +- .../platform/darwin/ios/platform_view_ios.h | 2 +- .../platform/darwin/ios/platform_view_ios.mm | 10 +- 18 files changed, 304 insertions(+), 190 deletions(-) diff --git a/engine/src/flutter/lib/ui/hooks.dart b/engine/src/flutter/lib/ui/hooks.dart index c5834a3939..f644ef8481 100644 --- a/engine/src/flutter/lib/ui/hooks.dart +++ b/engine/src/flutter/lib/ui/hooks.dart @@ -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) { diff --git a/engine/src/flutter/lib/ui/window.dart b/engine/src/flutter/lib/ui/window.dart index f20b236fc2..2a8a392c75 100644 --- a/engine/src/flutter/lib/ui/window.dart +++ b/engine/src/flutter/lib/ui/window.dart @@ -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 features = []; + 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. diff --git a/engine/src/flutter/lib/ui/window/window.cc b/engine/src/flutter/lib/ui/window/window.cc index 834e04f413..783b025b71 100644 --- a/engine/src/flutter/lib/ui/window/window.cc +++ b/engine/src/flutter/lib/ui/window/window.cc @@ -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 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 message) { diff --git a/engine/src/flutter/lib/ui/window/window.h b/engine/src/flutter/lib/ui/window/window.h index 8669b916b9..ba2da54f56 100644 --- a/engine/src/flutter/lib/ui/window/window.h +++ b/engine/src/flutter/lib/ui/window/window.h @@ -25,6 +25,13 @@ class Scene; Dart_Handle ToByteData(const std::vector& 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 message); void DispatchPointerDataPacket(const PointerDataPacket& packet); void DispatchSemanticsAction(int32_t id, diff --git a/engine/src/flutter/runtime/dart_vm_entry_points.txt b/engine/src/flutter/runtime/dart_vm_entry_points.txt index c7b9734d2a..d5110c7ad9 100644 --- a/engine/src/flutter/runtime/dart_vm_entry_points.txt +++ b/engine/src/flutter/runtime/dart_vm_entry_points.txt @@ -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 diff --git a/engine/src/flutter/runtime/runtime_controller.cc b/engine/src/flutter/runtime/runtime_controller.cc index 1b3f338791..d3509ed080 100644 --- a/engine/src/flutter/runtime/runtime_controller.cc +++ b/engine/src/flutter/runtime/runtime_controller.cc @@ -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; } diff --git a/engine/src/flutter/runtime/runtime_controller.h b/engine/src/flutter/runtime/runtime_controller.h index 637e9c68d1..c2a909f6ed 100644 --- a/engine/src/flutter/runtime/runtime_controller.h +++ b/engine/src/flutter/runtime/runtime_controller.h @@ -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_; diff --git a/engine/src/flutter/shell/common/engine.cc b/engine/src/flutter/shell/common/engine.cc index d06001ef2b..2375d02d77 100644 --- a/engine/src/flutter/shell/common/engine.cc +++ b/engine/src/flutter/shell/common/engine.cc @@ -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() { diff --git a/engine/src/flutter/shell/common/engine.h b/engine/src/flutter/shell/common/engine.h index d79c9cdd54..1a2c75ee60 100644 --- a/engine/src/flutter/shell/common/engine.h +++ b/engine/src/flutter/shell/common/engine.h @@ -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; diff --git a/engine/src/flutter/shell/common/platform_view.cc b/engine/src/flutter/shell/common/platform_view.cc index 48d3fbe94b..656fa91439 100644 --- a/engine/src/flutter/shell/common/platform_view.cc +++ b/engine/src/flutter/shell/common/platform_view.cc @@ -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) { diff --git a/engine/src/flutter/shell/common/platform_view.h b/engine/src/flutter/shell/common/platform_view.h index f4ba65baeb..a6fccae563 100644 --- a/engine/src/flutter/shell/common/platform_view.h +++ b/engine/src/flutter/shell/common/platform_view.h @@ -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); diff --git a/engine/src/flutter/shell/common/shell.cc b/engine/src/flutter/shell/common/shell.cc index 6f24521315..1e3085ca1d 100644 --- a/engine/src/flutter/shell/common/shell.cc +++ b/engine/src/flutter/shell/common/shell.cc @@ -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); } }); } diff --git a/engine/src/flutter/shell/common/shell.h b/engine/src/flutter/shell/common/shell.h index 8e2fbbc4c9..c8bd767312 100644 --- a/engine/src/flutter/shell/common/shell.h +++ b/engine/src/flutter/shell/common/shell.h @@ -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( diff --git a/engine/src/flutter/shell/platform/android/io/flutter/view/FlutterView.java b/engine/src/flutter/shell/platform/android/io/flutter/view/FlutterView.java index 5728992d8d..808c58a01b 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/view/FlutterView.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/view/FlutterView.java @@ -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). * - *

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}.

+ *

+ * 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}. + *

*/ 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 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 diff --git a/engine/src/flutter/shell/platform/android/platform_view_android_jni.cc b/engine/src/flutter/shell/platform/android/platform_view_android_jni.cc index 16215ca678..933a34dff9 100644 --- a/engine/src/flutter/shell/platform/android/platform_view_android_jni.cc +++ b/engine/src/flutter/shell/platform/android/platform_view_android_jni.cc @@ -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(&shell::SetSemanticsEnabled), }, { - .name = "nativeSetAssistiveTechnologyEnabled", - .signature = "(JZ)V", - .fnPtr = - reinterpret_cast(&shell::SetAssistiveTechnologyEnabled), + .name = "nativeSetAccessibilityFeatures", + .signature = "(JI)V", + .fnPtr = reinterpret_cast(&shell::SetAccessibilityFeatures), }, { .name = "nativeGetIsSoftwareRenderingEnabled", diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index fc8f3d380d..2839ece8c0 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -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(blink::AccessibilityFeatureFlag::kInvertColors); + if (UIAccessibilityIsReduceMotionEnabled()) + flags ^= static_cast(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(blink::AccessibilityFeatureFlag::kAccessibleNavigation); platformView->SetSemanticsEnabled(enabled || UIAccessibilityIsSpeakScreenEnabled()); - platformView->SetAssistiveTechnologyEnabled(enabled); + platformView->SetAccessibilityFeatures(flags); #endif } diff --git a/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios.h b/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios.h index abe90c18cf..014f045f70 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios.h +++ b/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios.h @@ -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( diff --git a/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios.mm b/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios.mm index 4ab1419cb7..7bf0895589 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios.mm @@ -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(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|