Skip to content

Commit a8716ef

Browse files
committed
fix: auto start PRO mode when upgrading to 4.0 on a rooted device
1 parent 40aed39 commit a8716ef

File tree

5 files changed

+99
-75
lines changed

5 files changed

+99
-75
lines changed

base/src/main/java/io/github/sds100/keymapper/base/BaseKeyMapperApp.kt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import io.github.sds100.keymapper.system.devices.AndroidDevicesAdapter
3434
import io.github.sds100.keymapper.system.inputmethod.KeyEventRelayServiceWrapperImpl
3535
import io.github.sds100.keymapper.system.permissions.AndroidPermissionAdapter
3636
import io.github.sds100.keymapper.system.permissions.Permission
37-
import io.github.sds100.keymapper.system.root.SuAdapterImpl
3837
import java.util.Calendar
3938
import javax.inject.Inject
4039
import kotlinx.coroutines.CoroutineScope
@@ -69,9 +68,6 @@ abstract class BaseKeyMapperApp : MultiDexApplication() {
6968
@Inject
7069
lateinit var accessibilityServiceAdapter: AccessibilityServiceAdapterImpl
7170

72-
@Inject
73-
lateinit var suAdapter: SuAdapterImpl
74-
7571
@Inject
7672
lateinit var autoGrantPermissionController: AutoGrantPermissionController
7773

base/src/main/java/io/github/sds100/keymapper/base/promode/SystemBridgeAutoStarter.kt

Lines changed: 55 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,16 @@ class SystemBridgeAutoStarter @Inject constructor(
7979
if (isShizukuStarted) {
8080
flowOf(AutoStartType.SHIZUKU)
8181
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
82-
combine(
82+
val isAdbAutoStartAllowed = combine(
8383
permissionAdapter.isGrantedFlow(Permission.WRITE_SECURE_SETTINGS),
8484
networkAdapter.isWifiConnected,
8585
) { isWriteSecureSettingsGranted, isWifiConnected ->
8686
isWriteSecureSettingsGranted &&
8787
isWifiConnected &&
8888
setupController.isAdbPaired()
89-
}.distinctUntilChanged()
89+
}
90+
91+
isAdbAutoStartAllowed.distinctUntilChanged()
9092
.map { isAdbAutoStartAllowed ->
9193
if (isAdbAutoStartAllowed) AutoStartType.ADB else null
9294
}.filterNotNull()
@@ -140,37 +142,10 @@ class SystemBridgeAutoStarter @Inject constructor(
140142
// it should be auto started.
141143
val isBoot = SystemClock.uptimeMillis() < 60000
142144

143-
// Do not autostart if the device was force rebooted. This may be a sign that PRO mode
144-
// was broken and the user was trying to reset it.
145-
val isCleanShutdown = preferences.get(Keys.isCleanShutdown).map { it ?: false }.first()
146-
147-
Timber.i(
148-
"SystemBridgeAutoStarter init: isBoot=$isBoot, isCleanShutdown=$isCleanShutdown",
149-
)
150-
151-
// Reset the value after reading it.
152-
preferences.set(Keys.isCleanShutdown, false)
153-
154-
val isBootAutoStartEnabled = preferences.get(Keys.isProModeAutoStartBootEnabled)
155-
.map { it ?: PreferenceDefaults.PRO_MODE_AUTOSTART_BOOT }
156-
.first()
157-
158-
// Wait 5 seconds for the system bridge to potentially connect itself to Key Mapper
159-
// before starting it.
160-
delay(5000)
161-
162-
val connectionState = connectionManager.connectionState.value
163-
164-
if (isBoot &&
165-
isCleanShutdown &&
166-
isBootAutoStartEnabled &&
167-
connectionState !is SystemBridgeConnectionState.Connected
168-
) {
169-
val autoStartType = autoStartTypeFlow.first()
170-
171-
if (autoStartType != null) {
172-
autoStart(autoStartType)
173-
}
145+
if (isBoot) {
146+
handleAutoStartOnBoot()
147+
} else {
148+
handleAutoStartFromPreVersion4()
174149
}
175150

176151
// Only start collecting the restart flow after potentially auto starting it for the first time.
@@ -183,6 +158,53 @@ class SystemBridgeAutoStarter @Inject constructor(
183158
}
184159
}
185160

161+
private suspend fun handleAutoStartOnBoot() {
162+
// Do not autostart if the device was force rebooted. This may be a sign that PRO mode
163+
// was broken and the user was trying to reset it.
164+
val isCleanShutdown = preferences.get(Keys.isCleanShutdown).map { it ?: false }.first()
165+
166+
Timber.i(
167+
"SystemBridgeAutoStarter init: isBoot=true, isCleanShutdown=$isCleanShutdown",
168+
)
169+
170+
// Reset the value after reading it.
171+
preferences.set(Keys.isCleanShutdown, false)
172+
173+
val isBootAutoStartEnabled = preferences.get(Keys.isProModeAutoStartBootEnabled)
174+
.map { it ?: PreferenceDefaults.PRO_MODE_AUTOSTART_BOOT }
175+
.first()
176+
177+
// Wait 5 seconds for the system bridge to potentially connect itself to Key Mapper
178+
// before starting it.
179+
delay(5000)
180+
181+
val connectionState = connectionManager.connectionState.value
182+
183+
if (isCleanShutdown &&
184+
isBootAutoStartEnabled &&
185+
connectionState !is SystemBridgeConnectionState.Connected
186+
) {
187+
val autoStartType = autoStartTypeFlow.first()
188+
189+
if (autoStartType != null) {
190+
autoStart(autoStartType)
191+
}
192+
}
193+
}
194+
195+
private suspend fun handleAutoStartFromPreVersion4() {
196+
val isFirstTime = preferences.get(Keys.handledRootToProModeUpgrade).first() == null
197+
198+
if (isFirstTime && suAdapter.isRootGranted.value) {
199+
Timber.i(
200+
"Auto starting system bridge because upgraded from pre version 4.0 and was rooted",
201+
)
202+
203+
autoStart(AutoStartType.ROOT)
204+
preferences.set(Keys.handledRootToProModeUpgrade, true)
205+
}
206+
}
207+
186208
private suspend fun autoStart(type: AutoStartType) {
187209
if (isSystemBridgeEmergencyKilled()) {
188210
Timber.w(

data/src/main/java/io/github/sds100/keymapper/data/Keys.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,4 +143,10 @@ object Keys {
143143
booleanPreferencesKey("key_key_event_actions_use_system_bridge")
144144

145145
val shellCommandScriptText = stringPreferencesKey("key_shell_command_script_text")
146+
147+
/**
148+
* This is stored as true when PRO Mode has been auto started after upgrading
149+
* to 4.0 on a rooted device.
150+
*/
151+
val handledRootToProModeUpgrade = booleanPreferencesKey("key_handled_root_to_pro_mode_upgrade")
146152
}

sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/starter/SystemBridgeStarter.kt

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,6 @@ import io.github.sds100.keymapper.sysbridge.R
2525
import io.github.sds100.keymapper.sysbridge.adb.AdbManager
2626
import io.github.sds100.keymapper.sysbridge.ktx.loge
2727
import io.github.sds100.keymapper.sysbridge.shizuku.ShizukuStarterService
28-
import kotlinx.coroutines.Dispatchers
29-
import kotlinx.coroutines.runBlocking
30-
import kotlinx.coroutines.sync.Mutex
31-
import kotlinx.coroutines.sync.withLock
32-
import kotlinx.coroutines.withContext
33-
import rikka.shizuku.Shizuku
34-
import timber.log.Timber
3528
import java.io.BufferedReader
3629
import java.io.DataInputStream
3730
import java.io.File
@@ -44,12 +37,19 @@ import java.util.zip.ZipEntry
4437
import java.util.zip.ZipFile
4538
import javax.inject.Inject
4639
import javax.inject.Singleton
40+
import kotlinx.coroutines.Dispatchers
41+
import kotlinx.coroutines.runBlocking
42+
import kotlinx.coroutines.sync.Mutex
43+
import kotlinx.coroutines.sync.withLock
44+
import kotlinx.coroutines.withContext
45+
import rikka.shizuku.Shizuku
46+
import timber.log.Timber
4747

4848
@Singleton
4949
class SystemBridgeStarter @Inject constructor(
5050
@ApplicationContext private val ctx: Context,
5151
private val adbManager: AdbManager,
52-
private val buildConfigProvider: BuildConfigProvider
52+
private val buildConfigProvider: BuildConfigProvider,
5353
) {
5454
private val userManager by lazy { ctx.getSystemService(UserManager::class.java)!! }
5555

@@ -60,10 +60,7 @@ class SystemBridgeStarter @Inject constructor(
6060
private val startMutex: Mutex = Mutex()
6161

6262
private val shizukuStarterConnection: ServiceConnection = object : ServiceConnection {
63-
override fun onServiceConnected(
64-
name: ComponentName?,
65-
binder: IBinder?
66-
) {
63+
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
6764
Timber.i("Shizuku starter service connected")
6865

6966
val service = IShizukuStarterService.Stub.asInterface(binder)
@@ -81,7 +78,6 @@ class SystemBridgeStarter @Inject constructor(
8178
}
8279
})
8380
}
84-
8581
} catch (e: RemoteException) {
8682
Timber.e("Exception starting with Shizuku starter service: $e")
8783
} finally {
@@ -119,7 +115,7 @@ class SystemBridgeStarter @Inject constructor(
119115
try {
120116
Shizuku.bindUserService(
121117
args,
122-
shizukuStarterConnection
118+
shizukuStarterConnection,
123119
)
124120
} catch (e: Exception) {
125121
Timber.e("Exception when starting System Bridge with Shizuku. $e")
@@ -158,7 +154,9 @@ class SystemBridgeStarter @Inject constructor(
158154
})
159155
}
160156

161-
suspend fun startSystemBridge(executeCommand: suspend (String) -> KMResult<String>): KMResult<String> {
157+
suspend fun startSystemBridge(
158+
executeCommand: suspend (String) -> KMResult<String>,
159+
): KMResult<String> {
162160
startMutex.withLock {
163161
val externalFilesParent = try {
164162
ctx.getExternalFilesDir(null)?.parentFile
@@ -176,7 +174,7 @@ class SystemBridgeStarter @Inject constructor(
176174
// Create the start.sh shell script
177175
writeStarterScript(
178176
outputStarterScript,
179-
outputStarterBinary.absolutePath
177+
outputStarterBinary.absolutePath,
180178
)
181179
Success(Unit)
182180
}
@@ -189,9 +187,12 @@ class SystemBridgeStarter @Inject constructor(
189187
.then { executeCommand(startCommand) }
190188
.then { output ->
191189
// Adb on Android 11 has no permission to access Android/data so use /data/user_de.
192-
if (output.contains("/Android/data/${ctx.packageName}/start.sh: Permission denied")) {
190+
if (output.contains(
191+
"/Android/data/${ctx.packageName}/start.sh: Permission denied",
192+
)
193+
) {
193194
Timber.w(
194-
"ADB has no permission to access Android/data/${ctx.packageName}/start.sh. Trying to use /data/user_de instead..."
195+
"ADB has no permission to access Android/data/${ctx.packageName}/start.sh. Trying to use /data/user_de instead...",
195196
)
196197

197198
startSystemBridgeFromProtectedStorage(executeCommand)
@@ -203,15 +204,15 @@ class SystemBridgeStarter @Inject constructor(
203204
}
204205

205206
private suspend fun startSystemBridgeFromProtectedStorage(
206-
executeCommand: suspend (String) -> KMResult<String>
207+
executeCommand: suspend (String) -> KMResult<String>,
207208
): KMResult<String> {
208209
val protectedStorageDir =
209210
ctx.createDeviceProtectedStorageContext().filesDir.parentFile!!
210211

211212
Timber.i("Protected storage dir: ${protectedStorageDir.absolutePath}")
212213

213214
try {
214-
Os.chmod(protectedStorageDir.absolutePath, 457 /* 0711 */)
215+
Os.chmod(protectedStorageDir.absolutePath, 457) /* 0711 */
215216
} catch (e: ErrnoException) {
216217
e.printStackTrace()
217218
}
@@ -227,7 +228,7 @@ class SystemBridgeStarter @Inject constructor(
227228

228229
writeStarterScript(
229230
outputStarterScript,
230-
outputStarterBinary.absolutePath
231+
outputStarterBinary.absolutePath,
231232
)
232233
}
233234

@@ -236,20 +237,19 @@ class SystemBridgeStarter @Inject constructor(
236237

237238
// Make starter binary executable
238239
try {
239-
Os.chmod(outputStarterBinary.absolutePath, 420 /* 0644 */)
240+
Os.chmod(outputStarterBinary.absolutePath, 420) /* 0644 */
240241
} catch (e: ErrnoException) {
241242
e.printStackTrace()
242243
}
243244

244245
// Make starter script executable
245246
try {
246-
Os.chmod(outputStarterScript.absolutePath, 420 /* 0644 */)
247+
Os.chmod(outputStarterScript.absolutePath, 420) /* 0644 */
247248
} catch (e: ErrnoException) {
248249
e.printStackTrace()
249250
}
250251

251252
return executeCommand(startCommand)
252-
253253
} catch (e: IOException) {
254254
loge("write files", e)
255255
return KMError.UnknownIOError
@@ -271,17 +271,15 @@ class SystemBridgeStarter @Inject constructor(
271271

272272
File("$libPath/$libraryName").copyTo(out)
273273
return Success(Unit)
274-
275274
} catch (e: Exception) {
276-
Timber.w("Native library not found. Extracting from APKs. Exception: ${e.toString()}")
275+
Timber.w("Native library not found. Extracting from APKs. Exception: $e")
277276

278277
val apkPaths: Array<String> = arrayOf(baseApkPath, *splitApkPaths)
279278

280279
Timber.i("APK paths: ${apkPaths.joinToString()}")
281280

282281
for (apk in apkPaths) {
283282
with(ZipFile(apk)) {
284-
285283
for (abi in Build.SUPPORTED_ABIS) {
286284
val expectedLibraryPath = "lib/$abi/$libraryName"
287285

system/src/main/java/io/github/sds100/keymapper/system/root/SuAdapter.kt

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,18 @@ package io.github.sds100.keymapper.system.root
33
import com.topjohnwu.superuser.Shell
44
import io.github.sds100.keymapper.system.shell.BaseShellAdapter
55
import io.github.sds100.keymapper.system.shell.ShellAdapter
6+
import javax.inject.Inject
7+
import javax.inject.Singleton
68
import kotlinx.coroutines.flow.MutableStateFlow
79
import kotlinx.coroutines.flow.StateFlow
810
import kotlinx.coroutines.flow.update
911
import timber.log.Timber
10-
import javax.inject.Inject
11-
import javax.inject.Singleton
1212

1313
@Singleton
14-
class SuAdapterImpl @Inject constructor() : BaseShellAdapter(), SuAdapter {
15-
override val isRootGranted: MutableStateFlow<Boolean> = MutableStateFlow(false)
16-
17-
init {
18-
Shell.getShell()
19-
invalidateIsRooted()
20-
}
14+
class SuAdapterImpl @Inject constructor() :
15+
BaseShellAdapter(),
16+
SuAdapter {
17+
override val isRootGranted: MutableStateFlow<Boolean> = MutableStateFlow(getIsRooted())
2118

2219
override fun requestPermission() {
2320
invalidateIsRooted()
@@ -31,8 +28,7 @@ class SuAdapterImpl @Inject constructor() : BaseShellAdapter(), SuAdapter {
3128
fun invalidateIsRooted() {
3229
try {
3330
// Close the shell so a new one is started without root permission.
34-
Shell.getShell().waitAndClose()
35-
val isRooted = Shell.isAppGrantedRoot() ?: false
31+
val isRooted = getIsRooted()
3632
isRootGranted.update { isRooted }
3733

3834
if (isRooted) {
@@ -44,6 +40,12 @@ class SuAdapterImpl @Inject constructor() : BaseShellAdapter(), SuAdapter {
4440
Timber.e("Exception invalidating root detection: $e")
4541
}
4642
}
43+
44+
private fun getIsRooted(): Boolean {
45+
Shell.getShell().waitAndClose()
46+
val isRooted = Shell.isAppGrantedRoot() ?: false
47+
return isRooted
48+
}
4749
}
4850

4951
interface SuAdapter : ShellAdapter {

0 commit comments

Comments
 (0)