Unexpected-Keyboard/srcs/juloo.keyboard2/Keyboard2.java
Jules Aguillon 6126578111 Avoid switching back from secondary layout automatically
Stay on the secondary layout after a config refresh or onStartInputView.
The information is kept until the keyboard is restarted.

Additionally, move tweaking the secondary layout to the Config class now
that physical equality is not needed.
2023-01-15 23:19:09 +01:00

391 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
{
static private final String TAG = "Keyboard2";
private Keyboard2View _keyboardView;
private KeyEventHandler _keyeventhandler;
// If not 'null', the layout to use instead of [_currentTextLayout].
private KeyboardData _currentSpecialLayout;
private Current_text_layout _currentTextLayout;
// Layout associated with the currently selected locale.
private KeyboardData _localeTextLayout;
private ViewGroup _emojiPane = null;
private Config _config;
private boolean _debug_logs = false;
/** Layout currently visible. */
KeyboardData current_layout()
{
if (_currentSpecialLayout != null)
return _currentSpecialLayout;
if (_currentTextLayout == Current_text_layout.SECONDARY)
return _config.second_layout;
return (_config.layout == null) ? _localeTextLayout : _config.layout;
}
void setTextLayout(Current_text_layout layout)
{
_currentTextLayout = layout;
_currentSpecialLayout = null;
_keyboardView.setKeyboard(current_layout());
}
@Override
public void onCreate()
{
super.onCreate();
KeyboardData.init(getResources());
SharedPreferences prefs = DirectBootAwarePreferences.get_shared_preferences(this);
prefs.registerOnSharedPreferenceChangeListener(this);
_keyeventhandler = new KeyEventHandler(getMainLooper(), this.new Receiver());
Config.initGlobalConfig(prefs, getResources(), _keyeventhandler);
_config = Config.globalConfig();
_keyboardView = (Keyboard2View)inflate_view(R.layout.keyboard);
_keyboardView.reset();
_debug_logs = getResources().getBoolean(R.bool.debug_logs);
}
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)
{
String s = subtype.getExtraValueOf("default_layout");
if (s != null)
_localeTextLayout = _config.layout_of_string(getResources(), s);
else
_localeTextLayout = KeyboardData.load(getResources(), R.xml.qwerty);
}
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;
}
}
private void refreshSubtypeImm()
{
InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
if (VERSION.SDK_INT < 28)
_config.shouldOfferSwitchingToNextInputMethod = true;
else
_config.shouldOfferSwitchingToNextInputMethod = shouldOfferSwitchingToNextInputMethod();
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);
}
}
if (_config.second_layout == null)
{
_config.shouldOfferSwitchingToSecond = false;
_currentTextLayout = Current_text_layout.PRIMARY;
}
else
{
_config.shouldOfferSwitchingToSecond = true;
}
}
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();
_keyeventhandler.actionId = info.actionId;
_config.swapEnterActionKey = false;
}
else
{
int action = info.imeOptions & EditorInfo.IME_MASK_ACTION;
_config.actionLabel = actionLabel_of_imeAction(action); // Might be null
_keyeventhandler.actionId = action;
_config.swapEnterActionKey =
(info.imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) == 0;
}
}
/** Might re-create the keyboard view. [_keyboardView.setKeyboard()] and
[setInputView()] must be called soon after. */
private void refresh_config()
{
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;
}
_keyboardView.reset();
}
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 void refresh_special_layout(EditorInfo info)
{
switch (info.inputType & InputType.TYPE_MASK_CLASS)
{
case InputType.TYPE_CLASS_NUMBER:
case InputType.TYPE_CLASS_PHONE:
case InputType.TYPE_CLASS_DATETIME:
_currentSpecialLayout = KeyboardData.load_pin_entry(getResources());
break;
default:
_currentSpecialLayout = null;
break;
}
}
@Override
public void onStartInputView(EditorInfo info, boolean restarting)
{
refresh_config();
refresh_action_label(info);
refresh_special_layout(info);
_keyboardView.setKeyboard(current_layout());
_keyeventhandler.started(info);
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(current_layout());
}
@Override
public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd)
{
super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart, candidatesEnd);
_keyeventhandler.selection_updated(oldSelStart, newSelStart);
}
@Override
public void onFinishInputView(boolean finishingInput)
{
super.onFinishInputView(finishingInput);
_keyboardView.reset();
}
@Override
public void onSharedPreferenceChanged(SharedPreferences _prefs, String _key)
{
refresh_config();
setInputView(_keyboardView);
_keyboardView.setKeyboard(current_layout());
}
@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 set_shift_state(boolean state, boolean lock)
{
_keyboardView.set_shift_state(state, lock);
}
public void switch_text()
{
_currentSpecialLayout = null;
_keyboardView.setKeyboard(current_layout());
}
public void switch_layout(int layout_id)
{
_keyboardView.setKeyboard(KeyboardData.load(getResources(), layout_id));
}
public void switch_second()
{
if (_config.second_layout != null)
setTextLayout(Current_text_layout.SECONDARY);
}
public void switch_primary()
{
setTextLayout(Current_text_layout.PRIMARY);
}
public void showKeyboardConfig()
{
Intent intent = new Intent(Keyboard2.this, SettingsActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
public InputConnection getCurrentInputConnection()
{
return Keyboard2.this.getCurrentInputConnection();
}
}
private IBinder getConnectionToken()
{
return getWindow().getWindow().getAttributes().token;
}
private View inflate_view(int layout)
{
return View.inflate(new ContextThemeWrapper(this, _config.theme), layout, null);
}
private static enum Current_text_layout
{
PRIMARY,
SECONDARY
}
}