Skip to content

Commit a143e10

Browse files
authored
Merge pull request #214 from esensar/feature/199-autofill-integration
Add autofill integration to the keyboard
2 parents 27bbc4a + 9205145 commit a143e10

File tree

10 files changed

+300
-34
lines changed

10 files changed

+300
-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: 88 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,37 @@ 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 maxWidth = resources.getDimensionPixelSize(R.dimen.suggestion_max_width)
107+
108+
return InlineSuggestionsRequest.Builder(
109+
listOf(
110+
InlinePresentationSpec.Builder(
111+
Size(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT),
112+
Size(maxWidth, ViewGroup.LayoutParams.WRAP_CONTENT)
113+
).setStyle(buildSuggestionTextStyle()).build()
114+
)
115+
).setMaxSuggestionCount(InlineSuggestionsRequest.SUGGESTION_COUNT_UNLIMITED)
116+
.build()
117+
}
118+
119+
@RequiresApi(Build.VERSION_CODES.R)
120+
override fun onInlineSuggestionsResponse(response: InlineSuggestionsResponse): Boolean {
121+
keyboardView?.clearClipboardViews()
122+
123+
response.inlineSuggestions.forEach {
124+
it.inflate(this, Size(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT), this.mainExecutor) { view ->
125+
// If inflation fails for whatever reason, passed view will be null
126+
if (view != null) {
127+
keyboardView?.addToClipboardViews(view, addToFront = it.info.isPinned)
128+
}
129+
}
130+
}
131+
132+
return true
133+
}
134+
93135
override fun onKey(code: Int) {
94136
val inputConnection = currentInputConnection
95137
if (keyboard == null || inputConnection == null) {
@@ -201,6 +243,7 @@ class SimpleKeyboardIME : InputMethodService(), OnKeyboardActionListener, Shared
201243
inputConnection.commitText(codeChar.toString(), 1)
202244
}
203245
}
246+
204247
else -> {
205248
inputConnection.commitText(codeChar.toString(), 1)
206249
if (originalText == null) {
@@ -335,6 +378,48 @@ class SimpleKeyboardIME : InputMethodService(), OnKeyboardActionListener, Shared
335378
}
336379
}
337380

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

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

Lines changed: 62 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.*
@@ -308,6 +312,13 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
308312
clearClipboardContent()
309313
toggleClipboardVisibility(false)
310314
}
315+
316+
suggestions_holder.addOnLayoutChangeListener(object : OnLayoutChangeListener {
317+
override fun onLayoutChange(v: View?, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int) {
318+
updateSuggestionsToolbarLayout()
319+
suggestions_holder.removeOnLayoutChangeListener(this)
320+
}
321+
})
311322
}
312323

313324
val clipboardManager = (context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)
@@ -746,8 +757,8 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
746757

747758
private fun hideClipboardViews() {
748759
mToolbarHolder?.apply {
749-
clipboard_value_holder?.beGone()
750-
clipboard_value_holder?.alpha = 0f
760+
clipboard_value?.beGone()
761+
clipboard_value?.alpha = 0f
751762
clipboard_clear?.beGone()
752763
clipboard_clear?.alpha = 0f
753764
}
@@ -764,10 +775,10 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
764775
}
765776

766777
private fun toggleClipboardVisibility(show: Boolean) {
767-
if ((show && mToolbarHolder?.clipboard_value_holder!!.alpha == 0f) || (!show && mToolbarHolder?.clipboard_value_holder!!.alpha == 1f)) {
778+
if ((show && mToolbarHolder?.clipboard_value!!.alpha == 0f) || (!show && mToolbarHolder?.clipboard_value!!.alpha == 1f)) {
768779
val newAlpha = if (show) 1f else 0f
769780
val animations = ArrayList<ObjectAnimator>()
770-
val clipboardValueAnimation = ObjectAnimator.ofFloat(mToolbarHolder!!.clipboard_value_holder!!, "alpha", newAlpha)
781+
val clipboardValueAnimation = ObjectAnimator.ofFloat(mToolbarHolder!!.clipboard_value!!, "alpha", newAlpha)
771782
animations.add(clipboardValueAnimation)
772783

773784
val clipboardClearAnimation = ObjectAnimator.ofFloat(mToolbarHolder!!.clipboard_clear!!, "alpha", newAlpha)
@@ -779,13 +790,13 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
779790
animSet.interpolator = AccelerateInterpolator()
780791
animSet.doOnStart {
781792
if (show) {
782-
mToolbarHolder?.clipboard_value_holder?.beVisible()
793+
mToolbarHolder?.clipboard_value?.beVisible()
783794
mToolbarHolder?.clipboard_clear?.beVisible()
784795
}
785796
}
786797
animSet.doOnEnd {
787798
if (!show) {
788-
mToolbarHolder?.clipboard_value_holder?.beGone()
799+
mToolbarHolder?.clipboard_value?.beGone()
789800
mToolbarHolder?.clipboard_clear?.beGone()
790801
}
791802
}
@@ -1381,10 +1392,12 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
13811392

13821393
fun closeClipboardManager() {
13831394
mClipboardManagerHolder?.clipboard_manager_holder?.beGone()
1395+
mToolbarHolder?.suggestions_holder?.showAllInlineContentViews()
13841396
}
13851397

13861398
private fun openClipboardManager() {
13871399
mClipboardManagerHolder!!.clipboard_manager_holder.beVisible()
1400+
mToolbarHolder?.suggestions_holder?.hideAllInlineContentViews()
13881401
setupStoredClips()
13891402
}
13901403

@@ -1614,4 +1627,46 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut
16141627
}
16151628
return keyColor
16161629
}
1630+
1631+
@RequiresApi(Build.VERSION_CODES.R)
1632+
fun addToClipboardViews(it: InlineContentView, addToFront: Boolean = false) {
1633+
if (mToolbarHolder?.autofill_suggestions_holder != null) {
1634+
val newLayoutParams = LinearLayout.LayoutParams(it.layoutParams)
1635+
newLayoutParams.updateMarginsRelative(start = resources.getDimensionPixelSize(R.dimen.normal_margin))
1636+
it.layoutParams = newLayoutParams
1637+
if (addToFront) {
1638+
mToolbarHolder?.autofill_suggestions_holder?.addView(it, 0)
1639+
} else {
1640+
mToolbarHolder?.autofill_suggestions_holder?.addView(it)
1641+
}
1642+
updateSuggestionsToolbarLayout()
1643+
}
1644+
}
1645+
1646+
@RequiresApi(Build.VERSION_CODES.R)
1647+
fun clearClipboardViews() {
1648+
mToolbarHolder?.autofill_suggestions_holder?.removeAllViews()
1649+
updateSuggestionsToolbarLayout()
1650+
}
1651+
1652+
private fun updateSuggestionsToolbarLayout() {
1653+
mToolbarHolder?.apply {
1654+
if (hasInlineViews()) {
1655+
// make room on suggestion toolbar for inline views
1656+
suggestions_items_holder?.gravity = Gravity.NO_GRAVITY
1657+
clipboard_value?.maxWidth = resources.getDimensionPixelSize(R.dimen.suggestion_max_width)
1658+
} else {
1659+
// restore original clipboard toolbar appearance
1660+
suggestions_items_holder?.gravity = Gravity.CENTER_HORIZONTAL
1661+
suggestions_holder?.measuredWidth?.also { maxWidth ->
1662+
clipboard_value?.maxWidth = maxWidth
1663+
}
1664+
}
1665+
}
1666+
}
1667+
1668+
/**
1669+
* Returns true if there are [InlineContentView]s in [autofill_suggestions_holder]
1670+
*/
1671+
private fun hasInlineViews() = (mToolbarHolder?.autofill_suggestions_holder?.childCount ?: 0) > 0
16171672
}

0 commit comments

Comments
 (0)