Skip to content

Commit

Permalink
Recommended implementation of combining characters implementation. (f…
Browse files Browse the repository at this point in the history
  • Loading branch information
matthew-carroll committed Feb 11, 2019
1 parent d521212 commit 4663d35
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 11 deletions.
1 change: 1 addition & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterFragmentActi
FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterPluginRegistry.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/android/AndroidKeyProcessor.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/dart/PlatformMessageHandler.java
Expand Down
1 change: 1 addition & 0 deletions shell/platform/android/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ java_library("flutter_shell_java") {
"io/flutter/app/FlutterPluginRegistry.java",
"io/flutter/embedding/engine/FlutterEngine.java",
"io/flutter/embedding/engine/FlutterJNI.java",
"io/flutter/embedding/engine/android/AndroidKeyProcessor.java",
"io/flutter/embedding/engine/dart/DartExecutor.java",
"io/flutter/embedding/engine/dart/DartMessenger.java",
"io/flutter/embedding/engine/dart/PlatformMessageHandler.java",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package io.flutter.embedding.engine.android;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;

import io.flutter.embedding.engine.systemchannels.KeyEventChannel;

public class AndroidKeyProcessor {
private final KeyEventChannel keyEventChannel;
private int combiningCharacter;

public AndroidKeyProcessor(@NonNull KeyEventChannel keyEventChannel) {
this.keyEventChannel = keyEventChannel;
}

public void onKeyUp(@NonNull KeyEvent keyEvent) {
Character complexCharacter = applyCombiningCharacterToBaseCharacter(keyEvent.getUnicodeChar());
keyEventChannel.keyUp(
new KeyEventChannel.FlutterKeyEvent(keyEvent, complexCharacter)
);
}

public void onKeyDown(@NonNull KeyEvent keyEvent) {
Character complexCharacter = applyCombiningCharacterToBaseCharacter(keyEvent.getUnicodeChar());
keyEventChannel.keyDown(
new KeyEventChannel.FlutterKeyEvent(keyEvent, complexCharacter)
);
}

/**
* Applies the given Unicode character in {@code newCharacterCodePoint} to a previously
* entered Unicode combining character and returns the combination of these characters
* if a combination exists.
* <p>
* This method mutates {@link #combiningCharacter} over time to combine characters.
* <p>
* One of the following things happens in this method:
* <ul>
* <li>If no previous {@link #combiningCharacter} exists and the {@code newCharacterCodePoint}
* is not a combining character, then {@code newCharacterCodePoint} is returned.</li>
* <li>If no previous {@link #combiningCharacter} exists and the {@code newCharacterCodePoint}
* is a combining character, then {@code newCharacterCodePoint} is saved as the
* {@link #combiningCharacter} and null is returned.</li>
* <li>If a previous {@link #combiningCharacter} exists and the {@code newCharacterCodePoint}
* is also a combining character, then the {@code newCharacterCodePoint} is combined with
* the existing {@link #combiningCharacter} and null is returned.</li>
* <li>If a previous {@link #combiningCharacter} exists and the {@code newCharacterCodePoint}
* is not a combining character, then the {@link #combiningCharacter} is applied to the
* regular {@code newCharacterCodePoint} and the resulting complex character is returned. The
* {@link #combiningCharacter} is cleared.</li>
* </ul>
* <p>
* The following reference explains the concept of a "combining character":
* https://en.wikipedia.org/wiki/Combining_character
*/
@Nullable
private Character applyCombiningCharacterToBaseCharacter(int newCharacterCodePoint) {
if (newCharacterCodePoint == 0) {
return null;
}

Character complexCharacter = (char) newCharacterCodePoint;
boolean isNewCodePointACombiningCharacter = (newCharacterCodePoint & KeyCharacterMap.COMBINING_ACCENT) != 0;
if (isNewCodePointACombiningCharacter) {
// If a combining character was entered before, combine this one with that one.
int plainCodePoint = newCharacterCodePoint & KeyCharacterMap.COMBINING_ACCENT_MASK;
if (combiningCharacter != 0) {
combiningCharacter = KeyCharacterMap.getDeadChar(combiningCharacter, plainCodePoint);
} else {
combiningCharacter = plainCodePoint;
}
} else {
// The new character is a regular character. Apply combiningCharacter to it, if it exists.
if (combiningCharacter != 0) {
int combinedChar = KeyCharacterMap.getDeadChar(combiningCharacter, newCharacterCodePoint);
if (combinedChar > 0) {
complexCharacter = (char) combinedChar;
}
combiningCharacter = 0;
}
}

return complexCharacter;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package io.flutter.embedding.engine.systemchannels;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.KeyEvent;

import java.util.HashMap;
Expand All @@ -26,7 +27,7 @@ public KeyEventChannel(@NonNull DartExecutor dartExecutor) {
this.channel = new BasicMessageChannel<>(dartExecutor, "flutter/keyevent", JSONMessageCodec.INSTANCE);
}

public void keyUp(@NonNull KeyEvent keyEvent) {
public void keyUp(@NonNull FlutterKeyEvent keyEvent) {
Map<String, Object> message = new HashMap<>();
message.put("type", "keyup");
message.put("keymap", "android");
Expand All @@ -35,7 +36,7 @@ public void keyUp(@NonNull KeyEvent keyEvent) {
channel.send(message);
}

public void keyDown(@NonNull KeyEvent keyEvent) {
public void keyDown(@NonNull FlutterKeyEvent keyEvent) {
Map<String, Object> message = new HashMap<>();
message.put("type", "keydown");
message.put("keymap", "android");
Expand All @@ -44,11 +45,68 @@ public void keyDown(@NonNull KeyEvent keyEvent) {
channel.send(message);
}

private void encodeKeyEvent(@NonNull KeyEvent event, @NonNull Map<String, Object> message) {
message.put("flags", event.getFlags());
message.put("codePoint", event.getUnicodeChar());
message.put("keyCode", event.getKeyCode());
message.put("scanCode", event.getScanCode());
message.put("metaState", event.getMetaState());
private void encodeKeyEvent(@NonNull FlutterKeyEvent event, @NonNull Map<String, Object> message) {
message.put("flags", event.flags);
message.put("plainCodePoint", event.plainCodePoint);
message.put("codePoint", event.codePoint);
message.put("keyCode", event.keyCode);
message.put("scanCode", event.scanCode);
message.put("metaState", event.metaState);
if (event.complexCharacter != null) {
message.put("character", event.complexCharacter.toString());
}
}

/**
* Key event as defined by Flutter.
*/
public static class FlutterKeyEvent {
public final int flags;
public final int plainCodePoint;
public final int codePoint;
public final int keyCode;
@Nullable
public final Character complexCharacter;
public final int scanCode;
public final int metaState;

public FlutterKeyEvent(
@NonNull KeyEvent androidKeyEvent
) {
this(androidKeyEvent, null);
}

public FlutterKeyEvent(
@NonNull KeyEvent androidKeyEvent,
@Nullable Character complexCharacter
) {
this(
androidKeyEvent.getFlags(),
androidKeyEvent.getUnicodeChar(0x0),
androidKeyEvent.getUnicodeChar(),
androidKeyEvent.getKeyCode(),
complexCharacter,
androidKeyEvent.getScanCode(),
androidKeyEvent.getMetaState()
);
}

public FlutterKeyEvent(
int flags,
int plainCodePoint,
int codePoint,
int keyCode,
@Nullable Character complexCharacter,
int scanCode,
int metaState
) {
this.flags = flags;
this.plainCodePoint = plainCodePoint;
this.codePoint = codePoint;
this.keyCode = keyCode;
this.complexCharacter = complexCharacter;
this.scanCode = scanCode;
this.metaState = metaState;
}
}
}
8 changes: 5 additions & 3 deletions shell/platform/android/io/flutter/view/FlutterView.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import io.flutter.app.FlutterPluginRegistry;
import io.flutter.embedding.engine.android.AndroidKeyProcessor;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.systemchannels.KeyEventChannel;
import io.flutter.embedding.engine.systemchannels.LifecycleChannel;
Expand Down Expand Up @@ -93,6 +94,7 @@ static final class ViewportMetrics {
private final SystemChannel systemChannel;
private final InputMethodManager mImm;
private final TextInputPlugin mTextInputPlugin;
private final AndroidKeyProcessor androidKeyProcessor;
private final SurfaceHolder.Callback mSurfaceCallback;
private final ViewportMetrics mMetrics;
private final AccessibilityManager mAccessibilityManager;
Expand Down Expand Up @@ -172,7 +174,7 @@ public void surfaceDestroyed(SurfaceHolder holder) {
addActivityLifecycleListener(platformPlugin);
mImm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
mTextInputPlugin = new TextInputPlugin(this);

androidKeyProcessor = new AndroidKeyProcessor(keyEventChannel);

setLocales(getResources().getConfiguration());
sendUserPlatformSettingsToDart();
Expand All @@ -183,7 +185,7 @@ public boolean onKeyUp(int keyCode, KeyEvent event) {
if (!isAttached()) {
return super.onKeyUp(keyCode, event);
}
keyEventChannel.keyUp(event);
androidKeyProcessor.onKeyUp(event);
return super.onKeyUp(keyCode, event);
}

Expand All @@ -199,7 +201,7 @@ public boolean onKeyDown(int keyCode, KeyEvent event) {
}
}

keyEventChannel.keyDown(event);
androidKeyProcessor.onKeyDown(event);
return super.onKeyDown(keyCode, event);
}

Expand Down

0 comments on commit 4663d35

Please sign in to comment.