Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
<application
android:name="io.github.sds100.keymapper.KeyMapperApp"
android:allowBackup="true"
android:directBootAware="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
Expand Down Expand Up @@ -55,6 +54,7 @@
<service
android:name=".system.accessibility.MyAccessibilityService"
android:configChanges="orientation|screenSize"
android:directBootAware="false"
android:exported="true"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
Expand All @@ -67,4 +67,4 @@
</service>

</application>
</manifest>
</manifest>
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package io.github.sds100.keymapper.system.accessibility

import android.content.Intent
import android.os.UserManager
import android.view.KeyEvent
import android.view.accessibility.AccessibilityEvent
import androidx.core.content.getSystemService
import dagger.hilt.android.AndroidEntryPoint
import io.github.sds100.keymapper.base.system.accessibility.BaseAccessibilityService
import io.github.sds100.keymapper.base.system.accessibility.BaseAccessibilityServiceController
Expand All @@ -14,6 +18,9 @@ class MyAccessibilityService : BaseAccessibilityService() {
lateinit var controllerFactory: AccessibilityServiceController.Factory

private var controller: AccessibilityServiceController? = null
private var loggedLockedInitDelay = false

private val userManager: UserManager? by lazy { getSystemService<UserManager>() }

override fun getController(): BaseAccessibilityServiceController? {
return controller
Expand All @@ -22,15 +29,47 @@ class MyAccessibilityService : BaseAccessibilityService() {
override fun onServiceConnected() {
super.onServiceConnected()

initializeControllerIfUserUnlocked()
}

override fun onAccessibilityEvent(event: AccessibilityEvent?) {
if (!initializeControllerIfUserUnlocked()) {
return
}

super.onAccessibilityEvent(event)
}

override fun onKeyEvent(event: KeyEvent?): Boolean {
if (!initializeControllerIfUserUnlocked()) {
return false
}

return super.onKeyEvent(event)
}

private fun initializeControllerIfUserUnlocked(): Boolean {
if (userManager?.isUserUnlocked == false) {
if (!loggedLockedInitDelay) {
Timber.i("Accessibility service: Delay init because locked.")
loggedLockedInitDelay = true
}

return false
}

loggedLockedInitDelay = false

/*
I would put this in onCreate but for some reason on some devices getting the application
context would return null
*/
if (controller == null) {
controller = controllerFactory.create(this)
controller?.onServiceConnected()
}

controller?.onServiceConnected()
return true
}

override fun onUnbind(intent: Intent?): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import io.github.sds100.keymapper.system.permissions.AndroidPermissionAdapter
import io.github.sds100.keymapper.system.permissions.Permission
import java.util.Calendar
import javax.inject.Inject
import dagger.Lazy
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.first
Expand All @@ -51,46 +52,46 @@ abstract class BaseKeyMapperApp : MultiDexApplication() {
private val tag = BaseKeyMapperApp::class.simpleName

@Inject
lateinit var appCoroutineScope: CoroutineScope
lateinit var appCoroutineScope: Lazy<CoroutineScope>

@Inject
lateinit var notificationController: NotificationController
lateinit var notificationController: Lazy<NotificationController>

@Inject
lateinit var packageManagerAdapter: AndroidPackageManagerAdapter
lateinit var packageManagerAdapter: Lazy<AndroidPackageManagerAdapter>

@Inject
lateinit var devicesAdapter: AndroidDevicesAdapter
lateinit var devicesAdapter: Lazy<AndroidDevicesAdapter>

@Inject
lateinit var permissionAdapter: AndroidPermissionAdapter
lateinit var permissionAdapter: Lazy<AndroidPermissionAdapter>

@Inject
lateinit var accessibilityServiceAdapter: AccessibilityServiceAdapterImpl
lateinit var accessibilityServiceAdapter: Lazy<AccessibilityServiceAdapterImpl>

@Inject
lateinit var autoGrantPermissionController: AutoGrantPermissionController
lateinit var autoGrantPermissionController: Lazy<AutoGrantPermissionController>

@Inject
lateinit var loggingTree: KeyMapperLoggingTree
lateinit var loggingTree: Lazy<KeyMapperLoggingTree>

@Inject
lateinit var settingsRepository: PreferenceRepositoryImpl
lateinit var settingsRepository: Lazy<PreferenceRepositoryImpl>

@Inject
lateinit var logRepository: LogRepository
lateinit var logRepository: Lazy<LogRepository>

@Inject
lateinit var keyEventRelayServiceWrapper: KeyEventRelayServiceWrapperImpl
lateinit var keyEventRelayServiceWrapper: Lazy<KeyEventRelayServiceWrapperImpl>

@Inject
lateinit var systemBridgeAutoStarter: SystemBridgeAutoStarter
lateinit var systemBridgeAutoStarter: Lazy<SystemBridgeAutoStarter>

@Inject
lateinit var systemBridgeConnectionManager: SystemBridgeConnectionManagerImpl
lateinit var systemBridgeConnectionManager: Lazy<SystemBridgeConnectionManagerImpl>

@Inject
lateinit var systemBridgeLogger: SystemBridgeLogger
lateinit var systemBridgeLogger: Lazy<SystemBridgeLogger>

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

Expand Down Expand Up @@ -118,16 +119,18 @@ abstract class BaseKeyMapperApp : MultiDexApplication() {
Log.i(tag, "KeyMapperApp: OnCreate")

Thread.setDefaultUncaughtExceptionHandler { thread, exception ->
// log in a blocking manner and always log regardless of whether the setting is turned on
val entry = LogEntryEntity(
id = 0,
time = Calendar.getInstance().timeInMillis,
severity = LogEntryEntity.SEVERITY_ERROR,
message = exception.stackTraceToString(),
)

runBlocking {
logRepository.insertSuspend(entry)
if (userManager?.isUserUnlocked != false) {
// log in a blocking manner and always log regardless of whether the setting is turned on
val entry = LogEntryEntity(
id = 0,
time = Calendar.getInstance().timeInMillis,
severity = LogEntryEntity.SEVERITY_ERROR,
message = exception.stackTraceToString(),
)

runBlocking {
logRepository.get().insertSuspend(entry)
}
}

priorExceptionHandler?.uncaughtException(thread, exception)
Expand Down Expand Up @@ -168,7 +171,7 @@ abstract class BaseKeyMapperApp : MultiDexApplication() {

registerReceiver(broadcastReceiver, intentFilter)

settingsRepository.get(Keys.darkTheme)
settingsRepository.get().get(Keys.darkTheme)
.map { it?.toIntOrNull() }
.map {
when (it) {
Expand All @@ -178,35 +181,35 @@ abstract class BaseKeyMapperApp : MultiDexApplication() {
}
}
.onEach { mode -> AppCompatDelegate.setDefaultNightMode(mode) }
.launchIn(appCoroutineScope)
.launchIn(appCoroutineScope.get())

if (BuildConfig.BUILD_TYPE == "debug" || BuildConfig.BUILD_TYPE == "debug_release") {
Timber.plant(Timber.DebugTree())
}

Timber.plant(loggingTree)
Timber.plant(loggingTree.get())

notificationController.init()
notificationController.get().init()

processLifecycleOwner.lifecycle.addObserver(
object : LifecycleObserver {
@Suppress("DEPRECATION")
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onResume() {
// when the user returns to the app let everything know that the permissions could have changed
notificationController.onOpenApp()
notificationController.get().onOpenApp()

if (BuildConfig.DEBUG &&
permissionAdapter.isGranted(Permission.WRITE_SECURE_SETTINGS)
permissionAdapter.get().isGranted(Permission.WRITE_SECURE_SETTINGS)
) {
accessibilityServiceAdapter.start()
accessibilityServiceAdapter.get().start()
}
}
},
)

appCoroutineScope.launch {
notificationController.openApp.collectLatest { intentAction ->
appCoroutineScope.get().launch {
notificationController.get().openApp.collectLatest { intentAction ->
Intent(this@BaseKeyMapperApp, getMainActivityClass()).apply {
action = intentAction
flags = Intent.FLAG_ACTIVITY_NEW_TASK
Expand All @@ -216,38 +219,38 @@ abstract class BaseKeyMapperApp : MultiDexApplication() {
}
}

notificationController.showToast.onEach { toast ->
notificationController.get().showToast.onEach { toast ->
Toast.makeText(this, toast, Toast.LENGTH_SHORT).show()
}.launchIn(appCoroutineScope)
}.launchIn(appCoroutineScope.get())

autoGrantPermissionController.start()
keyEventRelayServiceWrapper.bind()
autoGrantPermissionController.get().start()
keyEventRelayServiceWrapper.get().bind()

if (systemBridgeConnectionManager.isConnected()) {
if (systemBridgeConnectionManager.get().isConnected()) {
Timber.i("KeyMapperApp: System bridge is connected")
} else {
Timber.i("KeyMapperApp: System bridge is disconnected")
}

systemBridgeAutoStarter.init()
systemBridgeAutoStarter.get().init()

// Initialize SystemBridgeLogger to start receiving log messages from SystemBridge.
// Using Lazy<> to avoid circular dependency issues and ensure it's only created
// when the API level requirement is met.
systemBridgeLogger.start()
systemBridgeLogger.get().start()

appCoroutineScope.launch {
systemBridgeConnectionManager.connectionState.collect { state ->
appCoroutineScope.get().launch {
systemBridgeConnectionManager.get().connectionState.collect { state ->
if (state is SystemBridgeConnectionState.Connected) {
val isUsed =
settingsRepository.get(Keys.isSystemBridgeUsed).first() ?: false
settingsRepository.get().get(Keys.isSystemBridgeUsed).first() ?: false

// Enable the setting to use PRO mode for key event actions the first time they use PRO mode.
if (!isUsed) {
settingsRepository.set(Keys.keyEventActionsUseSystemBridge, true)
settingsRepository.get().set(Keys.keyEventActionsUseSystemBridge, true)
}

settingsRepository.set(Keys.isSystemBridgeUsed, true)
settingsRepository.get().set(Keys.isSystemBridgeUsed, true)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@ class BootBroadcastReceiver : BroadcastReceiver() {
Timber.i(
"Boot completed broadcast: time since boot = ${SystemClock.elapsedRealtime() / 1000}",
)
(context.applicationContext as? BaseKeyMapperApp)?.onBootUnlocked()
}

Intent.ACTION_LOCKED_BOOT_COMPLETED -> {
(context.applicationContext as? BaseKeyMapperApp)?.onBootUnlocked()
Timber.i(
"Locked boot completed broadcast: time since boot = ${SystemClock.elapsedRealtime() / 1000}",
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryController
import androidx.savedstate.SavedStateRegistryOwner
import dagger.hilt.android.AndroidEntryPoint
import dagger.Lazy
import io.github.sds100.keymapper.base.R
import io.github.sds100.keymapper.base.actions.talkback.TalkBackGestureType
import io.github.sds100.keymapper.base.actions.talkback.TalkbackGesturePerformer
Expand All @@ -54,10 +55,16 @@ abstract class BaseAccessibilityService :
SavedStateRegistryOwner {

@Inject
lateinit var accessibilityServiceAdapter: AccessibilityServiceAdapterImpl
lateinit var accessibilityServiceAdapterLazy: Lazy<AccessibilityServiceAdapterImpl>

val accessibilityServiceAdapter: AccessibilityServiceAdapterImpl
get() = accessibilityServiceAdapterLazy.get()

@Inject
lateinit var inputMethodAdapter: InputMethodAdapter
lateinit var inputMethodAdapterLazy: Lazy<InputMethodAdapter>

val inputMethodAdapter: InputMethodAdapter
get() = inputMethodAdapterLazy.get()

private var lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)
private var savedStateRegistryController: SavedStateRegistryController? =
Expand Down