diff --git a/app/USER_EXPERIENCE.md b/USER_EXPERIENCE.md similarity index 100% rename from app/USER_EXPERIENCE.md rename to USER_EXPERIENCE.md diff --git a/app/src/main/java/cfpi_fpsi/eu/titanboard/IME.java b/app/src/main/java/cfpi_fpsi/eu/titanboard/IME.java index d76b8dc..227044e 100644 --- a/app/src/main/java/cfpi_fpsi/eu/titanboard/IME.java +++ b/app/src/main/java/cfpi_fpsi/eu/titanboard/IME.java @@ -21,31 +21,35 @@ package cfpi_fpsi.eu.titanboard; import android.annotation.SuppressLint; import android.inputmethodservice.InputMethodService; -import android.os.Build; import android.os.Vibrator; import android.view.KeyEvent; import android.view.View; -import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView; +import java.util.HashMap; + +// TODO: Implement long-click for extra keys. +// TODO: Implement auto-correct. +// TODO: Implement auto-caps. +// TODO: Implement language switcher. +// TODO: Implement device recognition and a keymap picking based on it. +// TODO: Make preferences accessible and saveable. + public class IME extends InputMethodService { private MetaKeyManager mMetaKeyManager; private Keymap mKeymap; - private LinearLayout mInputView; private Vibrator mVibrator; @Override public void onCreate() { super.onCreate(); - // TODO, which already has been done anyways, is to support multiple phone models by using - // the "ro.vendor.build.fingerprint" property. The goal is to support at least the other two - // Unihertz phones: the Pocket and the Slim. However, as I don't have them, it'll be guesswork. - // Sym key support is already implemented. - mKeymap = new Keymap(getResources().getXml(R.xml.keymap_titan)); + // Ideally, detect the device, but for now, load all available keymaps. + + mKeymap = new Keymap(getResources().getXml(R.xml.keymap_titan_pl)); mVibrator = (Vibrator) getBaseContext().getSystemService(VIBRATOR_SERVICE); mMetaKeyManager = new MetaKeyManager(mVibrator); } @@ -84,6 +88,7 @@ public class IME extends InputMethodService { return false; case KeyEvent.KEYCODE_DEL: case KeyEvent.KEYCODE_ENTER: + mMetaKeyManager.adjustMetaStateAfterKeypress(); return false; case KeyEvent.KEYCODE_SPACE: conn.commitText(" ", 1); @@ -96,8 +101,9 @@ public class IME extends InputMethodService { return false; } - conn.commitText(getKey(keyCode), 1); + conn.commitText(key, 1); mMetaKeyManager.adjustMetaStateAfterKeypress(); + return true; } } @@ -120,49 +126,50 @@ public class IME extends InputMethodService { } private String getKey(int keyCode) { - if (metaKeyActive(MetaKeyManager.MetaKey.ALT)) { - return mKeymap.getValueAlt(keyCode); - } else if (metaKeyActive(MetaKeyManager.MetaKey.SHIFT)) { - return mKeymap.getValueShift(keyCode); - } else if (metaKeyActive(MetaKeyManager.MetaKey.SYM)) { - return mKeymap.getValueSym(keyCode); - } else { - return mKeymap.getValue(keyCode); + if (mMetaKeyManager.getAlt().isActive()) { + return mKeymap.getKeyAlt(keyCode); } - } - private boolean metaKeyActive(MetaKeyManager.MetaKey key) { - return mMetaKeyManager.getMetaKeyPressed() == key - || mMetaKeyManager.getMetaKeyLatched() == key - || mMetaKeyManager.getMetaKeyLocked() == key; + if (mMetaKeyManager.getSym().isActive() && mMetaKeyManager.getShift().isActive()) { + return mKeymap.getKeyShiftSym(keyCode); + } + + if (mMetaKeyManager.getSym().isActive()) { + return mKeymap.getKeySym(keyCode); + } + + if (mMetaKeyManager.getShift().isActive()) { + return mKeymap.getKeyShift(keyCode); + } + + // Nothing active. + return mKeymap.getKey(keyCode); } @SuppressLint("SetTextI18n") private void updateMetaStateTextView() { - TextView stateView = mInputView.findViewById(R.id.state); + TextView shiftStateView = mInputView.findViewById(R.id.state_shift); + TextView altStateView = mInputView.findViewById(R.id.state_alt); + TextView symStateView = mInputView.findViewById(R.id.state_sym); - if (mMetaKeyManager.getMetaKeyLatched() == MetaKeyManager.MetaKey.ALT) { - stateView.setText("#?"); - } else if (mMetaKeyManager.getMetaKeyLatched() == MetaKeyManager.MetaKey.SHIFT) { - stateView.setText("Aa"); - } else { - stateView.setText("aa"); + shiftStateView.setText(getStringForMetaKey( + mMetaKeyManager.getShift(), "aa", "Aa", "AA")); + altStateView.setText(getStringForMetaKey( + mMetaKeyManager.getAlt(), "..", "#?", "#!")); + symStateView.setText(getStringForMetaKey( + mMetaKeyManager.getSym(), "..", "þπ", "ÞΩ")); + } + + private String getStringForMetaKey(MetaKeyManager.MetaKey key, String normal, String latched, String pressed) { + if (key.isLatched()) { + return latched; } - if (mMetaKeyManager.getMetaKeyPressed() == MetaKeyManager.MetaKey.ALT - || mMetaKeyManager.getMetaKeyLocked() == MetaKeyManager.MetaKey.ALT) { - stateView.setText("#!"); - } else if (mMetaKeyManager.getMetaKeyPressed() == MetaKeyManager.MetaKey.SHIFT - || mMetaKeyManager.getMetaKeyLocked() == MetaKeyManager.MetaKey.SHIFT) { - stateView.setText("AA"); + if (key.isLocked() || key.isPressed()) { + return pressed; } - if (mMetaKeyManager.getMetaKeyLocked() != MetaKeyManager.MetaKey.NONE - && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - stateView.setTextAppearance(R.style.KeyboardStateLocked); - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - stateView.setTextAppearance(R.style.KeyboardState); - } + return normal; } /** diff --git a/app/src/main/java/cfpi_fpsi/eu/titanboard/Keymap.java b/app/src/main/java/cfpi_fpsi/eu/titanboard/Keymap.java index 64bd364..6878acb 100644 --- a/app/src/main/java/cfpi_fpsi/eu/titanboard/Keymap.java +++ b/app/src/main/java/cfpi_fpsi/eu/titanboard/Keymap.java @@ -28,7 +28,7 @@ import java.util.HashMap; public class Keymap extends HashMap { public Keymap(XmlPullParser parser) { int code = 0; - String value = null, valueAlt = null, valueSym = null; + String none = null, shift = null, alt = null, sym = null, shiftSym = null; try { while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { @@ -40,19 +40,25 @@ public class Keymap extends HashMap { case "code": code = Integer.parseInt(parser.getAttributeValue(i)); break; - case "value": - value = parser.getAttributeValue(i); + case "none": + none = parser.getAttributeValue(i); break; - case "valueAlt": - valueAlt = parser.getAttributeValue(i); + case "shift": + shift = parser.getAttributeValue(i); break; - case "valueSym": - valueSym = parser.getAttributeValue(i); + case "alt": + alt = parser.getAttributeValue(i); + break; + case "sym": + sym = parser.getAttributeValue(i); + break; + case "shiftSym": + shiftSym = parser.getAttributeValue(i); break; } } } else if (type == XmlPullParser.END_TAG && parser.getName().equals("key")) { - put(code, new String[]{value, valueAlt, valueSym}); + put(code, new String[]{none, shift, alt, sym, shiftSym}); } parser.next(); @@ -62,23 +68,28 @@ public class Keymap extends HashMap { } } - public String getValue(int keyCode) { + public String getKey(int keyCode) { String[] values = get(keyCode); return values != null ? values[0] : null; } - public String getValueAlt(int keyCode) { + public String getKeyShift(int keyCode) { String[] values = get(keyCode); return values != null ? values[1] : null; } - public String getValueShift(int keyCode) { - String[] values = get(keyCode); - return values != null ? values[0].toUpperCase() : null; - } - - public String getValueSym(int keyCode) { + public String getKeyAlt(int keyCode) { String[] values = get(keyCode); return values != null ? values[2] : null; } + + public String getKeySym(int keyCode) { + String[] values = get(keyCode); + return values != null ? values[3] : null; + } + + public String getKeyShiftSym(int keyCode) { + String[] values = get(keyCode); + return values != null ? values[4] : null; + } } diff --git a/app/src/main/java/cfpi_fpsi/eu/titanboard/KeymapManager.java b/app/src/main/java/cfpi_fpsi/eu/titanboard/KeymapManager.java new file mode 100644 index 0000000..1eab85f --- /dev/null +++ b/app/src/main/java/cfpi_fpsi/eu/titanboard/KeymapManager.java @@ -0,0 +1,23 @@ +/* + * An input method for the Unihertz Titan/physical keyboard phones + * Copyright (C) 2024 Emil Kosz + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package cfpi_fpsi.eu.titanboard; + +public class KeymapManager { +} diff --git a/app/src/main/java/cfpi_fpsi/eu/titanboard/MetaKeyManager.java b/app/src/main/java/cfpi_fpsi/eu/titanboard/MetaKeyManager.java index 0325da3..b658d91 100644 --- a/app/src/main/java/cfpi_fpsi/eu/titanboard/MetaKeyManager.java +++ b/app/src/main/java/cfpi_fpsi/eu/titanboard/MetaKeyManager.java @@ -23,11 +23,9 @@ import android.os.Vibrator; import android.view.KeyEvent; public class MetaKeyManager { - private MetaKey mMetaKeyPressed = MetaKey.NONE; - private MetaKey mMetaKeyLatched = MetaKey.NONE; - private MetaKey mMetaKeyLocked = MetaKey.NONE; - private boolean mMetaKeyUsed = false; - private long mKeyUpTime; + public MetaKey mShift = new MetaKey(); + public MetaKey mAlt = new MetaKey(); + public MetaKey mSym = new MetaKey(); private Vibrator mVibrator; @@ -39,14 +37,14 @@ public class MetaKeyManager { switch (keyCode) { case KeyEvent.KEYCODE_ALT_LEFT: case KeyEvent.KEYCODE_ALT_RIGHT: - updateMetaKeyDown(MetaKey.ALT, event); + updateMetaKeyDown(mAlt, event); break; case KeyEvent.KEYCODE_SHIFT_LEFT: case KeyEvent.KEYCODE_SHIFT_RIGHT: - updateMetaKeyDown(MetaKey.SHIFT, event); + updateMetaKeyDown(mShift, event); break; case KeyEvent.KEYCODE_SYM: - updateMetaKeyDown(MetaKey.SYM, event); + updateMetaKeyDown(mSym, event); break; default: break; @@ -55,24 +53,31 @@ public class MetaKeyManager { void updateMetaKeyDown(MetaKey key, KeyEvent event) { // Perhaps this function should be renamed to something... - mMetaKeyUsed = false; - mMetaKeyPressed = key; - mMetaKeyLatched = mMetaKeyLocked != MetaKey.NONE ? mMetaKeyLocked : mMetaKeyLatched; - mMetaKeyLocked = MetaKey.NONE; + key.setUsed(false); + key.setPressed(true); + key.setLatched(key.isLocked() ? key.isLocked() : key.isLatched()); + key.setLocked(false); + + if (mShift.isPressed() && mAlt.isPressed()) { + mSym.setLatched(!mSym.isLatched()); + mShift.setUsed(true); + mAlt.setUsed(true); + mVibrator.vibrate(20); + } } public void updateMetaStateUp(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_ALT_LEFT: case KeyEvent.KEYCODE_ALT_RIGHT: - updateMetaKeyUp(MetaKey.ALT, event); + updateMetaKeyUp(mAlt, event); break; case KeyEvent.KEYCODE_SHIFT_LEFT: case KeyEvent.KEYCODE_SHIFT_RIGHT: - updateMetaKeyUp(MetaKey.SHIFT, event); + updateMetaKeyUp(mShift, event); break; case KeyEvent.KEYCODE_SYM: - updateMetaKeyUp(MetaKey.SYM, event); + updateMetaKeyUp(mSym, event); break; default: break; @@ -80,59 +85,114 @@ public class MetaKeyManager { } public void updateMetaKeyUp(MetaKey key, KeyEvent event) { - if (event.getEventTime() - mKeyUpTime < 400 && mMetaKeyLatched == key) { - mMetaKeyLocked = key; + if (event.getEventTime() - key.getKeyUpTime() < 400) { + key.setLocked(true); mVibrator.vibrate(30); } - mKeyUpTime = event.getEventTime(); - mMetaKeyPressed = MetaKey.NONE; - if (mMetaKeyLatched == MetaKey.NONE && mMetaKeyLocked == MetaKey.NONE && !mMetaKeyUsed) { - mMetaKeyLatched = key; - } else { - mMetaKeyLatched = MetaKey.NONE; - } + key.setKeyUpTime(event.getEventTime()); + key.setPressed(false); + + key.setLatched(!key.isLatched() && !key.isLocked() && !key.isUsed()); } public void adjustMetaStateAfterKeypress() { - if (mMetaKeyLatched != MetaKey.NONE) { - mMetaKeyLatched = MetaKey.NONE; - mMetaKeyUsed = false; + adjustMetaStateHelper(mShift); + adjustMetaStateHelper(mAlt); + adjustMetaStateHelper(mSym); + } + + public void adjustMetaStateHelper(MetaKey key) { + if (key.isLatched()) { + key.setLatched(false); + key.setUsed(false); } - if (mMetaKeyPressed != MetaKey.NONE) { - mMetaKeyUsed = true; + if (key.isPressed()) { + key.setUsed(true); } } public void resetMetaKeyState() { - mMetaKeyPressed = MetaKey.NONE; - mMetaKeyLatched = MetaKey.NONE; - mMetaKeyLocked = MetaKey.NONE; - mMetaKeyUsed = false; + mShift.resetState(); + mAlt.resetState(); + mSym.resetState(); } - public void setMetaKeyLatched(MetaKey key) { - resetMetaKeyState(); - mMetaKeyLatched = key; + public MetaKey getShift() { + return mShift; } - public MetaKey getMetaKeyPressed() { - return mMetaKeyPressed; + public MetaKey getAlt() { + return mAlt; } - public MetaKey getMetaKeyLatched() { - return mMetaKeyLatched; + public MetaKey getSym() { + return mSym; } - public MetaKey getMetaKeyLocked() { - return mMetaKeyLocked; - } + public static class MetaKey { + private boolean mPressed = false; + private boolean mLatched = false; + private boolean mLocked = false; + private boolean mUsed = false; + private long mKeyUpTime; - public enum MetaKey { - ALT, - NONE, - SHIFT, - SYM + public void resetState() { + setPressed(false); + setLatched(false); + setLocked(false); + setUsed(false); + } + + public void setPressed(boolean state) { + mPressed = state; + } + + public void setLatched(boolean state) { + mLatched = state; + } + + public void setLocked(boolean state) { + mLocked = state; + } + + public void setUsed(boolean state) { + mUsed = state; + } + + public void setKeyUpTime(long time) { + mKeyUpTime = time; + } + + public boolean isPressed() { + return mPressed; + } + + public boolean isLatched() { + return mLatched; + } + + public boolean isLocked() { + return mLocked; + } + + public boolean isUsed() { + return mUsed; + } + + public boolean isActive() { + return isPressed() || isLocked() || isLatched(); + } + + public long getKeyUpTime() { + return mKeyUpTime; + } + + public enum MetaKeyType { + ALT, + SHIFT, + SYM + } } } diff --git a/app/src/main/res/layout/input.xml b/app/src/main/res/layout/input.xml index b639ef3..5131b13 100644 --- a/app/src/main/res/layout/input.xml +++ b/app/src/main/res/layout/input.xml @@ -10,29 +10,36 @@ - + - - + + +