blob: 3fb32814f56b8d0787a8392df7f5b1436ed89287 [file] [log] [blame]
package io.flutter.embedding.engine.systemchannels;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import io.flutter.Log;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.plugin.common.BasicMessageChannel;
import io.flutter.plugin.common.StandardMessageCodec;
import io.flutter.view.AccessibilityBridge;
import java.util.HashMap;
/**
* System channel that sends accessibility requests and events from Flutter to Android.
*
* <p>See {@link AccessibilityMessageHandler}, which lists all accessibility requests and events
* that might be sent from Flutter to the Android platform.
*/
public class AccessibilityChannel {
private static final String TAG = "AccessibilityChannel";
@NonNull public final BasicMessageChannel<Object> channel;
@NonNull public final FlutterJNI flutterJNI;
@Nullable private AccessibilityMessageHandler handler;
@VisibleForTesting
final BasicMessageChannel.MessageHandler<Object> parsingMessageHandler =
new BasicMessageChannel.MessageHandler<Object>() {
@Override
public void onMessage(
@Nullable Object message, @NonNull BasicMessageChannel.Reply<Object> reply) {
// If there is no handler to respond to this message then we don't need to
// parse it. Return.
if (handler == null) {
reply.reply(null);
return;
}
@SuppressWarnings("unchecked")
final HashMap<String, Object> annotatedEvent = (HashMap<String, Object>) message;
final String type = (String) annotatedEvent.get("type");
@SuppressWarnings("unchecked")
final HashMap<String, Object> data = (HashMap<String, Object>) annotatedEvent.get("data");
Log.v(TAG, "Received " + type + " message.");
switch (type) {
case "announce":
String announceMessage = (String) data.get("message");
if (announceMessage != null) {
handler.announce(announceMessage);
}
break;
case "tap":
{
Integer nodeId = (Integer) annotatedEvent.get("nodeId");
if (nodeId != null) {
handler.onTap(nodeId);
}
break;
}
case "longPress":
{
Integer nodeId = (Integer) annotatedEvent.get("nodeId");
if (nodeId != null) {
handler.onLongPress(nodeId);
}
break;
}
case "tooltip":
{
String tooltipMessage = (String) data.get("message");
if (tooltipMessage != null) {
handler.onTooltip(tooltipMessage);
}
break;
}
}
reply.reply(null);
}
};
/**
* Constructs an {@code AccessibilityChannel} that connects Android to the Dart code running in
* {@code dartExecutor}.
*
* <p>The given {@code dartExecutor} is permitted to be idle or executing code.
*
* <p>See {@link DartExecutor}.
*/
public AccessibilityChannel(@NonNull DartExecutor dartExecutor, @NonNull FlutterJNI flutterJNI) {
channel =
new BasicMessageChannel<>(
dartExecutor, "flutter/accessibility", StandardMessageCodec.INSTANCE);
channel.setMessageHandler(parsingMessageHandler);
this.flutterJNI = flutterJNI;
}
/**
* Informs Flutter that the Android OS currently has accessibility enabled.
*
* <p>To accommodate enabled accessibility, this method instructs Flutter to activate its
* semantics tree, which forms the basis of Flutter's accessibility support.
*/
public void onAndroidAccessibilityEnabled() {
flutterJNI.setSemanticsEnabled(true);
}
/**
* Informs Flutter that the Android OS currently has accessibility disabled.
*
* <p>Given that accessibility is not required at this time, this method instructs Flutter to
* deactivate its semantics tree.
*/
public void onAndroidAccessibilityDisabled() {
flutterJNI.setSemanticsEnabled(false);
}
/**
* Instructs Flutter to activate/deactivate accessibility features corresponding to the flags
* provided by {@code accessibilityFeatureFlags}.
*/
public void setAccessibilityFeatures(int accessibilityFeatureFlags) {
flutterJNI.setAccessibilityFeatures(accessibilityFeatureFlags);
}
/**
* Instructs Flutter to perform the given {@code action} on the {@code SemanticsNode} referenced
* by the given {@code virtualViewId}.
*
* <p>One might wonder why Flutter would need to be instructed that the user wants to perform an
* action. When the user is touching the screen in accessibility mode, Android takes over the
* touch input, categorizing input as one of a many accessibility gestures. Therefore, Flutter
* does not have an opportunity to react to said touch input. Instead, Flutter must be notified by
* Android of the desired action. Additionally, some accessibility systems use other input
* methods, such as speech, to take virtual actions. Android interprets those requests and then
* instructs the app to take the appropriate action.
*/
public void dispatchSemanticsAction(
int virtualViewId, @NonNull AccessibilityBridge.Action action) {
flutterJNI.dispatchSemanticsAction(virtualViewId, action);
}
/**
* Instructs Flutter to perform the given {@code action} on the {@code SemanticsNode} referenced
* by the given {@code virtualViewId}, passing the given {@code args}.
*/
public void dispatchSemanticsAction(
int virtualViewId, @NonNull AccessibilityBridge.Action action, @Nullable Object args) {
flutterJNI.dispatchSemanticsAction(virtualViewId, action, args);
}
/**
* Sets the {@link AccessibilityMessageHandler} which receives all events and requests that are
* parsed from the underlying accessibility channel.
*/
public void setAccessibilityMessageHandler(@Nullable AccessibilityMessageHandler handler) {
this.handler = handler;
flutterJNI.setAccessibilityDelegate(handler);
}
/**
* Handler that receives accessibility messages sent from Flutter to Android through a given
* {@link AccessibilityChannel}.
*
* <p>To register an {@code AccessibilityMessageHandler} with a {@link AccessibilityChannel}, see
* {@link AccessibilityChannel#setAccessibilityMessageHandler(AccessibilityMessageHandler)}.
*/
public interface AccessibilityMessageHandler extends FlutterJNI.AccessibilityDelegate {
/** The Dart application would like the given {@code message} to be announced. */
void announce(@NonNull String message);
/** The user has tapped on the widget with the given {@code nodeId}. */
void onTap(int nodeId);
/** The user has long pressed on the widget with the given {@code nodeId}. */
void onLongPress(int nodeId);
/** The user has opened a tooltip. */
void onTooltip(@NonNull String message);
}
}