1 package io.flutter.embedding.engine.systemchannels; 2 3 import android.support.annotation.NonNull; 4 import android.support.annotation.Nullable; 5 6 import java.util.HashMap; 7 8 import io.flutter.Log; 9 import io.flutter.embedding.engine.FlutterJNI; 10 import io.flutter.embedding.engine.dart.DartExecutor; 11 import io.flutter.plugin.common.BasicMessageChannel; 12 import io.flutter.plugin.common.StandardMessageCodec; 13 import io.flutter.view.AccessibilityBridge; 14 15 /** 16 * System channel that sends accessibility requests and events from Flutter to Android. 17 * <p> 18 * See {@link AccessibilityMessageHandler}, which lists all accessibility requests and 19 * events that might be sent from Flutter to the Android platform. 20 */ 21 public class AccessibilityChannel { 22 private static final String TAG = "AccessibilityChannel"; 23 24 @NonNull 25 public final BasicMessageChannel<Object> channel; 26 @NonNull 27 public final FlutterJNI flutterJNI; 28 @Nullable 29 private AccessibilityMessageHandler handler; 30 31 private final BasicMessageChannel.MessageHandler<Object> parsingMessageHandler = new BasicMessageChannel.MessageHandler<Object>() { 32 @Override 33 public void onMessage(@Nullable Object message, @NonNull BasicMessageChannel.Reply<Object> reply) { 34 // If there is no handler to respond to this message then we don't need to 35 // parse it. Return. 36 if (handler == null) { 37 return; 38 } 39 40 @SuppressWarnings("unchecked") 41 final HashMap<String, Object> annotatedEvent = (HashMap<String, Object>) message; 42 final String type = (String) annotatedEvent.get("type"); 43 @SuppressWarnings("unchecked") 44 final HashMap<String, Object> data = (HashMap<String, Object>) annotatedEvent.get("data"); 45 46 Log.v(TAG, "Received " + type + " message."); 47 switch (type) { 48 case "announce": 49 String announceMessage = (String) data.get("message"); 50 if (announceMessage != null) { 51 handler.announce(announceMessage); 52 } 53 break; 54 case "tap": { 55 Integer nodeId = (Integer) annotatedEvent.get("nodeId"); 56 if (nodeId != null) { 57 handler.onTap(nodeId); 58 } 59 break; 60 } 61 case "longPress": { 62 Integer nodeId = (Integer) annotatedEvent.get("nodeId"); 63 if (nodeId != null) { 64 handler.onLongPress(nodeId); 65 } 66 break; 67 } 68 case "tooltip": { 69 String tooltipMessage = (String) data.get("message"); 70 if (tooltipMessage != null) { 71 handler.onTooltip(tooltipMessage); 72 } 73 break; 74 } 75 } 76 } 77 }; 78 79 /** 80 * Constructs an {@code AccessibilityChannel} that connects Android to the Dart code 81 * running in {@code dartExecutor}. 82 * 83 * The given {@code dartExecutor} is permitted to be idle or executing code. 84 * 85 * See {@link DartExecutor}. 86 */ AccessibilityChannel(@onNull DartExecutor dartExecutor, @NonNull FlutterJNI flutterJNI)87 public AccessibilityChannel(@NonNull DartExecutor dartExecutor, @NonNull FlutterJNI flutterJNI) { 88 channel = new BasicMessageChannel<>(dartExecutor, "flutter/accessibility", StandardMessageCodec.INSTANCE); 89 channel.setMessageHandler(parsingMessageHandler); 90 this.flutterJNI = flutterJNI; 91 } 92 93 /** 94 * Informs Flutter that the Android OS currently has accessibility enabled. 95 * 96 * To accommodate enabled accessibility, this method instructs Flutter to activate 97 * its semantics tree, which forms the basis of Flutter's accessibility support. 98 */ onAndroidAccessibilityEnabled()99 public void onAndroidAccessibilityEnabled() { 100 flutterJNI.setSemanticsEnabled(true); 101 } 102 103 /** 104 * Informs Flutter that the Android OS currently has accessibility disabled. 105 * 106 * Given that accessibility is not required at this time, this method instructs Flutter 107 * to deactivate its semantics tree. 108 */ onAndroidAccessibilityDisabled()109 public void onAndroidAccessibilityDisabled() { 110 flutterJNI.setSemanticsEnabled(false); 111 } 112 113 /** 114 * Instructs Flutter to activate/deactivate accessibility features corresponding to the 115 * flags provided by {@code accessibilityFeatureFlags}. 116 */ setAccessibilityFeatures(int accessibilityFeatureFlags)117 public void setAccessibilityFeatures(int accessibilityFeatureFlags) { 118 flutterJNI.setAccessibilityFeatures(accessibilityFeatureFlags); 119 } 120 121 /** 122 * Instructs Flutter to perform the given {@code action} on the {@code SemanticsNode} 123 * referenced by the given {@code virtualViewId}. 124 * 125 * One might wonder why Flutter would need to be instructed that the user wants to perform 126 * an action. When the user is touching the screen in accessibility mode, Android takes over the 127 * touch input, categorizing input as one of a many accessibility gestures. Therefore, Flutter 128 * does not have an opportunity to react to said touch input. Instead, Flutter must be notified 129 * by Android of the desired action. Additionally, some accessibility systems use other input 130 * methods, such as speech, to take virtual actions. Android interprets those requests and then 131 * instructs the app to take the appropriate action. 132 */ dispatchSemanticsAction(int virtualViewId, @NonNull AccessibilityBridge.Action action)133 public void dispatchSemanticsAction(int virtualViewId, @NonNull AccessibilityBridge.Action action) { 134 flutterJNI.dispatchSemanticsAction(virtualViewId, action); 135 } 136 137 /** 138 * Instructs Flutter to perform the given {@code action} on the {@code SemanticsNode} 139 * referenced by the given {@code virtualViewId}, passing the given {@code args}. 140 */ dispatchSemanticsAction(int virtualViewId, @NonNull AccessibilityBridge.Action action, @Nullable Object args)141 public void dispatchSemanticsAction(int virtualViewId, @NonNull AccessibilityBridge.Action action, @Nullable Object args) { 142 flutterJNI.dispatchSemanticsAction(virtualViewId, action, args); 143 } 144 145 /** 146 * Sets the {@link AccessibilityMessageHandler} which receives all events and requests 147 * that are parsed from the underlying accessibility channel. 148 */ setAccessibilityMessageHandler(@ullable AccessibilityMessageHandler handler)149 public void setAccessibilityMessageHandler(@Nullable AccessibilityMessageHandler handler) { 150 this.handler = handler; 151 flutterJNI.setAccessibilityDelegate(handler); 152 } 153 154 /** 155 * Handler that receives accessibility messages sent from Flutter to Android 156 * through a given {@link AccessibilityChannel}. 157 * 158 * To register an {@code AccessibilityMessageHandler} with a {@link AccessibilityChannel}, 159 * see {@link AccessibilityChannel#setAccessibilityMessageHandler(AccessibilityMessageHandler)}. 160 */ 161 public interface AccessibilityMessageHandler extends FlutterJNI.AccessibilityDelegate { 162 /** 163 * The Dart application would like the given {@code message} to be announced. 164 */ announce(@onNull String message)165 void announce(@NonNull String message); 166 167 /** 168 * The user has tapped on the widget with the given {@code nodeId}. 169 */ onTap(int nodeId)170 void onTap(int nodeId); 171 172 /** 173 * The user has long pressed on the widget with the given {@code nodeId}. 174 */ onLongPress(int nodeId)175 void onLongPress(int nodeId); 176 177 /** 178 * The user has opened a tooltip. 179 */ onTooltip(@onNull String message)180 void onTooltip(@NonNull String message); 181 } 182 } 183