Skip to content

Commit 8142083

Browse files
committed
#1619 Automatically select the non key mapper keyboard when the device is locked and wanting to type.
1 parent a9c0256 commit 8142083

File tree

5 files changed

+104
-9
lines changed

5 files changed

+104
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ _See the changes from previous 3.0 Beta releases as well._
55
## Added
66

77
- #1620 enable Key Mapper Basic Input Method without user interaction on Android 13+.
8+
- #1619 Automatically select the non key mapper keyboard when the device is locked and wanting to type.
89

910
## Changed
1011

app/src/main/AndroidManifest.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
android:allowBackup="true"
8080
android:icon="@mipmap/ic_launcher"
8181
android:label="@string/app_name"
82+
android:directBootAware="true"
8283
android:roundIcon="@mipmap/ic_launcher_round"
8384
android:supportsRtl="true"
8485
android:theme="@style/AppTheme.NoActionBar"
@@ -174,6 +175,7 @@
174175
android:exported="true">
175176
<intent-filter>
176177
<action android:name="android.intent.action.BOOT_COMPLETED" />
178+
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
177179
</intent-filter>
178180
</receiver>
179181

@@ -231,8 +233,10 @@
231233
android:resource="@xml/config_accessibility_service" />
232234
</service>
233235

236+
<!-- IMPORTANT! Must be direct boot aware so it can automatically choose a proper keyboard when the user wants to unlock their phone. -->
234237
<service
235238
android:name=".system.inputmethod.KeyMapperImeService"
239+
android:directBootAware="true"
236240
android:exported="true"
237241
android:label="@string/ime_service_label"
238242
android:permission="android.permission.BIND_INPUT_METHOD">

app/src/main/java/io/github/sds100/keymapper/KeyMapperApp.kt

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package io.github.sds100.keymapper
22

3+
import android.annotation.SuppressLint
34
import android.content.Intent
45
import android.os.Build
6+
import android.os.UserManager
7+
import android.util.Log
58
import androidx.appcompat.app.AppCompatDelegate
9+
import androidx.core.content.getSystemService
610
import androidx.lifecycle.Lifecycle
711
import androidx.lifecycle.LifecycleObserver
812
import androidx.lifecycle.OnLifecycleEvent
@@ -64,7 +68,10 @@ import java.util.Calendar
6468
/**
6569
* Created by sds100 on 19/05/2020.
6670
*/
71+
@SuppressLint("LogNotTimber")
6772
class KeyMapperApp : MultiDexApplication() {
73+
private val TAG = KeyMapperApp::class.simpleName
74+
6875
val appCoroutineScope = MainScope()
6976

7077
val notificationAdapter by lazy { AndroidNotificationAdapter(this, appCoroutineScope) }
@@ -167,9 +174,16 @@ class KeyMapperApp : MultiDexApplication() {
167174

168175
private val processLifecycleOwner by lazy { ProcessLifecycleOwner.get() }
169176

177+
private val userManager: UserManager? by lazy { getSystemService<UserManager>() }
178+
179+
private val initLock: Any = Any()
180+
private var initialized = false
181+
170182
override fun onCreate() {
171183
val priorExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
172184

185+
Log.i(TAG, "KeyMapperApp: OnCreate")
186+
173187
Thread.setDefaultUncaughtExceptionHandler { thread, exception ->
174188
// log in a blocking manner and always log regardless of whether the setting is turned on
175189
val entry = LogEntryEntity(
@@ -188,9 +202,30 @@ class KeyMapperApp : MultiDexApplication() {
188202

189203
super.onCreate()
190204

191-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
192-
// DynamicColors.applyToActivitiesIfAvailable(this)
205+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && userManager?.isUserUnlocked == false) {
206+
Log.i(TAG, "KeyMapperApp: Delay init because locked.")
207+
// If the device is still encrypted and locked do not initialize anything that
208+
// may potentially need the encrypted app storage like databases.
209+
return
210+
}
211+
212+
synchronized(initLock) {
213+
init()
214+
initialized = true
193215
}
216+
}
217+
218+
fun onBootUnlocked() {
219+
synchronized(initLock) {
220+
if (!initialized) {
221+
init()
222+
}
223+
initialized = true
224+
}
225+
}
226+
227+
private fun init() {
228+
Log.i(TAG, "KeyMapperApp: Init")
194229

195230
ServiceLocator.settingsRepository(this).get(Keys.darkTheme)
196231
.map { it?.toIntOrNull() }

app/src/main/java/io/github/sds100/keymapper/system/BootBroadcastReceiver.kt

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package io.github.sds100.keymapper.system
33
import android.content.BroadcastReceiver
44
import android.content.Context
55
import android.content.Intent
6-
import io.github.sds100.keymapper.ServiceLocator
6+
import io.github.sds100.keymapper.KeyMapperApp
77

88
/**
99
* Created by sds100 on 24/03/2019.
@@ -12,12 +12,9 @@ import io.github.sds100.keymapper.ServiceLocator
1212
class BootBroadcastReceiver : BroadcastReceiver() {
1313
override fun onReceive(context: Context?, intent: Intent?) {
1414
context ?: return
15-
if (intent?.action == Intent.ACTION_BOOT_COMPLETED) {
16-
/*
17-
Initializing the controller will update any notifications since it will collect the values
18-
in the constructor
19-
*/
20-
ServiceLocator.notificationController(context)
15+
16+
if (intent?.action == Intent.ACTION_LOCKED_BOOT_COMPLETED) {
17+
(context.applicationContext as? KeyMapperApp)?.onBootUnlocked()
2118
}
2219
}
2320
}

app/src/main/java/io/github/sds100/keymapper/system/inputmethod/KeyMapperImeService.kt

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
package io.github.sds100.keymapper.system.inputmethod
22

3+
import android.annotation.SuppressLint
4+
import android.app.KeyguardManager
35
import android.content.BroadcastReceiver
46
import android.content.Context
57
import android.content.Intent
68
import android.content.IntentFilter
79
import android.inputmethodservice.InputMethodService
10+
import android.os.Build
11+
import android.os.UserManager
12+
import android.util.Log
813
import android.view.KeyEvent
914
import android.view.MotionEvent
15+
import android.view.inputmethod.EditorInfo
16+
import android.view.inputmethod.InputMethodManager
1017
import androidx.core.content.ContextCompat
18+
import androidx.core.content.getSystemService
1119
import io.github.sds100.keymapper.Constants
1220
import io.github.sds100.keymapper.api.IKeyEventRelayServiceCallback
1321
import io.github.sds100.keymapper.api.KeyEventRelayService
@@ -36,6 +44,15 @@ class KeyMapperImeService : InputMethodService() {
3644
"io.github.sds100.keymapper.inputmethod.EXTRA_KEY_EVENT"
3745
}
3846

47+
private val userManager: UserManager? by lazy { getSystemService<UserManager>() }
48+
private val inputMethodManager: InputMethodManager? by lazy {
49+
getSystemService<InputMethodManager>()
50+
}
51+
52+
private val keyguardManager: KeyguardManager? by lazy {
53+
getSystemService<KeyguardManager>()
54+
}
55+
3956
private val broadcastReceiver = object : BroadcastReceiver() {
4057
override fun onReceive(context: Context?, intent: Intent?) {
4158
val action = intent?.action ?: return
@@ -120,6 +137,23 @@ class KeyMapperImeService : InputMethodService() {
120137
keyEventRelayServiceWrapper.onCreate()
121138
}
122139

140+
override fun onStartInput(attribute: EditorInfo?, restarting: Boolean) {
141+
super.onStartInput(attribute, restarting)
142+
143+
// IMPORTANT! Select a keyboard with an actual GUI if the user needs
144+
// to unlock their device. This must not be in onCreate because
145+
// the switchInputMethod does not work there.
146+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && userManager?.isUserUnlocked == false) {
147+
selectNonBasicKeyboard()
148+
} else if (
149+
!restarting &&
150+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1 &&
151+
keyguardManager?.isDeviceLocked == true
152+
) {
153+
selectNonBasicKeyboard()
154+
}
155+
}
156+
123157
override fun onGenericMotionEvent(event: MotionEvent?): Boolean {
124158
event ?: return super.onGenericMotionEvent(null)
125159

@@ -174,4 +208,28 @@ class KeyMapperImeService : InputMethodService() {
174208

175209
super.onDestroy()
176210
}
211+
212+
@SuppressLint("LogNotTimber")
213+
private fun selectNonBasicKeyboard() {
214+
inputMethodManager ?: return
215+
216+
inputMethodManager!!.enabledInputMethodList
217+
.filter {
218+
it.packageName != "io.github.sds100.keymapper" &&
219+
it.packageName != "io.github.sds100.keymapper.debug" &&
220+
it.packageName != "io.github.sds100.keymapper.ci"
221+
}
222+
// Select a random one in case one of them can't be used on the lock screen such as
223+
// the Google Voice Typing keyboard. This is critical because i
224+
// f an input method can't be used
225+
// then it will select the Key Mapper Basic Input method again and loop forever.
226+
.randomOrNull()
227+
?.also {
228+
Log.e(
229+
KeyMapperImeService::class.simpleName,
230+
"Device is locked! Select ${it.id} input method",
231+
)
232+
switchInputMethod(it.id)
233+
}
234+
}
177235
}

0 commit comments

Comments
 (0)