Unexpected-Keyboard/srcs/juloo.keyboard2/KeyValue.java
Jules Aguillon 506912be60 Fix other modifiers not applied
This adds a "GESTURE" modifier in KeyValue that materialize the gesture
into [Modifiers] and allow it to be applied at the same time as the
other modifiers, in the right order.

This moves the key modifying code back into Pointer to allow more
modifier manipulations.
2024-05-25 02:19:55 +02:00

607 lines
22 KiB
Java

package juloo.keyboard2;
import android.view.KeyEvent;
import java.util.HashMap;
public final class KeyValue implements Comparable<KeyValue>
{
public static enum Event
{
CONFIG,
SWITCH_TEXT,
SWITCH_NUMERIC,
SWITCH_EMOJI,
SWITCH_BACK_EMOJI,
CHANGE_METHOD_PICKER,
CHANGE_METHOD_AUTO,
ACTION,
SWITCH_FORWARD,
SWITCH_BACKWARD,
SWITCH_GREEKMATH,
CAPS_LOCK,
SWITCH_VOICE_TYPING,
SWITCH_VOICE_TYPING_CHOOSER,
}
// Must be evaluated in the reverse order of their values.
public static enum Modifier
{
SHIFT,
GESTURE,
CTRL,
ALT,
META,
DOUBLE_AIGU,
DOT_ABOVE,
DOT_BELOW,
GRAVE,
AIGU,
CIRCONFLEXE,
TILDE,
CEDILLE,
TREMA,
HORN,
HOOK_ABOVE,
SUPERSCRIPT,
SUBSCRIPT,
RING,
CARON,
MACRON,
ORDINAL,
ARROWS,
BOX,
OGONEK,
SLASH,
ARROW_RIGHT,
BREVE,
BAR,
FN,
} // Last is be applied first
public static enum Editing
{
COPY,
PASTE,
CUT,
SELECT_ALL,
PASTE_PLAIN,
UNDO,
REDO,
// Android context menu actions
REPLACE,
SHARE,
ASSIST,
AUTOFILL,
}
public static enum Placeholder
{
REMOVED,
F11,
F12,
SHINDOT,
SINDOT,
OLE,
METEG
}
public static enum Kind
{
Char, String, Keyevent, Event, Compose_pending, Hangul_initial,
Hangul_medial, Modifier, Editing, Placeholder,
Cursor_move // Value is encoded as a 16-bit integer
}
private static final int FLAGS_OFFSET = 19;
private static final int KIND_OFFSET = 28;
// Behavior flags.
public static final int FLAG_LATCH = (1 << FLAGS_OFFSET << 0);
// Key can be locked by typing twice
public static final int FLAG_LOCK = (1 << FLAGS_OFFSET << 1);
// Special keys are not repeated and don't clear latched modifiers.
public static final int FLAG_SPECIAL = (1 << FLAGS_OFFSET << 2);
// Whether the symbol should be greyed out. For example, keys that are not
// part of the pending compose sequence.
public static final int FLAG_GREYED = (1 << FLAGS_OFFSET << 3);
// Rendering flags.
public static final int FLAG_KEY_FONT = (1 << FLAGS_OFFSET << 4); // special font file
public static final int FLAG_SMALLER_FONT = (1 << FLAGS_OFFSET << 5); // 25% smaller symbols
public static final int FLAG_SECONDARY = (1 << FLAGS_OFFSET << 6); // dimmer
// Used by [Pointers].
// Free: (1 << FLAGS_OFFSET << 7)
// Free: (1 << FLAGS_OFFSET << 8)
// Ranges for the different components
private static final int FLAGS_BITS =
FLAG_LATCH | FLAG_LOCK | FLAG_SPECIAL | FLAG_GREYED | FLAG_KEY_FONT |
FLAG_SMALLER_FONT | FLAG_SECONDARY;
private static final int KIND_BITS = (0b1111 << KIND_OFFSET); // 4 bits wide
private static final int VALUE_BITS = ~(FLAGS_BITS | KIND_BITS); // 20 bits wide
static
{
check((FLAGS_BITS & KIND_BITS) == 0); // No overlap
check((FLAGS_BITS | KIND_BITS | VALUE_BITS) == ~0); // No holes
// No kind is out of range
check((((Kind.values().length - 1) << KIND_OFFSET) & ~KIND_BITS) == 0);
}
private final String _symbol;
/** This field encodes three things: Kind, flags and value. */
private final int _code;
public Kind getKind()
{
return Kind.values()[(_code & KIND_BITS) >>> KIND_OFFSET];
}
public int getFlags()
{
return (_code & FLAGS_BITS);
}
public boolean hasFlagsAny(int has)
{
return ((_code & has) != 0);
}
/** The string to render on the keyboard.
When [getKind() == Kind.String], also the string to send. */
public String getString()
{
return _symbol;
}
/** Defined only when [getKind() == Kind.Char]. */
public char getChar()
{
return (char)(_code & VALUE_BITS);
}
/** Defined only when [getKind() == Kind.Keyevent]. */
public int getKeyevent()
{
return (_code & VALUE_BITS);
}
/** Defined only when [getKind() == Kind.Event]. */
public Event getEvent()
{
return Event.values()[(_code & VALUE_BITS)];
}
/** Defined only when [getKind() == Kind.Modifier]. */
public Modifier getModifier()
{
return Modifier.values()[(_code & VALUE_BITS)];
}
/** Defined only when [getKind() == Kind.Editing]. */
public Editing getEditing()
{
return Editing.values()[(_code & VALUE_BITS)];
}
/** Defined only when [getKind() == Kind.Placeholder]. */
public Placeholder getPlaceholder()
{
return Placeholder.values()[(_code & VALUE_BITS)];
}
/** Defined only when [getKind() == Kind.Compose_pending]. */
public int getPendingCompose()
{
return (_code & VALUE_BITS);
}
/** Defined only when [getKind()] is [Kind.Hangul_initial] or
[Kind.Hangul_medial]. */
public int getHangulPrecomposed()
{
return (_code & VALUE_BITS);
}
/** Defined only when [getKind() == Kind.Cursor_move]. */
public short getCursorMove()
{
return (short)(_code & VALUE_BITS);
}
/* Update the char and the symbol. */
public KeyValue withChar(char c)
{
return new KeyValue(String.valueOf(c), Kind.Char, c, getFlags());
}
public KeyValue withSymbol(String s)
{
return new KeyValue(s, (_code & KIND_BITS), (_code & VALUE_BITS), getFlags());
}
public KeyValue withKeyevent(int code)
{
return new KeyValue(_symbol, Kind.Keyevent, code, getFlags());
}
public KeyValue withFlags(int f)
{
return new KeyValue(_symbol, (_code & KIND_BITS), (_code & VALUE_BITS), f);
}
@Override
public boolean equals(Object obj)
{
return sameKey((KeyValue)obj);
}
public int compareTo(KeyValue snd)
{
// Compare the kind and value first, then the flags.
int d = (_code & ~FLAGS_BITS) - (snd._code & ~FLAGS_BITS);
if (d != 0)
return d;
d = _code - snd._code;
if (d != 0)
return d;
return _symbol.compareTo(snd._symbol);
}
/** Type-safe alternative to [equals]. */
public boolean sameKey(KeyValue snd)
{
if (snd == null)
return false;
return _symbol.equals(snd._symbol) && _code == snd._code;
}
@Override
public int hashCode()
{
return _symbol.hashCode() + _code;
}
public KeyValue(String s, int kind, int value, int flags)
{
_symbol = s;
_code = (kind & KIND_BITS) | (flags & FLAGS_BITS) | (value & VALUE_BITS);
}
public KeyValue(String s, Kind k, int v, int f)
{
this(s, (k.ordinal() << KIND_OFFSET), v, f);
}
private static KeyValue charKey(String symbol, char c, int flags)
{
return new KeyValue(symbol, Kind.Char, c, flags);
}
private static KeyValue charKey(int symbol, char c, int flags)
{
return charKey(String.valueOf((char)symbol), c, flags);
}
private static KeyValue modifierKey(String symbol, Modifier m, int flags)
{
if (symbol.length() > 1)
flags |= FLAG_SMALLER_FONT;
return new KeyValue(symbol, Kind.Modifier, m.ordinal(),
FLAG_LATCH | FLAG_SPECIAL | FLAG_SECONDARY | flags);
}
private static KeyValue modifierKey(int symbol, Modifier m, int flags)
{
return modifierKey(String.valueOf((char)symbol), m, flags | FLAG_KEY_FONT);
}
private static KeyValue diacritic(int symbol, Modifier m)
{
return new KeyValue(String.valueOf((char)symbol), Kind.Modifier, m.ordinal(),
FLAG_LATCH | FLAG_SPECIAL | FLAG_KEY_FONT);
}
private static KeyValue eventKey(String symbol, Event e, int flags)
{
return new KeyValue(symbol, Kind.Event, e.ordinal(), flags | FLAG_SPECIAL | FLAG_SECONDARY);
}
private static KeyValue eventKey(int symbol, Event e, int flags)
{
return eventKey(String.valueOf((char)symbol), e, flags | FLAG_KEY_FONT);
}
private static KeyValue keyeventKey(String symbol, int code, int flags)
{
return new KeyValue(symbol, Kind.Keyevent, code, flags | FLAG_SECONDARY);
}
private static KeyValue keyeventKey(int symbol, int code, int flags)
{
return keyeventKey(String.valueOf((char)symbol), code, flags | FLAG_KEY_FONT);
}
private static KeyValue editingKey(String symbol, Editing action, int flags)
{
return new KeyValue(symbol, Kind.Editing, action.ordinal(),
flags | FLAG_SPECIAL | FLAG_SECONDARY);
}
private static KeyValue editingKey(String symbol, Editing action)
{
return editingKey(symbol, action, FLAG_SMALLER_FONT);
}
private static KeyValue editingKey(int symbol, Editing action)
{
return editingKey(String.valueOf((char)symbol), action, FLAG_KEY_FONT);
}
/** A key that moves the cursor [d] times to the right. If [d] is negative,
it moves the cursor [abs(d)] times to the left. */
public static KeyValue cursorMoveKey(int d)
{
int symbol = (d < 0) ? 0xE008 : 0xE006;
return new KeyValue(String.valueOf((char)symbol), Kind.Cursor_move,
((short)d) & 0xFFFF,
FLAG_SPECIAL | FLAG_SECONDARY | FLAG_KEY_FONT);
}
/** A key that do nothing but has a unique ID. */
private static KeyValue placeholderKey(Placeholder id)
{
return new KeyValue("", Kind.Placeholder, id.ordinal(), 0);
}
public static KeyValue makeStringKey(String str)
{
return makeStringKey(str, 0);
}
public static KeyValue makeCharKey(char c)
{
return new KeyValue(String.valueOf(c), Kind.Char, c, 0);
}
public static KeyValue makeComposePending(String symbol, int state, int flags)
{
return new KeyValue(symbol, Kind.Compose_pending, state,
flags | FLAG_LATCH);
}
public static KeyValue makeComposePending(int symbol, int state, int flags)
{
return makeComposePending(String.valueOf((char)symbol), state,
flags | FLAG_KEY_FONT);
}
public static KeyValue makeHangulInitial(String symbol, int initial_idx)
{
return new KeyValue(symbol, Kind.Hangul_initial, initial_idx * 588 + 44032,
FLAG_LATCH);
}
public static KeyValue makeHangulMedial(int precomposed, int medial_idx)
{
precomposed += medial_idx * 28;
return new KeyValue(String.valueOf((char)precomposed), Kind.Hangul_medial,
precomposed, FLAG_LATCH);
}
public static KeyValue makeHangulFinal(int precomposed, int final_idx)
{
precomposed += final_idx;
return KeyValue.makeCharKey((char)precomposed);
}
/** Make a key that types a string. A char key is returned for a string of
length 1. */
public static KeyValue makeStringKey(String str, int flags)
{
if (str.length() == 1)
return new KeyValue(str, Kind.Char, str.charAt(0), flags);
else
return new KeyValue(str, Kind.String, 0, flags | FLAG_SMALLER_FONT);
}
/** Make a modifier key for passing to [KeyModifier]. */
public static KeyValue makeInternalModifier(Modifier mod)
{
return new KeyValue("", Kind.Modifier, mod.ordinal(), 0);
}
public static KeyValue getKeyByName(String name)
{
switch (name)
{
/* These symbols have special meaning when in `srcs/layouts` and are
escaped in standard layouts. The backslash is not stripped when parsed
from the custom layout option. */
case "\\?": return makeStringKey("?");
case "\\#": return makeStringKey("#");
case "\\@": return makeStringKey("@");
case "\\\\": return makeStringKey("\\");
/* Modifiers and dead-keys */
case "shift": return modifierKey(0xE00A, Modifier.SHIFT, 0);
case "ctrl": return modifierKey("Ctrl", Modifier.CTRL, 0);
case "alt": return modifierKey("Alt", Modifier.ALT, 0);
case "accent_aigu": return diacritic(0xE050, Modifier.AIGU);
case "accent_caron": return diacritic(0xE051, Modifier.CARON);
case "accent_cedille": return diacritic(0xE052, Modifier.CEDILLE);
case "accent_circonflexe": return diacritic(0xE053, Modifier.CIRCONFLEXE);
case "accent_grave": return diacritic(0xE054, Modifier.GRAVE);
case "accent_macron": return diacritic(0xE055, Modifier.MACRON);
case "accent_ring": return diacritic(0xE056, Modifier.RING);
case "accent_tilde": return diacritic(0xE057, Modifier.TILDE);
case "accent_trema": return diacritic(0xE058, Modifier.TREMA);
case "accent_ogonek": return diacritic(0xE059, Modifier.OGONEK);
case "accent_dot_above": return diacritic(0xE05A, Modifier.DOT_ABOVE);
case "accent_double_aigu": return diacritic(0xE05B, Modifier.DOUBLE_AIGU);
case "accent_slash": return diacritic(0xE05C, Modifier.SLASH);
case "accent_arrow_right": return diacritic(0xE05D, Modifier.ARROW_RIGHT);
case "accent_breve": return diacritic(0xE05E, Modifier.BREVE);
case "accent_bar": return diacritic(0xE05F, Modifier.BAR);
case "accent_dot_below": return diacritic(0xE060, Modifier.DOT_BELOW);
case "accent_horn": return diacritic(0xE061, Modifier.HORN);
case "accent_hook_above": return diacritic(0xE062, Modifier.HOOK_ABOVE);
case "superscript": return modifierKey("Sup", Modifier.SUPERSCRIPT, 0);
case "subscript": return modifierKey("Sub", Modifier.SUBSCRIPT, 0);
case "ordinal": return modifierKey("Ord", Modifier.ORDINAL, 0);
case "arrows": return modifierKey("Arr", Modifier.ARROWS, 0);
case "box": return modifierKey("Box", Modifier.BOX, 0);
case "fn": return modifierKey("Fn", Modifier.FN, 0);
case "meta": return modifierKey("Meta", Modifier.META, 0);
/* Special event keys */
case "config": return eventKey(0xE004, Event.CONFIG, FLAG_SMALLER_FONT);
case "switch_text": return eventKey("ABC", Event.SWITCH_TEXT, FLAG_SMALLER_FONT);
case "switch_numeric": return eventKey("123+", Event.SWITCH_NUMERIC, FLAG_SMALLER_FONT);
case "switch_emoji": return eventKey(0xE001, Event.SWITCH_EMOJI, FLAG_SMALLER_FONT);
case "switch_back_emoji": return eventKey("ABC", Event.SWITCH_BACK_EMOJI, 0);
case "switch_forward": return eventKey(0xE013, Event.SWITCH_FORWARD, FLAG_SMALLER_FONT);
case "switch_backward": return eventKey(0xE014, Event.SWITCH_BACKWARD, FLAG_SMALLER_FONT);
case "switch_greekmath": return eventKey("πλ∇¬", Event.SWITCH_GREEKMATH, FLAG_SMALLER_FONT);
case "change_method": return eventKey(0xE009, Event.CHANGE_METHOD_PICKER, FLAG_SMALLER_FONT);
case "change_method_prev": return eventKey(0xE009, Event.CHANGE_METHOD_AUTO, FLAG_SMALLER_FONT);
case "action": return eventKey("Action", Event.ACTION, FLAG_SMALLER_FONT); // Will always be replaced
case "capslock": return eventKey(0xE012, Event.CAPS_LOCK, 0);
case "voice_typing": return eventKey(0xE015, Event.SWITCH_VOICE_TYPING, FLAG_SMALLER_FONT);
case "voice_typing_chooser": return eventKey(0xE015, Event.SWITCH_VOICE_TYPING_CHOOSER, FLAG_SMALLER_FONT);
/* Key events */
case "esc": return keyeventKey("Esc", KeyEvent.KEYCODE_ESCAPE, FLAG_SMALLER_FONT);
case "enter": return keyeventKey(0xE00E, KeyEvent.KEYCODE_ENTER, 0);
case "up": return keyeventKey(0xE005, KeyEvent.KEYCODE_DPAD_UP, 0);
case "right": return keyeventKey(0xE006, KeyEvent.KEYCODE_DPAD_RIGHT, 0);
case "down": return keyeventKey(0xE007, KeyEvent.KEYCODE_DPAD_DOWN, 0);
case "left": return keyeventKey(0xE008, KeyEvent.KEYCODE_DPAD_LEFT, 0);
case "page_up": return keyeventKey(0xE002, KeyEvent.KEYCODE_PAGE_UP, 0);
case "page_down": return keyeventKey(0xE003, KeyEvent.KEYCODE_PAGE_DOWN, 0);
case "home": return keyeventKey(0xE00B, KeyEvent.KEYCODE_MOVE_HOME, 0);
case "end": return keyeventKey(0xE00C, KeyEvent.KEYCODE_MOVE_END, 0);
case "backspace": return keyeventKey(0xE011, KeyEvent.KEYCODE_DEL, 0);
case "delete": return keyeventKey(0xE010, KeyEvent.KEYCODE_FORWARD_DEL, 0);
case "insert": return keyeventKey("Ins", KeyEvent.KEYCODE_INSERT, FLAG_SMALLER_FONT);
case "f1": return keyeventKey("F1", KeyEvent.KEYCODE_F1, 0);
case "f2": return keyeventKey("F2", KeyEvent.KEYCODE_F2, 0);
case "f3": return keyeventKey("F3", KeyEvent.KEYCODE_F3, 0);
case "f4": return keyeventKey("F4", KeyEvent.KEYCODE_F4, 0);
case "f5": return keyeventKey("F5", KeyEvent.KEYCODE_F5, 0);
case "f6": return keyeventKey("F6", KeyEvent.KEYCODE_F6, 0);
case "f7": return keyeventKey("F7", KeyEvent.KEYCODE_F7, 0);
case "f8": return keyeventKey("F8", KeyEvent.KEYCODE_F8, 0);
case "f9": return keyeventKey("F9", KeyEvent.KEYCODE_F9, 0);
case "f10": return keyeventKey("F10", KeyEvent.KEYCODE_F10, 0);
case "f11": return keyeventKey("F11", KeyEvent.KEYCODE_F11, FLAG_SMALLER_FONT);
case "f12": return keyeventKey("F12", KeyEvent.KEYCODE_F12, FLAG_SMALLER_FONT);
case "tab": return keyeventKey(0xE00F, KeyEvent.KEYCODE_TAB, FLAG_SMALLER_FONT);
/* Spaces */
case "\\t": return charKey("\\t", '\t', 0); // Send the tab character
case "\\n": return charKey("\\n", '\n', 0); // Send the newline character
case "space": return charKey(0xE00D, ' ', FLAG_KEY_FONT | FLAG_SMALLER_FONT | FLAG_GREYED);
case "nbsp": return charKey("\u237d", '\u00a0', FLAG_SMALLER_FONT);
/* bidi */
case "lrm": return charKey("", '\u200e', 0); // Send left-to-right mark
case "rlm": return charKey("", '\u200f', 0); // Send right-to-left mark
case "b(": return charKey("(", ')', 0);
case "b)": return charKey(")", '(', 0);
case "b[": return charKey("[", ']', 0);
case "b]": return charKey("]", '[', 0);
case "b{": return charKey("{", '}', 0);
case "b}": return charKey("}", '{', 0);
case "blt": return charKey("<", '>', 0);
case "bgt": return charKey(">", '<', 0);
/* hebrew niqqud */
case "qamats": return charKey("\u05E7\u05B8", '\u05B8', 0); // kamatz
case "patah": return charKey("\u05E4\u05B7", '\u05B7', 0); // patach
case "sheva": return charKey("\u05E9\u05B0", '\u05B0', 0);
case "dagesh": return charKey("\u05D3\u05BC", '\u05BC', 0); // or mapiq
case "hiriq": return charKey("\u05D7\u05B4", '\u05B4', 0);
case "segol": return charKey("\u05E1\u05B6", '\u05B6', 0);
case "tsere": return charKey("\u05E6\u05B5", '\u05B5', 0);
case "holam": return charKey("\u05D5\u05B9", '\u05B9', 0);
case "qubuts": return charKey("\u05E7\u05BB", '\u05BB', 0); // kubuts
case "hataf_patah": return charKey("\u05D7\u05B2\u05E4\u05B7", '\u05B2', 0); // reduced patach
case "hataf_qamats": return charKey("\u05D7\u05B3\u05E7\u05B8", '\u05B3', 0); // reduced kamatz
case "hataf_segol": return charKey("\u05D7\u05B1\u05E1\u05B6", '\u05B1', 0); // reduced segol
case "shindot": return charKey("\u05E9\u05C1", '\u05C1', 0);
case "shindot_placeholder": return placeholderKey(Placeholder.SHINDOT);
case "sindot": return charKey("\u05E9\u05C2", '\u05C2', 0);
case "sindot_placeholder": return placeholderKey(Placeholder.SINDOT);
/* hebrew punctuation */
case "geresh": return charKey("\u05F3", '\u05F3', 0);
case "gershayim": return charKey("\u05F4", '\u05F4', 0);
case "maqaf": return charKey("\u05BE", '\u05BE', 0);
/* hebrew biblical */
case "rafe": return charKey("\u05E4\u05BF", '\u05BF', 0);
case "ole": return charKey("\u05E2\u05AB", '\u05AB', 0);
case "ole_placeholder": return placeholderKey(Placeholder.OLE);
case "meteg": return charKey("\u05DE\u05BD", '\u05BD', 0); // or siluq or sof-pasuq
case "meteg_placeholder": return placeholderKey(Placeholder.METEG);
/* intending/preventing ligature - supported by many scripts*/
case "zwj": return charKey("zwj", '\u200D', 0); // zero-width joiner (provides ligature)
case "zwnj": return charKey("zwnj", '\u200C', 0); // zero-width non joiner (prevents unintended ligature)
/* Editing keys */
case "copy": return editingKey(0xE030, Editing.COPY);
case "paste": return editingKey(0xE032, Editing.PASTE);
case "cut": return editingKey(0xE031, Editing.CUT);
case "selectAll": return editingKey(0xE033, Editing.SELECT_ALL);
case "shareText": return editingKey(0xE034, Editing.SHARE);
case "pasteAsPlainText": return editingKey(0xE035, Editing.PASTE_PLAIN);
case "undo": return editingKey(0xE036, Editing.UNDO);
case "redo": return editingKey(0xE037, Editing.REDO);
case "cursor_left": return cursorMoveKey(-1);
case "cursor_right": return cursorMoveKey(1);
// These keys are not used
case "replaceText": return editingKey("repl", Editing.REPLACE);
case "textAssist": return editingKey(0xE038, Editing.ASSIST);
case "autofill": return editingKey("auto", Editing.AUTOFILL);
/* The compose key */
case "compose": return makeComposePending(0xE016, 0, FLAG_SECONDARY | FLAG_SMALLER_FONT | FLAG_SPECIAL);
/* Placeholder keys */
case "removed": return placeholderKey(Placeholder.REMOVED);
case "f11_placeholder": return placeholderKey(Placeholder.F11);
case "f12_placeholder": return placeholderKey(Placeholder.F12);
// Korean Hangul
case "": return makeHangulInitial("", 0);
case "": return makeHangulInitial("", 1);
case "": return makeHangulInitial("", 2);
case "": return makeHangulInitial("", 3);
case "": return makeHangulInitial("", 4);
case "": return makeHangulInitial("", 5);
case "": return makeHangulInitial("", 6);
case "": return makeHangulInitial("", 7);
case "": return makeHangulInitial("", 8);
case "": return makeHangulInitial("", 9);
case "": return makeHangulInitial("", 10);
case "": return makeHangulInitial("", 11);
case "": return makeHangulInitial("", 12);
case "": return makeHangulInitial("", 13);
case "": return makeHangulInitial("", 14);
case "": return makeHangulInitial("", 15);
case "": return makeHangulInitial("", 16);
case "": return makeHangulInitial("", 17);
case "": return makeHangulInitial("", 18);
/* Fallback to a string key that types its name */
default: return makeStringKey(name);
}
}
// Substitute for [assert], which has no effect on Android.
private static void check(boolean b)
{
if (!b)
throw new RuntimeException("Assertion failure");
}
}