diff --git a/java/res/drawable/ic_ime_switch.xml b/java/res/drawable/ic_ime_switch.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2302161c1c25ccb8a22cee698953551e94b27131
--- /dev/null
+++ b/java/res/drawable/ic_ime_switch.xml
@@ -0,0 +1,9 @@
+
+
+
\ No newline at end of file
diff --git a/java/res/layout/main_keyboard_frame.xml b/java/res/layout/main_keyboard_frame.xml
index ebf746679c603d9899ed78f591c665bfcf1c806d..89e0f3a96a930fafddb98174e69fb29c1166bf24 100644
--- a/java/res/layout/main_keyboard_frame.xml
+++ b/java/res/layout/main_keyboard_frame.xml
@@ -18,28 +18,48 @@
*/
-->
-
+ android:layout_gravity="bottom">
-
-
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ android:orientation="vertical">
-
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/java/res/values/e-strings.xml b/java/res/values/e-strings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..b4012e6a6c8db24c980ba6af44a8a61467c3bbe0
--- /dev/null
+++ b/java/res/values/e-strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ Speech to text
+
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 45ebcbc21b0e71b9b9303e56774f4105002f5db6..eb7bfcbebd16f94ddec07eaac67486be64a3aa5e 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -23,6 +23,10 @@ import static com.android.inputmethod.latin.common.Constants.ImeOption.NO_MICROP
import static com.android.inputmethod.latin.common.Constants.ImeOption.NO_MICROPHONE_COMPAT;
import android.Manifest.permission;
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.pm.PackageManager;
+import androidx.core.content.ContextCompat;
import android.app.ActivityOptions;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
@@ -56,7 +60,10 @@ import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
+import android.widget.ImageButton;
import androidx.annotation.NonNull;
@@ -104,6 +111,9 @@ import com.android.inputmethod.latin.utils.StatsUtilsManager;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import com.android.inputmethod.latin.utils.ViewLayoutUtils;
+import android.content.ContentResolver;
+
+import foundation.e.stt.AccountInfoContentProvider;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -131,6 +141,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private static final int MAX_SPACESLIDE_CHARS = 32;
+ private static final String KEY_STT_KEYBOARD = "foundation.e.stt/.MurenaWhisperInputService";
+
/**
* A broadcast intent action to hide the software keyboard.
*/
@@ -210,6 +222,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public final UIHandler mHandler = new UIHandler(this);
+ private ImageButton floatingButton;
+
public static final class UIHandler extends LeakGuardHandlerWrapper {
private static final int MSG_UPDATE_SHIFT_STATE = 0;
private static final int MSG_PENDING_IMS_CALLBACK = 1;
@@ -841,6 +855,23 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mIsHardwareAcceleratedDrawingEnabled);
}
+ private boolean isSttKeyboardActivated() {
+ InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ List enabledImeList = inputMethodManager.getEnabledInputMethodList();
+ for (InputMethodInfo inputMethodInfo : enabledImeList) {
+ if (inputMethodInfo.getId().equals(KEY_STT_KEYBOARD)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void switchToSttIme() {
+ if (isSttKeyboardActivated()) {
+ switchInputMethod(KEY_STT_KEYBOARD);
+ }
+ }
+
@Override
public void setInputView(final View view) {
super.setInputView(view);
@@ -851,6 +882,27 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (hasSuggestionStripView()) {
mSuggestionStripView.setListener(this, view);
}
+
+ floatingButton = mInputView.findViewById(R.id.floating_button);
+ floatingButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ switchToSttIme();
+ }
+ });
+ manageFloatingButtonVisibility();
+ }
+
+ private void manageFloatingButtonVisibility() {
+ if (shouldShowFloatingButton()) {
+ floatingButton.setVisibility(View.VISIBLE);
+ } else {
+ floatingButton.setVisibility(View.GONE);
+ }
+ }
+
+ private boolean shouldShowFloatingButton() {
+ return AccountInfoContentProvider.isPremiumAccount(getApplicationContext()) && isSttKeyboardActivated();
}
@Override
@@ -867,6 +919,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public void onStartInputView(final EditorInfo editorInfo, final boolean restarting) {
mHandler.onStartInputView(editorInfo, restarting);
mStatsUtilsManager.onStartInputView();
+
+ if (floatingButton != null) {
+ manageFloatingButtonVisibility();
+ }
}
@Override
diff --git a/java/src/foundation/e/stt/AccountInfoContentProvider.java b/java/src/foundation/e/stt/AccountInfoContentProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..e38aef6ff8a812ee9afd389b6e48acfdae019e74
--- /dev/null
+++ b/java/src/foundation/e/stt/AccountInfoContentProvider.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2025 MURENA SAS
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+package foundation.e.stt;
+
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.content.Context;
+import android.util.Log;
+
+public class AccountInfoContentProvider {
+
+ private static final String TAG = "AccountInfoContentProvider";
+ private static final Uri ACCOUNT_URI = Uri.parse("content://foundation.e.stt.provider/account");
+ private static final String COLUMN_IS_PREMIUM = "is_premium";
+ private static final int PREMIUM_VALUE = 1;
+
+ public static boolean isPremiumAccount(Context context) {
+ Cursor cursor = null;
+ try {
+ ContentResolver resolver = context.getContentResolver();
+ String[] projection = new String[] { COLUMN_IS_PREMIUM };
+ cursor = resolver.query(ACCOUNT_URI, projection, null, null, null);
+
+ if (cursor != null && cursor.moveToFirst()) {
+ int columnIndex = cursor.getColumnIndex(COLUMN_IS_PREMIUM);
+ if (columnIndex != -1) {
+ int value = cursor.getInt(columnIndex);
+ return value == PREMIUM_VALUE;
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error failed to read status", e);
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return false;
+ }
+}
\ No newline at end of file