Unexpected-Keyboard/srcs/juloo.keyboard2/Keyboard2.java
Jules Aguillon 51b330c616 Allow switching quickly between two layouts
A new option allow to choose a secondary layout, the switching key is
placed on the top edge of the space bar.

The "Programming layout" option was basically doing that but it was
possible to choose from a few layouts only. It is improved and renamed.

The 'LayoutListPreference' allows setting the string for the first entry
but otherwise share the rest of the array.

Add nice icons from materialdesignicons.
2022-11-13 00:24:23 +01:00

403 lines
12 KiB
Java

package juloo.keyboard2;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.inputmethodservice.InputMethodService;
import android.os.Build.VERSION;
import android.os.IBinder;
import android.text.InputType;
import android.util.Log;
import android.util.LogPrinter;
import android.view.ContextThemeWrapper;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class Keyboard2 extends InputMethodService
implements SharedPreferences.OnSharedPreferenceChangeListener,
Autocapitalisation.Callback
{
static private final String TAG = "Keyboard2";
private Keyboard2View _keyboardView;
private int _currentTextLayout;
private ViewGroup _emojiPane = null;
private Config _config;
private Autocapitalisation _autocap;
private boolean _debug_logs = false;
private KeyboardData getLayout(int resId)
{
return KeyboardData.load(getResources(), resId);
}
@Override
public void onCreate()
{
super.onCreate();
SharedPreferences prefs = DirectBootAwarePreferences.get_shared_preferences(this);
prefs.registerOnSharedPreferenceChangeListener(this);
Config.initGlobalConfig(prefs, getResources(), new KeyEventHandler(this.new Receiver()));
_config = Config.globalConfig();
_keyboardView = (Keyboard2View)inflate_view(R.layout.keyboard);
_keyboardView.reset();
_debug_logs = getResources().getBoolean(R.bool.debug_logs);
_autocap = new Autocapitalisation(getMainLooper(), this);
}
public void update_shift_state(boolean should_enable, boolean should_disable)
{
if (should_enable)
_keyboardView.set_shift_state(true, false);
else if (should_disable)
_keyboardView.set_shift_state(false, false);
}
private List<InputMethodSubtype> getEnabledSubtypes(InputMethodManager imm)
{
String pkg = getPackageName();
for (InputMethodInfo imi : imm.getEnabledInputMethodList())
if (imi.getPackageName().equals(pkg))
return imm.getEnabledInputMethodSubtypeList(imi, true);
return Arrays.asList();
}
private void refreshSubtypeLayout(InputMethodSubtype subtype)
{
int l = _config.layout;
if (l == -1)
{
String s = subtype.getExtraValueOf("default_layout");
if (s != null)
l = Config.layoutId_of_string(s);
else
l = R.xml.qwerty;
}
_currentTextLayout = l;
}
private void extra_keys_of_subtype(Set<KeyValue> dst, InputMethodSubtype subtype)
{
String extra_keys = subtype.getExtraValueOf("extra_keys");
if (extra_keys == null)
return;
String[] ks = extra_keys.split("\\|");
for (int i = 0; i < ks.length; i++)
dst.add(KeyValue.getKeyByName(ks[i]));
}
private void refreshAccentsOption(InputMethodManager imm, InputMethodSubtype subtype)
{
HashSet<KeyValue> extra_keys = new HashSet<KeyValue>();
List<InputMethodSubtype> enabled_subtypes = getEnabledSubtypes(imm);
switch (_config.accents)
{
// '3' was "all accents", now unused
case 1: case 3:
extra_keys_of_subtype(extra_keys, subtype);
for (InputMethodSubtype s : enabled_subtypes)
extra_keys_of_subtype(extra_keys, s);
break;
case 2:
extra_keys_of_subtype(extra_keys, subtype);
break;
case 4: break;
default: throw new IllegalArgumentException();
}
_config.extra_keys_subtype = extra_keys;
if (enabled_subtypes.size() > 1)
_config.shouldOfferSwitchingToNextInputMethod = true;
}
private void refreshSubtypeLegacyFallback()
{
// Fallback for the accents option: Only respect the "None" case
switch (_config.accents)
{
case 1: case 2: case 3: _config.extra_keys_subtype = null; break;
case 4: _config.extra_keys_subtype = new HashSet<KeyValue>(); break;
}
// Fallback for the layout option: Use qwerty in the "system settings" case
_currentTextLayout = (_config.layout == -1) ? R.xml.qwerty : _config.layout;
}
private void refreshSubtypeImm()
{
InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
_config.shouldOfferSwitchingToNextInputMethod = imm.shouldOfferSwitchingToNextInputMethod(getConnectionToken());
if (VERSION.SDK_INT < 12)
{
// Subtypes won't work well under API level 12 (getExtraValueOf)
refreshSubtypeLegacyFallback();
}
else
{
InputMethodSubtype subtype = imm.getCurrentInputMethodSubtype();
if (subtype == null)
{
// On some rare cases, [subtype] is null.
refreshSubtypeLegacyFallback();
}
else
{
refreshSubtypeLayout(subtype);
refreshAccentsOption(imm, subtype);
}
}
_config.shouldOfferSwitchingToSecond =
_config.second_layout != -1 &&
_currentTextLayout != _config.second_layout;
}
private String actionLabel_of_imeAction(int action)
{
int res;
switch (action)
{
case EditorInfo.IME_ACTION_NEXT: res = R.string.key_action_next; break;
case EditorInfo.IME_ACTION_DONE: res = R.string.key_action_done; break;
case EditorInfo.IME_ACTION_GO: res = R.string.key_action_go; break;
case EditorInfo.IME_ACTION_PREVIOUS: res = R.string.key_action_prev; break;
case EditorInfo.IME_ACTION_SEARCH: res = R.string.key_action_search; break;
case EditorInfo.IME_ACTION_SEND: res = R.string.key_action_send; break;
case EditorInfo.IME_ACTION_UNSPECIFIED:
case EditorInfo.IME_ACTION_NONE:
default: return null;
}
return getResources().getString(res);
}
private void refresh_action_label(EditorInfo info)
{
// First try to look at 'info.actionLabel', if it isn't set, look at
// 'imeOptions'.
if (info.actionLabel != null)
{
_config.actionLabel = info.actionLabel.toString();
_config.actionId = info.actionId;
_config.swapEnterActionKey = false;
}
else
{
int action = info.imeOptions & EditorInfo.IME_MASK_ACTION;
_config.actionLabel = actionLabel_of_imeAction(action); // Might be null
_config.actionId = action;
_config.swapEnterActionKey =
(info.imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) == 0;
}
}
private void refreshConfig()
{
int prev_theme = _config.theme;
_config.refresh(getResources());
refreshSubtypeImm();
// Refreshing the theme config requires re-creating the views
if (prev_theme != _config.theme)
{
_keyboardView = (Keyboard2View)inflate_view(R.layout.keyboard);
_emojiPane = null;
}
}
private void log_editor_info(EditorInfo info)
{
LogPrinter p = new LogPrinter(Log.DEBUG, TAG);
info.dump(p, "");
if (info.extras != null)
Log.d(TAG, "extras: "+info.extras.toString());
Log.d(TAG, "swapEnterActionKey: "+_config.swapEnterActionKey);
Log.d(TAG, "actionLabel: "+_config.actionLabel);
}
private int chooseLayout(EditorInfo info)
{
switch (info.inputType & InputType.TYPE_MASK_CLASS)
{
case InputType.TYPE_CLASS_NUMBER:
return R.xml.pin;
case InputType.TYPE_CLASS_PHONE:
case InputType.TYPE_CLASS_DATETIME:
return R.xml.pin;
default:
return _currentTextLayout;
}
}
@Override
public void onStartInputView(EditorInfo info, boolean restarting)
{
refreshConfig();
refresh_action_label(info);
_keyboardView.setKeyboard(getLayout(chooseLayout(info)));
_autocap.started(info, getCurrentInputConnection());
setInputView(_keyboardView);
if (_debug_logs)
log_editor_info(info);
}
@Override
public void setInputView(View v)
{
ViewParent parent = v.getParent();
if (parent != null && parent instanceof ViewGroup)
((ViewGroup)parent).removeView(v);
super.setInputView(v);
}
@Override
public void onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype)
{
refreshSubtypeImm();
_keyboardView.setKeyboard(getLayout(_currentTextLayout));
}
@Override
public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd)
{
super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart, candidatesEnd);
_autocap.selection_updated(oldSelStart, newSelStart);
}
@Override
public void onFinishInputView(boolean finishingInput)
{
super.onFinishInputView(finishingInput);
_keyboardView.reset();
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)
{
refreshConfig();
}
@Override
public boolean onEvaluateFullscreenMode()
{
/* Entirely disable fullscreen mode. */
return false;
}
/** Not static */
public class Receiver implements KeyEventHandler.IReceiver
{
public void switchToNextInputMethod()
{
InputMethodManager imm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
imm.showInputMethodPicker();
// deprecated in version 28: imm.switchToNextInputMethod(getConnectionToken(), false);
// added in version 28: switchToNextInputMethod(false);
}
public void setPane_emoji()
{
if (_emojiPane == null)
_emojiPane = (ViewGroup)inflate_view(R.layout.emoji_pane);
setInputView(_emojiPane);
}
public void setPane_normal()
{
setInputView(_keyboardView);
}
public void performAction()
{
InputConnection conn = getCurrentInputConnection();
if (conn == null)
return;
conn.performEditorAction(_config.actionId);
}
public void enableCapsLock()
{
_keyboardView.set_shift_state(true, true);
}
public void switchMain()
{
_keyboardView.setKeyboard(getLayout(_currentTextLayout));
}
public void switchNumeric()
{
_keyboardView.setKeyboard(getLayout(R.xml.numeric));
}
public void switchGreekmath()
{
_keyboardView.setKeyboard(getLayout(R.xml.greekmath));
}
public void switchSecond()
{
if (_config.second_layout == -1)
return;
KeyboardData layout =
getLayout(_config.second_layout).mapKeys(new KeyboardData.MapKeyValues() {
public KeyValue apply(KeyValue key, boolean localized)
{
if (key.getKind() == KeyValue.Kind.Event
&& key.getEvent() == KeyValue.Event.SWITCH_SECOND)
return KeyValue.getKeyByName("switch_second_back");
return key;
}
});
_keyboardView.setKeyboard(layout);
}
public void sendKeyEvent(int eventAction, int eventCode, int meta)
{
InputConnection conn = getCurrentInputConnection();
if (conn == null)
return;
conn.sendKeyEvent(new KeyEvent(1, 1, eventAction, eventCode, 0, meta));
if (eventAction == KeyEvent.ACTION_UP)
_autocap.event_sent(eventCode, meta);
}
public void showKeyboardConfig()
{
Intent intent = new Intent(Keyboard2.this, SettingsActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
public void commitText(String text)
{
getCurrentInputConnection().commitText(text, 1);
_autocap.typed(text);
}
public void commitChar(char c)
{
sendKeyChar(c);
_autocap.typed(c);
}
}
private IBinder getConnectionToken()
{
return getWindow().getWindow().getAttributes().token;
}
private View inflate_view(int layout)
{
return View.inflate(new ContextThemeWrapper(this, _config.theme), layout, null);
}
}