• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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