diff options
| author | Jules Aguillon | 2023-12-30 00:56:55 +0100 |
|---|---|---|
| committer | Jules Aguillon | 2023-12-30 00:56:55 +0100 |
| commit | 51a41ec90af505b153cc7a016de3c1e8a18dc427 (patch) | |
| tree | d8e0464eadb3032c36192c483370d5c720fe43ae /srcs | |
| parent | 7e7a5e4425a7825a9b0d899d6d13710acae82600 (diff) | |
| download | unexpected-keyboard-51a41ec90af505b153cc7a016de3c1e8a18dc427.tar.gz unexpected-keyboard-51a41ec90af505b153cc7a016de3c1e8a18dc427.zip | |
Voice IME chooser popup
Bring a popup for choosing the voice IME when the voice key is pressed
for the first time or the list of voice IMEs installed on the device
change.
A preference stores the last selected IME and the last seen list of
IMEs.
Diffstat (limited to 'srcs')
| -rw-r--r-- | srcs/juloo.keyboard2/Config.java | 5 | ||||
| -rw-r--r-- | srcs/juloo.keyboard2/Keyboard2.java | 27 | ||||
| -rw-r--r-- | srcs/juloo.keyboard2/StringUtils.java | 12 | ||||
| -rw-r--r-- | srcs/juloo.keyboard2/Utils.java | 30 | ||||
| -rw-r--r-- | srcs/juloo.keyboard2/VoiceImeSwitcher.java | 143 |
5 files changed, 182 insertions, 35 deletions
diff --git a/srcs/juloo.keyboard2/Config.java b/srcs/juloo.keyboard2/Config.java index 31fe0b0..f8b2070 100644 --- a/srcs/juloo.keyboard2/Config.java +++ b/srcs/juloo.keyboard2/Config.java @@ -389,6 +389,11 @@ final class Config return _globalConfig; } + public static SharedPreferences globalPrefs() + { + return _globalConfig._prefs; + } + public static interface IKeyEventHandler { public void key_down(KeyValue value, boolean is_swipe); diff --git a/srcs/juloo.keyboard2/Keyboard2.java b/srcs/juloo.keyboard2/Keyboard2.java index d045396..0e91f8c 100644 --- a/srcs/juloo.keyboard2/Keyboard2.java +++ b/srcs/juloo.keyboard2/Keyboard2.java @@ -151,7 +151,7 @@ public class Keyboard2 extends InputMethodService _config.shouldOfferSwitchingToNextInputMethod = true; else _config.shouldOfferSwitchingToNextInputMethod = shouldOfferSwitchingToNextInputMethod(); - _config.shouldOfferVoiceTyping = (get_voice_typing_im(imm) != null); + _config.shouldOfferVoiceTyping = true; KeyboardData default_layout = null; _config.extra_keys_subtype = null; if (VERSION.SDK_INT >= 12) @@ -224,20 +224,6 @@ public class Keyboard2 extends InputMethodService _keyboardView.reset(); } - /** Returns the id and subtype of the voice typing IM. Returns [null] if none - is installed or if the feature is unsupported. */ - SimpleEntry<String, InputMethodSubtype> get_voice_typing_im(InputMethodManager imm) - { - if (VERSION.SDK_INT < 11) // Due to InputMethodSubtype - return null; - for (InputMethodInfo im : imm.getEnabledInputMethodList()) - for (InputMethodSubtype imst : imm.getEnabledInputMethodSubtypeList(im, true)) - // Switch to the first IM that has a subtype of this mode - if (imst.getMode().equals("voice")) - return new SimpleEntry(im.getId(), imst); - return null; - } - private KeyboardData refresh_special_layout(EditorInfo info) { switch (info.inputType & InputType.TYPE_MASK_CLASS) @@ -433,14 +419,9 @@ public class Keyboard2 extends InputMethodService break; case SWITCH_VOICE_TYPING: - SimpleEntry<String, InputMethodSubtype> im = get_voice_typing_im(get_imm()); - if (im == null) - return; - // Best-effort. Good enough for triggering Google's voice typing. - if (VERSION.SDK_INT < 28) - switchInputMethod(im.getKey()); - else - switchInputMethod(im.getKey(), im.getValue()); + if (!VoiceImeSwitcher.switch_to_voice_ime(Keyboard2.this, get_imm(), + Config.globalPrefs())) + _config.shouldOfferVoiceTyping = false; break; } } diff --git a/srcs/juloo.keyboard2/StringUtils.java b/srcs/juloo.keyboard2/StringUtils.java deleted file mode 100644 index 2994509..0000000 --- a/srcs/juloo.keyboard2/StringUtils.java +++ /dev/null @@ -1,12 +0,0 @@ -package juloo.keyboard2; - -final class Utils -{ - /** Turn the first letter of a string uppercase. */ - public static String capitalize_string(String s) - { - // Make sure not to cut a code point in half - int i = s.offsetByCodePoints(0, 1); - return s.substring(0, i).toUpperCase() + s.substring(i); - } -} diff --git a/srcs/juloo.keyboard2/Utils.java b/srcs/juloo.keyboard2/Utils.java new file mode 100644 index 0000000..cd28dfa --- /dev/null +++ b/srcs/juloo.keyboard2/Utils.java @@ -0,0 +1,30 @@ +package juloo.keyboard2; + +import android.app.AlertDialog; +import android.os.IBinder; +import android.view.Window; +import android.view.WindowManager; + +class Utils +{ + /** Turn the first letter of a string uppercase. */ + public static String capitalize_string(String s) + { + // Make sure not to cut a code point in half + int i = s.offsetByCodePoints(0, 1); + return s.substring(0, i).toUpperCase() + s.substring(i); + } + + /** Like [dialog.show()] but properly configure layout params when called + from an IME. [token] is the input view's [getWindowToken()]. */ + public static void show_dialog_on_ime(AlertDialog dialog, IBinder token) + { + Window win = dialog.getWindow(); + WindowManager.LayoutParams lp = win.getAttributes(); + lp.token = token; + lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; + win.setAttributes(lp); + win.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + dialog.show(); + } +} diff --git a/srcs/juloo.keyboard2/VoiceImeSwitcher.java b/srcs/juloo.keyboard2/VoiceImeSwitcher.java new file mode 100644 index 0000000..7ddda5e --- /dev/null +++ b/srcs/juloo.keyboard2/VoiceImeSwitcher.java @@ -0,0 +1,143 @@ +package juloo.keyboard2; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.inputmethodservice.InputMethodService; +import android.os.Build.VERSION; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.InputMethodSubtype; +import android.widget.ArrayAdapter; +import java.util.AbstractMap.SimpleEntry; +import java.util.ArrayList; +import java.util.List; + +class VoiceImeSwitcher +{ + static final String PREF_LAST_USED = "voice_ime_last_used"; + static final String PREF_KNOWN_IMES = "voice_ime_known"; + + /** Switch to the voice ime. This might open a chooser popup. Preferences are + used to store the last selected voice ime and to detect whether the + chooser popup must be shown. Returns [false] if the detection failed and + is unlikely to succeed. */ + public static boolean switch_to_voice_ime(InputMethodService ims, + InputMethodManager imm, SharedPreferences prefs) + { + if (VERSION.SDK_INT < 11) // Due to InputMethodSubtype + return false; + List<IME> imes = get_voice_ime_list(imm); + String last_used = prefs.getString(PREF_LAST_USED, null); + String last_known_imes = prefs.getString(PREF_KNOWN_IMES, null); + IME last_used_ime = get_ime_by_id(imes, last_used); + if (imes.size() == 0) + return false; + if (last_used == null || last_known_imes == null || last_used_ime == null + || !last_known_imes.equals(serialize_ime_ids(imes))) + choose_voice_ime_and_update_prefs(ims, prefs, imes); + else + switch_input_method(ims, last_used_ime); + return true; + } + + /** Show the voice IME chooser popup and switch to the selected IME. + Preferences are updated so that future calls to [switch_to_voice_ime] + switch to the newly selected IME. */ + static void choose_voice_ime_and_update_prefs(final InputMethodService ims, + final SharedPreferences prefs, final List<IME> imes) + { + List<String> ime_display_names = get_ime_display_names(ims, imes); + ArrayAdapter layouts = new ArrayAdapter(ims, android.R.layout.simple_list_item_1, ime_display_names); + AlertDialog dialog = new AlertDialog.Builder(ims) + .setAdapter(layouts, new DialogInterface.OnClickListener(){ + public void onClick(DialogInterface _dialog, int which) + { + IME selected = imes.get(which); + prefs.edit() + .putString(PREF_LAST_USED, selected.get_id()) + .putString(PREF_KNOWN_IMES, serialize_ime_ids(imes)) + .commit(); + switch_input_method(ims, selected); + } + }) + .create(); + Utils.show_dialog_on_ime(dialog, ims.getWindow().getWindow().getDecorView().getWindowToken()); + } + + static void switch_input_method(InputMethodService ims, IME ime) + { + if (VERSION.SDK_INT < 28) + ims.switchInputMethod(ime.get_id()); + else + ims.switchInputMethod(ime.get_id(), ime.subtype); + } + + static IME get_ime_by_id(List<IME> imes, String id) + { + if (id != null) + for (IME ime : imes) + if (ime.get_id().equals(id)) + return ime; + return null; + } + + static List<String> get_ime_display_names(InputMethodService ims, List<IME> imes) + { + List<String> names = new ArrayList<String>(); + for (IME ime : imes) + names.add(ime.get_display_name(ims)); + return names; + } + + static List<IME> get_voice_ime_list(InputMethodManager imm) + { + List<IME> imes = new ArrayList<IME>(); + for (InputMethodInfo im : imm.getEnabledInputMethodList()) + for (InputMethodSubtype imst : imm.getEnabledInputMethodSubtypeList(im, true)) + if (imst.getMode().equals("voice")) + imes.add(new IME(im, imst)); + return imes; + } + + /** The chooser popup is shown whether this string changes. */ + static String serialize_ime_ids(List<IME> imes) + { + StringBuilder b = new StringBuilder(); + for (IME ime : imes) + { + b.append(ime.get_id()); + b.append(','); + } + return b.toString(); + } + + static class IME + { + public final InputMethodInfo im; + public final InputMethodSubtype subtype; + + IME(InputMethodInfo im_, InputMethodSubtype st) + { + im = im_; + subtype = st; + } + + String get_id() { return im.getId(); } + + /** Localised display name. */ + String get_display_name(Context ctx) + { + String subtype_name = ""; + if (VERSION.SDK_INT >= 14) + { + subtype_name = subtype.getDisplayName(ctx, im.getPackageName(), null).toString(); + if (!subtype_name.equals("")) + subtype_name = " - " + subtype_name; + } + return im.loadLabel(ctx.getPackageManager()).toString() + subtype_name; + } + } +} |
