Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reland "Hardware Keyboard: Android" #33567

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -1367,7 +1367,10 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/Flutt
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterTextureView.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterView.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/KeyChannelResponder.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/KeyData.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/KeyEmbedderResponder.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/KeyboardManager.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/KeyboardMap.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/MotionEventTracker.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/RenderMode.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/SplashScreen.java
Expand Down
2 changes: 1 addition & 1 deletion lib/ui/key.dart
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ class KeyData {

String? _escapeCharacter() {
if (character == null) {
return character ?? '<none>';
return '<none>';
}
switch (character!) {
case '\n':
Expand Down
3 changes: 3 additions & 0 deletions lib/ui/window/key_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ enum class KeyEventType : int64_t {
// a different way in KeyDataPacket.
//
// This structure is unpacked by hooks.dart.
//
// Changes to this struct must also be made to
// io/flutter/embedding/android/KeyData.java.
struct alignas(8) KeyData {
// Timestamp in microseconds from an arbitrary and consistent start point
uint64_t timestamp;
Expand Down
3 changes: 3 additions & 0 deletions lib/ui/window/key_data_packet.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
namespace flutter {

// A byte stream representing a key event, to be sent to the framework.
//
// Changes to the marshalling format here must also be made to
// io/flutter/embedding/android/KeyData.java.
class KeyDataPacket {
public:
// Build the key data packet by providing information.
Expand Down
3 changes: 3 additions & 0 deletions shell/platform/android/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,10 @@ android_java_sources = [
"io/flutter/embedding/android/FlutterTextureView.java",
"io/flutter/embedding/android/FlutterView.java",
"io/flutter/embedding/android/KeyChannelResponder.java",
"io/flutter/embedding/android/KeyData.java",
"io/flutter/embedding/android/KeyEmbedderResponder.java",
"io/flutter/embedding/android/KeyboardManager.java",
"io/flutter/embedding/android/KeyboardMap.java",
"io/flutter/embedding/android/MotionEventTracker.java",
"io/flutter/embedding/android/RenderMode.java",
"io/flutter/embedding/android/SplashScreen.java",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

package io.flutter.embedding.android;

import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import androidx.annotation.NonNull;
import io.flutter.embedding.engine.systemchannels.KeyEventChannel;
Expand All @@ -19,66 +18,15 @@ public class KeyChannelResponder implements KeyboardManager.Responder {
private static final String TAG = "KeyChannelResponder";

@NonNull private final KeyEventChannel keyEventChannel;
private int combiningCharacter;

@NonNull
private final KeyboardManager.CharacterCombiner characterCombiner =
new KeyboardManager.CharacterCombiner();

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

/**
* 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>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>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>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.
* </ul>
*
* <p>The following reference explains the concept of a "combining character":
* https://en.wikipedia.org/wiki/Combining_character
*/
Character applyCombiningCharacterToBaseCharacter(int newCharacterCodePoint) {
char 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;
}

@Override
public void handleEvent(
@NonNull KeyEvent keyEvent, @NonNull OnKeyEventHandledCallback onKeyEventHandledCallback) {
Expand All @@ -92,7 +40,7 @@ public void handleEvent(
}

final Character complexCharacter =
applyCombiningCharacterToBaseCharacter(keyEvent.getUnicodeChar());
characterCombiner.applyCombiningCharacterToBaseCharacter(keyEvent.getUnicodeChar());
KeyEventChannel.FlutterKeyEvent flutterEvent =
new KeyEventChannel.FlutterKeyEvent(keyEvent, complexCharacter);

Expand Down
140 changes: 140 additions & 0 deletions shell/platform/android/io/flutter/embedding/android/KeyData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.embedding.android;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
* The resulting Flutter key events generated by {@link KeyEmbedderResponder}, and are sent through
* the messenger after being marshalled with {@link #toBytes()}.
*
* <p>This class is the Java adaption of {@code KeyData} and {@code KeyDataPacket} in the C engine.
* Changes made to either side must also be made to the other.
*
* <p>Each {@link KeyData} corresponds to a {@code ui.KeyData} in the framework.
*/
public class KeyData {
private static final String TAG = "KeyData";

/**
* The channel that key data should be sent through.
*
* <p>Must be kept in sync with kFlutterKeyDataChannel in embedder.cc
*/
public static final String CHANNEL = "flutter/keydata";

// The number of fields except for `character`.
private static final int FIELD_COUNT = 5;
private static final int BYTES_PER_FIELD = 8;

/** The action type of the key data. */
public enum Type {
kDown(0),
kUp(1),
kRepeat(2);

private long value;

private Type(long value) {
this.value = value;
}

public long getValue() {
return value;
}

static Type fromLong(long value) {
switch ((int) value) {
case 0:
return kDown;
case 1:
return kUp;
case 2:
return kRepeat;
default:
throw new AssertionError("Unexpected Type value");
}
}
}

/** Creates an empty {@link KeyData}. */
public KeyData() {}

/**
* Unmarshal fields from a buffer.
*
* <p>For the binary format, see {@code lib/ui/window/key_data_packet.h}.
*/
public KeyData(@NonNull ByteBuffer buffer) {
final long charSize = buffer.getLong();
this.timestamp = buffer.getLong();
this.type = Type.fromLong(buffer.getLong());
this.physicalKey = buffer.getLong();
this.logicalKey = buffer.getLong();
this.synthesized = buffer.getLong() != 0;

if (buffer.remaining() != charSize) {
throw new AssertionError(
String.format(
"Unexpected char length: charSize is %d while buffer has position %d, capacity %d, limit %d",
charSize, buffer.position(), buffer.capacity(), buffer.limit()));
}
this.character = null;
if (charSize != 0) {
final byte[] strBytes = new byte[(int) charSize];
buffer.get(strBytes, 0, (int) charSize);
try {
this.character = new String(strBytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new AssertionError("UTF-8 unsupported");
}
}
}

long timestamp;
Type type;
long physicalKey;
long logicalKey;
boolean synthesized;

/** The character of this key data encoded in UTF-8. */
@Nullable String character;

/**
* Marshal the key data to a new byte buffer.
*
* <p>For the binary format, see {@code lib/ui/window/key_data_packet.h}.
*
* @return the marshalled bytes.
*/
ByteBuffer toBytes() {
byte[] charBytes;
try {
charBytes = character == null ? null : character.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new AssertionError("UTF-8 not supported");
}
final int charSize = charBytes == null ? 0 : charBytes.length;
final ByteBuffer packet =
ByteBuffer.allocateDirect((1 + FIELD_COUNT) * BYTES_PER_FIELD + charSize);
packet.order(ByteOrder.LITTLE_ENDIAN);

packet.putLong(charSize);
packet.putLong(timestamp);
packet.putLong(type.getValue());
packet.putLong(physicalKey);
packet.putLong(logicalKey);
packet.putLong(synthesized ? 1L : 0L);
if (charBytes != null) {
packet.put(charBytes);
}

return packet;
}
}
Loading