Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 58d946b7 authored by Nicolas Melin's avatar Nicolas Melin
Browse files

Add an explicit opt-in when using Murena Voice to Text the first time

parent 4ade1531
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -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>
+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
        }
    }
}
+58 −5
Original line number Diff line number Diff line
@@ -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
@@ -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) {
@@ -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()
            }
        }
    }
@@ -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")
        }
    }
}
+27 −13
Original line number Diff line number Diff line
@@ -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) {

@@ -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)
            }
        }
    }
+8 −1
Original line number Diff line number Diff line
@@ -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) {
@@ -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