1 // Copyright 2013 The Flutter Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package io.flutter.embedding.android; 6 7 import android.support.annotation.NonNull; 8 import android.support.annotation.Nullable; 9 import android.view.KeyCharacterMap; 10 import android.view.KeyEvent; 11 12 import io.flutter.embedding.engine.systemchannels.KeyEventChannel; 13 import io.flutter.plugin.editing.TextInputPlugin; 14 15 public class AndroidKeyProcessor { 16 @NonNull 17 private final KeyEventChannel keyEventChannel; 18 @NonNull 19 private final TextInputPlugin textInputPlugin; 20 private int combiningCharacter; 21 AndroidKeyProcessor(@onNull KeyEventChannel keyEventChannel, @NonNull TextInputPlugin textInputPlugin)22 public AndroidKeyProcessor(@NonNull KeyEventChannel keyEventChannel, @NonNull TextInputPlugin textInputPlugin) { 23 this.keyEventChannel = keyEventChannel; 24 this.textInputPlugin = textInputPlugin; 25 } 26 onKeyUp(@onNull KeyEvent keyEvent)27 public void onKeyUp(@NonNull KeyEvent keyEvent) { 28 Character complexCharacter = applyCombiningCharacterToBaseCharacter(keyEvent.getUnicodeChar()); 29 keyEventChannel.keyUp( 30 new KeyEventChannel.FlutterKeyEvent(keyEvent, complexCharacter) 31 ); 32 } 33 onKeyDown(@onNull KeyEvent keyEvent)34 public void onKeyDown(@NonNull KeyEvent keyEvent) { 35 if (textInputPlugin.getLastInputConnection() != null 36 && textInputPlugin.getInputMethodManager().isAcceptingText()) { 37 textInputPlugin.getLastInputConnection().sendKeyEvent(keyEvent); 38 } 39 40 Character complexCharacter = applyCombiningCharacterToBaseCharacter(keyEvent.getUnicodeChar()); 41 keyEventChannel.keyDown( 42 new KeyEventChannel.FlutterKeyEvent(keyEvent, complexCharacter) 43 ); 44 } 45 46 /** 47 * Applies the given Unicode character in {@code newCharacterCodePoint} to a previously 48 * entered Unicode combining character and returns the combination of these characters 49 * if a combination exists. 50 * <p> 51 * This method mutates {@link #combiningCharacter} over time to combine characters. 52 * <p> 53 * One of the following things happens in this method: 54 * <ul> 55 * <li>If no previous {@link #combiningCharacter} exists and the {@code newCharacterCodePoint} 56 * is not a combining character, then {@code newCharacterCodePoint} is returned.</li> 57 * <li>If no previous {@link #combiningCharacter} exists and the {@code newCharacterCodePoint} 58 * is a combining character, then {@code newCharacterCodePoint} is saved as the 59 * {@link #combiningCharacter} and null is returned.</li> 60 * <li>If a previous {@link #combiningCharacter} exists and the {@code newCharacterCodePoint} 61 * is also a combining character, then the {@code newCharacterCodePoint} is combined with 62 * the existing {@link #combiningCharacter} and null is returned.</li> 63 * <li>If a previous {@link #combiningCharacter} exists and the {@code newCharacterCodePoint} 64 * is not a combining character, then the {@link #combiningCharacter} is applied to the 65 * regular {@code newCharacterCodePoint} and the resulting complex character is returned. The 66 * {@link #combiningCharacter} is cleared.</li> 67 * </ul> 68 * <p> 69 * The following reference explains the concept of a "combining character": 70 * https://en.wikipedia.org/wiki/Combining_character 71 */ 72 @Nullable applyCombiningCharacterToBaseCharacter(int newCharacterCodePoint)73 private Character applyCombiningCharacterToBaseCharacter(int newCharacterCodePoint) { 74 if (newCharacterCodePoint == 0) { 75 return null; 76 } 77 78 Character complexCharacter = (char) newCharacterCodePoint; 79 boolean isNewCodePointACombiningCharacter = (newCharacterCodePoint & KeyCharacterMap.COMBINING_ACCENT) != 0; 80 if (isNewCodePointACombiningCharacter) { 81 // If a combining character was entered before, combine this one with that one. 82 int plainCodePoint = newCharacterCodePoint & KeyCharacterMap.COMBINING_ACCENT_MASK; 83 if (combiningCharacter != 0) { 84 combiningCharacter = KeyCharacterMap.getDeadChar(combiningCharacter, plainCodePoint); 85 } else { 86 combiningCharacter = plainCodePoint; 87 } 88 } else { 89 // The new character is a regular character. Apply combiningCharacter to it, if it exists. 90 if (combiningCharacter != 0) { 91 int combinedChar = KeyCharacterMap.getDeadChar(combiningCharacter, newCharacterCodePoint); 92 if (combinedChar > 0) { 93 complexCharacter = (char) combinedChar; 94 } 95 combiningCharacter = 0; 96 } 97 } 98 99 return complexCharacter; 100 } 101 } 102