@@ -226,6 +226,77 @@ public Boolean answer(InvocationOnMock invocation) throws Throwable {
226226 verify (fakeRootView , times (1 )).dispatchKeyEvent (fakeKeyEvent );
227227 }
228228
229+ public void respondsCorrectlyWhenEventsAreReturnedOutOfOrder () {
230+ FlutterEngine flutterEngine = mockFlutterEngine ();
231+ KeyEventChannel fakeKeyEventChannel = flutterEngine .getKeyEventChannel ();
232+ View fakeView = mock (View .class );
233+ View fakeRootView = mock (View .class );
234+ when (fakeView .getRootView ())
235+ .then (
236+ new Answer <View >() {
237+ @ Override
238+ public View answer (InvocationOnMock invocation ) throws Throwable {
239+ return fakeRootView ;
240+ }
241+ });
242+
243+ ArgumentCaptor <KeyEventChannel .EventResponseHandler > handlerCaptor =
244+ ArgumentCaptor .forClass (KeyEventChannel .EventResponseHandler .class );
245+ verify (fakeKeyEventChannel ).setEventResponseHandler (handlerCaptor .capture ());
246+ AndroidKeyProcessor processor =
247+ new AndroidKeyProcessor (fakeView , fakeKeyEventChannel , mock (TextInputPlugin .class ));
248+ ArgumentCaptor <KeyEventChannel .FlutterKeyEvent > event1Captor =
249+ ArgumentCaptor .forClass (KeyEventChannel .FlutterKeyEvent .class );
250+ ArgumentCaptor <KeyEventChannel .FlutterKeyEvent > event2Captor =
251+ ArgumentCaptor .forClass (KeyEventChannel .FlutterKeyEvent .class );
252+ FakeKeyEvent fakeKeyEvent1 = new FakeKeyEvent (KeyEvent .ACTION_DOWN , 65 );
253+ FakeKeyEvent fakeKeyEvent2 = new FakeKeyEvent (KeyEvent .ACTION_DOWN , 20 );
254+
255+ boolean result1 = processor .onKeyEvent (fakeKeyEvent1 );
256+ boolean result2 = processor .onKeyEvent (fakeKeyEvent2 );
257+ assertEquals (true , processor .isPendingEvent (fakeKeyEvent1 ));
258+ assertEquals (true , processor .isPendingEvent (fakeKeyEvent2 ));
259+ assertEquals (true , result1 );
260+ assertEquals (true , result2 );
261+
262+ // Capture the FlutterKeyEvent so we can find out its event ID to use when
263+ // faking our response.
264+ verify (fakeKeyEventChannel , times (1 )).keyDown (event1Captor .capture ());
265+ verify (fakeKeyEventChannel , times (1 )).keyDown (event2Captor .capture ());
266+ boolean [] dispatchResult = {true , true };
267+ when (fakeView .dispatchKeyEvent (any (KeyEvent .class )))
268+ .then (
269+ new Answer <Boolean >() {
270+ @ Override
271+ public Boolean answer (InvocationOnMock invocation ) throws Throwable {
272+ KeyEvent event = (KeyEvent ) invocation .getArguments ()[0 ];
273+ assertEquals (true , fakeKeyEvent1 == event || fakeKeyEvent2 == event );
274+ if (fakeKeyEvent1 == event ) {
275+ dispatchResult [0 ] = processor .onKeyEvent (fakeKeyEvent1 );
276+ return dispatchResult [0 ];
277+ } else {
278+ dispatchResult [1 ] = processor .onKeyEvent (fakeKeyEvent2 );
279+ return dispatchResult [1 ];
280+ }
281+ }
282+ });
283+
284+ assertEquals (true , processor .isPendingEvent (fakeKeyEvent1 ));
285+ assertEquals (true , processor .isPendingEvent (fakeKeyEvent2 ));
286+
287+ // Fake a "handled" response from the framework, but do it in reverse order.
288+ handlerCaptor .getValue ().onKeyEventNotHandled (event2Captor .getValue ().event );
289+ handlerCaptor .getValue ().onKeyEventNotHandled (event1Captor .getValue ().event );
290+
291+ verify (fakeView , times (1 )).dispatchKeyEvent (fakeKeyEvent1 );
292+ verify (fakeView , times (1 )).dispatchKeyEvent (fakeKeyEvent2 );
293+ assertEquals (false , dispatchResult [0 ]);
294+ assertEquals (false , dispatchResult [1 ]);
295+ verify (fakeKeyEventChannel , times (0 )).keyUp (any (KeyEventChannel .FlutterKeyEvent .class ));
296+ verify (fakeRootView , times (1 )).dispatchKeyEvent (fakeKeyEvent1 );
297+ verify (fakeRootView , times (1 )).dispatchKeyEvent (fakeKeyEvent2 );
298+ }
299+
229300 @ NonNull
230301 private FlutterEngine mockFlutterEngine () {
231302 // Mock FlutterEngine and all of its required direct calls.
0 commit comments