forked from extern/Unexpected-Keyboard
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.
This commit is contained in:
parent
7e7a5e4425
commit
51a41ec90a
@ -389,6 +389,11 @@ final class Config
|
|||||||
return _globalConfig;
|
return _globalConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static SharedPreferences globalPrefs()
|
||||||
|
{
|
||||||
|
return _globalConfig._prefs;
|
||||||
|
}
|
||||||
|
|
||||||
public static interface IKeyEventHandler
|
public static interface IKeyEventHandler
|
||||||
{
|
{
|
||||||
public void key_down(KeyValue value, boolean is_swipe);
|
public void key_down(KeyValue value, boolean is_swipe);
|
||||||
|
@ -151,7 +151,7 @@ public class Keyboard2 extends InputMethodService
|
|||||||
_config.shouldOfferSwitchingToNextInputMethod = true;
|
_config.shouldOfferSwitchingToNextInputMethod = true;
|
||||||
else
|
else
|
||||||
_config.shouldOfferSwitchingToNextInputMethod = shouldOfferSwitchingToNextInputMethod();
|
_config.shouldOfferSwitchingToNextInputMethod = shouldOfferSwitchingToNextInputMethod();
|
||||||
_config.shouldOfferVoiceTyping = (get_voice_typing_im(imm) != null);
|
_config.shouldOfferVoiceTyping = true;
|
||||||
KeyboardData default_layout = null;
|
KeyboardData default_layout = null;
|
||||||
_config.extra_keys_subtype = null;
|
_config.extra_keys_subtype = null;
|
||||||
if (VERSION.SDK_INT >= 12)
|
if (VERSION.SDK_INT >= 12)
|
||||||
@ -224,20 +224,6 @@ public class Keyboard2 extends InputMethodService
|
|||||||
_keyboardView.reset();
|
_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)
|
private KeyboardData refresh_special_layout(EditorInfo info)
|
||||||
{
|
{
|
||||||
switch (info.inputType & InputType.TYPE_MASK_CLASS)
|
switch (info.inputType & InputType.TYPE_MASK_CLASS)
|
||||||
@ -433,14 +419,9 @@ public class Keyboard2 extends InputMethodService
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case SWITCH_VOICE_TYPING:
|
case SWITCH_VOICE_TYPING:
|
||||||
SimpleEntry<String, InputMethodSubtype> im = get_voice_typing_im(get_imm());
|
if (!VoiceImeSwitcher.switch_to_voice_ime(Keyboard2.this, get_imm(),
|
||||||
if (im == null)
|
Config.globalPrefs()))
|
||||||
return;
|
_config.shouldOfferVoiceTyping = false;
|
||||||
// Best-effort. Good enough for triggering Google's voice typing.
|
|
||||||
if (VERSION.SDK_INT < 28)
|
|
||||||
switchInputMethod(im.getKey());
|
|
||||||
else
|
|
||||||
switchInputMethod(im.getKey(), im.getValue());
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
30
srcs/juloo.keyboard2/Utils.java
Normal file
30
srcs/juloo.keyboard2/Utils.java
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
143
srcs/juloo.keyboard2/VoiceImeSwitcher.java
Normal file
143
srcs/juloo.keyboard2/VoiceImeSwitcher.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user