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

Commit 30fc6811 authored by Frank Preel's avatar Frank Preel Committed by Mohammed Althaf T
Browse files

LatinIME: Add icon on the keyboard to access STT feature

parent b5fe3769
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:height="24dp"
    android:viewportHeight="24"
    android:viewportWidth="24"
    android:width="24dp">
    <path
        android:fillColor="@color/e_keyboard_key_text"
        android:pathData="M12,14c1.66,0 2.99,-1.34 2.99,-3L15,5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v6c0,1.66 1.34,3 3,3zM17.3,11c0,3 -2.54,5.1 -5.3,5.1S6.7,14 6.7,11L5,11c0,3.41 2.72,6.23 6,6.72L11,21h2v-3.28c3.28,-0.48 6,-3.3 6,-6.72h-1.7z"/>
</vector>
+37 −19
Original line number Diff line number Diff line
@@ -18,10 +18,15 @@
*/
-->

<LinearLayout
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:orientation="vertical">

@@ -43,3 +48,16 @@
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>

    <ImageButton
        android:id="@+id/floating_button"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:padding="12dp"
        android:layout_gravity="top|end"
        android:background="@android:color/transparent"
        android:src="@drawable/ic_ime_switch"
        android:contentDescription="@string/ime_switch_button"
        android:focusable="true"
        android:clickable="true" />
</FrameLayout>
+19 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright Murena SAS 2025
  ~ 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 <https://www.gnu.org/licenses/>.
  -->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="ime_switch_button">Speech to text</string>
</resources>
+76 −0
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.inputmethodservice.InputMethodService;
@@ -51,12 +52,16 @@ import android.view.Display;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
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;

@@ -105,6 +110,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;
@@ -132,6 +139,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.
     */
@@ -211,6 +220,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen

    public final UIHandler mHandler = new UIHandler(this);

    private ImageButton mFloatingButton;
    private final Rect mFloatingButtonTouchableBounds = new Rect();

    public static final class UIHandler extends LeakGuardHandlerWrapper<LatinIME> {
        private static final int MSG_UPDATE_SHIFT_STATE = 0;
        private static final int MSG_PENDING_IMS_CALLBACK = 1;
@@ -858,6 +870,23 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
                mIsHardwareAcceleratedDrawingEnabled);
    }

    private boolean isSttKeyboardActivated() {
        InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        List<InputMethodInfo> 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);
@@ -868,6 +897,30 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
        if (hasSuggestionStripView()) {
            mSuggestionStripView.setListener(this, view);
        }

        mFloatingButton = mInputView.findViewById(R.id.floating_button);
        mFloatingButton.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) {
            mFloatingButton.setVisibility(View.VISIBLE);
        } else {
            mFloatingButton.setVisibility(View.GONE);
        }
    }

    private boolean shouldShowFloatingButton() {
        return AccountInfoContentProvider.isPremiumAccount(getApplicationContext())
                && isSttKeyboardActivated() && hasSuggestionStripView();
    }

    @Override
@@ -884,6 +937,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
    public void onStartInputView(final EditorInfo editorInfo, final boolean restarting) {
        mHandler.onStartInputView(editorInfo, restarting);
        mStatsUtilsManager.onStartInputView();

        if (mFloatingButton != null) {
            manageFloatingButtonVisibility();
        }
    }

    @Override
@@ -1282,12 +1339,31 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
            final int touchBottom = inputHeight;
            outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION;
            outInsets.touchableRegion.set(touchLeft, touchTop, touchRight, touchBottom);
            extendTouchableRegionForFloatingButton(outInsets);
        }
        outInsets.contentTopInsets = visibleTopY;
        outInsets.visibleTopInsets = visibleTopY;
        mInsetsUpdater.setInsets(outInsets);
    }

    private void extendTouchableRegionForFloatingButton(final InputMethodService.Insets outInsets) {
        if (mFloatingButton == null || mFloatingButton.getVisibility() != View.VISIBLE) {
            return;
        }
        if (!(mInputView instanceof ViewGroup)) {
            return;
        }
        final int width = mFloatingButton.getWidth();
        final int height = mFloatingButton.getHeight();
        if (width <= 0 || height <= 0) {
            return;
        }
        mFloatingButtonTouchableBounds.set(0, 0, width, height);
        ((ViewGroup) mInputView).offsetDescendantRectToMyCoords(
                mFloatingButton, mFloatingButtonTouchableBounds);
        outInsets.touchableRegion.union(mFloatingButtonTouchableBounds);
    }

    public void startShowingInputView(final boolean needsToLoadKeyboard) {
        mIsExecutingStartShowingInputView = true;
        // This {@link #showWindow(boolean)} will eventually call back
+56 −0
Original line number Diff line number Diff line
/*
 * 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 <https://www.gnu.org/licenses/>.
 *
 */
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;
    }
}