5
5
package io .flutter .embedding .engine .android ;
6
6
7
7
import android .content .Context ;
8
+ import android .content .res .Configuration ;
9
+ import android .graphics .Rect ;
10
+ import android .os .Build ;
11
+ import android .os .LocaleList ;
8
12
import android .support .annotation .NonNull ;
9
13
import android .support .annotation .Nullable ;
14
+ import android .text .format .DateFormat ;
10
15
import android .util .AttributeSet ;
11
16
import android .util .Log ;
17
+ import android .view .KeyEvent ;
18
+ import android .view .MotionEvent ;
19
+ import android .view .WindowInsets ;
20
+ import android .view .inputmethod .EditorInfo ;
21
+ import android .view .inputmethod .InputConnection ;
22
+ import android .view .inputmethod .InputMethod ;
23
+ import android .view .inputmethod .InputMethodManager ;
12
24
import android .widget .FrameLayout ;
13
25
26
+ import java .util .ArrayList ;
27
+ import java .util .List ;
28
+ import java .util .Locale ;
29
+
14
30
import io .flutter .embedding .engine .FlutterEngine ;
15
31
import io .flutter .embedding .engine .renderer .FlutterRenderer ;
32
+ import io .flutter .plugin .editing .TextInputPlugin ;
16
33
17
34
/**
18
35
* Displays a Flutter UI on an Android device.
@@ -50,6 +67,16 @@ public class FlutterView extends FrameLayout {
50
67
@ Nullable
51
68
private FlutterEngine flutterEngine ;
52
69
70
+ // Components that process various types of Android View input and events,
71
+ // possibly storing intermediate state, and communicating those events to Flutter.
72
+ //
73
+ // These components essentially add some additional behavioral logic on top of
74
+ // existing, stateless system channels, e.g., KeyEventChannel, TextInputChannel, etc.
75
+ @ Nullable
76
+ private TextInputPlugin textInputPlugin ;
77
+ @ Nullable
78
+ private AndroidKeyProcessor androidKeyProcessor ;
79
+
53
80
/**
54
81
* Constructs a {@code FlutterSurfaceView} programmatically, without any XML attributes.
55
82
*
@@ -103,6 +130,176 @@ private void init() {
103
130
}
104
131
}
105
132
133
+ //------- Start: Process View configuration that Flutter cares about. ------
134
+ /**
135
+ * Sends relevant configuration data from Android to Flutter when the Android
136
+ * {@link Configuration} changes.
137
+ *
138
+ * The Android {@link Configuration} might change as a result of device orientation
139
+ * change, device language change, device text scale factor change, etc.
140
+ */
141
+ @ Override
142
+ protected void onConfigurationChanged (Configuration newConfig ) {
143
+ super .onConfigurationChanged (newConfig );
144
+ sendLocalesToFlutter (newConfig );
145
+ sendUserSettingsToFlutter ();
146
+ }
147
+
148
+ /**
149
+ * Invoked when this {@code FlutterView} changes size, including upon initial
150
+ * measure.
151
+ *
152
+ * The initial measure reports an {@code oldWidth} and {@code oldHeight} of zero.
153
+ *
154
+ * Flutter cares about the width and height of the view that displays it on the host
155
+ * platform. Therefore, when this method is invoked, the new width and height are
156
+ * communicated to Flutter as the "physical size" of the view that displays Flutter's
157
+ * UI.
158
+ */
159
+ @ Override
160
+ protected void onSizeChanged (int width , int height , int oldWidth , int oldHeight ) {
161
+ // TODO(mattcarroll): hookup to viewport metrics.
162
+ super .onSizeChanged (width , height , oldWidth , oldHeight );
163
+ }
164
+
165
+ /**
166
+ * Invoked when Android's desired window insets change, i.e., padding.
167
+ *
168
+ * Flutter does not use a standard {@code View} hierarchy and therefore Flutter is
169
+ * unaware of these insets. Therefore, this method calculates the viewport metrics
170
+ * that Flutter should use and then sends those metrics to Flutter.
171
+ *
172
+ * This callback is not present in API < 20, which means lower API devices will see
173
+ * the wider than expected padding when the status and navigation bars are hidden.
174
+ */
175
+ @ Override
176
+ public final WindowInsets onApplyWindowInsets (WindowInsets insets ) {
177
+ // TODO(mattcarroll): hookup to Flutter metrics.
178
+ return insets ;
179
+ }
180
+
181
+ /**
182
+ * Invoked when Android's desired window insets change, i.e., padding.
183
+ *
184
+ * {@code fitSystemWindows} is an earlier version of
185
+ * {@link #onApplyWindowInsets(WindowInsets)}. See that method for more details
186
+ * about how window insets relate to Flutter.
187
+ */
188
+ @ Override
189
+ @ SuppressWarnings ("deprecation" )
190
+ protected boolean fitSystemWindows (Rect insets ) {
191
+ // TODO(mattcarroll): hookup to Flutter metrics.
192
+ return super .fitSystemWindows (insets );
193
+ }
194
+ //------- End: Process View configuration that Flutter cares about. --------
195
+
196
+ //-------- Start: Process UI I/O that Flutter cares about. -------
197
+ /**
198
+ * Creates an {@link InputConnection} to work with a {@link android.view.inputmethod.InputMethodManager}.
199
+ *
200
+ * Any {@code View} that can take focus or process text input must implement this
201
+ * method by returning a non-null {@code InputConnection}. Flutter may render one or
202
+ * many focusable and text-input widgets, therefore {@code FlutterView} must support
203
+ * an {@code InputConnection}.
204
+ *
205
+ * The {@code InputConnection} returned from this method comes from a
206
+ * {@link TextInputPlugin}, which is owned by this {@code FlutterView}. A
207
+ * {@link TextInputPlugin} exists to encapsulate the nuances of input communication,
208
+ * rather than spread that logic throughout this {@code FlutterView}.
209
+ */
210
+ @ Override
211
+ public InputConnection onCreateInputConnection (EditorInfo outAttrs ) {
212
+ if (!isAttachedToFlutterEngine ()) {
213
+ return super .onCreateInputConnection (outAttrs );
214
+ }
215
+
216
+ return textInputPlugin .createInputConnection (this , outAttrs );
217
+ }
218
+
219
+ /**
220
+ * Invoked when key is released.
221
+ *
222
+ * This method is typically invoked in response to the release of a physical
223
+ * keyboard key or a D-pad button. It is generally not invoked when a virtual
224
+ * software keyboard is used, though a software keyboard may choose to invoke
225
+ * this method in some situations.
226
+ *
227
+ * {@link KeyEvent}s are sent from Android to Flutter. {@link AndroidKeyProcessor}
228
+ * may do some additional work with the given {@link KeyEvent}, e.g., combine this
229
+ * {@code keyCode} with the previous {@code keyCode} to generate a unicode combined
230
+ * character.
231
+ */
232
+ @ Override
233
+ public boolean onKeyUp (int keyCode , KeyEvent event ) {
234
+ if (!isAttachedToFlutterEngine ()) {
235
+ return super .onKeyUp (keyCode , event );
236
+ }
237
+
238
+ androidKeyProcessor .onKeyUp (event );
239
+ return super .onKeyUp (keyCode , event );
240
+ }
241
+
242
+ /**
243
+ * Invoked when key is pressed.
244
+ *
245
+ * This method is typically invoked in response to the press of a physical
246
+ * keyboard key or a D-pad button. It is generally not invoked when a virtual
247
+ * software keyboard is used, though a software keyboard may choose to invoke
248
+ * this method in some situations.
249
+ *
250
+ * {@link KeyEvent}s are sent from Android to Flutter. {@link AndroidKeyProcessor}
251
+ * may do some additional work with the given {@link KeyEvent}, e.g., combine this
252
+ * {@code keyCode} with the previous {@code keyCode} to generate a unicode combined
253
+ * character.
254
+ */
255
+ @ Override
256
+ public boolean onKeyDown (int keyCode , KeyEvent event ) {
257
+ if (!isAttachedToFlutterEngine ()) {
258
+ return super .onKeyDown (keyCode , event );
259
+ }
260
+
261
+ androidKeyProcessor .onKeyDown (event );
262
+ return super .onKeyDown (keyCode , event );
263
+ }
264
+
265
+ /**
266
+ * Invoked by Android when a user touch event occurs.
267
+ *
268
+ * Flutter handles all of its own gesture detection and processing, therefore this
269
+ * method forwards all {@link MotionEvent} data from Android to Flutter.
270
+ */
271
+ @ Override
272
+ public boolean onTouchEvent (MotionEvent event ) {
273
+ if (!isAttachedToFlutterEngine ()) {
274
+ return false ;
275
+ }
276
+
277
+ // TODO(mattcarroll): forward event to touch processore when it's merged in.
278
+ return false ;
279
+ }
280
+
281
+ /**
282
+ * Invoked by Android when a hover-compliant input system causes a hover event.
283
+ *
284
+ * An example of hover events is a stylus sitting near an Android screen. As the
285
+ * stylus moves from outside a {@code View} to hover over a {@code View}, or move
286
+ * around within a {@code View}, or moves from over a {@code View} to outside a
287
+ * {@code View}, a corresponding {@link MotionEvent} is reported via this method.
288
+ *
289
+ * Hover events can be used for accessibility touch exploration and therefore are
290
+ * processed here for accessibility purposes.
291
+ */
292
+ @ Override
293
+ public boolean onHoverEvent (MotionEvent event ) {
294
+ if (!isAttachedToFlutterEngine ()) {
295
+ return false ;
296
+ }
297
+
298
+ // TODO(mattcarroll): hook up to accessibility.
299
+ return false ;
300
+ }
301
+ //-------- End: Process UI I/O that Flutter cares about. ---------
302
+
106
303
/**
107
304
* Connects this {@code FlutterView} to the given {@link FlutterEngine}.
108
305
*
@@ -129,6 +326,26 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
129
326
130
327
// Instruct our FlutterRenderer that we are now its designated RenderSurface.
131
328
this .flutterEngine .getRenderer ().attachToRenderSurface (renderSurface );
329
+
330
+ // Initialize various components that know how to process Android View I/O
331
+ // in a way that Flutter understands.
332
+ textInputPlugin = new TextInputPlugin (
333
+ this ,
334
+ this .flutterEngine .getDartExecutor ()
335
+ );
336
+ androidKeyProcessor = new AndroidKeyProcessor (
337
+ this .flutterEngine .getKeyEventChannel (),
338
+ textInputPlugin
339
+ );
340
+
341
+ // Inform the Android framework that it should retrieve a new InputConnection
342
+ // now that an engine is attached.
343
+ // TODO(mattcarroll): once this is proven to work, move this line ot TextInputPlugin
344
+ textInputPlugin .getInputMethodManager ().restartInput (this );
345
+
346
+ // Push View and Context related information from Android to Flutter.
347
+ sendUserSettingsToFlutter ();
348
+ sendLocalesToFlutter (getResources ().getConfiguration ());
132
349
}
133
350
134
351
/**
@@ -147,6 +364,12 @@ public void detachFromFlutterEngine() {
147
364
}
148
365
Log .d (TAG , "Detaching from Flutter Engine" );
149
366
367
+ // Inform the Android framework that it should retrieve a new InputConnection
368
+ // now that the engine is detached. The new InputConnection will be null, which
369
+ // signifies that this View does not process input (until a new engine is attached).
370
+ // TODO(mattcarroll): once this is proven to work, move this line ot TextInputPlugin
371
+ textInputPlugin .getInputMethodManager ().restartInput (this );
372
+
150
373
// Instruct our FlutterRenderer that we are no longer interested in being its RenderSurface.
151
374
flutterEngine .getRenderer ().detachFromRenderSurface ();
152
375
flutterEngine = null ;
@@ -163,6 +386,42 @@ private boolean isAttachedToFlutterEngine() {
163
386
return flutterEngine != null ;
164
387
}
165
388
389
+ /**
390
+ * Send the current {@link Locale} configuration to Flutter.
391
+ *
392
+ * FlutterEngine must be non-null when this method is invoked.
393
+ */
394
+ @ SuppressWarnings ("deprecation" )
395
+ private void sendLocalesToFlutter (Configuration config ) {
396
+ List <Locale > locales = new ArrayList <>();
397
+ if (Build .VERSION .SDK_INT >= android .os .Build .VERSION_CODES .N ) {
398
+ LocaleList localeList = config .getLocales ();
399
+ int localeCount = localeList .size ();
400
+ for (int index = 0 ; index < localeCount ; ++index ) {
401
+ Locale locale = localeList .get (index );
402
+ locales .add (locale );
403
+ }
404
+ } else {
405
+ locales .add (config .locale );
406
+ }
407
+ flutterEngine .getLocalizationChannel ().sendLocales (locales );
408
+ }
409
+
410
+ /**
411
+ * Send various user preferences of this Android device to Flutter.
412
+ *
413
+ * For example, sends the user's "text scale factor" preferences, as well as the user's clock
414
+ * format preference.
415
+ *
416
+ * FlutterEngine must be non-null when this method is invoked.
417
+ */
418
+ private void sendUserSettingsToFlutter () {
419
+ flutterEngine .getSettingsChannel ().startMessage ()
420
+ .setTextScaleFactor (getResources ().getConfiguration ().fontScale )
421
+ .setUse24HourFormat (DateFormat .is24HourFormat (getContext ()))
422
+ .send ();
423
+ }
424
+
166
425
/**
167
426
* Render modes for a {@link FlutterView}.
168
427
*/
0 commit comments