Skip to content

Commit ab3cfc3

Browse files
committed
Add autofill integration to the keyboard
* Activated autofill support for our input method in method.xml * Implemented required methods in our IME * Styled views as similar as possible to the clipboard item * Added InlineContentViewHorizontalScrollView which properly clips these views, since they are owned by another process and are usually drawn above our views This closes #199
1 parent e41c6c0 commit ab3cfc3

File tree

10 files changed

+277
-34
lines changed

10 files changed

+277
-34
lines changed

app/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ android {
6767
dependencies {
6868
implementation 'com.github.SimpleMobileTools:Simple-Commons:d6cddfa7d8'
6969
implementation 'androidx.emoji2:emoji2-bundled:1.2.0'
70+
implementation 'androidx.autofill:autofill:1.1.0'
7071

7172
kapt 'androidx.room:room-compiler:2.5.1'
7273
implementation 'androidx.room:room-runtime:2.5.1'

app/src/main/kotlin/com/simplemobiletools/keyboard/activities/ManageClipboardItemsActivity.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class ManageClipboardItemsActivity : SimpleActivity(), RefreshRecyclerViewListen
3333
super.onCreate(savedInstanceState)
3434
setContentView(R.layout.activity_manage_clipboard_items)
3535
setupOptionsMenu()
36-
updateTextColors(clipboard_items_holder)
36+
updateTextColors(suggestions_items_holder)
3737
updateClips()
3838

3939
updateMaterialActivityViews(clipboard_coordinator, clipboard_items_list, useTransparentNavigation = true, useTopSearchMenu = false)
@@ -73,14 +73,17 @@ class ManageClipboardItemsActivity : SimpleActivity(), RefreshRecyclerViewListen
7373
addOrEditClip()
7474
true
7575
}
76+
7677
R.id.export_clips -> {
7778
exportClips()
7879
true
7980
}
81+
8082
R.id.import_clips -> {
8183
importClips()
8284
true
8385
}
86+
8487
else -> false
8588
}
8689
}

app/src/main/kotlin/com/simplemobiletools/keyboard/services/SimpleKeyboardIME.kt

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,28 @@
11
package com.simplemobiletools.keyboard.services
22

3+
import android.annotation.SuppressLint
34
import android.content.SharedPreferences
5+
import android.graphics.drawable.Icon
46
import android.inputmethodservice.InputMethodService
7+
import android.os.Build
8+
import android.os.Bundle
59
import android.text.InputType.*
610
import android.text.TextUtils
11+
import android.util.Size
712
import android.view.KeyEvent
813
import android.view.View
9-
import android.view.inputmethod.CursorAnchorInfo
10-
import android.view.inputmethod.EditorInfo
14+
import android.view.ViewGroup
15+
import android.view.inputmethod.*
1116
import android.view.inputmethod.EditorInfo.IME_ACTION_NONE
1217
import android.view.inputmethod.EditorInfo.IME_FLAG_NO_ENTER_ACTION
1318
import android.view.inputmethod.EditorInfo.IME_MASK_ACTION
14-
import android.view.inputmethod.ExtractedTextRequest
19+
import android.widget.inline.InlinePresentationSpec
20+
import androidx.annotation.RequiresApi
21+
import androidx.autofill.inline.UiVersions
22+
import androidx.autofill.inline.common.ImageViewStyle
23+
import androidx.autofill.inline.common.TextViewStyle
24+
import androidx.autofill.inline.common.ViewStyle
25+
import androidx.autofill.inline.v1.InlineSuggestionUi
1526
import com.simplemobiletools.commons.extensions.getSharedPrefs
1627
import com.simplemobiletools.keyboard.R
1728
import com.simplemobiletools.keyboard.extensions.config
@@ -90,6 +101,38 @@ class SimpleKeyboardIME : InputMethodService(), OnKeyboardActionListener, Shared
90101
keyboardView?.invalidateAllKeys()
91102
}
92103

104+
@RequiresApi(Build.VERSION_CODES.R)
105+
override fun onCreateInlineSuggestionsRequest(uiExtras: Bundle): InlineSuggestionsRequest {
106+
val minWidth = resources.getDimensionPixelSize(R.dimen.suggestion_min_width)
107+
val maxWidth = resources.getDimensionPixelSize(R.dimen.suggestion_max_width)
108+
109+
return InlineSuggestionsRequest.Builder(
110+
listOf(
111+
InlinePresentationSpec.Builder(
112+
Size(minWidth, ViewGroup.LayoutParams.WRAP_CONTENT),
113+
Size(maxWidth, ViewGroup.LayoutParams.WRAP_CONTENT)
114+
).setStyle(buildSuggestionTextStyle()).build()
115+
)
116+
).setMaxSuggestionCount(InlineSuggestionsRequest.SUGGESTION_COUNT_UNLIMITED)
117+
.build()
118+
}
119+
120+
@RequiresApi(Build.VERSION_CODES.R)
121+
override fun onInlineSuggestionsResponse(response: InlineSuggestionsResponse): Boolean {
122+
keyboardView?.clearClipboardViews()
123+
124+
response.inlineSuggestions.forEach {
125+
it.inflate(this, Size(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT), this.mainExecutor) { view ->
126+
// If inflation fails for whatever reason, passed view will be null
127+
if (view != null) {
128+
keyboardView?.addToClipboardViews(view, addToFront = it.info.isPinned)
129+
}
130+
}
131+
}
132+
133+
return true
134+
}
135+
93136
override fun onKey(code: Int) {
94137
val inputConnection = currentInputConnection
95138
if (keyboard == null || inputConnection == null) {
@@ -201,6 +244,7 @@ class SimpleKeyboardIME : InputMethodService(), OnKeyboardActionListener, Shared
201244
inputConnection.commitText(codeChar.toString(), 1)
202245
}
203246
}
247+
204248
else -> {
205249
inputConnection.commitText(codeChar.toString(), 1)
206250
if (originalText == null) {
@@ -335,6 +379,48 @@ class SimpleKeyboardIME : InputMethodService(), OnKeyboardActionListener, Shared
335379
}
336380
}
337381

382+
@RequiresApi(Build.VERSION_CODES.R)
383+
@SuppressLint("RestrictedApi", "UseCompatLoadingForDrawables")
384+
private fun buildSuggestionTextStyle(): Bundle {
385+
val stylesBuilder = UiVersions.newStylesBuilder()
386+
387+
val verticalPadding = resources.getDimensionPixelSize(R.dimen.small_margin)
388+
val horizontalPadding = resources.getDimensionPixelSize(R.dimen.activity_margin)
389+
390+
val textSize = resources.getDimension(R.dimen.label_text_size) / resources.displayMetrics.scaledDensity
391+
392+
val chipStyle =
393+
ViewStyle.Builder()
394+
.setBackground(Icon.createWithResource(this, R.drawable.clipboard_background))
395+
.setPadding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding)
396+
.build()
397+
398+
val iconStyle = ImageViewStyle.Builder().build()
399+
400+
val style = InlineSuggestionUi.newStyleBuilder()
401+
.setSingleIconChipStyle(chipStyle)
402+
.setChipStyle(chipStyle)
403+
.setStartIconStyle(iconStyle)
404+
.setEndIconStyle(iconStyle)
405+
.setSingleIconChipIconStyle(iconStyle)
406+
.setTitleStyle(
407+
TextViewStyle.Builder()
408+
.setLayoutMargin(0, 0, horizontalPadding, 0)
409+
.setTextColor(resources.getColor(R.color.default_text_color, theme))
410+
.setTextSize(textSize)
411+
.build()
412+
)
413+
.setSubtitleStyle(
414+
TextViewStyle.Builder()
415+
.setTextColor(resources.getColor(R.color.default_text_color, theme))
416+
.setTextSize(textSize)
417+
.build()
418+
)
419+
.build()
420+
stylesBuilder.addStyle(style)
421+
return stylesBuilder.build()
422+
}
423+
338424
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
339425
keyboardView?.setupKeyboard()
340426
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package com.simplemobiletools.keyboard.views
2+
3+
import android.content.Context
4+
import android.graphics.Rect
5+
import android.os.Build
6+
import android.util.AttributeSet
7+
import android.view.View
8+
import android.view.ViewTreeObserver.OnDrawListener
9+
import android.widget.HorizontalScrollView
10+
import android.widget.inline.InlineContentView
11+
import androidx.annotation.AttrRes
12+
import androidx.core.view.allViews
13+
import com.simplemobiletools.commons.extensions.beInvisible
14+
import com.simplemobiletools.commons.extensions.beVisible
15+
16+
17+
/**
18+
* [HorizontalScrollView] adapted for holding [InlineContentView] instances
19+
* It can hold other views too, but it will ensure [InlineContentView] instances
20+
* these are properly clipped and not drawn over rest of the window,
21+
* but still remaining clickable
22+
* (since setting [InlineContentView.setZOrderedOnTop] to false prevents clicking)
23+
*/
24+
class InlineContentViewHorizontalScrollView @JvmOverloads constructor(
25+
context: Context, attrs: AttributeSet? = null,
26+
@AttrRes defStyleAttr: Int = 0
27+
) : HorizontalScrollView(context, attrs, defStyleAttr), OnDrawListener {
28+
29+
override fun onAttachedToWindow() {
30+
super.onAttachedToWindow()
31+
viewTreeObserver.addOnDrawListener(this)
32+
}
33+
34+
override fun onDetachedFromWindow() {
35+
super.onDetachedFromWindow()
36+
viewTreeObserver.removeOnDrawListener(this)
37+
}
38+
39+
override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {
40+
super.onScrollChanged(l, t, oldl, oldt)
41+
clipDescendantInlineContentViews()
42+
}
43+
44+
override fun onDraw() {
45+
clipDescendantInlineContentViews()
46+
}
47+
48+
fun hideAllInlineContentViews() {
49+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
50+
return
51+
}
52+
allViews.forEach {
53+
if (it is InlineContentView) {
54+
it.beInvisible()
55+
}
56+
}
57+
}
58+
59+
fun showAllInlineContentViews() {
60+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
61+
return
62+
}
63+
allViews.forEach {
64+
if (it is InlineContentView) {
65+
it.beVisible()
66+
}
67+
}
68+
}
69+
70+
private fun clipDescendantInlineContentViews() {
71+
// This is only needed for InlineContentViews which are not available before this version
72+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
73+
return
74+
}
75+
76+
allViews.forEach {
77+
if (it is InlineContentView) {
78+
val parentBounds = Rect(scrollX, scrollY, width + scrollX, height + scrollY)
79+
offsetRectIntoDescendantCoords(it, parentBounds)
80+
it.clipBounds = parentBounds
81+
}
82+
}
83+
}
84+
}

app/src/main/kotlin/com/simplemobiletools/keyboard/views/MyKeyboardView.kt

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import android.content.Intent
1010
import android.graphics.*
1111
import android.graphics.Paint.Align
1212
import android.graphics.drawable.*
13+
import android.os.Build
1314
import android.os.Handler
1415
import android.os.Looper
1516
import android.os.Message
@@ -18,11 +19,14 @@ import android.util.TypedValue
1819
import android.view.*
1920
import android.view.animation.AccelerateInterpolator
2021
import android.view.inputmethod.EditorInfo
22+
import android.widget.LinearLayout
2123
import android.widget.PopupWindow
2224
import android.widget.TextView
25+
import android.widget.inline.InlineContentView
26+
import androidx.annotation.RequiresApi
2327
import androidx.core.animation.doOnEnd
2428
import androidx.core.animation.doOnStart
25-
import androidx.core.view.ViewCompat
29+
import androidx.core.view.*
2630
import androidx.emoji2.text.EmojiCompat
2731
import androidx.emoji2.text.EmojiCompat.EMOJI_SUPPORTED
2832
import com.simplemobiletools.commons.extensions.*
@@ -746,8 +750,8 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
746750

747751
private fun hideClipboardViews() {
748752
mToolbarHolder?.apply {
749-
clipboard_value_holder?.beGone()
750-
clipboard_value_holder?.alpha = 0f
753+
clipboard_value?.beGone()
754+
clipboard_value?.alpha = 0f
751755
clipboard_clear?.beGone()
752756
clipboard_clear?.alpha = 0f
753757
}
@@ -764,10 +768,10 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
764768
}
765769

766770
private fun toggleClipboardVisibility(show: Boolean) {
767-
if ((show && mToolbarHolder?.clipboard_value_holder!!.alpha == 0f) || (!show && mToolbarHolder?.clipboard_value_holder!!.alpha == 1f)) {
771+
if ((show && mToolbarHolder?.clipboard_value!!.alpha == 0f) || (!show && mToolbarHolder?.clipboard_value!!.alpha == 1f)) {
768772
val newAlpha = if (show) 1f else 0f
769773
val animations = ArrayList<ObjectAnimator>()
770-
val clipboardValueAnimation = ObjectAnimator.ofFloat(mToolbarHolder!!.clipboard_value_holder!!, "alpha", newAlpha)
774+
val clipboardValueAnimation = ObjectAnimator.ofFloat(mToolbarHolder!!.clipboard_value!!, "alpha", newAlpha)
771775
animations.add(clipboardValueAnimation)
772776

773777
val clipboardClearAnimation = ObjectAnimator.ofFloat(mToolbarHolder!!.clipboard_clear!!, "alpha", newAlpha)
@@ -779,13 +783,13 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
779783
animSet.interpolator = AccelerateInterpolator()
780784
animSet.doOnStart {
781785
if (show) {
782-
mToolbarHolder?.clipboard_value_holder?.beVisible()
786+
mToolbarHolder?.clipboard_value?.beVisible()
783787
mToolbarHolder?.clipboard_clear?.beVisible()
784788
}
785789
}
786790
animSet.doOnEnd {
787791
if (!show) {
788-
mToolbarHolder?.clipboard_value_holder?.beGone()
792+
mToolbarHolder?.clipboard_value?.beGone()
789793
mToolbarHolder?.clipboard_clear?.beGone()
790794
}
791795
}
@@ -1381,10 +1385,12 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
13811385

13821386
fun closeClipboardManager() {
13831387
mClipboardManagerHolder?.clipboard_manager_holder?.beGone()
1388+
mToolbarHolder?.suggestions_holder?.showAllInlineContentViews()
13841389
}
13851390

13861391
private fun openClipboardManager() {
13871392
mClipboardManagerHolder!!.clipboard_manager_holder.beVisible()
1393+
mToolbarHolder?.suggestions_holder?.hideAllInlineContentViews()
13881394
setupStoredClips()
13891395
}
13901396

@@ -1614,4 +1620,25 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
16141620
}
16151621
return keyColor
16161622
}
1623+
1624+
@RequiresApi(Build.VERSION_CODES.R)
1625+
fun addToClipboardViews(it: InlineContentView, addToFront: Boolean = false) {
1626+
if (mToolbarHolder?.autofill_suggestions_holder != null) {
1627+
val newLayoutParams = LinearLayout.LayoutParams(it.layoutParams)
1628+
newLayoutParams.updateMarginsRelative(start = resources.getDimensionPixelSize(R.dimen.normal_margin))
1629+
it.layoutParams = newLayoutParams
1630+
if (addToFront) {
1631+
mToolbarHolder?.autofill_suggestions_holder?.addView(it, 0)
1632+
} else {
1633+
mToolbarHolder?.autofill_suggestions_holder?.addView(it)
1634+
}
1635+
}
1636+
}
1637+
1638+
@RequiresApi(Build.VERSION_CODES.R)
1639+
fun clearClipboardViews() {
1640+
if (mToolbarHolder?.autofill_suggestions_holder != null) {
1641+
mToolbarHolder?.autofill_suggestions_holder?.removeAllViews()
1642+
}
1643+
}
16171644
}

app/src/main/res/layout/activity_main.xml

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,13 @@
4949

5050
<com.simplemobiletools.commons.views.MyEditText
5151
android:id="@+id/text_edittext"
52-
android:inputType="textCapSentences"
5352
android:layout_width="match_parent"
5453
android:layout_height="wrap_content"
5554
android:layout_marginStart="@dimen/activity_margin"
5655
android:layout_marginTop="@dimen/activity_margin"
5756
android:layout_marginEnd="@dimen/activity_margin"
58-
android:layout_marginBottom="@dimen/activity_margin" />
57+
android:layout_marginBottom="@dimen/activity_margin"
58+
android:inputType="textCapSentences" />
5959

6060
<com.simplemobiletools.commons.views.MyEditText
6161
android:id="@+id/text_editphone"
@@ -67,6 +67,26 @@
6767
android:layout_marginBottom="@dimen/activity_margin"
6868
android:inputType="phone" />
6969

70+
<com.simplemobiletools.commons.views.MyEditText
71+
android:id="@+id/text_editemail"
72+
android:layout_width="match_parent"
73+
android:layout_height="wrap_content"
74+
android:layout_marginStart="@dimen/activity_margin"
75+
android:layout_marginTop="@dimen/activity_margin"
76+
android:layout_marginEnd="@dimen/activity_margin"
77+
android:layout_marginBottom="@dimen/activity_margin"
78+
android:inputType="textEmailAddress" />
79+
80+
<com.simplemobiletools.commons.views.MyEditText
81+
android:id="@+id/text_editpassword"
82+
android:layout_width="match_parent"
83+
android:layout_height="wrap_content"
84+
android:layout_marginStart="@dimen/activity_margin"
85+
android:layout_marginTop="@dimen/activity_margin"
86+
android:layout_marginEnd="@dimen/activity_margin"
87+
android:layout_marginBottom="@dimen/activity_margin"
88+
android:inputType="textPassword" />
89+
7090
</LinearLayout>
7191
</RelativeLayout>
7292
</androidx.core.widget.NestedScrollView>

0 commit comments

Comments
 (0)