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:
@@ -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:
|
||||
|
||||
@@ -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(""))
|
||||
|
||||
Reference in New Issue
Block a user