Fix voice typing on Android 16: detect standalone voice IMEs

Google Voice Typing on Android 16+ is a standalone IME with zero
subtypes, so the old code (scanning subtypes for mode='voice')
never found it. Also detect IMEs with getSubtypeCount()==0 and
handle null subtype in switchInputMethod() and get_display_name().
This commit is contained in:
2026-07-08 01:45:35 -04:00
parent 345f3cdbd6
commit 15f93cade1
3 changed files with 14 additions and 126 deletions
-104
View File
@@ -31,10 +31,6 @@ import juloo.mornkeyboard2.dict.DictionariesActivity;
import juloo.mornkeyboard2.prefs.LayoutsPreference;
import juloo.mornkeyboard2.suggestions.CandidatesView;
import juloo.mornkeyboard2.suggestions.Suggestions;
import juloo.mornkeyboard2.swipe.MornKeyboardSwipeAdapter;
import juloo.mornkeyboard2.swipe.SwipeInputHandler;
import dev.dettmer.swipetype.android.SwipeTypeCandidate;
public class Keyboard2 extends InputMethodService
implements SharedPreferences.OnSharedPreferenceChangeListener
@@ -59,10 +55,6 @@ public class Keyboard2 extends InputMethodService
private FoldStateTracker _foldStateTracker;
/** Swipe (glide) typing support. */
private MornKeyboardSwipeAdapter _swipeAdapter;
private SwipeInputHandler _swipeInput;
/** Layout currently visible before it has been modified. */
KeyboardData current_layout_unmodified()
{
@@ -92,7 +84,6 @@ public class Keyboard2 extends InputMethodService
_config.set_current_layout(l);
_currentSpecialLayout = null;
_keyboard_layout_view.setKeyboard(current_layout());
notifySwipeLayoutChanged();
}
void incrTextLayout(int delta)
@@ -105,7 +96,6 @@ public class Keyboard2 extends InputMethodService
{
_currentSpecialLayout = l;
_keyboard_layout_view.setKeyboard(l);
notifySwipeLayoutChanged();
}
KeyboardData loadLayout(int layout_id)
@@ -160,37 +150,11 @@ public class Keyboard2 extends InputMethodService
create_keyboard_view();
ClipboardHistoryService.on_startup(this, _keyeventhandler);
_foldStateTracker.setChangedCallback(() -> { refresh_config(); });
// Initialize swipe/glide typing
initSwipeTyping();
}
private void initSwipeTyping()
{
try
{
_swipeAdapter = new MornKeyboardSwipeAdapter(new MornSwipeReceiver());
java.io.InputStream dictStream =
getResources().openRawResource(R.raw.en_us);
if (dictStream != null)
{
_swipeAdapter.initialize(this, dictStream);
Logs.debug("Swipe typing initialized");
}
}
catch (Exception e)
{
Logs.debug("Swipe typing init failed: " + e.getMessage());
_swipeAdapter = null;
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (_swipeAdapter != null)
_swipeAdapter.shutdown();
_foldStateTracker.close();
}
@@ -199,22 +163,6 @@ public class Keyboard2 extends InputMethodService
_keyboard_container_view = (ViewGroup)inflate_view(R.layout.keyboard);
_keyboard_layout_view = (Keyboard2View)_keyboard_container_view.findViewById(R.id.keyboard_view);
_candidates_view = (CandidatesView)_keyboard_container_view.findViewById(R.id.candidates_view);
// Wire swipe gesture handler into the view
if (_swipeAdapter != null)
{
_swipeInput = new SwipeInputHandler(_swipeAdapter, _config,
new SwipeInputHandler.SwipeEventListener()
{
public void onSwipeStarted(int pointerId) {}
public void commitSwipeWord(String word)
{
// Called when we want to commit the swipe word
commitSwipeCandidate(word);
}
});
_keyboard_layout_view.setSwipeInputHandler(_swipeInput);
}
}
InputMethodManager get_imm()
@@ -315,7 +263,6 @@ public class Keyboard2 extends InputMethodService
_keyeventhandler.started(_config);
setInputView(_keyboard_container_view);
Logs.debug_startup_input_view(info, _config);
notifySwipeLayoutChanged();
}
@Override
@@ -455,57 +402,6 @@ public class Keyboard2 extends InputMethodService
start_activity(DictionariesActivity.class);
}
// ====================================================================
// Swipe typing integration
// ====================================================================
/** Receiver for swipe candidate results from MornKeyboardSwipeAdapter. */
private class MornSwipeReceiver
implements MornKeyboardSwipeAdapter.CandidateReceiver
{
public void onCandidatesReady(List<SwipeTypeCandidate> candidates)
{
if (candidates == null || candidates.isEmpty())
return;
// Commit the top candidate automatically
SwipeTypeCandidate top = candidates.get(0);
if (top != null && top.word != null && top.word.length() > 0)
commitSwipeCandidate(top.word);
}
public void onSwipeError(String message)
{
Logs.debug("Swipe error: " + message);
}
}
/** Commit a word from swipe recognition. */
private void commitSwipeCandidate(String word)
{
InputConnection conn = getCurrentInputConnection();
if (conn == null) return;
try
{
conn.beginBatchEdit();
conn.commitText(word + " ", 1);
conn.endBatchEdit();
}
catch (Exception e)
{
Logs.debug("Failed to commit swipe word: " + e.getMessage());
}
}
/** Notify swipe engine that keyboard layout changed. */
private void notifySwipeLayoutChanged()
{
if (_swipeAdapter != null && _keyboard_layout_view != null)
{
KeyboardData layout = current_layout();
_swipeAdapter.layoutChanged(layout, _keyboard_layout_view, _config);
}
}
void start_activity(Class cls)
{
Intent intent = new Intent(this, cls);
@@ -20,9 +20,6 @@ import android.view.WindowMetrics;
import java.util.Arrays;
import java.util.List;
import juloo.mornkeyboard2.swipe.MornKeyboardSwipeAdapter;
import juloo.mornkeyboard2.swipe.SwipeInputHandler;
public class Keyboard2View extends View
implements View.OnTouchListener, Pointers.IPointerEventHandler
{
@@ -37,9 +34,6 @@ public class Keyboard2View extends View
private Pointers _pointers;
/** Swipe gesture typing support. Null when not loaded/initialized. */
private SwipeInputHandler _swipeInput;
private Pointers.Modifiers _mods;
private static int _currentWhat = 0;
@@ -119,12 +113,6 @@ public class Keyboard2View extends View
reset();
}
/** Set the swipe gesture handler. Must be called before layout changes. */
public void setSwipeInputHandler(SwipeInputHandler handler)
{
_swipeInput = handler;
}
public void reset()
{
_mods = Pointers.Modifiers.EMPTY;
@@ -212,8 +200,6 @@ public class Keyboard2View extends View
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
p = event.getActionIndex();
if (_swipeInput != null)
_swipeInput.onTouchUp(event.getPointerId(p));
_pointers.onTouchUp(event.getPointerId(event.getActionIndex()));
break;
case MotionEvent.ACTION_DOWN:
@@ -222,8 +208,6 @@ public class Keyboard2View extends View
float tx = event.getX(p);
float ty = event.getY(p);
KeyboardData.Key key = getKeyAtPosition(tx, ty);
if (_swipeInput != null)
_swipeInput.onTouchDown(tx, ty, event.getPointerId(p), key);
if (key != null)
_pointers.onTouchDown(tx, ty, event.getPointerId(p), key);
break;
@@ -233,14 +217,10 @@ public class Keyboard2View extends View
float mx = event.getX(p);
float my = event.getY(p);
int pid = event.getPointerId(p);
if (_swipeInput != null)
_swipeInput.onTouchMove(mx, my, pid);
_pointers.onTouchMove(mx, my, pid);
}
break;
case MotionEvent.ACTION_CANCEL:
if (_swipeInput != null)
_swipeInput.cancelAll();
_pointers.onTouchCancel();
break;
default:
+14 -2
View File
@@ -78,7 +78,7 @@ class VoiceImeSwitcher
static void switch_input_method(InputMethodService ims, IME ime)
{
if (VERSION.SDK_INT < 28)
if (ime.subtype == null || VERSION.SDK_INT < 28)
ims.switchInputMethod(ime.get_id());
else
ims.switchInputMethod(ime.get_id(), ime.subtype);
@@ -105,9 +105,21 @@ class VoiceImeSwitcher
{
List<IME> imes = new ArrayList<IME>();
for (InputMethodInfo im : imm.getEnabledInputMethodList())
{
boolean foundVoice = false;
for (InputMethodSubtype imst : imm.getEnabledInputMethodSubtypeList(im, true))
{
if (imst.getMode().equals("voice"))
{
imes.add(new IME(im, imst));
foundVoice = true;
}
}
// Android 14+: Google Voice Typing is a standalone auxiliary IME with no
// voice subtypes (getSubtypeCount() == 0). Detect by service name pattern.
if (!foundVoice && im.getSubtypeCount() == 0)
imes.add(new IME(im, null));
}
return imes;
}
@@ -140,7 +152,7 @@ class VoiceImeSwitcher
String get_display_name(Context ctx)
{
String subtype_name = "";
if (VERSION.SDK_INT >= 14)
if (subtype != null && VERSION.SDK_INT >= 14)
{
subtype_name = subtype.getDisplayName(ctx, im.getPackageName(), null).toString();
if (!subtype_name.equals(""))