diff --git a/build.gradle b/build.gradle
index 78ecd02275d47861a0319f3e952728ff0495b5ef..d0e5c480bf3e2417bee67812010c183a771c653f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -15,23 +15,12 @@ android {
namespace 'com.android.inputmethod.latin'
- // Required if using classes in android.test.runner
- useLibrary 'android.test.runner'
-
- // Required if using classes in android.test.base
- useLibrary 'android.test.base'
-
- // Required if using classes in android.test.mock
- useLibrary 'android.test.mock'
-
defaultConfig {
minSdkVersion 30
targetSdkVersion 34
versionName "1.0"
applicationId 'com.android.inputmethod.latin'
- testApplicationId 'com.android.inputmethod.latin.tests'
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = false
signingConfig signingConfigs.debug
@@ -65,12 +54,6 @@ android {
java.srcDirs = ['common/src', 'java/src']
manifest.srcFile 'java/AndroidManifest.xml'
}
-
- androidTest {
- res.srcDirs = ['tests/res']
- java.srcDirs = ['tests/src']
- manifest.srcFile "tests/AndroidManifest.xml"
- }
}
@@ -90,6 +73,7 @@ android {
repositories {
maven { url "../../../prebuilts/fullsdk-darwin/extras/android/m2repository" }
maven { url "../../../prebuilts/fullsdk-linux/extras/android/m2repository" }
+ maven { url "https://gitlab.e.foundation/api/v4/groups/9/-/packages/maven" }
mavenCentral()
google()
jcenter()
@@ -101,12 +85,5 @@ dependencies {
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.preference:preference:1.2.1'
- testImplementation 'junit:junit:4.12'
- androidTestImplementation "org.mockito:mockito-core:1.9.5"
- androidTestImplementation 'com.google.dexmaker:dexmaker:1.2'
- androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:1.2'
- androidTestImplementation 'androidx.test.ext:junit:1.1.1'
- androidTestImplementation 'androidx.test:rules:1.1.1'
- androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
- androidTestImplementation 'androidx.annotation:annotation:1.0.0'
+ implementation("foundation.e:elib:0.0.1-alpha11")
}
diff --git a/java/Android.bp b/java/Android.bp
index 054065ebf89a1d6b8ae1b80127368f346b40b7a7..895ac1cfa171e9fce7f327604cb14e4c6f5c5f2a 100644
--- a/java/Android.bp
+++ b/java/Android.bp
@@ -47,6 +47,7 @@ android_app {
"androidx.legacy_legacy-support-v4",
"androidx.recyclerview_recyclerview",
"androidx.viewpager2_viewpager2",
+ "elib",
],
// Do not compress dictionary files to mmap dict data runtime
diff --git a/java/res/drawable/ic_ime_switch.xml b/java/res/drawable/ic_ime_switch.xml
new file mode 100644
index 0000000000000000000000000000000000000000..128cd4451e48b59a197477b5f246d3c54a5dc0ee
--- /dev/null
+++ b/java/res/drawable/ic_ime_switch.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/java/res/layout/main_keyboard_frame.xml b/java/res/layout/main_keyboard_frame.xml
index ebf746679c603d9899ed78f591c665bfcf1c806d..e27a993dda99d660f67c47ea9edd0caca57fb7fb 100644
--- a/java/res/layout/main_keyboard_frame.xml
+++ b/java/res/layout/main_keyboard_frame.xml
@@ -18,28 +18,46 @@
*/
-->
-
+ 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/res/xml/prefs_screen_preferences.xml b/java/res/xml/prefs_screen_preferences.xml
index d2dc51e7f1c369410d8a3403a12e69eb5339b29c..c7f8b588bb35c8b04010da07302a84922b0bbba0 100644
--- a/java/res/xml/prefs_screen_preferences.xml
+++ b/java/res/xml/prefs_screen_preferences.xml
@@ -66,6 +66,6 @@
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 3c891716d9b8cc0d402869e86b8ee5b94a678dd6..cdd3a7244d339de4ba2a3a9e73fe8963c662454c 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -56,7 +56,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;
@@ -75,6 +78,7 @@ import com.android.inputmethod.event.InputTransaction;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardActionListener;
import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.keyboard.KeyboardLayoutSet;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.MainKeyboardView;
import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
@@ -104,6 +108,8 @@ import com.android.inputmethod.latin.utils.StatsUtilsManager;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import com.android.inputmethod.latin.utils.ViewLayoutUtils;
+import foundation.e.stt.AccountInfoContentProvider;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -131,6 +137,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 +218,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;
@@ -808,6 +818,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
cleanupInternalStateForFinishInput();
}
}
+ KeyboardLayoutSet.onKeyboardThemeChanged();
super.onConfigurationChanged(conf);
}
@@ -856,6 +867,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);
@@ -866,6 +894,30 @@ 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() {
+ final SettingsValues currentSettingsValues = mSettings.getCurrent();
+ if (shouldShowFloatingButton()
+ && currentSettingsValues.mInputAttributes.mShouldShowSuggestions) {
+ floatingButton.setVisibility(View.VISIBLE);
+ } else {
+ floatingButton.setVisibility(View.GONE);
+ }
+ }
+
+ private boolean shouldShowFloatingButton() {
+ return AccountInfoContentProvider.isPremiumAccount(getApplicationContext())
+ && isSttKeyboardActivated() && hasSuggestionStripView();
}
@Override
@@ -882,6 +934,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/com/android/inputmethod/latin/SystemBroadcastReceiver.java b/java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java
index 90221512ffd752d2f6569e058d815b415ef84d53..109cace92f1c796b8ccea58606094416a22d8ae9 100644
--- a/java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java
+++ b/java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java
@@ -86,6 +86,7 @@ public final class SystemBroadcastReceiver extends BroadcastReceiver {
} else if (Intent.ACTION_BOOT_COMPLETED.equals(intentAction)) {
Log.i(TAG, "Boot has been completed");
toggleAppIcon(context);
+ disableVoiceKey(context);
} else if (Intent.ACTION_LOCALE_CHANGED.equals(intentAction)) {
Log.i(TAG, "System locale changed");
KeyboardLayoutSet.onSystemLocaleChanged();
@@ -156,4 +157,15 @@ public final class SystemBroadcastReceiver extends BroadcastReceiver {
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
}
+
+ public static void disableVoiceKey(final Context context) {
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ if (prefs.getBoolean(Settings.PREF_VOICE_INPUT_KEY, false) &&
+ !prefs.getBoolean(Settings.PREF_FORCED_DISABLE_VOICE_INPUT_KEY, false)) {
+ prefs.edit()
+ .putBoolean(Settings.PREF_VOICE_INPUT_KEY, false)
+ .putBoolean(Settings.PREF_FORCED_DISABLE_VOICE_INPUT_KEY, true)
+ .apply();
+ }
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index 803153e7b694d0d569c557c933c8b1fe4a20cca2..420b61a32c93cbf601511a1aa0c3c5cb8ec01735 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -58,6 +58,8 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
// PREF_VOICE_MODE_OBSOLETE is obsolete. Use PREF_VOICE_INPUT_KEY instead.
public static final String PREF_VOICE_MODE_OBSOLETE = "voice_mode";
public static final String PREF_VOICE_INPUT_KEY = "pref_voice_input_key";
+ // Added by /e/ to disable old enabled voice key
+ public static final String PREF_FORCED_DISABLE_VOICE_INPUT_KEY = "pref_forced_disable_voice_input_key";
public static final String PREF_EDIT_PERSONAL_DICTIONARY = "edit_personal_dictionary";
public static final String PREF_CONFIGURE_DICTIONARIES_KEY = "configure_dictionaries_key";
// PREF_AUTO_CORRECTION_THRESHOLD_OBSOLETE is obsolete. Use PREF_AUTO_CORRECTION instead.
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index c4219b9d50ab47922e933f614cb88dd60f3b52cc..6055b791602398bf42f0d6b6b1bf0587b584268e 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -377,7 +377,7 @@ public class SettingsValues {
.remove(Settings.PREF_VOICE_MODE_OBSOLETE)
.apply();
}
- return prefs.getBoolean(Settings.PREF_VOICE_INPUT_KEY, true);
+ return prefs.getBoolean(Settings.PREF_VOICE_INPUT_KEY, false);
}
public String dump() {
diff --git a/java/src/foundation/e/stt/AccountInfoContentProvider.java b/java/src/foundation/e/stt/AccountInfoContentProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..a3344a3ebb31bb34d556b91f01a8a4c75e3e9381
--- /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;
+ }
+}