@@ -74,6 +74,54 @@ public void destroyTest() {
7474 .setEventResponseHandler (isNull (KeyEventChannel .EventResponseHandler .class ));
7575 }
7676
77+ public void removesPendingEventsWhenKeyDownHandled () {
78+ FlutterEngine flutterEngine = mockFlutterEngine ();
79+ KeyEventChannel fakeKeyEventChannel = flutterEngine .getKeyEventChannel ();
80+ View fakeView = mock (View .class );
81+ View fakeRootView = mock (View .class );
82+ when (fakeView .getRootView ())
83+ .then (
84+ new Answer <View >() {
85+ @ Override
86+ public View answer (InvocationOnMock invocation ) throws Throwable {
87+ return fakeRootView ;
88+ }
89+ });
90+
91+ ArgumentCaptor <KeyEventChannel .EventResponseHandler > handlerCaptor =
92+ ArgumentCaptor .forClass (KeyEventChannel .EventResponseHandler .class );
93+ verify (fakeKeyEventChannel ).setEventResponseHandler (handlerCaptor .capture ());
94+ AndroidKeyProcessor processor =
95+ new AndroidKeyProcessor (fakeView , fakeKeyEventChannel , mock (TextInputPlugin .class ));
96+ ArgumentCaptor <KeyEventChannel .FlutterKeyEvent > eventCaptor =
97+ ArgumentCaptor .forClass (KeyEventChannel .FlutterKeyEvent .class );
98+ FakeKeyEvent fakeKeyEvent = new FakeKeyEvent (KeyEvent .ACTION_DOWN , 65 );
99+
100+ boolean result = processor .onKeyEvent (fakeKeyEvent );
101+ assertEquals (true , processor .isPendingEvent (fakeKeyEvent ));
102+ assertEquals (true , result );
103+
104+ // Capture the FlutterKeyEvent so we can find out its event ID to use when
105+ // faking our response.
106+ verify (fakeKeyEventChannel , times (1 )).keyDown (eventCaptor .capture ());
107+ boolean [] dispatchResult = {true };
108+ when (fakeView .dispatchKeyEvent (any (KeyEvent .class )))
109+ .then (
110+ new Answer <Boolean >() {
111+ @ Override
112+ public Boolean answer (InvocationOnMock invocation ) throws Throwable {
113+ KeyEvent event = (KeyEvent ) invocation .getArguments ()[0 ];
114+ assertEquals (fakeKeyEvent , event );
115+ dispatchResult [0 ] = processor .onKeyEvent (event );
116+ return dispatchResult [0 ];
117+ }
118+ });
119+
120+ // Fake a response from the framework.
121+ handlerCaptor .getValue ().onKeyEventHandled (eventCaptor .getValue ().event );
122+ assertEquals (false , processor .isPendingEvent (fakeKeyEvent ));
123+ }
124+
77125 public void synthesizesEventsWhenKeyDownNotHandled () {
78126 FlutterEngine flutterEngine = mockFlutterEngine ();
79127 KeyEventChannel fakeKeyEventChannel = flutterEngine .getKeyEventChannel ();
@@ -98,6 +146,7 @@ public View answer(InvocationOnMock invocation) throws Throwable {
98146 FakeKeyEvent fakeKeyEvent = new FakeKeyEvent (KeyEvent .ACTION_DOWN , 65 );
99147
100148 boolean result = processor .onKeyEvent (fakeKeyEvent );
149+ assertEquals (true , processor .isPendingEvent (fakeKeyEvent ));
101150 assertEquals (true , result );
102151
103152 // Capture the FlutterKeyEvent so we can find out its event ID to use when
@@ -118,6 +167,7 @@ public Boolean answer(InvocationOnMock invocation) throws Throwable {
118167
119168 // Fake a response from the framework.
120169 handlerCaptor .getValue ().onKeyEventNotHandled (eventCaptor .getValue ().event );
170+ assertEquals (true , processor .isPendingEvent (fakeKeyEvent ));
121171 verify (fakeView , times (1 )).dispatchKeyEvent (fakeKeyEvent );
122172 assertEquals (false , dispatchResult [0 ]);
123173 verify (fakeKeyEventChannel , times (0 )).keyUp (any (KeyEventChannel .FlutterKeyEvent .class ));
@@ -148,6 +198,7 @@ public View answer(InvocationOnMock invocation) throws Throwable {
148198 FakeKeyEvent fakeKeyEvent = new FakeKeyEvent (KeyEvent .ACTION_UP , 65 );
149199
150200 boolean result = processor .onKeyEvent (fakeKeyEvent );
201+ assertEquals (true , processor .isPendingEvent (fakeKeyEvent ));
151202 assertEquals (true , result );
152203
153204 // Capture the FlutterKeyEvent so we can find out its event ID to use when
@@ -168,12 +219,84 @@ public Boolean answer(InvocationOnMock invocation) throws Throwable {
168219
169220 // Fake a response from the framework.
170221 handlerCaptor .getValue ().onKeyEventNotHandled (eventCaptor .getValue ().event );
222+ assertEquals (true , processor .isPendingEvent (fakeKeyEvent ));
171223 verify (fakeView , times (1 )).dispatchKeyEvent (fakeKeyEvent );
172224 assertEquals (false , dispatchResult [0 ]);
173225 verify (fakeKeyEventChannel , times (0 )).keyUp (any (KeyEventChannel .FlutterKeyEvent .class ));
174226 verify (fakeRootView , times (1 )).dispatchKeyEvent (fakeKeyEvent );
175227 }
176228
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+
177300 @ NonNull
178301 private FlutterEngine mockFlutterEngine () {
179302 // Mock FlutterEngine and all of its required direct calls.
0 commit comments