Skip to content

Commit 076d1e2

Browse files
committed
feat: allow users to trigger rime action via adb shell
1 parent 2c3a5c1 commit 076d1e2

File tree

7 files changed

+96
-155
lines changed

7 files changed

+96
-155
lines changed

app/src/main/java/com/osfans/trime/TrimeApplication.kt

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ import android.app.Application
88
import android.content.Intent
99
import android.os.Process
1010
import android.util.Log
11+
import androidx.core.content.ContextCompat
1112
import androidx.core.content.edit
1213
import androidx.preference.PreferenceManager
1314
import com.osfans.trime.data.db.ClipboardHelper
1415
import com.osfans.trime.data.db.CollectionHelper
1516
import com.osfans.trime.data.db.DraftHelper
1617
import com.osfans.trime.data.prefs.AppPrefs
18+
import com.osfans.trime.receiver.RimeIntentReceiver
1719
import com.osfans.trime.ui.main.LogActivity
1820
import kotlinx.coroutines.CoroutineName
1921
import kotlinx.coroutines.MainScope
@@ -28,19 +30,21 @@ import kotlin.system.exitProcess
2830
* classes everywhere.
2931
*/
3032
class TrimeApplication : Application() {
31-
companion object {
32-
private var instance: TrimeApplication? = null
33-
private var lastPid: Int? = null
33+
val coroutineScope = MainScope() + CoroutineName("TrimeApplication")
3434

35-
fun getInstance() = instance ?: throw IllegalStateException("Trime application is not created!")
35+
private val rimeIntentReceiver = RimeIntentReceiver()
3636

37-
fun getLastPid() = lastPid
38-
39-
private const val MAX_STACKTRACE_SIZE = 128000
37+
private fun registerBroadcastReceiver() {
38+
ContextCompat.registerReceiver(
39+
this,
40+
rimeIntentReceiver,
41+
RimeIntentReceiver.intentFilter,
42+
PERMISSION_TEST_INPUT_METHOD,
43+
null,
44+
ContextCompat.RECEIVER_EXPORTED,
45+
)
4046
}
4147

42-
val coroutineScope = MainScope() + CoroutineName("TrimeApplication")
43-
4448
override fun onCreate() {
4549
super.onCreate()
4650
if (!BuildConfig.DEBUG) {
@@ -128,9 +132,34 @@ class TrimeApplication : Application() {
128132
ClipboardHelper.init(applicationContext)
129133
CollectionHelper.init(applicationContext)
130134
DraftHelper.init(applicationContext)
135+
registerBroadcastReceiver()
131136
} catch (e: Exception) {
132137
e.fillInStackTrace()
133138
return
134139
}
135140
}
141+
142+
companion object {
143+
private var instance: TrimeApplication? = null
144+
private var lastPid: Int? = null
145+
146+
fun getInstance() = instance ?: throw IllegalStateException("Trime application is not created!")
147+
148+
fun getLastPid() = lastPid
149+
150+
private const val MAX_STACKTRACE_SIZE = 128000
151+
152+
/**
153+
* This permission is requested by com.android.shell, makes it possible to start
154+
* deploy from `adb shell am` command:
155+
* ```sh
156+
* adb shell am broadcast -a com.osfans.trime.action.DEPLOY
157+
* ```
158+
* https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-7.0.0_r1/packages/Shell/AndroidManifest.xml#67
159+
*
160+
* other candidate: android.permission.TEST_INPUT_METHOD requires Android 14
161+
* https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-14.0.0_r1/packages/Shell/AndroidManifest.xml#628
162+
*/
163+
const val PERMISSION_TEST_INPUT_METHOD = "android.permission.READ_INPUT_STATE"
164+
}
136165
}

app/src/main/java/com/osfans/trime/core/Rime.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ class Rime :
7979
getCurrentRimeSchema() == ".default" // 無方案
8080
}
8181

82+
override suspend fun syncUserData(): Boolean =
83+
withRimeContext {
84+
syncRimeUserData()
85+
}
86+
8287
override suspend fun processKey(
8388
value: Int,
8489
modifiers: UInt,

app/src/main/java/com/osfans/trime/core/RimeApi.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ interface RimeApi {
2323

2424
suspend fun isEmpty(): Boolean
2525

26+
suspend fun syncUserData(): Boolean
27+
2628
suspend fun processKey(
2729
value: Int,
2830
modifiers: UInt = 0u,

app/src/main/java/com/osfans/trime/daemon/RimeDaemon.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,11 @@ object RimeDaemon {
131131
}
132132
}
133133

134+
/**
135+
* Reuse a session for remote service
136+
*/
137+
fun getFirstSessionOrNull() = sessions.firstNotNullOfOrNull { it.value }
138+
134139
private const val CHANNEL_ID = "rime-daemon"
135140
private const val MESSAGE_ID = 2331
136141
private var restartId = 0

app/src/main/java/com/osfans/trime/ime/broadcast/IntentReceiver.kt

Lines changed: 0 additions & 138 deletions
This file was deleted.

app/src/main/java/com/osfans/trime/ime/core/TrimeInputMethodService.kt

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ import com.osfans.trime.data.prefs.PreferenceDelegateProvider
5353
import com.osfans.trime.data.theme.ColorManager
5454
import com.osfans.trime.data.theme.Theme
5555
import com.osfans.trime.data.theme.ThemeManager
56-
import com.osfans.trime.ime.broadcast.IntentReceiver
5756
import com.osfans.trime.ime.candidates.popup.PopupCandidatesMode
5857
import com.osfans.trime.ime.candidates.suggestion.InlineSuggestionHandler
5958
import com.osfans.trime.ime.composition.CandidatesView
@@ -92,7 +91,6 @@ open class TrimeInputMethodService : LifecycleInputMethodService() {
9291
private var candidatesView: CandidatesView? = null
9392
private val inputDeviceManager = InputDeviceManager()
9493
private var initializationUi: InitializationUi? = null
95-
private var mIntentReceiver: IntentReceiver? = null
9694
private val locales = Array(2) { Locale.getDefault() }
9795

9896
private lateinit var inlineSuggestionHandler: InlineSuggestionHandler
@@ -212,10 +210,6 @@ open class TrimeInputMethodService : LifecycleInputMethodService() {
212210
// could crash
213211
// and lead to a crash loop
214212
Timber.d("onCreate")
215-
mIntentReceiver =
216-
IntentReceiver().also {
217-
it.registerReceiver(this)
218-
}
219213
postRimeJob {
220214
ColorManager.init(resources.configuration)
221215
ThemeManager.init()
@@ -334,8 +328,6 @@ open class TrimeInputMethodService : LifecycleInputMethodService() {
334328
}
335329

336330
override fun onDestroy() {
337-
mIntentReceiver?.unregisterReceiver(this)
338-
mIntentReceiver = null
339331
InputFeedbackManager.destroy()
340332
inputView = null
341333
recreateInputViewPrefs.forEach {
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2015 - 2025 Rime community
3+
* SPDX-License-Identifier: GPL-3.0-or-later
4+
*/
5+
6+
package com.osfans.trime.receiver
7+
8+
import android.content.BroadcastReceiver
9+
import android.content.Context
10+
import android.content.Intent
11+
import android.content.IntentFilter
12+
import com.osfans.trime.BuildConfig
13+
import com.osfans.trime.daemon.RimeDaemon
14+
import timber.log.Timber
15+
16+
class RimeIntentReceiver : BroadcastReceiver() {
17+
override fun onReceive(
18+
context: Context,
19+
intent: Intent,
20+
) {
21+
val rime = RimeDaemon.getFirstSessionOrNull() ?: return Timber.w("No active rime session, skipping")
22+
Timber.i("Received broadcast ${intent.action}")
23+
when (intent.action) {
24+
ACTION_DEPLOY -> {
25+
Timber.i("try to start maintenance ...")
26+
RimeDaemon.restartRime(true)
27+
}
28+
ACTION_SYNC_USER_DATA -> {
29+
Timber.i("try to sync rime user data ...")
30+
rime.run { syncUserData() }
31+
}
32+
else -> {}
33+
}
34+
}
35+
36+
companion object {
37+
private const val ACTION_DEPLOY = "${BuildConfig.APPLICATION_ID}.action.DEPLOY"
38+
private const val ACTION_SYNC_USER_DATA = "${BuildConfig.APPLICATION_ID}.action.SYNC_USER_DATA"
39+
40+
val intentFilter =
41+
IntentFilter().apply {
42+
addAction(ACTION_DEPLOY)
43+
addAction(ACTION_SYNC_USER_DATA)
44+
}
45+
}
46+
}

0 commit comments

Comments
 (0)