Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Conversation

@LongCatIsLooong
Copy link
Contributor

flutter/flutter#44918

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on
    writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.
  • The reviewer has submitted any presubmit flakes in this PR using the engine presubmit flakes form before re-triggering the failure.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@NonNull private final KeyEventChannel keyEventChannel;
@NonNull private final TextInputPlugin textInputPlugin;
/** A class for handling combining characters in a {@link KeyEvent} stream. */
class AndroidKeyProcessor {
Copy link
Contributor Author

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.

Copy link
Contributor

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?

Copy link
Contributor Author

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?

Copy link
Contributor

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) {
Copy link
Contributor Author

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).

Copy link
Contributor

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.

Copy link
Contributor Author

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) {
Copy link
Contributor

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 {
Copy link
Contributor

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;
Copy link
Contributor

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 {
Copy link
Contributor

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.

import androidx.annotation.NonNull;
import io.flutter.embedding.engine.systemchannels.KeyEventChannel;

/** A light wrapper around a {@link KeyEventChannel}, turning it into a {@link PrimaryResponder}. */
Copy link
Contributor

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* 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):
Copy link
Contributor

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:

Suggested change
* types of "responder"s (in the listed order):
* types of destinations (in the listed order):

There are other similar references in other documentations.

Copy link
Contributor Author

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.

Copy link
Contributor

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:"?

Comment on lines 106 to 109
*
* <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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
*
* <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".

Copy link
Contributor Author

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.

onUnhandled(keyEvent);
}

return !isRedispatchedEvent;
Copy link
Contributor

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.

Copy link
Contributor

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.
Copy link
Contributor

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) {
Copy link
Contributor

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; or
  • TextInputPlugin -> InputConnectionAdapter -> KeyboardManager.
    This makes the entire logic entangled, and I'll guess is the reason why there is an instanceof in handleKeyEvent.

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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).

Copy link
Contributor

@dkwingsmt dkwingsmt Apr 29, 2021

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?

Copy link
Contributor

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.

Copy link
Contributor Author

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.

@chinmaygarde
Copy link
Member

@LongCatIsLooong Is this good for re-review now? @dkwingsmt Another review pass please?

@chinmaygarde
Copy link
Member

@dkwingsmt Another review please?

Copy link
Contributor

@dkwingsmt dkwingsmt left a 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;
Copy link
Contributor

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.

@chinmaygarde
Copy link
Member

The presubmit failures are related to the patch as well and need to be patched.

@chinmaygarde
Copy link
Member

@LongCatIsLooong Any updates on patching the presubmits?

@chinmaygarde
Copy link
Member

Closing as stale. Please re-open as necessary.

@LongCatIsLooong
Copy link
Contributor Author

LongCatIsLooong commented Jun 4, 2021

Sorry for the inactivity. Updated the PR so TextInputPlugin no longer directly keeps a KeyboardManager instance, instead its createInputConnection method takes an additional KeyboardManager argument.

@LongCatIsLooong LongCatIsLooong force-pushed the android-platform-keys branch 4 times, most recently from dd29487 to f8360ed Compare June 4, 2021 21:42
@LongCatIsLooong LongCatIsLooong force-pushed the android-platform-keys branch from f8360ed to e98ade9 Compare June 4, 2021 21:56
@LongCatIsLooong LongCatIsLooong added the waiting for tree to go green This PR is approved and tested, but waiting for the tree to be green to land. label Jun 5, 2021
@fluttergithubbot fluttergithubbot merged commit a882102 into flutter:master Jun 5, 2021
@LongCatIsLooong LongCatIsLooong deleted the android-platform-keys branch June 5, 2021 02:01
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 6, 2021
@dkwingsmt dkwingsmt mentioned this pull request Jul 28, 2021
13 tasks
naudzghebre pushed a commit to naudzghebre/engine that referenced this pull request Sep 2, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

cla: yes platform-android waiting for tree to go green This PR is approved and tested, but waiting for the tree to be green to land.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants