Loading android/app/src/main/AndroidManifest.xml +5 −0 Original line number Diff line number Diff line Loading @@ -57,6 +57,11 @@ <action android:name="android.intent.action.MAIN" /> </intent-filter> </activity> <activity android:name=".ConsentActivity" android:theme="@style/ActivityAlertDialogTheme" android:exported="false"> </activity> <receiver android:name=".StartupReceiver" android:exported="false"> <intent-filter> Loading android/app/src/main/java/foundation/e/stt/ConsentActivity.kt 0 → 100644 +83 −0 Original line number Diff line number Diff line package foundation.e.stt import android.content.Intent import android.graphics.Paint import android.graphics.text.LineBreaker import android.os.Build import android.os.Bundle import android.widget.TextView import androidx.annotation.IntDef import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import com.google.android.material.dialog.MaterialAlertDialogBuilder import androidx.core.net.toUri @IntDef(ConsentActivity.CONSENT_NOT_YET_GIVEN, ConsentActivity.CONSENT_GIVEN, ConsentActivity.CONSENT_REFUSED) @Retention(AnnotationRetention.SOURCE) annotation class ConsentStatus class ConsentActivity : AppCompatActivity() { companion object { const val CONSENT_NOT_YET_GIVEN = 0 const val CONSENT_GIVEN = 1 const val CONSENT_REFUSED = 2 @ConsentStatus fun hasGivenConsent(): Int = UserPreference.hasGivenConsentForSTT fun setConsentGiven(@ConsentStatus given: Int) { UserPreference.hasGivenConsentForSTT = given } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) UserPreference.init(this) if (hasGivenConsent() != CONSENT_NOT_YET_GIVEN) { finish() return } showConsentDialog() } private fun showConsentDialog() { val messageTextView = TextView(this).apply { text = getString(R.string.consent_dialog_body) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { justificationMode = LineBreaker.JUSTIFICATION_MODE_INTER_WORD } setPadding(50, 50, 50, 50) } val dialog = MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme) .setTitle(R.string.consent_dialog_title) .setView(messageTextView) .setPositiveButton(R.string.consent_dialog_yes) { _, _ -> setConsentGiven(CONSENT_GIVEN) finish() } .setNegativeButton(R.string.consent_dialog_no) { _, _ -> setConsentGiven(CONSENT_REFUSED) finish() } .setNeutralButton(R.string.consent_dialog_terms_of_service, null) .setCancelable(false) .show() dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener { val url = getString(R.string.consent_dialog_terms_of_service_url) val intent = Intent(Intent.ACTION_VIEW, url.toUri()) intent.resolveActivity(packageManager)?.let { startActivity(intent) } } dialog.getButton(AlertDialog.BUTTON_NEUTRAL)?.apply { paintFlags = paintFlags or Paint.UNDERLINE_TEXT_FLAG } } } android/app/src/main/java/foundation/e/stt/MurenaWhisperInputService.kt +58 −5 Original line number Diff line number Diff line Loading @@ -2,6 +2,7 @@ package foundation.e.stt import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.graphics.Color import android.inputmethodservice.InputMethodService import android.net.ConnectivityManager Loading Loading @@ -62,6 +63,8 @@ class MurenaWhisperInputService : InputMethodService() { private var alreadyInitialized: Boolean = false private var accountIsPremium: Boolean = false private lateinit var consentPrefs: SharedPreferences private val consentListener = createConsentListener() fun initApp() { if (!accountIsPremium) { Loading Loading @@ -355,19 +358,29 @@ class MurenaWhisperInputService : InputMethodService() { } override fun onWindowShown() { Log.d(TAG, "onWindowShown"); Log.d(TAG, "onWindowShown") CoroutineScope(Dispatchers.Main).launch { hideKeyboard() accountIsPremium = AccountManagerHelper.accountIsPremium(this@MurenaWhisperInputService) Log.d(TAG, "accountIsPremium: $accountIsPremium") super.onWindowShown() if (!alreadyInitialized) { initApp() alreadyInitialized = true UserPreference.init(this@MurenaWhisperInputService); if (ConsentActivity.hasGivenConsent() == ConsentActivity.CONSENT_NOT_YET_GIVEN) { consentPrefs = applicationContext .getSharedPreferences(UserPreference.PREF_NAME, Context.MODE_PRIVATE) consentPrefs.registerOnSharedPreferenceChangeListener(consentListener) val intent = Intent(applicationContext, ConsentActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK startActivity(intent) } else { Log.d(TAG, "Keyboard already initialized") showKeyboard() initIfNeeded() } } } Loading Loading @@ -401,4 +414,44 @@ class MurenaWhisperInputService : InputMethodService() { languageBtn?.visibility = View.VISIBLE } } private fun hideKeyboard() { Handler(Looper.getMainLooper()).post { keyboardView?.visibility = View.INVISIBLE } } private fun showKeyboard() { Handler(Looper.getMainLooper()).post { keyboardView?.visibility = View.VISIBLE } } private fun createConsentListener(): SharedPreferences.OnSharedPreferenceChangeListener { return SharedPreferences.OnSharedPreferenceChangeListener { sp, key -> UserPreference.init(this) if (key == UserPreference.PREF_CONSENT_STT) { sp.unregisterOnSharedPreferenceChangeListener(consentListener) val granted = sp.getInt(key, ConsentActivity.CONSENT_NOT_YET_GIVEN) if (granted == ConsentActivity.CONSENT_GIVEN) { showKeyboard() initIfNeeded() } else { StartupReceiver.SttKeyboardManager.disableSttKeyboard(this) onSwitchIme() } } } } private fun initIfNeeded() { if (!alreadyInitialized) { initApp() alreadyInitialized = true } else { Log.d(TAG, "Keyboard already initialized") } } } android/app/src/main/java/foundation/e/stt/StartupReceiver.kt +27 −13 Original line number Diff line number Diff line Loading @@ -6,13 +6,33 @@ import android.content.ContentResolver import android.content.Context import android.content.Intent import android.provider.Settings import android.text.TextUtils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import foundation.e.stt.UserPreference class StartupReceiver : BroadcastReceiver() { object SttKeyboardManager { fun disableSttKeyboard(context: Context) { val cr: ContentResolver? = context.contentResolver val imeId = ComponentName(context, MurenaWhisperInputService::class.java).flattenToShortString() val enabledImes: String? = Settings.Secure.getString(cr, Settings.Secure.ENABLED_INPUT_METHODS) val newEnabledImes = enabledImes ?.split(":") ?.filter { it != imeId } ?.joinToString(":") Settings.Secure.putString(cr, Settings.Secure.ENABLED_INPUT_METHODS, newEnabledImes) } fun enabledSttKeyboard(context: Context) { val cr: ContentResolver? = context.contentResolver val imeId = ComponentName(context, MurenaWhisperInputService::class.java).flattenToShortString() var enabledImes: String? = Settings.Secure.getString(cr, Settings.Secure.ENABLED_INPUT_METHODS) enabledImes = if (enabledImes.isNullOrEmpty()) imeId else "$enabledImes:$imeId" UserPreference.isSttFeatureActivated = true Settings.Secure.putString(cr, Settings.Secure.ENABLED_INPUT_METHODS, enabledImes) } } override fun onReceive(context: Context, intent: Intent) { if (Intent.ACTION_BOOT_COMPLETED == intent.action) { Loading @@ -25,23 +45,17 @@ class StartupReceiver : BroadcastReceiver() { CoroutineScope(Dispatchers.Main).launch { var enabledImes: String? = Settings.Secure.getString(cr, Settings.Secure.ENABLED_INPUT_METHODS) val accountIsPremium = AccountManagerHelper.accountIsPremium(context) val accountIsPremium = AccountManagerHelper.accountIsPremium(context.applicationContext) val sttKeyboardActivated = !enabledImes.isNullOrEmpty() && enabledImes.contains(imeId) if (accountIsPremium && !sttKeyboardActivated && !UserPreference.isSttFeatureActivated) { enabledImes = if (enabledImes.isNullOrEmpty()) imeId else "$enabledImes:$imeId" UserPreference.isSttFeatureActivated = true Settings.Secure.putString(cr, Settings.Secure.ENABLED_INPUT_METHODS, enabledImes) if (accountIsPremium && !sttKeyboardActivated && !UserPreference.isSttFeatureActivated && UserPreference.hasGivenConsentForSTT != ConsentActivity.CONSENT_REFUSED) { SttKeyboardManager.enabledSttKeyboard(context) } else if (!accountIsPremium && sttKeyboardActivated) { // Remove the STT keyboard if it's enabled for a non-premium account val newEnabledImes = enabledImes ?.split(":") ?.filter { it != imeId } ?.joinToString(":") Settings.Secure.putString(cr, Settings.Secure.ENABLED_INPUT_METHODS, newEnabledImes) SttKeyboardManager.disableSttKeyboard(context) } AccountManagerHelper.refreshCache(context) AccountManagerHelper.refreshCache(context.applicationContext) } } } Loading android/app/src/main/java/foundation/e/stt/UserPreference.kt +8 −1 Original line number Diff line number Diff line Loading @@ -26,8 +26,9 @@ object UserPreference { private lateinit var sharedPreferences: SharedPreferences private val PREF_NAME = "user_preference" val PREF_NAME = "user_preference" private val PREF_AUTO_ACTIVATED = "auto_activated" val PREF_CONSENT_STT = "consent_stt" fun init(context: Context) { if (!::sharedPreferences.isInitialized) { Loading @@ -43,4 +44,10 @@ object UserPreference { var isSttFeatureActivated: Boolean get() = sharedPreferences.getBoolean(PREF_AUTO_ACTIVATED, false) set(value) = sharedPreferences.edit() { putBoolean(PREF_AUTO_ACTIVATED, value) } @get:ConsentStatus @setparam:ConsentStatus var hasGivenConsentForSTT: Int get() = sharedPreferences.getInt(PREF_CONSENT_STT, ConsentActivity.CONSENT_NOT_YET_GIVEN) set(value) = sharedPreferences.edit() { putInt(PREF_CONSENT_STT, value) } } Loading
android/app/src/main/AndroidManifest.xml +5 −0 Original line number Diff line number Diff line Loading @@ -57,6 +57,11 @@ <action android:name="android.intent.action.MAIN" /> </intent-filter> </activity> <activity android:name=".ConsentActivity" android:theme="@style/ActivityAlertDialogTheme" android:exported="false"> </activity> <receiver android:name=".StartupReceiver" android:exported="false"> <intent-filter> Loading
android/app/src/main/java/foundation/e/stt/ConsentActivity.kt 0 → 100644 +83 −0 Original line number Diff line number Diff line package foundation.e.stt import android.content.Intent import android.graphics.Paint import android.graphics.text.LineBreaker import android.os.Build import android.os.Bundle import android.widget.TextView import androidx.annotation.IntDef import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import com.google.android.material.dialog.MaterialAlertDialogBuilder import androidx.core.net.toUri @IntDef(ConsentActivity.CONSENT_NOT_YET_GIVEN, ConsentActivity.CONSENT_GIVEN, ConsentActivity.CONSENT_REFUSED) @Retention(AnnotationRetention.SOURCE) annotation class ConsentStatus class ConsentActivity : AppCompatActivity() { companion object { const val CONSENT_NOT_YET_GIVEN = 0 const val CONSENT_GIVEN = 1 const val CONSENT_REFUSED = 2 @ConsentStatus fun hasGivenConsent(): Int = UserPreference.hasGivenConsentForSTT fun setConsentGiven(@ConsentStatus given: Int) { UserPreference.hasGivenConsentForSTT = given } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) UserPreference.init(this) if (hasGivenConsent() != CONSENT_NOT_YET_GIVEN) { finish() return } showConsentDialog() } private fun showConsentDialog() { val messageTextView = TextView(this).apply { text = getString(R.string.consent_dialog_body) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { justificationMode = LineBreaker.JUSTIFICATION_MODE_INTER_WORD } setPadding(50, 50, 50, 50) } val dialog = MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme) .setTitle(R.string.consent_dialog_title) .setView(messageTextView) .setPositiveButton(R.string.consent_dialog_yes) { _, _ -> setConsentGiven(CONSENT_GIVEN) finish() } .setNegativeButton(R.string.consent_dialog_no) { _, _ -> setConsentGiven(CONSENT_REFUSED) finish() } .setNeutralButton(R.string.consent_dialog_terms_of_service, null) .setCancelable(false) .show() dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener { val url = getString(R.string.consent_dialog_terms_of_service_url) val intent = Intent(Intent.ACTION_VIEW, url.toUri()) intent.resolveActivity(packageManager)?.let { startActivity(intent) } } dialog.getButton(AlertDialog.BUTTON_NEUTRAL)?.apply { paintFlags = paintFlags or Paint.UNDERLINE_TEXT_FLAG } } }
android/app/src/main/java/foundation/e/stt/MurenaWhisperInputService.kt +58 −5 Original line number Diff line number Diff line Loading @@ -2,6 +2,7 @@ package foundation.e.stt import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.graphics.Color import android.inputmethodservice.InputMethodService import android.net.ConnectivityManager Loading Loading @@ -62,6 +63,8 @@ class MurenaWhisperInputService : InputMethodService() { private var alreadyInitialized: Boolean = false private var accountIsPremium: Boolean = false private lateinit var consentPrefs: SharedPreferences private val consentListener = createConsentListener() fun initApp() { if (!accountIsPremium) { Loading Loading @@ -355,19 +358,29 @@ class MurenaWhisperInputService : InputMethodService() { } override fun onWindowShown() { Log.d(TAG, "onWindowShown"); Log.d(TAG, "onWindowShown") CoroutineScope(Dispatchers.Main).launch { hideKeyboard() accountIsPremium = AccountManagerHelper.accountIsPremium(this@MurenaWhisperInputService) Log.d(TAG, "accountIsPremium: $accountIsPremium") super.onWindowShown() if (!alreadyInitialized) { initApp() alreadyInitialized = true UserPreference.init(this@MurenaWhisperInputService); if (ConsentActivity.hasGivenConsent() == ConsentActivity.CONSENT_NOT_YET_GIVEN) { consentPrefs = applicationContext .getSharedPreferences(UserPreference.PREF_NAME, Context.MODE_PRIVATE) consentPrefs.registerOnSharedPreferenceChangeListener(consentListener) val intent = Intent(applicationContext, ConsentActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK startActivity(intent) } else { Log.d(TAG, "Keyboard already initialized") showKeyboard() initIfNeeded() } } } Loading Loading @@ -401,4 +414,44 @@ class MurenaWhisperInputService : InputMethodService() { languageBtn?.visibility = View.VISIBLE } } private fun hideKeyboard() { Handler(Looper.getMainLooper()).post { keyboardView?.visibility = View.INVISIBLE } } private fun showKeyboard() { Handler(Looper.getMainLooper()).post { keyboardView?.visibility = View.VISIBLE } } private fun createConsentListener(): SharedPreferences.OnSharedPreferenceChangeListener { return SharedPreferences.OnSharedPreferenceChangeListener { sp, key -> UserPreference.init(this) if (key == UserPreference.PREF_CONSENT_STT) { sp.unregisterOnSharedPreferenceChangeListener(consentListener) val granted = sp.getInt(key, ConsentActivity.CONSENT_NOT_YET_GIVEN) if (granted == ConsentActivity.CONSENT_GIVEN) { showKeyboard() initIfNeeded() } else { StartupReceiver.SttKeyboardManager.disableSttKeyboard(this) onSwitchIme() } } } } private fun initIfNeeded() { if (!alreadyInitialized) { initApp() alreadyInitialized = true } else { Log.d(TAG, "Keyboard already initialized") } } }
android/app/src/main/java/foundation/e/stt/StartupReceiver.kt +27 −13 Original line number Diff line number Diff line Loading @@ -6,13 +6,33 @@ import android.content.ContentResolver import android.content.Context import android.content.Intent import android.provider.Settings import android.text.TextUtils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import foundation.e.stt.UserPreference class StartupReceiver : BroadcastReceiver() { object SttKeyboardManager { fun disableSttKeyboard(context: Context) { val cr: ContentResolver? = context.contentResolver val imeId = ComponentName(context, MurenaWhisperInputService::class.java).flattenToShortString() val enabledImes: String? = Settings.Secure.getString(cr, Settings.Secure.ENABLED_INPUT_METHODS) val newEnabledImes = enabledImes ?.split(":") ?.filter { it != imeId } ?.joinToString(":") Settings.Secure.putString(cr, Settings.Secure.ENABLED_INPUT_METHODS, newEnabledImes) } fun enabledSttKeyboard(context: Context) { val cr: ContentResolver? = context.contentResolver val imeId = ComponentName(context, MurenaWhisperInputService::class.java).flattenToShortString() var enabledImes: String? = Settings.Secure.getString(cr, Settings.Secure.ENABLED_INPUT_METHODS) enabledImes = if (enabledImes.isNullOrEmpty()) imeId else "$enabledImes:$imeId" UserPreference.isSttFeatureActivated = true Settings.Secure.putString(cr, Settings.Secure.ENABLED_INPUT_METHODS, enabledImes) } } override fun onReceive(context: Context, intent: Intent) { if (Intent.ACTION_BOOT_COMPLETED == intent.action) { Loading @@ -25,23 +45,17 @@ class StartupReceiver : BroadcastReceiver() { CoroutineScope(Dispatchers.Main).launch { var enabledImes: String? = Settings.Secure.getString(cr, Settings.Secure.ENABLED_INPUT_METHODS) val accountIsPremium = AccountManagerHelper.accountIsPremium(context) val accountIsPremium = AccountManagerHelper.accountIsPremium(context.applicationContext) val sttKeyboardActivated = !enabledImes.isNullOrEmpty() && enabledImes.contains(imeId) if (accountIsPremium && !sttKeyboardActivated && !UserPreference.isSttFeatureActivated) { enabledImes = if (enabledImes.isNullOrEmpty()) imeId else "$enabledImes:$imeId" UserPreference.isSttFeatureActivated = true Settings.Secure.putString(cr, Settings.Secure.ENABLED_INPUT_METHODS, enabledImes) if (accountIsPremium && !sttKeyboardActivated && !UserPreference.isSttFeatureActivated && UserPreference.hasGivenConsentForSTT != ConsentActivity.CONSENT_REFUSED) { SttKeyboardManager.enabledSttKeyboard(context) } else if (!accountIsPremium && sttKeyboardActivated) { // Remove the STT keyboard if it's enabled for a non-premium account val newEnabledImes = enabledImes ?.split(":") ?.filter { it != imeId } ?.joinToString(":") Settings.Secure.putString(cr, Settings.Secure.ENABLED_INPUT_METHODS, newEnabledImes) SttKeyboardManager.disableSttKeyboard(context) } AccountManagerHelper.refreshCache(context) AccountManagerHelper.refreshCache(context.applicationContext) } } } Loading
android/app/src/main/java/foundation/e/stt/UserPreference.kt +8 −1 Original line number Diff line number Diff line Loading @@ -26,8 +26,9 @@ object UserPreference { private lateinit var sharedPreferences: SharedPreferences private val PREF_NAME = "user_preference" val PREF_NAME = "user_preference" private val PREF_AUTO_ACTIVATED = "auto_activated" val PREF_CONSENT_STT = "consent_stt" fun init(context: Context) { if (!::sharedPreferences.isInitialized) { Loading @@ -43,4 +44,10 @@ object UserPreference { var isSttFeatureActivated: Boolean get() = sharedPreferences.getBoolean(PREF_AUTO_ACTIVATED, false) set(value) = sharedPreferences.edit() { putBoolean(PREF_AUTO_ACTIVATED, value) } @get:ConsentStatus @setparam:ConsentStatus var hasGivenConsentForSTT: Int get() = sharedPreferences.getInt(PREF_CONSENT_STT, ConsentActivity.CONSENT_NOT_YET_GIVEN) set(value) = sharedPreferences.edit() { putInt(PREF_CONSENT_STT, value) } }