55package io .flutter .embedding .engine .android ;
66
77import android .content .Context ;
8+ import android .content .res .Configuration ;
9+ import android .graphics .Rect ;
10+ import android .os .Build ;
11+ import android .os .LocaleList ;
812import android .support .annotation .NonNull ;
913import android .support .annotation .Nullable ;
14+ import android .text .format .DateFormat ;
1015import android .util .AttributeSet ;
1116import 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 ;
1224import android .widget .FrameLayout ;
1325
26+ import java .util .ArrayList ;
27+ import java .util .List ;
28+ import java .util .Locale ;
29+
1430import io .flutter .embedding .engine .FlutterEngine ;
1531import io .flutter .embedding .engine .renderer .FlutterRenderer ;
32+ import io .flutter .plugin .editing .TextInputPlugin ;
1633
1734/**
1835 * Displays a Flutter UI on an Android device.
@@ -50,6 +67,16 @@ public class FlutterView extends FrameLayout {
5067 @ Nullable
5168 private FlutterEngine flutterEngine ;
5269
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+
5380 /**
5481 * Constructs a {@code FlutterSurfaceView} programmatically, without any XML attributes.
5582 *
@@ -103,6 +130,176 @@ private void init() {
103130 }
104131 }
105132
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+
106303 /**
107304 * Connects this {@code FlutterView} to the given {@link FlutterEngine}.
108305 *
@@ -129,6 +326,26 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
129326
130327 // Instruct our FlutterRenderer that we are now its designated RenderSurface.
131328 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 ());
132349 }
133350
134351 /**
@@ -147,6 +364,12 @@ public void detachFromFlutterEngine() {
147364 }
148365 Log .d (TAG , "Detaching from Flutter Engine" );
149366
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+
150373 // Instruct our FlutterRenderer that we are no longer interested in being its RenderSurface.
151374 flutterEngine .getRenderer ().detachFromRenderSurface ();
152375 flutterEngine = null ;
@@ -163,6 +386,42 @@ private boolean isAttachedToFlutterEngine() {
163386 return flutterEngine != null ;
164387 }
165388
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+
166425 /**
167426 * Render modes for a {@link FlutterView}.
168427 */
0 commit comments