-
Notifications
You must be signed in to change notification settings - Fork 6k
[Android KeyEvents] Split AndroidKeyProcessor into separate classes #25628
[Android KeyEvents] Split AndroidKeyProcessor into separate classes #25628
Conversation
64fae02 to
d52523d
Compare
| @NonNull private final KeyEventChannel keyEventChannel; | ||
| @NonNull private final TextInputPlugin textInputPlugin; | ||
| /** A class for handling combining characters in a {@link KeyEvent} stream. */ | ||
| class AndroidKeyProcessor { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might need a name change. This class now only handles dead keys, and only has an applyCombiningCharacterToBaseCharacter method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we eliminate this class/file, and just incorporate the functionality in one of the other classes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to @dkwingsmt we're going to add a different type of primary responder so I thought we might want to keep this around in case it's useful in the other responder as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Greg is right: merge the logic of this class into the channel responder. The embedder responder will use a completely separate logic. This make it easier to design embedder responder in the future. If shared logic is useful we will write them again.
| */ | ||
| @Nullable | ||
| private Character applyCombiningCharacterToBaseCharacter(int newCharacterCodePoint) { | ||
| if (newCharacterCodePoint == 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason that we treated 0 differently and returned null specifically in this case (and didn't clear the combining key).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If so, I don't remember it. Maybe zero is a special code point that indicates some change in state, or informational event that should be ignored? It could also be that zero indicates a non-printable character like shift or meta, in which case we might ignore it since it might allow for combining with an uppercase character or something. Anyhow, seems like we should look into why we might get a zero here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the original call site:
Character complexCharacter = applyCombiningCharacterToBaseCharacter(keyEvent.getUnicodeChar());
It looks like the method takes an unicode code point as input, and 0 is occupied in unicode: https://codepoints.net/U+0000, wikipedia says it's just the string terminator \0 in C: https://en.wikipedia.org/wiki/Null_character so it probably doesn't have another special meaning assigned to it?
| */ | ||
| @Nullable | ||
| private Character applyCombiningCharacterToBaseCharacter(int newCharacterCodePoint) { | ||
| if (newCharacterCodePoint == 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If so, I don't remember it. Maybe zero is a special code point that indicates some change in state, or informational event that should be ignored? It could also be that zero indicates a non-printable character like shift or meta, in which case we might ignore it since it might allow for combining with an uppercase character or something. Anyhow, seems like we should look into why we might get a zero here.
| @NonNull private final KeyEventChannel keyEventChannel; | ||
| @NonNull private final TextInputPlugin textInputPlugin; | ||
| /** A class for handling combining characters in a {@link KeyEvent} stream. */ | ||
| class AndroidKeyProcessor { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we eliminate this class/file, and just incorporate the functionality in one of the other classes?
| public boolean handleEvent(@NonNull KeyEvent keyEvent) { | ||
| final boolean isRedispatchedEvent = redispatchedEvents.remove(keyEvent); | ||
| if (isRedispatchedEvent) { | ||
| return !isRedispatchedEvent; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could just return false here.
| @NonNull private final KeyEventChannel keyEventChannel; | ||
| @NonNull private final TextInputPlugin textInputPlugin; | ||
| /** A class for handling combining characters in a {@link KeyEvent} stream. */ | ||
| class AndroidKeyProcessor { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Greg is right: merge the logic of this class into the channel responder. The embedder responder will use a completely separate logic. This make it easier to design embedder responder in the future. If shared logic is useful we will write them again.
shell/platform/android/io/flutter/embedding/android/KeyboardManager.java
Show resolved
Hide resolved
| import androidx.annotation.NonNull; | ||
| import io.flutter.embedding.engine.systemchannels.KeyEventChannel; | ||
|
|
||
| /** A light wrapper around a {@link KeyEventChannel}, turning it into a {@link PrimaryResponder}. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This documentation is very underwhelming. Consider using the one from macOS (as below) or other platforms.
/**
* A primary responder of |FlutterKeyboardManager| that handles events by
* sending the raw information through the method channel.
*
* This class corresponds to the RawKeyboard API in the framework.
*/
| * re-dispatches those not handled by the primary responders. | ||
| * | ||
| * <p>Flutter uses asynchronous event handling to avoid blocking the UI thread, but Android requires | ||
| * that events are handled synchronously. So, when a key event is received by Flutter, it tells |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| * that events are handled synchronously. So, when a key event is received by Flutter, it tells | |
| * that events are handled synchronously. So, when a key event (that is not recognized as redispatched) is received by Flutter, it tells |
| * synthesizes a new event to send to Android, without handling it this time. | ||
| * | ||
| * <p>A new {@link KeyEvent} sent to a {@link KeyboardManager} can be propagated to 3 different | ||
| * types of "responder"s (in the listed order): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid using this term as anything but the class. Maybe:
| * types of "responder"s (in the listed order): | |
| * types of destinations (in the listed order): |
There are other similar references in other documentations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm I still prefer "responders" here. I think it's a more established term. "destinations" or "handlers" doesn't sound as good to me since "responders" also conveys that it responds (whether the event is handled), in addition to handling the key event.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's pretty confusing since it's the name for a class. It might not be that expressive either, since technically redispatching to the system does not really respond anything. What about "A new even sent to a KeyboardManager have 3 candidate processors:"?
| * | ||
| * <p>If a {@link PrimaryResponder} fails to call the {@link OnKeyEventHandledCallback} callback, | ||
| * the {@link KeyEvent} will never be sent to the {@link TextInputPlugin}, and the {@link | ||
| * KeyboardManager} class can't detect such errors as there is no timeout. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| * | |
| * <p>If a {@link PrimaryResponder} fails to call the {@link OnKeyEventHandledCallback} callback, | |
| * the {@link KeyEvent} will never be sent to the {@link TextInputPlugin}, and the {@link | |
| * KeyboardManager} class can't detect such errors as there is no timeout. |
We've said "must".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is mostly for debugging. Knowing the symptoms would probably help people with debugging, since there's no good way to catch the programmer error in our implementation.
shell/platform/android/io/flutter/embedding/android/KeyboardManager.java
Show resolved
Hide resolved
| onUnhandled(keyEvent); | ||
| } | ||
|
|
||
| return !isRedispatchedEvent; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would always be true.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't resolved yet.
| /** | ||
| * Called whenever the framework responds that a given key event was handled by the framework. | ||
| * | ||
| * @param event the event to be marked as being handled by the framework. Must not be null. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you delete the wrong doc?
|
|
||
| public void setKeyEventProcessor(AndroidKeyProcessor processor) { | ||
| keyProcessor = processor; | ||
| public void setKeyboardManager(KeyboardManager processor) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels so wrong. If I understand coorectly, depending on the Android engine we're using, we either have
KeyboardManager->TextInputPlugin->InputConnectionAdapter; orTextInputPlugin->InputConnectionAdapter->KeyboardManager.
This makes the entire logic entangled, and I'll guess is the reason why there is aninstanceofinhandleKeyEvent.
Can we somehow resolve this, either by merging some logic or splitting classes? I'm not familiar with the system but I'm glad to discuss if it helps.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This happens because KeyboardManger does not own TextInputPlugin, or vice versa, but they depend on each other. KeyboardManager clearly shouldn't own TextInputPlugin. TextInputPlugin can probably own KeyboardManager but I'm not sure we want to do that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The instanceof exists because the InputConnection protocol by itself can not distinguish a redispatched event from regular key events. An alternative to this is to pass the keyboard manager to InputConnectionAdater and override its sendKeyEvent and let the keyboard manager shortcircuit redispatched events (this is what the previous implementation does).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On other platforms, KeyboardManger owns TextInputPlugin. Is there any problem specific for Android?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically on other platforms, KeyboardManger owns 3 things:
KeyboardManger -> responders
-> textInputPlugin
-> redispatchEvent // A callback
which correspond to the 3 "responders" in the comment here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looked deeper into this, it looks like the text input plugin only uses the keyboard manager to create new input connections which need the keyboard manager to send IME key events to. It makes more sense to change the createInputConnection method to take a keyboard manager.
41d403b to
d96fcb0
Compare
d96fcb0 to
001d9b7
Compare
|
@LongCatIsLooong Is this good for re-review now? @dkwingsmt Another review pass please? |
|
@dkwingsmt Another review please? |
dkwingsmt
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
| onUnhandled(keyEvent); | ||
| } | ||
|
|
||
| return !isRedispatchedEvent; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't resolved yet.
|
The presubmit failures are related to the patch as well and need to be patched. |
|
@LongCatIsLooong Any updates on patching the presubmits? |
|
Closing as stale. Please re-open as necessary. |
|
Sorry for the inactivity. Updated the PR so |
350bbce to
62ed56b
Compare
dd29487 to
f8360ed
Compare
f8360ed to
e98ade9
Compare
flutter/flutter#44918
Pre-launch Checklist
writing and running engine tests.
///).If you need help, consider asking for advice on the #hackers-new channel on Discord.