Skip to content

Commit b146feb

Browse files
committed
POC IME implementation
1 parent 4be2e3e commit b146feb

4 files changed

Lines changed: 131 additions & 1 deletion

File tree

app/src/main/java/org/koreader/launcher/EventReceiver.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class EventReceiver : BroadcastReceiver() {
3333
return filter
3434
}
3535

36-
private fun write(code: Int?) {
36+
public fun write(code: Int?) {
3737
if (code == null) {
3838
Log.e(tag, "Invalid code: must be a 32-bit integer")
3939
return

app/src/main/java/org/koreader/launcher/LuaInterface.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,8 @@ interface LuaInterface {
8181
fun startTestActivity()
8282
fun showFrontlightDialog(title: String, dim: String, warmth: String, okButton: String, cancelButton: String)
8383
fun showToast(message: String, longTimeout: Boolean)
84+
// IME bridge
85+
fun startTextInput()
86+
fun stopTextInput()
87+
fun dequeueCommittedText(): String?
8488
}

app/src/main/java/org/koreader/launcher/MainActivity.kt

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,23 @@ import android.provider.Settings
1818
import android.util.Log
1919
import android.view.*
2020
import android.widget.Toast
21+
import android.widget.EditText
22+
import android.text.TextWatcher
23+
import android.text.Editable
24+
import android.view.inputmethod.EditorInfo
25+
import android.text.InputType
26+
import android.view.inputmethod.InputMethodManager
2127
import androidx.annotation.RequiresApi
2228
import androidx.core.app.ActivityCompat
2329
import androidx.core.content.ContextCompat
2430
import org.koreader.launcher.device.Device
2531
import org.koreader.launcher.dialog.LightDialog
2632
import org.koreader.launcher.extensions.*
2733
import java.io.File
34+
import java.io.FileWriter
2835
import java.util.Locale
2936
import java.util.concurrent.CountDownLatch
37+
import java.util.concurrent.ConcurrentLinkedQueue
3038

3139
class MainActivity : NativeActivity(), LuaInterface,
3240
ActivityCompat.OnRequestPermissionsResultCallback {
@@ -67,6 +75,88 @@ class MainActivity : NativeActivity(), LuaInterface,
6775
// Hardware orientation for this device (usually match android boot logo)
6876
private var screenIsLandscape: Boolean = false
6977

78+
/*---------------------------------------------------------------
79+
* IME bridge state *
80+
*--------------------------------------------------------------*/
81+
private var imeEditText: EditText? = null
82+
private val imeQueue = ConcurrentLinkedQueue<String>()
83+
84+
private fun ensureImeEditText(): EditText {
85+
var et = imeEditText
86+
if (et == null) {
87+
et = EditText(this)
88+
et.isSingleLine = false
89+
et.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI or EditorInfo.IME_ACTION_NONE
90+
et.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE
91+
et.isFocusable = true
92+
et.isFocusableInTouchMode = true
93+
et.visibility = View.VISIBLE
94+
et.alpha = 0f
95+
et.addTextChangedListener(object: TextWatcher {
96+
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
97+
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
98+
Log.i(tag, "text changed!");
99+
if (s == null) return
100+
if (count > 0) {
101+
val end = start + count
102+
if (start >= 0 && end <= s.length) {
103+
val inserted = s.subSequence(start, end).toString()
104+
Log.i(tag, "in if");
105+
if (inserted.isNotEmpty()) {
106+
Log.i(tag, "got text $inserted")
107+
imeQueue.add(inserted)
108+
// 120 == AEVENT_TEXT_INPUT
109+
event.write(120)
110+
et.text?.clear()
111+
}
112+
}
113+
}
114+
}
115+
override fun afterTextChanged(s: Editable?) {}
116+
})
117+
118+
// Attach to window without disturbing native content
119+
val lp = ViewGroup.LayoutParams(1, 1)
120+
addContentView(et, lp)
121+
imeEditText = et
122+
}
123+
return et
124+
}
125+
126+
@Suppress("unused")
127+
override fun startTextInput() {
128+
runOnUiThread {
129+
val et = ensureImeEditText()
130+
if (et.visibility != View.VISIBLE) et.visibility = View.VISIBLE
131+
et.alpha = 0f
132+
et.post {
133+
if (!et.isFocused) et.requestFocus()
134+
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
135+
imm.showSoftInput(et, InputMethodManager.SHOW_IMPLICIT)
136+
}
137+
}
138+
}
139+
140+
@Suppress("unused")
141+
override fun stopTextInput() {
142+
runOnUiThread {
143+
imeEditText?.let { et ->
144+
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
145+
imm.hideSoftInputFromWindow(et.windowToken, 0)
146+
et.clearFocus()
147+
et.alpha = 0f
148+
et.visibility = View.VISIBLE
149+
et.text?.clear()
150+
}
151+
}
152+
}
153+
154+
@Suppress("unused")
155+
override fun dequeueCommittedText(): String? {
156+
Log.i(tag, "dequeueCommittedText")
157+
return imeQueue.poll()
158+
}
159+
70160
companion object {
71161
private const val TAG_SURFACE = "Surface"
72162
private const val MANDATORY_PERMISSIONS_ID = 1
@@ -116,6 +206,9 @@ class MainActivity : NativeActivity(), LuaInterface,
116206

117207
// Window background must be black for vertical and horizontal lines to be visible
118208
window.setBackgroundDrawableResource(android.R.color.black)
209+
// Allow IME to interact with this window and resize content with soft keyboard
210+
window.clearFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM)
211+
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
119212

120213
val surfaceKind: String = if (device.needsView) {
121214
view = NativeSurfaceView(this)

assets/android.lua

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,7 @@ enum {
581581
AEVENT_POWER_CONNECTED = 100,
582582
AEVENT_POWER_DISCONNECTED = 101,
583583
AEVENT_DOWNLOAD_COMPLETE = 110,
584+
AEVENT_TEXT_INPUT = 120,
584585
};
585586

586587
enum {
@@ -2423,6 +2424,38 @@ local function run(android_app_state)
24232424
end)
24242425
end
24252426

2427+
-- IME/Text input bridge
2428+
android.startTextInput = function()
2429+
JNI:context(android.app.activity.vm, function(jni)
2430+
jni:callVoidMethod(
2431+
android.app.activity.clazz,
2432+
"startTextInput",
2433+
"()V"
2434+
)
2435+
end)
2436+
end
2437+
2438+
android.stopTextInput = function()
2439+
JNI:context(android.app.activity.vm, function(jni)
2440+
jni:callVoidMethod(
2441+
android.app.activity.clazz,
2442+
"stopTextInput",
2443+
"()V"
2444+
)
2445+
end)
2446+
end
2447+
2448+
android.dequeueCommittedText = function()
2449+
return JNI:context(android.app.activity.vm, function(jni)
2450+
local text = jni:callObjectMethod(
2451+
android.app.activity.clazz,
2452+
"dequeueCommittedText",
2453+
"()Ljava/lang/String;"
2454+
)
2455+
return jni:to_string(text)
2456+
end)
2457+
end
2458+
24262459
android.setClipboardText = function(text)
24272460
android.DEBUG("setting clipboard text to: " .. text)
24282461
JNI:context(android.app.activity.vm, function(jni)

0 commit comments

Comments
 (0)