Merge pull request #214 from esensar/feature/199-autofill-integration

Add autofill integration to the keyboard
This commit is contained in:
Tibor Kaputa 2023-07-07 18:57:23 +02:00 committed by GitHub
commit a143e100db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 299 additions and 33 deletions

View File

@ -67,6 +67,7 @@ android {
dependencies { dependencies {
implementation 'com.github.SimpleMobileTools:Simple-Commons:d6cddfa7d8' implementation 'com.github.SimpleMobileTools:Simple-Commons:d6cddfa7d8'
implementation 'androidx.emoji2:emoji2-bundled:1.2.0' implementation 'androidx.emoji2:emoji2-bundled:1.2.0'
implementation 'androidx.autofill:autofill:1.1.0'
kapt 'androidx.room:room-compiler:2.5.1' kapt 'androidx.room:room-compiler:2.5.1'
implementation 'androidx.room:room-runtime:2.5.1' implementation 'androidx.room:room-runtime:2.5.1'

View File

@ -33,7 +33,7 @@ class ManageClipboardItemsActivity : SimpleActivity(), RefreshRecyclerViewListen
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_manage_clipboard_items) setContentView(R.layout.activity_manage_clipboard_items)
setupOptionsMenu() setupOptionsMenu()
updateTextColors(clipboard_items_holder) updateTextColors(suggestions_items_holder)
updateClips() updateClips()
updateMaterialActivityViews(clipboard_coordinator, clipboard_items_list, useTransparentNavigation = true, useTopSearchMenu = false) updateMaterialActivityViews(clipboard_coordinator, clipboard_items_list, useTransparentNavigation = true, useTopSearchMenu = false)
@ -73,14 +73,17 @@ class ManageClipboardItemsActivity : SimpleActivity(), RefreshRecyclerViewListen
addOrEditClip() addOrEditClip()
true true
} }
R.id.export_clips -> { R.id.export_clips -> {
exportClips() exportClips()
true true
} }
R.id.import_clips -> { R.id.import_clips -> {
importClips() importClips()
true true
} }
else -> false else -> false
} }
} }

View File

@ -1,17 +1,28 @@
package com.simplemobiletools.keyboard.services package com.simplemobiletools.keyboard.services
import android.annotation.SuppressLint
import android.content.SharedPreferences import android.content.SharedPreferences
import android.graphics.drawable.Icon
import android.inputmethodservice.InputMethodService import android.inputmethodservice.InputMethodService
import android.os.Build
import android.os.Bundle
import android.text.InputType.* import android.text.InputType.*
import android.text.TextUtils import android.text.TextUtils
import android.util.Size
import android.view.KeyEvent import android.view.KeyEvent
import android.view.View import android.view.View
import android.view.inputmethod.CursorAnchorInfo import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.*
import android.view.inputmethod.EditorInfo.IME_ACTION_NONE import android.view.inputmethod.EditorInfo.IME_ACTION_NONE
import android.view.inputmethod.EditorInfo.IME_FLAG_NO_ENTER_ACTION import android.view.inputmethod.EditorInfo.IME_FLAG_NO_ENTER_ACTION
import android.view.inputmethod.EditorInfo.IME_MASK_ACTION import android.view.inputmethod.EditorInfo.IME_MASK_ACTION
import android.view.inputmethod.ExtractedTextRequest import android.widget.inline.InlinePresentationSpec
import androidx.annotation.RequiresApi
import androidx.autofill.inline.UiVersions
import androidx.autofill.inline.common.ImageViewStyle
import androidx.autofill.inline.common.TextViewStyle
import androidx.autofill.inline.common.ViewStyle
import androidx.autofill.inline.v1.InlineSuggestionUi
import com.simplemobiletools.commons.extensions.getSharedPrefs import com.simplemobiletools.commons.extensions.getSharedPrefs
import com.simplemobiletools.keyboard.R import com.simplemobiletools.keyboard.R
import com.simplemobiletools.keyboard.extensions.config import com.simplemobiletools.keyboard.extensions.config
@ -90,6 +101,37 @@ class SimpleKeyboardIME : InputMethodService(), OnKeyboardActionListener, Shared
keyboardView?.invalidateAllKeys() keyboardView?.invalidateAllKeys()
} }
@RequiresApi(Build.VERSION_CODES.R)
override fun onCreateInlineSuggestionsRequest(uiExtras: Bundle): InlineSuggestionsRequest {
val maxWidth = resources.getDimensionPixelSize(R.dimen.suggestion_max_width)
return InlineSuggestionsRequest.Builder(
listOf(
InlinePresentationSpec.Builder(
Size(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT),
Size(maxWidth, ViewGroup.LayoutParams.WRAP_CONTENT)
).setStyle(buildSuggestionTextStyle()).build()
)
).setMaxSuggestionCount(InlineSuggestionsRequest.SUGGESTION_COUNT_UNLIMITED)
.build()
}
@RequiresApi(Build.VERSION_CODES.R)
override fun onInlineSuggestionsResponse(response: InlineSuggestionsResponse): Boolean {
keyboardView?.clearClipboardViews()
response.inlineSuggestions.forEach {
it.inflate(this, Size(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT), this.mainExecutor) { view ->
// If inflation fails for whatever reason, passed view will be null
if (view != null) {
keyboardView?.addToClipboardViews(view, addToFront = it.info.isPinned)
}
}
}
return true
}
override fun onKey(code: Int) { override fun onKey(code: Int) {
val inputConnection = currentInputConnection val inputConnection = currentInputConnection
if (keyboard == null || inputConnection == null) { if (keyboard == null || inputConnection == null) {
@ -201,6 +243,7 @@ class SimpleKeyboardIME : InputMethodService(), OnKeyboardActionListener, Shared
inputConnection.commitText(codeChar.toString(), 1) inputConnection.commitText(codeChar.toString(), 1)
} }
} }
else -> { else -> {
inputConnection.commitText(codeChar.toString(), 1) inputConnection.commitText(codeChar.toString(), 1)
if (originalText == null) { if (originalText == null) {
@ -335,6 +378,48 @@ class SimpleKeyboardIME : InputMethodService(), OnKeyboardActionListener, Shared
} }
} }
@RequiresApi(Build.VERSION_CODES.R)
@SuppressLint("RestrictedApi", "UseCompatLoadingForDrawables")
private fun buildSuggestionTextStyle(): Bundle {
val stylesBuilder = UiVersions.newStylesBuilder()
val verticalPadding = resources.getDimensionPixelSize(R.dimen.small_margin)
val horizontalPadding = resources.getDimensionPixelSize(R.dimen.activity_margin)
val textSize = resources.getDimension(R.dimen.label_text_size) / resources.displayMetrics.scaledDensity
val chipStyle =
ViewStyle.Builder()
.setBackground(Icon.createWithResource(this, R.drawable.clipboard_background))
.setPadding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding)
.build()
val iconStyle = ImageViewStyle.Builder().build()
val style = InlineSuggestionUi.newStyleBuilder()
.setSingleIconChipStyle(chipStyle)
.setChipStyle(chipStyle)
.setStartIconStyle(iconStyle)
.setEndIconStyle(iconStyle)
.setSingleIconChipIconStyle(iconStyle)
.setTitleStyle(
TextViewStyle.Builder()
.setLayoutMargin(0, 0, horizontalPadding, 0)
.setTextColor(resources.getColor(R.color.default_text_color, theme))
.setTextSize(textSize)
.build()
)
.setSubtitleStyle(
TextViewStyle.Builder()
.setTextColor(resources.getColor(R.color.default_text_color, theme))
.setTextSize(textSize)
.build()
)
.build()
stylesBuilder.addStyle(style)
return stylesBuilder.build()
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
keyboardView?.setupKeyboard() keyboardView?.setupKeyboard()
} }

View File

@ -0,0 +1,82 @@
package com.simplemobiletools.keyboard.views
import android.content.Context
import android.graphics.Rect
import android.util.AttributeSet
import android.view.ViewTreeObserver.OnDrawListener
import android.widget.HorizontalScrollView
import android.widget.inline.InlineContentView
import androidx.annotation.AttrRes
import androidx.core.view.allViews
import com.simplemobiletools.commons.extensions.beInvisible
import com.simplemobiletools.commons.extensions.beVisible
import com.simplemobiletools.commons.helpers.isRPlus
/**
* [HorizontalScrollView] adapted for holding [InlineContentView] instances
* It can hold other views too, but it will ensure [InlineContentView] instances
* these are properly clipped and not drawn over rest of the window,
* but still remaining clickable
* (since setting [InlineContentView.setZOrderedOnTop] to false prevents clicking)
*/
class InlineContentViewHorizontalScrollView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null,
@AttrRes defStyleAttr: Int = 0
) : HorizontalScrollView(context, attrs, defStyleAttr), OnDrawListener {
override fun onAttachedToWindow() {
super.onAttachedToWindow()
viewTreeObserver.addOnDrawListener(this)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
viewTreeObserver.removeOnDrawListener(this)
}
override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {
super.onScrollChanged(l, t, oldl, oldt)
clipDescendantInlineContentViews()
}
override fun onDraw() {
clipDescendantInlineContentViews()
}
fun hideAllInlineContentViews() {
if (!isRPlus()) {
return
}
allViews.forEach {
if (it is InlineContentView) {
it.beInvisible()
}
}
}
fun showAllInlineContentViews() {
if (!isRPlus()) {
return
}
allViews.forEach {
if (it is InlineContentView) {
it.beVisible()
}
}
}
private fun clipDescendantInlineContentViews() {
// This is only needed for InlineContentViews which are not available before this version
if (!isRPlus()) {
return
}
allViews.forEach {
if (it is InlineContentView) {
val parentBounds = Rect(scrollX, scrollY, width + scrollX, height + scrollY)
offsetRectIntoDescendantCoords(it, parentBounds)
it.clipBounds = parentBounds
}
}
}
}

View File

@ -10,6 +10,7 @@ import android.content.Intent
import android.graphics.* import android.graphics.*
import android.graphics.Paint.Align import android.graphics.Paint.Align
import android.graphics.drawable.* import android.graphics.drawable.*
import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.os.Message import android.os.Message
@ -18,11 +19,14 @@ import android.util.TypedValue
import android.view.* import android.view.*
import android.view.animation.AccelerateInterpolator import android.view.animation.AccelerateInterpolator
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.LinearLayout
import android.widget.PopupWindow import android.widget.PopupWindow
import android.widget.TextView import android.widget.TextView
import android.widget.inline.InlineContentView
import androidx.annotation.RequiresApi
import androidx.core.animation.doOnEnd import androidx.core.animation.doOnEnd
import androidx.core.animation.doOnStart import androidx.core.animation.doOnStart
import androidx.core.view.ViewCompat import androidx.core.view.*
import androidx.emoji2.text.EmojiCompat import androidx.emoji2.text.EmojiCompat
import androidx.emoji2.text.EmojiCompat.EMOJI_SUPPORTED import androidx.emoji2.text.EmojiCompat.EMOJI_SUPPORTED
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
@ -308,6 +312,13 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
clearClipboardContent() clearClipboardContent()
toggleClipboardVisibility(false) toggleClipboardVisibility(false)
} }
suggestions_holder.addOnLayoutChangeListener(object : OnLayoutChangeListener {
override fun onLayoutChange(v: View?, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int) {
updateSuggestionsToolbarLayout()
suggestions_holder.removeOnLayoutChangeListener(this)
}
})
} }
val clipboardManager = (context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager) val clipboardManager = (context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)
@ -746,8 +757,8 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
private fun hideClipboardViews() { private fun hideClipboardViews() {
mToolbarHolder?.apply { mToolbarHolder?.apply {
clipboard_value_holder?.beGone() clipboard_value?.beGone()
clipboard_value_holder?.alpha = 0f clipboard_value?.alpha = 0f
clipboard_clear?.beGone() clipboard_clear?.beGone()
clipboard_clear?.alpha = 0f clipboard_clear?.alpha = 0f
} }
@ -764,10 +775,10 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
} }
private fun toggleClipboardVisibility(show: Boolean) { private fun toggleClipboardVisibility(show: Boolean) {
if ((show && mToolbarHolder?.clipboard_value_holder!!.alpha == 0f) || (!show && mToolbarHolder?.clipboard_value_holder!!.alpha == 1f)) { if ((show && mToolbarHolder?.clipboard_value!!.alpha == 0f) || (!show && mToolbarHolder?.clipboard_value!!.alpha == 1f)) {
val newAlpha = if (show) 1f else 0f val newAlpha = if (show) 1f else 0f
val animations = ArrayList<ObjectAnimator>() val animations = ArrayList<ObjectAnimator>()
val clipboardValueAnimation = ObjectAnimator.ofFloat(mToolbarHolder!!.clipboard_value_holder!!, "alpha", newAlpha) val clipboardValueAnimation = ObjectAnimator.ofFloat(mToolbarHolder!!.clipboard_value!!, "alpha", newAlpha)
animations.add(clipboardValueAnimation) animations.add(clipboardValueAnimation)
val clipboardClearAnimation = ObjectAnimator.ofFloat(mToolbarHolder!!.clipboard_clear!!, "alpha", newAlpha) val clipboardClearAnimation = ObjectAnimator.ofFloat(mToolbarHolder!!.clipboard_clear!!, "alpha", newAlpha)
@ -779,13 +790,13 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
animSet.interpolator = AccelerateInterpolator() animSet.interpolator = AccelerateInterpolator()
animSet.doOnStart { animSet.doOnStart {
if (show) { if (show) {
mToolbarHolder?.clipboard_value_holder?.beVisible() mToolbarHolder?.clipboard_value?.beVisible()
mToolbarHolder?.clipboard_clear?.beVisible() mToolbarHolder?.clipboard_clear?.beVisible()
} }
} }
animSet.doOnEnd { animSet.doOnEnd {
if (!show) { if (!show) {
mToolbarHolder?.clipboard_value_holder?.beGone() mToolbarHolder?.clipboard_value?.beGone()
mToolbarHolder?.clipboard_clear?.beGone() mToolbarHolder?.clipboard_clear?.beGone()
} }
} }
@ -1381,10 +1392,12 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
fun closeClipboardManager() { fun closeClipboardManager() {
mClipboardManagerHolder?.clipboard_manager_holder?.beGone() mClipboardManagerHolder?.clipboard_manager_holder?.beGone()
mToolbarHolder?.suggestions_holder?.showAllInlineContentViews()
} }
private fun openClipboardManager() { private fun openClipboardManager() {
mClipboardManagerHolder!!.clipboard_manager_holder.beVisible() mClipboardManagerHolder!!.clipboard_manager_holder.beVisible()
mToolbarHolder?.suggestions_holder?.hideAllInlineContentViews()
setupStoredClips() setupStoredClips()
} }
@ -1614,4 +1627,46 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
} }
return keyColor return keyColor
} }
@RequiresApi(Build.VERSION_CODES.R)
fun addToClipboardViews(it: InlineContentView, addToFront: Boolean = false) {
if (mToolbarHolder?.autofill_suggestions_holder != null) {
val newLayoutParams = LinearLayout.LayoutParams(it.layoutParams)
newLayoutParams.updateMarginsRelative(start = resources.getDimensionPixelSize(R.dimen.normal_margin))
it.layoutParams = newLayoutParams
if (addToFront) {
mToolbarHolder?.autofill_suggestions_holder?.addView(it, 0)
} else {
mToolbarHolder?.autofill_suggestions_holder?.addView(it)
}
updateSuggestionsToolbarLayout()
}
}
@RequiresApi(Build.VERSION_CODES.R)
fun clearClipboardViews() {
mToolbarHolder?.autofill_suggestions_holder?.removeAllViews()
updateSuggestionsToolbarLayout()
}
private fun updateSuggestionsToolbarLayout() {
mToolbarHolder?.apply {
if (hasInlineViews()) {
// make room on suggestion toolbar for inline views
suggestions_items_holder?.gravity = Gravity.NO_GRAVITY
clipboard_value?.maxWidth = resources.getDimensionPixelSize(R.dimen.suggestion_max_width)
} else {
// restore original clipboard toolbar appearance
suggestions_items_holder?.gravity = Gravity.CENTER_HORIZONTAL
suggestions_holder?.measuredWidth?.also { maxWidth ->
clipboard_value?.maxWidth = maxWidth
}
}
}
}
/**
* Returns true if there are [InlineContentView]s in [autofill_suggestions_holder]
*/
private fun hasInlineViews() = (mToolbarHolder?.autofill_suggestions_holder?.childCount ?: 0) > 0
} }

View File

@ -49,13 +49,13 @@
<com.simplemobiletools.commons.views.MyEditText <com.simplemobiletools.commons.views.MyEditText
android:id="@+id/text_edittext" android:id="@+id/text_edittext"
android:inputType="textCapSentences"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_margin" android:layout_marginStart="@dimen/activity_margin"
android:layout_marginTop="@dimen/activity_margin" android:layout_marginTop="@dimen/activity_margin"
android:layout_marginEnd="@dimen/activity_margin" android:layout_marginEnd="@dimen/activity_margin"
android:layout_marginBottom="@dimen/activity_margin" /> android:layout_marginBottom="@dimen/activity_margin"
android:inputType="textCapSentences" />
<com.simplemobiletools.commons.views.MyEditText <com.simplemobiletools.commons.views.MyEditText
android:id="@+id/text_editphone" android:id="@+id/text_editphone"
@ -67,6 +67,26 @@
android:layout_marginBottom="@dimen/activity_margin" android:layout_marginBottom="@dimen/activity_margin"
android:inputType="phone" /> android:inputType="phone" />
<com.simplemobiletools.commons.views.MyEditText
android:id="@+id/text_editemail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_margin"
android:layout_marginTop="@dimen/activity_margin"
android:layout_marginEnd="@dimen/activity_margin"
android:layout_marginBottom="@dimen/activity_margin"
android:inputType="textEmailAddress" />
<com.simplemobiletools.commons.views.MyEditText
android:id="@+id/text_editpassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_margin"
android:layout_marginTop="@dimen/activity_margin"
android:layout_marginEnd="@dimen/activity_margin"
android:layout_marginBottom="@dimen/activity_margin"
android:inputType="textPassword" />
</LinearLayout> </LinearLayout>
</RelativeLayout> </RelativeLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View File

@ -23,7 +23,7 @@
android:scrollbars="none"> android:scrollbars="none">
<RelativeLayout <RelativeLayout
android:id="@+id/clipboard_items_holder" android:id="@+id/suggestions_items_holder"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">

View File

@ -27,35 +27,51 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<RelativeLayout <com.simplemobiletools.keyboard.views.InlineContentViewHorizontalScrollView
android:id="@+id/clipboard_value_holder" android:id="@+id/suggestions_holder"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/medium_margin" android:layout_marginStart="@dimen/medium_margin"
android:layout_marginEnd="@dimen/normal_margin" android:layout_marginEnd="@dimen/normal_margin"
android:fillViewport="true"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/pinned_clipboard_items" app:layout_constraintEnd_toStartOf="@+id/pinned_clipboard_items"
app:layout_constraintStart_toEndOf="@+id/clipboard_clear" app:layout_constraintStart_toEndOf="@+id/clipboard_clear"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
<TextView <LinearLayout
android:id="@+id/clipboard_value" android:id="@+id/suggestions_items_holder"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_centerInParent="true" android:gravity="center_horizontal"
android:autoLink="none" android:orientation="horizontal">
android:background="@drawable/clipboard_background"
android:ellipsize="end"
android:gravity="center"
android:lines="1"
android:paddingStart="@dimen/activity_margin"
android:paddingTop="@dimen/small_margin"
android:paddingEnd="@dimen/activity_margin"
android:paddingBottom="@dimen/small_margin"
android:textSize="@dimen/label_text_size"
tools:text="Clipboard content" />
</RelativeLayout> <TextView
android:id="@+id/clipboard_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoLink="none"
android:background="@drawable/clipboard_background"
android:ellipsize="end"
android:gravity="center"
android:lines="1"
android:paddingStart="@dimen/activity_margin"
android:paddingTop="@dimen/small_margin"
android:paddingEnd="@dimen/activity_margin"
android:paddingBottom="@dimen/small_margin"
android:textSize="@dimen/label_text_size"
tools:text="Clipboard content" />
<LinearLayout
android:id="@+id/autofill_suggestions_holder"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal" />
</LinearLayout>
</com.simplemobiletools.keyboard.views.InlineContentViewHorizontalScrollView>
<ImageView <ImageView
android:id="@+id/pinned_clipboard_items" android:id="@+id/pinned_clipboard_items"

View File

@ -11,6 +11,7 @@
<dimen name="emoji_item_size">46dp</dimen> <dimen name="emoji_item_size">46dp</dimen>
<dimen name="emoji_top_bar_elevation">4dp</dimen> <dimen name="emoji_top_bar_elevation">4dp</dimen>
<dimen name="emoji_palette_btn_size">42dp</dimen> <dimen name="emoji_palette_btn_size">42dp</dimen>
<dimen name="suggestion_max_width">200dp</dimen>
<dimen name="keyboard_text_size">22sp</dimen> <dimen name="keyboard_text_size">22sp</dimen>
<dimen name="preview_text_size">26sp</dimen> <dimen name="preview_text_size">26sp</dimen>

View File

@ -1,7 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<input-method xmlns:android="http://schemas.android.com/apk/res/android" <input-method xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:settingsActivity="com.simplemobiletools.keyboard.activities.SettingsActivity"> android:settingsActivity="com.simplemobiletools.keyboard.activities.SettingsActivity"
android:supportsInlineSuggestions="true"
tools:targetApi="r">
<subtype android:imeSubtypeMode="Keyboard" /> <subtype android:imeSubtypeMode="Keyboard" />