diff --git a/CHANGELOG.md b/CHANGELOG.md index 0376953500..38c136de1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,20 @@ +## [4.0.0 Beta 2](https://github.com/sds100/KeyMapper/releases/tag/v4.0.0-beta.02) + +#### 08 November 2025 + +## Added + +- #1890 add button to save log to file and share it. The clipboard button now cuts off older entries and keeps newest ones. + +## Fixed + +- Only autostart PRO mode with Shizuku if Shizuku permission is granted. Otherwise fallback to method with Wireless Debugging and WRITE_SECURE_SETTINGS permission. +- Starting system bridge for the first time would be janky because granting READ_LOGS kills the app process. Only grant for READ_LOGS when sharing logcat from settings. +- #1886 mobile data actions work in PRO mode. + ## [4.0.0 Beta 1](https://github.com/sds100/KeyMapper/releases/tag/v4.0.0-beta.01) -#### TO BE RELEASED +#### 01 November 2025 ## Added diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 032e987d4b..8127b3ee91 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -75,6 +75,8 @@ -keep class io.github.sds100.keymapper.api.IKeyEventRelayService$Stub { *; } -keep class io.github.sds100.keymapper.api.IKeyEventRelayServiceCallback { *; } -keep class io.github.sds100.keymapper.api.IKeyEventRelayServiceCallback$Stub { *; } +-keep class com.android.internal.telephony.ITelephony { *; } +-keep class com.android.internal.telephony.ITelephony$Stub { *; } -keepattributes *Annotation*, InnerClasses -dontnote kotlinx.serialization.AnnotationsKt # core serialization annotations diff --git a/app/version.properties b/app/version.properties index 5f88270472..886f7bcec6 100644 --- a/app/version.properties +++ b/app/version.properties @@ -1,3 +1,3 @@ -VERSION_NAME=4.0.0-beta.1 -VERSION_CODE=185 +VERSION_NAME=4.0.0-beta.2 +VERSION_CODE=187 VERSION_NUM=01 \ No newline at end of file diff --git a/base/src/main/assets/whats-new.txt b/base/src/main/assets/whats-new.txt index 2e842ec8ee..d170dd02d8 100644 --- a/base/src/main/assets/whats-new.txt +++ b/base/src/main/assets/whats-new.txt @@ -1,5 +1,5 @@ ✨ Screen-off remapping -You can now remap buttons when the screen is off (including the power button) for free with PRO mode. +You can now remap ALL buttons when the screen is off (including the power button) for free with PRO mode. 🎯 New Actions • Run shell commands diff --git a/base/src/main/java/io/github/sds100/keymapper/base/BaseKeyMapperApp.kt b/base/src/main/java/io/github/sds100/keymapper/base/BaseKeyMapperApp.kt index 7cea935435..0ea1d4b5c2 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/BaseKeyMapperApp.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/BaseKeyMapperApp.kt @@ -29,6 +29,7 @@ import io.github.sds100.keymapper.data.repositories.LogRepository import io.github.sds100.keymapper.data.repositories.PreferenceRepositoryImpl import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionManagerImpl import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionState +import io.github.sds100.keymapper.sysbridge.manager.isConnected import io.github.sds100.keymapper.system.apps.AndroidPackageManagerAdapter import io.github.sds100.keymapper.system.devices.AndroidDevicesAdapter import io.github.sds100.keymapper.system.inputmethod.KeyEventRelayServiceWrapperImpl @@ -224,6 +225,12 @@ abstract class BaseKeyMapperApp : MultiDexApplication() { autoGrantPermissionController.start() keyEventRelayServiceWrapper.bind() + if (systemBridgeConnectionManager.isConnected()) { + Timber.i("KeyMapperApp: System bridge is connected") + } else { + Timber.i("KeyMapperApp: System bridge is disconnected") + } + if (Build.VERSION.SDK_INT >= Constants.SYSTEM_BRIDGE_MIN_API) { systemBridgeAutoStarter.init() diff --git a/base/src/main/java/io/github/sds100/keymapper/base/BaseMainActivity.kt b/base/src/main/java/io/github/sds100/keymapper/base/BaseMainActivity.kt index 812ca3b3fb..7b8f927f18 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/BaseMainActivity.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/BaseMainActivity.kt @@ -201,9 +201,10 @@ abstract class BaseMainActivity : AppCompatActivity() { // the activities have not necessarily resumed at that point. permissionAdapter.onPermissionsChanged() serviceAdapter.invalidateState() - suAdapter.invalidateIsRooted() + suAdapter.requestPermission() systemBridgeSetupController.invalidateSettings() networkAdapter.invalidateState() + onboardingUseCase.handledMigrateScreenOffKeyMapsNotification() } override fun onDestroy() { diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/swipescreen/SwipePickDisplayCoordinateViewModel.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/swipescreen/SwipePickDisplayCoordinateViewModel.kt index da694457b9..e4fa5cb8ce 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/swipescreen/SwipePickDisplayCoordinateViewModel.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/swipescreen/SwipePickDisplayCoordinateViewModel.kt @@ -3,7 +3,6 @@ package io.github.sds100.keymapper.base.actions.swipescreen import android.accessibilityservice.GestureDescription import android.graphics.Bitmap import android.graphics.Point -import android.os.Build import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel @@ -96,9 +95,7 @@ class SwipePickDisplayCoordinateViewModel @Inject constructor( var maxFingerCount = 10 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - maxFingerCount = GestureDescription.getMaxStrokeCount() - } + maxFingerCount = GestureDescription.getMaxStrokeCount() if (count > maxFingerCount) { return@map resourceProvider.getString( diff --git a/base/src/main/java/io/github/sds100/keymapper/base/home/KeyMapListScreen.kt b/base/src/main/java/io/github/sds100/keymapper/base/home/KeyMapListScreen.kt index 9d8a47a83d..1ed0b69eaa 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/home/KeyMapListScreen.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/home/KeyMapListScreen.kt @@ -506,6 +506,9 @@ private fun getTriggerErrorMessage(error: TriggerError): String { TriggerError.EVDEV_DEVICE_NOT_FOUND -> stringResource( R.string.trigger_error_evdev_device_not_found, ) + TriggerError.MIGRATE_SCREEN_OFF_TRIGGER -> stringResource( + R.string.trigger_error_migrate_screen_off_key_map, + ) } } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/input/EvdevHandleCache.kt b/base/src/main/java/io/github/sds100/keymapper/base/input/EvdevHandleCache.kt index 5db30e4278..aa592e491e 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/input/EvdevHandleCache.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/input/EvdevHandleCache.kt @@ -55,10 +55,10 @@ class EvdevHandleCache @Inject constructor( devicesAdapter.connectedInputDevices, systemBridgeConnectionManager.connectionState, ) { _, connectionState -> - if (connectionState !is SystemBridgeConnectionState.Connected) { - devicesByPath.value = emptyMap() - } else { + if (connectionState is SystemBridgeConnectionState.Connected) { invalidate() + } else { + devicesByPath.value = emptyMap() } }.collect() } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/input/InputEventHub.kt b/base/src/main/java/io/github/sds100/keymapper/base/input/InputEventHub.kt index 4aa5ca5ce8..c557db49e1 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/input/InputEventHub.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/input/InputEventHub.kt @@ -21,6 +21,7 @@ import io.github.sds100.keymapper.sysbridge.IEvdevCallback import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionManager import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionState import io.github.sds100.keymapper.sysbridge.manager.isConnected +import io.github.sds100.keymapper.sysbridge.utils.SystemBridgeError import io.github.sds100.keymapper.system.inputevents.KMEvdevEvent import io.github.sds100.keymapper.system.inputevents.KMGamePadEvent import io.github.sds100.keymapper.system.inputevents.KMInputEvent @@ -304,8 +305,11 @@ class InputEventHubImpl @Inject constructor( .onSuccess { result -> Timber.i("Grabbed evdev devices [${evdevDevices.joinToString { it.name }}]") } - .onFailure { - Timber.e("Failed to grab evdev devices.") + .onFailure { error -> + // Do not log if it is expected to prevent log spam. + if (error !is SystemBridgeError.Disconnected) { + Timber.e("Failed to grab evdev devices.") + } } } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/keymaps/DisplayKeyMapUseCase.kt b/base/src/main/java/io/github/sds100/keymapper/base/keymaps/DisplayKeyMapUseCase.kt index 8cda19f4fa..74f7a31df3 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/keymaps/DisplayKeyMapUseCase.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/keymaps/DisplayKeyMapUseCase.kt @@ -181,6 +181,7 @@ class DisplayKeyMapUseCaseImpl @Inject constructor( TriggerError.EVDEV_DEVICE_NOT_FOUND, TriggerError.FLOATING_BUTTON_DELETED, TriggerError.SYSTEM_BRIDGE_UNSUPPORTED, + TriggerError.MIGRATE_SCREEN_OFF_TRIGGER, -> {} } } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/logging/DisplayLogUseCase.kt b/base/src/main/java/io/github/sds100/keymapper/base/logging/DisplayLogUseCase.kt index fee1623fc8..afb92c66c0 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/logging/DisplayLogUseCase.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/logging/DisplayLogUseCase.kt @@ -1,26 +1,39 @@ package io.github.sds100.keymapper.base.logging +import android.content.Context +import androidx.core.net.toUri +import dagger.hilt.android.qualifiers.ApplicationContext import io.github.sds100.keymapper.base.R +import io.github.sds100.keymapper.base.utils.ShareUtils import io.github.sds100.keymapper.base.utils.ui.ResourceProvider +import io.github.sds100.keymapper.common.BuildConfigProvider import io.github.sds100.keymapper.data.entities.LogEntryEntity import io.github.sds100.keymapper.data.repositories.LogRepository import io.github.sds100.keymapper.system.clipboard.ClipboardAdapter import io.github.sds100.keymapper.system.files.FileAdapter +import io.github.sds100.keymapper.system.files.FileUtils +import io.github.sds100.keymapper.system.files.IFile import java.text.SimpleDateFormat import java.util.Date import java.util.Locale import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext class DisplayLogUseCaseImpl @Inject constructor( + @ApplicationContext private val ctx: Context, + private val coroutineScope: CoroutineScope, private val repository: LogRepository, private val resourceProvider: ResourceProvider, private val clipboardAdapter: ClipboardAdapter, private val fileAdapter: FileAdapter, + private val buildConfigProvider: BuildConfigProvider, ) : DisplayLogUseCase { private val dateFormat = SimpleDateFormat("MM/dd HH:mm:ss.SSS", Locale.getDefault()) private val severityString: Map = mapOf( @@ -40,7 +53,7 @@ class DisplayLogUseCaseImpl @Inject constructor( override suspend fun copyToClipboard() { val logEntries = repository.log.first() - val logText = createLogText(logEntries) + val logText = createLogClipboardText(logEntries) clipboardAdapter.copy( label = resourceProvider.getString(R.string.clip_key_mapper_log), @@ -48,11 +61,45 @@ class DisplayLogUseCaseImpl @Inject constructor( ) } - private fun createLogText(logEntries: List): String { - return logEntries.joinToString(separator = "\n") { entry -> - val date = dateFormat.format(Date(entry.time)) + private fun createLogClipboardText(logEntries: List): String { + return buildString { + append("Key Mapper log (newest first). Note: it may be cut off due to clipboard limits") + appendLine() + appendLine() - return@joinToString "$date ${severityString[entry.severity]} ${entry.message}" + logEntries + .reversed() + .joinToString(separator = "\n", transform = ::entryToString) + .also { append(it) } + } + } + + private fun entryToString(entry: LogEntryEntity): String { + val date = dateFormat.format(Date(entry.time)) + + return "$date ${severityString[entry.severity]} ${entry.message}" + } + + override fun shareFile() { + val fileName = "logs/key_mapper_log_${FileUtils.createFileDate()}.txt" + + coroutineScope.launch { + withContext(Dispatchers.IO) { + val logEntries = repository.log.first() + val logText = logEntries.joinToString(separator = "\n", transform = ::entryToString) + + val file: IFile = fileAdapter.getPrivateFile(fileName) + file.createFile() + + with(file.outputStream()?.bufferedWriter()) { + this?.write(logText) + this?.flush() + } + + val publicUri = fileAdapter.getPublicUriForPrivateFile(file) + + ShareUtils.shareFile(ctx, publicUri.toUri(), buildConfigProvider.packageName) + } } } } @@ -61,4 +108,5 @@ interface DisplayLogUseCase { val log: Flow> fun clearLog() suspend fun copyToClipboard() + fun shareFile() } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/logging/LogScreen.kt b/base/src/main/java/io/github/sds100/keymapper/base/logging/LogScreen.kt index 13e7fbf58e..e57d3d728f 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/logging/LogScreen.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/logging/LogScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.outlined.ContentCopy +import androidx.compose.material.icons.outlined.Share import androidx.compose.material3.BottomAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -53,6 +54,7 @@ fun LogScreen( modifier = modifier, onBackClick = onBackClick, onCopyToClipboardClick = viewModel::onCopyToClipboardClick, + onShareClick = viewModel::onShareFileClick, onClearLogClick = viewModel::onClearLogClick, content = { Content( @@ -69,6 +71,7 @@ private fun LogScreen( modifier: Modifier = Modifier, onBackClick: () -> Unit = {}, onCopyToClipboardClick: () -> Unit = {}, + onShareClick: () -> Unit = {}, onClearLogClick: () -> Unit = {}, content: @Composable () -> Unit, ) { @@ -96,6 +99,13 @@ private fun LogScreen( ) } Spacer(Modifier.weight(1f)) + IconButton(onClick = onShareClick) { + Icon( + imageVector = Icons.Outlined.Share, + contentDescription = stringResource(R.string.action_share_log), + ) + } + IconButton(onClick = onCopyToClipboardClick) { Icon( imageVector = Icons.Outlined.ContentCopy, diff --git a/base/src/main/java/io/github/sds100/keymapper/base/logging/LogViewModel.kt b/base/src/main/java/io/github/sds100/keymapper/base/logging/LogViewModel.kt index b40264e249..5e6156ca63 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/logging/LogViewModel.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/logging/LogViewModel.kt @@ -42,6 +42,12 @@ class LogViewModel @Inject constructor(private val displayLogUseCase: DisplayLog } } + fun onShareFileClick() { + viewModelScope.launch { + displayLogUseCase.shareFile() + } + } + fun onClearLogClick() { displayLogUseCase.clearLog() } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/logging/ShareLogcatUseCase.kt b/base/src/main/java/io/github/sds100/keymapper/base/logging/ShareLogcatUseCase.kt index 4565769d8b..951cde08aa 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/logging/ShareLogcatUseCase.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/logging/ShareLogcatUseCase.kt @@ -1,5 +1,6 @@ package io.github.sds100.keymapper.base.logging +import android.Manifest import android.content.Context import androidx.core.net.toUri import dagger.hilt.android.qualifiers.ApplicationContext @@ -12,6 +13,8 @@ import io.github.sds100.keymapper.common.utils.then import io.github.sds100.keymapper.system.files.FileAdapter import io.github.sds100.keymapper.system.files.FileUtils import io.github.sds100.keymapper.system.files.IFile +import io.github.sds100.keymapper.system.permissions.Permission +import io.github.sds100.keymapper.system.permissions.PermissionAdapter import io.github.sds100.keymapper.system.shell.ShellAdapter import javax.inject.Inject import kotlinx.coroutines.Dispatchers @@ -22,9 +25,18 @@ class ShareLogcatUseCaseImpl @Inject constructor( @ApplicationContext private val ctx: Context, private val fileAdapter: FileAdapter, private val shellAdapter: ShellAdapter, + private val permissionAdapter: PermissionAdapter, private val buildConfigProvider: BuildConfigProvider, ) : ShareLogcatUseCase { + override fun isPermissionGranted(): Boolean { + return permissionAdapter.isGranted(Permission.READ_LOGS) + } + + override fun grantPermission(): KMResult<*> { + return permissionAdapter.grant(Manifest.permission.READ_LOGS) + } + override suspend fun share(): KMResult { val fileName = "logs/logcat_${FileUtils.createFileDate()}.txt" @@ -45,5 +57,7 @@ class ShareLogcatUseCaseImpl @Inject constructor( } interface ShareLogcatUseCase { + fun isPermissionGranted(): Boolean + fun grantPermission(): KMResult<*> suspend fun share(): KMResult } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/onboarding/OnboardingTipDelegate.kt b/base/src/main/java/io/github/sds100/keymapper/base/onboarding/OnboardingTipDelegate.kt index 7b950dab51..33ced838b9 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/onboarding/OnboardingTipDelegate.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/onboarding/OnboardingTipDelegate.kt @@ -177,6 +177,12 @@ class OnboardingTipDelegateImpl @Inject constructor( val hasBackKey = trigger.keys.any { it is KeyEventTriggerKey && it.keyCode == KeyEvent.KEYCODE_BACK } val hasImeKey = trigger.keys.any { it is KeyEventTriggerKey && it.requiresIme } + val hasVolumeKey = trigger.keys + .filterIsInstance() + .any { + it.keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || + it.keyCode == KeyEvent.KEYCODE_VOLUME_UP + } when { showPowerButtonEmergencyTip -> { @@ -212,17 +218,16 @@ class OnboardingTipDelegateImpl @Inject constructor( triggerTip.value = tipModel } - // DISABLE UNTIL PRO MODE IS STABLE -// hasVolumeKey && !shownVolumeButtonsProModeTip -> { -// val tip = OnboardingTipModel( -// id = VOLUME_BUTTONS_PRO_MODE_TIP_ID, -// title = getString(R.string.tip_volume_buttons_pro_mode_title), -// message = getString(R.string.tip_volume_buttons_pro_mode_text), -// isDismissable = true, -// buttonText = getString(R.string.tip_volume_buttons_pro_mode_button), -// ) -// triggerTip.value = tip -// } + hasVolumeKey && !shownVolumeButtonsProModeTip -> { + val tip = OnboardingTipModel( + id = VOLUME_BUTTONS_PRO_MODE_TIP_ID, + title = getString(R.string.tip_volume_buttons_pro_mode_title), + message = getString(R.string.tip_volume_buttons_pro_mode_text), + isDismissable = true, + buttonText = getString(R.string.tip_volume_buttons_pro_mode_button), + ) + triggerTip.value = tip + } hasCapsLockKey && !shownCapsLockProModeTip -> { val tip = OnboardingTipModel( diff --git a/base/src/main/java/io/github/sds100/keymapper/base/onboarding/OnboardingUseCase.kt b/base/src/main/java/io/github/sds100/keymapper/base/onboarding/OnboardingUseCase.kt index fef578271f..d29de0b78f 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/onboarding/OnboardingUseCase.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/onboarding/OnboardingUseCase.kt @@ -1,14 +1,15 @@ package io.github.sds100.keymapper.base.onboarding import androidx.datastore.preferences.core.Preferences -import io.github.sds100.keymapper.base.utils.VersionHelper +import io.github.sds100.keymapper.base.keymaps.KeyMap +import io.github.sds100.keymapper.base.keymaps.KeyMapEntityMapper +import io.github.sds100.keymapper.base.trigger.TriggerErrorSnapshot import io.github.sds100.keymapper.common.BuildConfigProvider import io.github.sds100.keymapper.data.Keys import io.github.sds100.keymapper.data.repositories.KeyMapRepository import io.github.sds100.keymapper.data.repositories.PreferenceRepository import io.github.sds100.keymapper.data.utils.PrefDelegate import io.github.sds100.keymapper.system.files.FileAdapter -import io.github.sds100.keymapper.system.leanback.LeanbackAdapter import io.github.sds100.keymapper.system.permissions.Permission import io.github.sds100.keymapper.system.permissions.PermissionAdapter import io.github.sds100.keymapper.system.shizuku.ShizukuAdapter @@ -16,13 +17,13 @@ import javax.inject.Inject import javax.inject.Singleton import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map @Singleton class OnboardingUseCaseImpl @Inject constructor( private val settingsRepository: PreferenceRepository, private val fileAdapter: FileAdapter, - private val leanbackAdapter: LeanbackAdapter, private val shizukuAdapter: ShizukuAdapter, private val permissionAdapter: PermissionAdapter, private val keyMapRepository: KeyMapRepository, @@ -44,28 +45,6 @@ class OnboardingUseCaseImpl @Inject constructor( readText() } - override var approvedFloatingButtonFeaturePrompt by PrefDelegate( - Keys.approvedFloatingButtonFeaturePrompt, - false, - ) - - /** - * Show only when they *upgrade* to the new version and after they've - * completed the app intro, which asks them whether they want to receive notifications. - */ - override val showFloatingButtonFeatureNotification: Flow = combine( - get(Keys.lastInstalledVersionCodeBackground).map { it ?: -1 }, - get(Keys.approvedFloatingButtonFeaturePrompt).map { it ?: false }, - ) { oldVersionCode, approvedPrompt -> - oldVersionCode < VersionHelper.FLOATING_BUTTON_MIN_VERSION && !approvedPrompt - } - - override fun showedFloatingButtonFeatureNotification() { - set(Keys.lastInstalledVersionCodeBackground, buildConfigProvider.versionCode) - } - - override fun isTvDevice(): Boolean = leanbackAdapter.isTvDevice() - override val promptForShizukuPermission: Flow = combine( settingsRepository.get(Keys.shownShizukuPermissionPrompt), shizukuAdapter.isInstalled, @@ -105,17 +84,39 @@ class OnboardingUseCaseImpl @Inject constructor( OnboardingTapTarget.CREATE_KEY_MAP -> Keys.shownTapTargetCreateKeyMap } } + + override val showMigrateScreenOffKeyMapsNotification: Flow = + get(Keys.handledMigrateScreenOffKeyMapsNotification).map { isHandled -> + if (isHandled == true) { + return@map false + } + + val keyMaps = keyMapRepository.getAll() + .first() + .map { keyMap -> KeyMapEntityMapper.fromEntity(keyMap, emptyList()) } + + isScreenOffTriggerMigrationRequired(keyMaps) + } + + override fun handledMigrateScreenOffKeyMapsNotification() = + set(Keys.handledMigrateScreenOffKeyMapsNotification, true) + + private fun isScreenOffTriggerMigrationRequired(keyMapList: List): Boolean { + for (keyMap in keyMapList) { + for (key in keyMap.trigger.keys) { + if (TriggerErrorSnapshot.isScreenOffTriggerMigrationRequired(keyMap.trigger, key)) { + return true + } + } + } + + return false + } } interface OnboardingUseCase { var shownAppIntro: Boolean - fun isTvDevice(): Boolean - - val showFloatingButtonFeatureNotification: Flow - fun showedFloatingButtonFeatureNotification() - var approvedFloatingButtonFeaturePrompt: Boolean - val showWhatsNew: Flow fun showedWhatsNew() fun getWhatsNewText(): String @@ -129,4 +130,7 @@ interface OnboardingUseCase { fun showTapTarget(tapTarget: OnboardingTapTarget): Flow fun completedTapTarget(tapTarget: OnboardingTapTarget) + + val showMigrateScreenOffKeyMapsNotification: Flow + fun handledMigrateScreenOffKeyMapsNotification() } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/promode/SystemBridgeAutoStarter.kt b/base/src/main/java/io/github/sds100/keymapper/base/promode/SystemBridgeAutoStarter.kt index 9d73d14af0..4c9e5fe176 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/promode/SystemBridgeAutoStarter.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/promode/SystemBridgeAutoStarter.kt @@ -17,6 +17,7 @@ import io.github.sds100.keymapper.data.repositories.PreferenceRepository import io.github.sds100.keymapper.sysbridge.BuildConfig import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionManager import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionState +import io.github.sds100.keymapper.sysbridge.manager.isConnected import io.github.sds100.keymapper.sysbridge.service.SystemBridgeSetupController import io.github.sds100.keymapper.system.network.NetworkAdapter import io.github.sds100.keymapper.system.notifications.NotificationAdapter @@ -76,8 +77,16 @@ class SystemBridgeAutoStarter @Inject constructor( if (isRooted) { flowOf(AutoStartType.ROOT) } else { - shizukuAdapter.isStarted.flatMapLatest { isShizukuStarted -> - if (isShizukuStarted) { + val useShizukuFlow = + combine( + shizukuAdapter.isStarted, + permissionAdapter.isGrantedFlow(Permission.SHIZUKU), + ) { isStarted, isGranted -> + isStarted && isGranted + } + + useShizukuFlow.flatMapLatest { useShizuku -> + if (useShizuku) { flowOf(AutoStartType.SHIZUKU) } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val isAdbAutoStartAllowed = combine( @@ -217,6 +226,11 @@ class SystemBridgeAutoStarter @Inject constructor( return } + if (connectionManager.isConnected()) { + Timber.i("Not auto starting with $type because already connected.") + return + } + lastAutoStartTime = SystemClock.elapsedRealtime() when (type) { diff --git a/base/src/main/java/io/github/sds100/keymapper/base/promode/SystemBridgeSetupAssistantController.kt b/base/src/main/java/io/github/sds100/keymapper/base/promode/SystemBridgeSetupAssistantController.kt index d7e0ae42db..464524777f 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/promode/SystemBridgeSetupAssistantController.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/promode/SystemBridgeSetupAssistantController.kt @@ -5,7 +5,6 @@ import android.os.Build import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityNodeInfo import androidx.annotation.RequiresApi -import androidx.core.app.NotificationManagerCompat import androidx.core.content.getSystemService import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -29,7 +28,6 @@ import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionManage import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionState import io.github.sds100.keymapper.sysbridge.service.SystemBridgeSetupController import io.github.sds100.keymapper.sysbridge.service.SystemBridgeSetupStep -import io.github.sds100.keymapper.system.notifications.NotificationChannelModel import io.github.sds100.keymapper.system.notifications.NotificationModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -110,8 +108,6 @@ class SystemBridgeSetupAssistantController @AssistedInject constructor( private var foundPairingCode: String? = null fun onServiceConnected() { - createNotificationChannel() - coroutineScope.launch { setupController.setupAssistantStep.collect { step -> if (step == null) { @@ -140,15 +136,6 @@ class SystemBridgeSetupAssistantController @AssistedInject constructor( } } - private fun createNotificationChannel() { - val notificationChannel = NotificationChannelModel( - id = NotificationController.Companion.CHANNEL_SETUP_ASSISTANT, - name = getString(R.string.pro_mode_setup_assistant_notification_channel), - importance = NotificationManagerCompat.IMPORTANCE_MAX, - ) - manageNotifications.createChannel(notificationChannel) - } - fun teardown() { dismissNotification() stopInteracting() @@ -232,8 +219,6 @@ class SystemBridgeSetupAssistantController @AssistedInject constructor( private suspend fun onPairingSuccess() { setupController.startWithAdb() - stopInteracting() - val isStarted = try { withTimeout(10000L) { systemBridgeConnectionManager.connectionState @@ -247,6 +232,7 @@ class SystemBridgeSetupAssistantController @AssistedInject constructor( } if (isStarted) { + Timber.i("System bridge started after pairing. Going back to Key Mapper.") getKeyMapperAppTask()?.moveToFront() } else { Timber.e("Failed to start system bridge after pairing.") @@ -258,6 +244,8 @@ class SystemBridgeSetupAssistantController @AssistedInject constructor( ), ) } + + stopInteracting() } private fun clickPairWithCodeButton(rootNode: AccessibilityNodeInfo) { @@ -382,7 +370,7 @@ class SystemBridgeSetupAssistantController @AssistedInject constructor( private fun getKeyMapperAppTask(): ActivityManager.AppTask? { val task = activityManager.appTasks - .firstOrNull { + ?.firstOrNull { it.taskInfo.topActivity?.className == keyMapperClassProvider.getMainActivity().name } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/settings/SettingsViewModel.kt b/base/src/main/java/io/github/sds100/keymapper/base/settings/SettingsViewModel.kt index 201fe308be..540bf753fa 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/settings/SettingsViewModel.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/settings/SettingsViewModel.kt @@ -293,12 +293,32 @@ class SettingsViewModel @Inject constructor( fun onShareLogcatClick() { viewModelScope.launch { - shareLogcatUseCase.share().onFailure { error -> - val dialog = DialogModel.Ok( - title = getString(R.string.dialog_title_share_logcat_error), - message = error.getFullMessage(this@SettingsViewModel), + if (shareLogcatUseCase.isPermissionGranted()) { + shareLogcatUseCase.share().onFailure { error -> + val dialog = DialogModel.Ok( + title = getString(R.string.dialog_title_share_logcat_error), + message = error.getFullMessage(this@SettingsViewModel), + ) + showDialog("logcat_error", dialog) + } + } else { + val dialog = DialogModel.Alert( + title = getString(R.string.dialog_title_grant_read_logs_permission_title), + message = getString(R.string.dialog_title_grant_read_logs_permission_message), + positiveButtonText = getString(R.string.pos_proceed), + negativeButtonText = getString(R.string.neg_cancel), ) - showDialog("logcat_error", dialog) + + val response = showDialog("read_logs_permission", dialog) + + if (response == DialogResponse.POSITIVE) { + shareLogcatUseCase.grantPermission().onFailure { error -> + val dialog = DialogModel.Ok( + message = error.getFullMessage(resourceProvider), + ) + showDialog("grant_read_log_failure", dialog) + } + } } } } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/system/accessibility/BaseAccessibilityService.kt b/base/src/main/java/io/github/sds100/keymapper/base/system/accessibility/BaseAccessibilityService.kt index 1408163049..24b064b4ad 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/system/accessibility/BaseAccessibilityService.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/system/accessibility/BaseAccessibilityService.kt @@ -11,8 +11,11 @@ import android.content.res.Configuration import android.graphics.Path import android.graphics.Point import android.os.Build +import android.os.Handler +import android.os.HandlerThread import android.view.KeyEvent import android.view.accessibility.AccessibilityEvent +import android.view.accessibility.AccessibilityNodeInfo import android.view.inputmethod.EditorInfo import androidx.annotation.RequiresApi import androidx.core.content.getSystemService @@ -64,7 +67,11 @@ abstract class BaseAccessibilityService : override val rootNode: AccessibilityNodeModel? get() { - return rootInActiveWindow?.toModel() + return try { + rootInActiveWindow?.toModel() + } catch (e: Exception) { + null + } } override val activeWindowPackageNames: List @@ -151,6 +158,12 @@ abstract class BaseAccessibilityService : abstract fun getController(): BaseAccessibilityServiceController? + /** + * Use a separate thread for dispatching gestures so they do not cause an ANR. + */ + private val gestureHandlerThread: HandlerThread = HandlerThread("gesture_thread") + private var gestureHandler: Handler? = null + override fun onCreate() { super.onCreate() Timber.i("Accessibility service: onCreate") @@ -205,6 +218,9 @@ abstract class BaseAccessibilityService : fingerprintGestureCallback?.let { fingerprintGestureController.registerFingerprintGestureCallback(it, null) } + + gestureHandlerThread.start() + gestureHandler = Handler(gestureHandlerThread.looper) } override fun onUnbind(intent: Intent?): Boolean { @@ -217,6 +233,8 @@ abstract class BaseAccessibilityService : override fun onDestroy() { lifecycleRegistry.currentState = Lifecycle.State.DESTROYED + gestureHandlerThread.quit() + fingerprintGestureController .unregisterFingerprintGestureCallback(fingerprintGestureCallback) @@ -246,7 +264,15 @@ abstract class BaseAccessibilityService : event ?: return if (event.eventType == AccessibilityEvent.TYPE_WINDOWS_CHANGED) { - _activeWindowPackage.update { rootInActiveWindow?.packageName?.toString() } + // Catch exceptions because there is a crash report where + // getRootInActivityWindow() fails internally inside the AccessibilityService. + val rootNode: AccessibilityNodeInfo? = try { + rootInActiveWindow + } catch (_: Exception) { + null + } + + _activeWindowPackage.update { rootNode?.packageName?.toString() } } getController()?.onAccessibilityEvent(event) @@ -339,7 +365,7 @@ abstract class BaseAccessibilityService : addStroke(it) }.build() - val success = dispatchGesture(gestureDescription, null, null) + val success = dispatchGesture(gestureDescription, null, gestureHandler) return if (success) { Success(Unit) @@ -438,7 +464,7 @@ abstract class BaseAccessibilityService : } } - val success = dispatchGesture(gestureBuilder.build(), null, null) + val success = dispatchGesture(gestureBuilder.build(), null, gestureHandler) return if (success) { Success(Unit) @@ -456,54 +482,50 @@ abstract class BaseAccessibilityService : duration: Int, inputEventAction: InputEventAction, ): KMResult<*> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - if (fingerCount >= GestureDescription.getMaxStrokeCount()) { - return KMError.GestureStrokeCountTooHigh - } - if (duration >= GestureDescription.getMaxGestureDuration()) { - return KMError.GestureDurationTooHigh - } - - val gestureBuilder = GestureDescription.Builder() - val distributedPoints: List = - MathUtils.distributePointsOnCircle(Point(x, y), distance.toFloat() / 2, fingerCount) - - for (index in distributedPoints.indices) { - val p = Path() - if (pinchType == PinchScreenType.PINCH_IN) { - p.moveTo(x.toFloat(), y.toFloat()) - p.lineTo( - distributedPoints[index].x.toFloat(), - distributedPoints[index].y.toFloat(), - ) - } else { - p.moveTo( - distributedPoints[index].x.toFloat(), - distributedPoints[index].y.toFloat(), - ) - p.lineTo(x.toFloat(), y.toFloat()) - } - - gestureBuilder.addStroke(StrokeDescription(p, 0, duration.toLong())) - } + if (fingerCount >= GestureDescription.getMaxStrokeCount()) { + return KMError.GestureStrokeCountTooHigh + } + if (duration >= GestureDescription.getMaxGestureDuration()) { + return KMError.GestureDurationTooHigh + } - val success = dispatchGesture(gestureBuilder.build(), null, null) + val gestureBuilder = GestureDescription.Builder() + val distributedPoints: List = + MathUtils.distributePointsOnCircle(Point(x, y), distance.toFloat() / 2, fingerCount) - return if (success) { - Success(Unit) + for (index in distributedPoints.indices) { + val p = Path() + if (pinchType == PinchScreenType.PINCH_IN) { + p.moveTo(x.toFloat(), y.toFloat()) + p.lineTo( + distributedPoints[index].x.toFloat(), + distributedPoints[index].y.toFloat(), + ) } else { - KMError.FailedToDispatchGesture + p.moveTo( + distributedPoints[index].x.toFloat(), + distributedPoints[index].y.toFloat(), + ) + p.lineTo(x.toFloat(), y.toFloat()) } + + gestureBuilder.addStroke(StrokeDescription(p, 0, duration.toLong())) } - return KMError.SdkVersionTooLow(Build.VERSION_CODES.N) + val success = dispatchGesture(gestureBuilder.build(), null, gestureHandler) + + return if (success) { + Success(Unit) + } else { + KMError.FailedToDispatchGesture + } } override fun performActionOnNode( findNode: (node: AccessibilityNodeModel) -> Boolean, performAction: (node: AccessibilityNodeModel) -> AccessibilityNodeAction?, ): KMResult<*> { - val node = rootInActiveWindow.findNodeRecursively { + val node = rootInActiveWindow?.findNodeRecursively { findNode(it.toModel()) } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/system/inputmethod/ImeInputEventInjector.kt b/base/src/main/java/io/github/sds100/keymapper/base/system/inputmethod/ImeInputEventInjector.kt index 1f099f6756..41272fea50 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/system/inputmethod/ImeInputEventInjector.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/system/inputmethod/ImeInputEventInjector.kt @@ -8,6 +8,8 @@ import io.github.sds100.keymapper.system.inputmethod.InputMethodAdapter import io.github.sds100.keymapper.system.inputmethod.KeyEventRelayServiceWrapper import javax.inject.Inject import javax.inject.Singleton +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import timber.log.Timber /** @@ -23,7 +25,7 @@ class ImeInputEventInjectorImpl @Inject constructor( private const val CALLBACK_ID_INPUT_METHOD = "input_method" } - override fun inputKeyEvent(event: InjectKeyEventModel) { + override suspend fun inputKeyEvent(event: InjectKeyEventModel) { Timber.d("Inject key event with input method $event") val imePackageName = inputMethodAdapter.chosenIme.value?.packageName @@ -33,11 +35,15 @@ class ImeInputEventInjectorImpl @Inject constructor( return } - keyEventRelayService.sendKeyEvent( - event.toAndroidKeyEvent(), - imePackageName, - CALLBACK_ID_INPUT_METHOD, - ) + // This IPC call can potentially take a long time so do not block the main thread + // and cause an ANR. + withContext(Dispatchers.IO) { + keyEventRelayService.sendKeyEvent( + event.toAndroidKeyEvent(), + imePackageName, + CALLBACK_ID_INPUT_METHOD, + ) + } } override fun inputText(text: String) { @@ -102,5 +108,5 @@ class ImeInputEventInjectorImpl @Inject constructor( interface ImeInputEventInjector { fun inputText(text: String) - fun inputKeyEvent(event: InjectKeyEventModel) + suspend fun inputKeyEvent(event: InjectKeyEventModel) } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/system/notifications/NotificationController.kt b/base/src/main/java/io/github/sds100/keymapper/base/system/notifications/NotificationController.kt index 2c9fcc417b..7bdb118f7d 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/system/notifications/NotificationController.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/system/notifications/NotificationController.kt @@ -60,6 +60,7 @@ class NotificationController @Inject constructor( // private const val ID_FEATURE_ASSISTANT_TRIGGER = 900 private const val ID_FEATURE_FLOATING_BUTTONS = 901 + private const val ID_MIGRATE_SCREEN_OFF_KEY_MAPS = 902 const val CHANNEL_TOGGLE_KEY_MAPS = "channel_toggle_remaps" @@ -70,6 +71,7 @@ class NotificationController @Inject constructor( const val CHANNEL_TOGGLE_KEYBOARD = "channel_toggle_keymapper_keyboard" const val CHANNEL_NEW_FEATURES = "channel_new_features" const val CHANNEL_SETUP_ASSISTANT = "channel_setup_assistant" + const val CHANNEL_VERSION_MIGRATION = "channel_version_migration" @Deprecated("Removed in 2.0. This channel shouldn't exist") private const val CHANNEL_ID_WARNINGS = "channel_warnings" @@ -100,6 +102,14 @@ class NotificationController @Inject constructor( ), ) + manageNotifications.createChannel( + NotificationChannelModel( + id = CHANNEL_SETUP_ASSISTANT, + name = getString(R.string.pro_mode_setup_assistant_notification_channel), + importance = NotificationManagerCompat.IMPORTANCE_MAX, + ), + ) + combine( controlAccessibilityService.serviceState, pauseMappings.isPaused, @@ -124,20 +134,6 @@ class NotificationController @Inject constructor( } }.flowOn(dispatchers.default()).launchIn(coroutineScope) - coroutineScope.launch(dispatchers.default()) { - // suspend until the notification should be shown. - onboardingUseCase.showFloatingButtonFeatureNotification.first { it } - - manageNotifications.show(floatingButtonFeatureNotification()) - - // Only save that the notification is shown if the app has - // permissions to show notifications so that it is shown - // the next time permission is granted. - if (manageNotifications.isPermissionGranted()) { - onboardingUseCase.showedFloatingButtonFeatureNotification() - } - } - hideInputMethod.onHiddenChange.onEach { isHidden -> manageNotifications.createChannel( NotificationChannelModel( @@ -190,6 +186,23 @@ class NotificationController @Inject constructor( } } } + + coroutineScope.launch { + if (onboardingUseCase.showMigrateScreenOffKeyMapsNotification.first()) { + manageNotifications.show( + NotificationModel( + id = ID_MIGRATE_SCREEN_OFF_KEY_MAPS, + channel = CHANNEL_NEW_FEATURES, + title = getString(R.string.notification_migrate_screen_off_key_map_title), + text = getString(R.string.notification_migrate_screen_off_key_map_text), + icon = R.drawable.ic_baseline_warning_24, + onClickAction = KMNotificationAction.Activity.MainActivity(), + showOnLockscreen = true, + onGoing = false, + ), + ) + } + } } fun onOpenApp() { @@ -391,22 +404,6 @@ class NotificationController @Inject constructor( priority = NotificationCompat.PRIORITY_LOW, ) - private fun floatingButtonFeatureNotification(): NotificationModel = NotificationModel( - id = ID_FEATURE_FLOATING_BUTTONS, - channel = CHANNEL_NEW_FEATURES, - title = getString(R.string.notification_floating_buttons_feature_title), - text = getString(R.string.notification_floating_buttons_feature_text), - icon = R.drawable.outline_bubble_chart_24, - onClickAction = KMNotificationAction.Activity.MainActivity( - BaseMainActivity.ACTION_USE_FLOATING_BUTTONS, - ), - priority = NotificationCompat.PRIORITY_LOW, - autoCancel = true, - onGoing = false, - showOnLockscreen = false, - bigTextStyle = true, - ) - private fun showSystemBridgeStartedNotification() { val model = NotificationModel( id = ID_SYSTEM_BRIDGE_STATUS, diff --git a/base/src/main/java/io/github/sds100/keymapper/base/system/permissions/RequestPermissionDelegate.kt b/base/src/main/java/io/github/sds100/keymapper/base/system/permissions/RequestPermissionDelegate.kt index 7db51d9faa..22067867e6 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/system/permissions/RequestPermissionDelegate.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/system/permissions/RequestPermissionDelegate.kt @@ -110,6 +110,8 @@ class RequestPermissionDelegate( requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) } } + + Permission.READ_LOGS -> permissionAdapter.grant(Manifest.permission.READ_LOGS) } } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/trigger/BaseConfigTriggerViewModel.kt b/base/src/main/java/io/github/sds100/keymapper/base/trigger/BaseConfigTriggerViewModel.kt index 439ed29438..9ea19d39e7 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/trigger/BaseConfigTriggerViewModel.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/trigger/BaseConfigTriggerViewModel.kt @@ -23,10 +23,13 @@ import io.github.sds100.keymapper.base.utils.navigation.NavDestination import io.github.sds100.keymapper.base.utils.navigation.NavigationProvider import io.github.sds100.keymapper.base.utils.navigation.navigate import io.github.sds100.keymapper.base.utils.ui.CheckBoxListItem +import io.github.sds100.keymapper.base.utils.ui.DialogModel import io.github.sds100.keymapper.base.utils.ui.DialogProvider +import io.github.sds100.keymapper.base.utils.ui.DialogResponse import io.github.sds100.keymapper.base.utils.ui.LinkType import io.github.sds100.keymapper.base.utils.ui.ResourceProvider import io.github.sds100.keymapper.base.utils.ui.ViewModelHelper +import io.github.sds100.keymapper.base.utils.ui.showDialog import io.github.sds100.keymapper.common.models.EvdevDeviceInfo import io.github.sds100.keymapper.common.utils.AccessibilityServiceError import io.github.sds100.keymapper.common.utils.InputDeviceUtils @@ -526,6 +529,22 @@ abstract class BaseConfigTriggerViewModel( showTriggerSetup(TriggerSetupShortcut.GAMEPAD) } + TriggerError.MIGRATE_SCREEN_OFF_TRIGGER -> { + val dialog = DialogModel.Alert( + title = getString(R.string.dialog_title_migrate_screen_off_key_map), + message = getString(R.string.dialog_message_migrate_screen_off_key_map), + positiveButtonText = + getString(R.string.dialog_button_migrate_screen_off_key_map), + negativeButtonText = getString(R.string.neg_cancel), + ) + + val response = showDialog("migrate_screen_off", dialog) + + if (response == DialogResponse.POSITIVE) { + showTriggerSetup(TriggerSetupShortcut.OTHER, forceProMode = true) + } + } + else -> displayKeyMap.fixTriggerError(error) } } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/trigger/KeyEventTriggerKey.kt b/base/src/main/java/io/github/sds100/keymapper/base/trigger/KeyEventTriggerKey.kt index ebd8104ea8..c5b5f20d9f 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/trigger/KeyEventTriggerKey.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/trigger/KeyEventTriggerKey.kt @@ -4,6 +4,7 @@ import io.github.sds100.keymapper.base.keymaps.ClickType import io.github.sds100.keymapper.common.utils.hasFlag import io.github.sds100.keymapper.common.utils.withFlag import io.github.sds100.keymapper.data.entities.KeyEventTriggerKeyEntity +import io.github.sds100.keymapper.data.entities.TriggerEntity import io.github.sds100.keymapper.data.entities.TriggerKeyEntity import java.util.UUID import kotlinx.serialization.Serializable @@ -74,7 +75,7 @@ data class KeyEventTriggerKey( entity.flags.hasFlag(KeyEventTriggerKeyEntity.FLAG_DETECTION_SOURCE_INPUT_METHOD) val detectWithScancode = - entity.flags.hasFlag(KeyEventTriggerKeyEntity.FLAG_DETECT_WITH_SCAN_CODE) + entity.flags.hasFlag(TriggerEntity.TRIGGER_FLAG_VIBRATE) return KeyEventTriggerKey( uid = entity.uid, diff --git a/base/src/main/java/io/github/sds100/keymapper/base/trigger/Trigger.kt b/base/src/main/java/io/github/sds100/keymapper/base/trigger/Trigger.kt index 303993d860..4150648257 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/trigger/Trigger.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/trigger/Trigger.kt @@ -28,6 +28,15 @@ data class Trigger( val sequenceTriggerTimeout: Int? = null, val triggerFromOtherApps: Boolean = false, val showToast: Boolean = false, + + /** + * This is true if the user turned on the "screen off" option in versions prior to v4.0.0. This + * option has now been removed and replaced with the system bridge but they will have to + * record their keys again and create an EvdevTriggerKey. We can not handle this migration + * automatically because we could grab the wrong evdev device or if there is a bug + * severely mess up their device without them knowing the cause. + */ + val legacyDetectScreenOff: Boolean = false, ) { fun isVibrateAllowed(): Boolean = true @@ -102,6 +111,7 @@ object TriggerEntityMapper { else -> TriggerMode.Undefined } + @Suppress("DEPRECATION") return Trigger( keys = keys, mode = mode, @@ -127,6 +137,9 @@ object TriggerEntityMapper { triggerFromOtherApps = entity.flags.hasFlag(TriggerEntity.TRIGGER_FLAG_FROM_OTHER_APPS), showToast = entity.flags.hasFlag(TriggerEntity.TRIGGER_FLAG_SHOW_TOAST), + legacyDetectScreenOff = entity.flags.hasFlag( + TriggerEntity.TRIGGER_FLAG_SCREEN_OFF_TRIGGERS, + ), ) } @@ -195,6 +208,13 @@ object TriggerEntityMapper { flags = flags.withFlag(TriggerEntity.TRIGGER_FLAG_SHOW_TOAST) } + // Still persist this so that any errors do not disappear when the key map is saved + // for whatever reason. + if (trigger.legacyDetectScreenOff) { + @Suppress("DEPRECATION") + flags = flags.withFlag(TriggerEntity.TRIGGER_FLAG_SCREEN_OFF_TRIGGERS) + } + val keys = trigger.keys.map { key -> when (key) { is AssistantTriggerKey -> AssistantTriggerKey.toEntity(key) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/trigger/TriggerError.kt b/base/src/main/java/io/github/sds100/keymapper/base/trigger/TriggerError.kt index eea5a79fc6..29c38e5609 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/trigger/TriggerError.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/trigger/TriggerError.kt @@ -24,4 +24,6 @@ enum class TriggerError(val isFixable: Boolean) { SYSTEM_BRIDGE_DISCONNECTED(isFixable = true), EVDEV_DEVICE_NOT_FOUND(isFixable = false), + + MIGRATE_SCREEN_OFF_TRIGGER(isFixable = true), } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/trigger/TriggerErrorSnapshot.kt b/base/src/main/java/io/github/sds100/keymapper/base/trigger/TriggerErrorSnapshot.kt index 86335276b2..71760e8051 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/trigger/TriggerErrorSnapshot.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/trigger/TriggerErrorSnapshot.kt @@ -35,9 +35,32 @@ data class TriggerErrorSnapshot( KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP, ) + + fun isScreenOffTriggerMigrationRequired(trigger: Trigger, key: TriggerKey): Boolean = + trigger.legacyDetectScreenOff && + key is KeyEventTriggerKey && + key.keyCode in LEGACY_SCREEN_OFF_KEY_CODES + + /** + * These are the key codes that were detected with the getevent command prior to v4.0.0. + */ + val LEGACY_SCREEN_OFF_KEY_CODES: Set = setOf( + KeyEvent.KEYCODE_VOLUME_DOWN, + KeyEvent.KEYCODE_VOLUME_UP, + KeyEvent.KEYCODE_HEADSETHOOK, + KeyEvent.KEYCODE_FOCUS, + KeyEvent.KEYCODE_CAMERA, + KeyEvent.KEYCODE_MENU, + KeyEvent.KEYCODE_ASSIST, + KeyEvent.KEYCODE_SEARCH, + ) } fun getTriggerError(keyMap: KeyMap, key: TriggerKey): TriggerError? { + if (isScreenOffTriggerMigrationRequired(keyMap.trigger, key)) { + return TriggerError.MIGRATE_SCREEN_OFF_TRIGGER + } + purchases.onSuccess { purchases -> if (key is AssistantTriggerKey && !purchases.contains(ProductId.ASSISTANT_TRIGGER)) { return TriggerError.ASSISTANT_TRIGGER_NOT_PURCHASED diff --git a/base/src/main/java/io/github/sds100/keymapper/base/trigger/TriggerKeyListItem.kt b/base/src/main/java/io/github/sds100/keymapper/base/trigger/TriggerKeyListItem.kt index d04962ecb5..3fb455de52 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/trigger/TriggerKeyListItem.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/trigger/TriggerKeyListItem.kt @@ -305,6 +305,9 @@ private fun getErrorMessage(error: TriggerError): String { TriggerError.EVDEV_DEVICE_NOT_FOUND -> stringResource( R.string.trigger_error_evdev_device_not_found, ) + TriggerError.MIGRATE_SCREEN_OFF_TRIGGER -> stringResource( + R.string.trigger_error_migrate_screen_off_key_map, + ) } } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/utils/ErrorUtils.kt b/base/src/main/java/io/github/sds100/keymapper/base/utils/ErrorUtils.kt index 5a835bc1e2..a9214a7152 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/utils/ErrorUtils.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/utils/ErrorUtils.kt @@ -57,6 +57,8 @@ fun KMError.getFullMessage(resourceProvider: ResourceProvider): String { R.string.error_find_nearby_devices_permission_denied Permission.POST_NOTIFICATIONS -> R.string.error_notifications_permission_denied + Permission.READ_LOGS -> + R.string.error_read_logs_permission_denied } resourceProvider.getString(resId) diff --git a/base/src/main/res/menu/menu_log.xml b/base/src/main/res/menu/menu_log.xml deleted file mode 100644 index d23e3dd5ac..0000000000 --- a/base/src/main/res/menu/menu_log.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/base/src/main/res/menu/menu_settings.xml b/base/src/main/res/menu/menu_settings.xml deleted file mode 100644 index 9ac5830634..0000000000 --- a/base/src/main/res/menu/menu_settings.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - \ No newline at end of file diff --git a/base/src/main/res/values/strings.xml b/base/src/main/res/values/strings.xml index 188a2d3f59..2a2fadb1d3 100644 --- a/base/src/main/res/values/strings.xml +++ b/base/src/main/res/values/strings.xml @@ -80,8 +80,9 @@ PRO mode is unsupported on this Android version PRO mode not started! Trigger device not connected! + Migrate this screen off trigger - Your key maps will stop working randomly! + Disable battery optimisation. Your key maps are paused! Unpause The accessibility service needs to be turned on for your key maps to work! @@ -100,10 +101,10 @@ Open %s Tap screen (%d, %d) Tap screen (%s) - Swipe with %d finger(s) from coordinates %d/%d to %d/%d in %dms - Swipe with %d finger(s) from coordinates %d/%d to %d/%d in %dms (%s) - %s with %d finger(s) on coordinates %d/%d with a pinch distance of %dpx in %dms - %s with %d finger(s) on coordinates %d/%d to with a pinch distance of %dpx %dms (%s) + Swipe with %d finger(s) from (%d,%d) to (%d,%d) in %dms + Swipe with %d finger(s) from (%d,%d) to (%d,%d) in %dms (%s) + %s with %d finger(s) on (%d,%d) with a pinch distance of %dpx in %dms + %s with %d finger(s) on (%d,%d) to with a pinch distance of %dpx %dms (%s) Call %s Play sound: %s Play unknown sound @@ -387,7 +388,7 @@ Back up everything Tap to pause Tap to resume - Save + Share Toggle short messages Copy Clear @@ -447,7 +448,7 @@ Key Mapper was interrupted - Key Mapper tried to run in the background but was stopped by the system.\nThis can happen if you have battery or memory optimization turned on.\n\nTo fix this, you can try following an online guide. You should also restart the service when you\'re done. + Key Mapper tried to run in the background but was stopped by the system.\nThis can happen if you have battery or memory optimisation turned on.\n\nTo fix this, you can try following an online guide. You should also restart the service when you\'re done. Proceed Ignore @@ -476,6 +477,14 @@ Turn on notifications Some actions and options need this permission to work. You can also get notified when there is important news about the app. + Migrate this screen off trigger + We\'re sorry for the disruption. We\'ve introduced a *free* feature called PRO mode that replaces the old screen off remapping option. This feature is much more reliable and unlocks remapping the power button too.\n\nDue to the way it works you will need to record this trigger again with PRO mode. + Proceed + + Grant READ_LOGS permission + You will need to be rooted or have enabled PRO mode for this to work. The system will then close the app when it grants this permission. You will need to open it again yourself and try sharing the logcat again. + + Done Kill Guide @@ -490,6 +499,7 @@ Save Understood Turn on + Proceed Turn off Cancel @@ -541,6 +551,8 @@ Tap \'toggle\' to switch to and from the Key Mapper Input Method. Toggle + Your screen off key maps need attention! + We\'re sorry for the disruption but you will need to record your screen off key maps again. @@ -861,6 +873,7 @@ Denied permission to answer and end phone calls! Denied permission to see paired Bluetooth devices! Denied permission to show notifications! + Denied permission to read logs! Must be 2 or more! Must be %d or less! Must be greater than 0! @@ -1361,7 +1374,7 @@ PRO mode provides better compatibility for Caps Lock remapping. Tap \'Use PRO mode\' and record it again. Use PRO mode Screen off remapping? - Use PRO mode if you want your trigger to work when the screen is off. Tap \'Use PRO mode\' and record it again. + Any trigger can now work when the screen is off for free with PRO mode! You will need to record it again. Use PRO mode App pinning warning Using the back button as a trigger may conflict with app pinning, causing a black screen on unlock. A reboot will fix it. @@ -1384,8 +1397,6 @@ Configure button Edit layout Requires Android 11 or newer. - Don’t have enough buttons? Now you can make your own! - Floating buttons display over the apps you want. They work just like real buttons, and you can place, style, and map them however you like. Trigger diff --git a/base/src/test/java/io/github/sds100/keymapper/base/trigger/TriggerErrorSnapshotTest.kt b/base/src/test/java/io/github/sds100/keymapper/base/trigger/TriggerErrorSnapshotTest.kt new file mode 100644 index 0000000000..3466ea36fc --- /dev/null +++ b/base/src/test/java/io/github/sds100/keymapper/base/trigger/TriggerErrorSnapshotTest.kt @@ -0,0 +1,206 @@ +package io.github.sds100.keymapper.base.trigger + +import android.view.KeyEvent +import io.github.sds100.keymapper.base.keymaps.ClickType +import io.github.sds100.keymapper.base.keymaps.KeyMap +import io.github.sds100.keymapper.base.purchasing.ProductId +import io.github.sds100.keymapper.base.system.accessibility.FingerprintGestureType +import io.github.sds100.keymapper.common.models.EvdevDeviceInfo +import io.github.sds100.keymapper.common.utils.KMResult +import io.github.sds100.keymapper.common.utils.Success +import io.github.sds100.keymapper.system.inputevents.Scancode +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.hamcrest.CoreMatchers.`is` +import org.hamcrest.MatcherAssert.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.junit.MockitoJUnitRunner + +@ExperimentalCoroutinesApi +@RunWith(MockitoJUnitRunner::class) +class TriggerErrorSnapshotTest { + + @Test + fun `do not show error for a key event trigger that did not support screen off remapping to migrate with pro mode`() { + val trigger = Trigger( + legacyDetectScreenOff = true, + keys = listOf( + KeyEventTriggerKey( + keyCode = KeyEvent.KEYCODE_A, + clickType = ClickType.SHORT_PRESS, + device = KeyEventTriggerDevice.Internal, + ), + ), + ) + + assertThat( + TriggerErrorSnapshot.isScreenOffTriggerMigrationRequired(trigger, trigger.keys[0]), + `is`(false), + ) + } + + @Test + fun `do not show error for an evdev trigger to migrate with pro mode`() { + val trigger = Trigger( + legacyDetectScreenOff = true, + keys = listOf( + EvdevTriggerKey( + keyCode = KeyEvent.KEYCODE_VOLUME_DOWN, + scanCode = Scancode.KEY_VOLUMEDOWN, + device = EvdevDeviceInfo("gpio_keys", 0, 0, 0), + ), + ), + ) + + assertThat( + TriggerErrorSnapshot.isScreenOffTriggerMigrationRequired(trigger, trigger.keys[0]), + `is`(false), + ) + } + + @Test + fun `do not show error for a volume trigger that does not have screen off remap enabled`() { + val trigger = Trigger( + legacyDetectScreenOff = false, + keys = listOf( + KeyEventTriggerKey( + keyCode = KeyEvent.KEYCODE_VOLUME_UP, + clickType = ClickType.SHORT_PRESS, + device = KeyEventTriggerDevice.Internal, + ), + ), + ) + + assertThat( + TriggerErrorSnapshot.isScreenOffTriggerMigrationRequired(trigger, trigger.keys[0]), + `is`(false), + ) + } + + @Test + fun `show error for legacy screen off volume triggers with also an evdev key to migrate with pro mode`() { + val trigger = Trigger( + legacyDetectScreenOff = true, + keys = listOf( + EvdevTriggerKey( + keyCode = KeyEvent.KEYCODE_VOLUME_DOWN, + scanCode = Scancode.KEY_VOLUMEDOWN, + device = EvdevDeviceInfo("gpio_keys", 0, 0, 0), + ), + KeyEventTriggerKey( + keyCode = KeyEvent.KEYCODE_VOLUME_UP, + clickType = ClickType.SHORT_PRESS, + device = KeyEventTriggerDevice.Internal, + ), + ), + ) + + assertThat( + TriggerErrorSnapshot.isScreenOffTriggerMigrationRequired(trigger, trigger.keys[0]), + `is`(false), + ) + + assertThat( + TriggerErrorSnapshot.isScreenOffTriggerMigrationRequired(trigger, trigger.keys[1]), + `is`(true), + ) + } + + @Test + fun `show error for legacy screen off volume triggers with multiple keys to migrate with pro mode`() { + val trigger = Trigger( + legacyDetectScreenOff = true, + keys = listOf( + KeyEventTriggerKey( + keyCode = KeyEvent.KEYCODE_A, + clickType = ClickType.SHORT_PRESS, + device = KeyEventTriggerDevice.Internal, + ), + FingerprintTriggerKey( + type = FingerprintGestureType.SWIPE_DOWN, + clickType = ClickType.LONG_PRESS, + ), + KeyEventTriggerKey( + keyCode = KeyEvent.KEYCODE_VOLUME_UP, + clickType = ClickType.SHORT_PRESS, + device = KeyEventTriggerDevice.Internal, + ), + ), + ) + + assertThat( + TriggerErrorSnapshot.isScreenOffTriggerMigrationRequired(trigger, trigger.keys[0]), + `is`(false), + ) + + assertThat( + TriggerErrorSnapshot.isScreenOffTriggerMigrationRequired(trigger, trigger.keys[1]), + `is`(false), + ) + + assertThat( + TriggerErrorSnapshot.isScreenOffTriggerMigrationRequired(trigger, trigger.keys[2]), + `is`(true), + ) + } + + @Test + fun `show error for legacy screen off volume triggers to migrate with pro mode if pro mode is enabled`() { + val trigger = Trigger( + legacyDetectScreenOff = true, + keys = listOf( + KeyEventTriggerKey( + keyCode = KeyEvent.KEYCODE_VOLUME_UP, + clickType = ClickType.SHORT_PRESS, + device = KeyEventTriggerDevice.Internal, + ), + ), + ) + + assertThat( + TriggerErrorSnapshot.isScreenOffTriggerMigrationRequired(trigger, trigger.keys[0]), + `is`(true), + ) + } + + @Test + fun `show error for legacy screen off volume triggers to migrate with pro mode if pro mode is disabled`() { + val trigger = Trigger( + legacyDetectScreenOff = true, + keys = listOf( + KeyEventTriggerKey( + keyCode = KeyEvent.KEYCODE_VOLUME_DOWN, + clickType = ClickType.SHORT_PRESS, + device = KeyEventTriggerDevice.Internal, + ), + ), + ) + + val keyMap = KeyMap(trigger = trigger) + + val error = createTriggerErrorSnapshot( + isSystemBridgeConnected = false, + ).getTriggerError(keyMap, trigger.keys[0]) + assertThat(error, `is`(TriggerError.MIGRATE_SCREEN_OFF_TRIGGER)) + } + + private fun createTriggerErrorSnapshot( + isKeyMapperImeChosen: Boolean = true, + isDndAccessGranted: Boolean = true, + isRootGranted: Boolean = false, + purchases: KMResult> = Success(emptySet()), + showDpadImeSetupError: Boolean = false, + isSystemBridgeConnected: Boolean? = true, + evdevDevices: List? = emptyList(), + ): TriggerErrorSnapshot { + return TriggerErrorSnapshot( + isKeyMapperImeChosen = isKeyMapperImeChosen, + isDndAccessGranted = isDndAccessGranted, + isRootGranted = isRootGranted, + purchases = purchases, + showDpadImeSetupError = showDpadImeSetupError, + isSystemBridgeConnected = isSystemBridgeConnected, + evdevDevices = evdevDevices, + ) + } +} diff --git a/data/src/main/java/io/github/sds100/keymapper/data/Keys.kt b/data/src/main/java/io/github/sds100/keymapper/data/Keys.kt index 013df385f9..f13bec696e 100644 --- a/data/src/main/java/io/github/sds100/keymapper/data/Keys.kt +++ b/data/src/main/java/io/github/sds100/keymapper/data/Keys.kt @@ -46,8 +46,8 @@ object Keys { // val approvedAssistantTriggerFeaturePrompt = // booleanPreferencesKey("pref_approved_assistant_trigger_feature_prompt") - val approvedFloatingButtonFeaturePrompt = - booleanPreferencesKey("pref_approved_floating_button_feature_prompt") +// val approvedFloatingButtonFeaturePrompt = +// booleanPreferencesKey("pref_approved_floating_button_feature_prompt") val shownParallelTriggerOrderExplanation = booleanPreferencesKey("key_shown_parallel_trigger_order_warning") @@ -149,4 +149,7 @@ object Keys { * to 4.0 on a rooted device. */ val handledRootToProModeUpgrade = booleanPreferencesKey("key_handled_root_to_pro_mode_upgrade") + + val handledMigrateScreenOffKeyMapsNotification = + booleanPreferencesKey("key_handled_migrate_screen_off_key_maps_notification") } diff --git a/data/src/main/java/io/github/sds100/keymapper/data/db/dao/KeyMapDao.kt b/data/src/main/java/io/github/sds100/keymapper/data/db/dao/KeyMapDao.kt index 52a213c074..ec692c2871 100644 --- a/data/src/main/java/io/github/sds100/keymapper/data/db/dao/KeyMapDao.kt +++ b/data/src/main/java/io/github/sds100/keymapper/data/db/dao/KeyMapDao.kt @@ -61,7 +61,7 @@ interface KeyMapDao { @Query("UPDATE $TABLE_NAME SET $KEY_GROUP_UID=(:groupUid) WHERE $KEY_UID in (:uid)") suspend fun setKeyMapGroup(groupUid: String?, vararg uid: String) - @Insert(onConflict = OnConflictStrategy.ABORT) + @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insert(vararg keyMap: KeyMapEntity) @Delete @@ -73,7 +73,7 @@ interface KeyMapDao { @Query("DELETE FROM $TABLE_NAME WHERE $KEY_UID in (:uid)") suspend fun deleteById(vararg uid: String) - @Update(onConflict = OnConflictStrategy.ABORT) + @Update(onConflict = OnConflictStrategy.REPLACE) suspend fun update(vararg keyMap: KeyMapEntity) @Query("SELECT COUNT(*) FROM $TABLE_NAME") diff --git a/sysbridge/NDK_VERSION b/sysbridge/NDK_VERSION new file mode 100644 index 0000000000..b48e72d6d9 --- /dev/null +++ b/sysbridge/NDK_VERSION @@ -0,0 +1 @@ +27.2.12479018 \ No newline at end of file diff --git a/sysbridge/build.gradle.kts b/sysbridge/build.gradle.kts index 758bc153e9..c1bf2d10a2 100644 --- a/sysbridge/build.gradle.kts +++ b/sysbridge/build.gradle.kts @@ -13,6 +13,16 @@ android { namespace = "io.github.sds100.keymapper.sysbridge" compileSdk = libs.versions.compile.sdk.get().toInt() + // Read NDK version from NDK_VERSION file, with fallback to gradle.properties + // The NDK version is stored in a file so the same value can be used across multiple modules. + val ndkVersionFile = project.file("NDK_VERSION") + val ndkVersionFromFile = if (ndkVersionFile.exists()) { + ndkVersionFile.readText().trim() + } else { + null + } + ndkVersion = ndkVersionFromFile!! + defaultConfig { // Must be API 29 so that the binder-ndk library can be found. minSdk = 29 diff --git a/sysbridge/src/main/cpp/CMakeLists.txt b/sysbridge/src/main/cpp/CMakeLists.txt index f87842b840..5781ee5879 100644 --- a/sysbridge/src/main/cpp/CMakeLists.txt +++ b/sysbridge/src/main/cpp/CMakeLists.txt @@ -94,6 +94,13 @@ add_library(evdev SHARED android/libbase/stringprintf.cpp ${aidl_src_dir}/io/github/sds100/keymapper/sysbridge/IEvdevCallback.cpp) +# Set a stable custom output directory for libevdev.so so it can be linked to in other modules +# Outputs to custom "libs" directory in the sysbridge module build directory +set_target_properties(evdev PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../../../build/libs/${ANDROID_ABI}" + LIBRARY_OUTPUT_DIRECTORY_DEBUG "${CMAKE_CURRENT_SOURCE_DIR}/../../../build/libs/${ANDROID_ABI}" + LIBRARY_OUTPUT_DIRECTORY_RELEASE "${CMAKE_CURRENT_SOURCE_DIR}/../../../build/libs/${ANDROID_ABI}") + find_library( binder_ndk-lib binder_ndk diff --git a/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/manager/SystemBridgeConnectionManager.kt b/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/manager/SystemBridgeConnectionManager.kt index 04edccdda9..58db6df87f 100644 --- a/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/manager/SystemBridgeConnectionManager.kt +++ b/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/manager/SystemBridgeConnectionManager.kt @@ -92,6 +92,11 @@ class SystemBridgeConnectionManagerImpl @Inject constructor( fun onBinderReceived(binder: IBinder) { val systemBridge = ISystemBridge.Stub.asInterface(binder) + // Can not use Timber because the content provider is called before the application's + // onCreate where the Timber Tree is installed. The content provider then + // calls this message. + Log.i(TAG, "Received system bridge binder") + synchronized(systemBridgeLock) { if (systemBridge.versionCode == buildConfigProvider.versionCode) { // Only link to death if it is the same version code so restarting it @@ -111,16 +116,20 @@ class SystemBridgeConnectionManagerImpl @Inject constructor( time = SystemClock.elapsedRealtime(), ) } + + // Use Timber here even though it may not be planted. The Application class + // will check whether it is connected when it plants the Timber tree. + Timber.i("ConnectionManager: System bridge connected") } else { - coroutineScope.launch(Dispatchers.IO) { - // Can not use Timber because the content provider is called before the application's - // onCreate where the Timber Tree is installed. The content provider then - // calls this message. - Log.w( - TAG, - "System Bridge version mismatch! Restarting it. App: ${buildConfigProvider.versionCode}, System Bridge: ${systemBridge.versionCode}", - ) + // Can not use Timber because the content provider is called before the application's + // onCreate where the Timber Tree is installed. The content provider then + // calls this message. + Log.w( + TAG, + "System Bridge version mismatch! Restarting it. App: ${buildConfigProvider.versionCode}, System Bridge: ${systemBridge.versionCode}", + ) + coroutineScope.launch(Dispatchers.IO) { restartSystemBridge(systemBridge) } } @@ -196,9 +205,11 @@ class SystemBridgeConnectionManagerImpl @Inject constructor( -1 } + // WARNING! Granting some permissions (e.g READ_LOGS) will cause the system to kill + // the app process and restart it. This is normal, expected behavior and can not be + // worked around. Do not grant any other permissions automatically here. systemBridge.grantPermission(Manifest.permission.WRITE_SECURE_SETTINGS, deviceId) - systemBridge.grantPermission(Manifest.permission.READ_LOGS, deviceId) - Timber.i("Granted WRITE_SECURE_SETTINGS and READ_LOGS permission with System Bridge") + Timber.i("Granted WRITE_SECURE_SETTINGS permission with System Bridge") if (ContextCompat.checkSelfPermission( ctx, diff --git a/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridgeSetupController.kt b/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridgeSetupController.kt index 8d30f0a6c4..beedaf047c 100644 --- a/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridgeSetupController.kt +++ b/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridgeSetupController.kt @@ -318,7 +318,7 @@ class SystemBridgeSetupControllerImpl @Inject constructor( private fun getKeyMapperAppTask(): ActivityManager.AppTask? { val task = activityManager.appTasks - .firstOrNull { + ?.firstOrNull { it.taskInfo.topActivity?.className == keyMapperClassProvider.getMainActivity().name } diff --git a/system/build.gradle.kts b/system/build.gradle.kts index b70b399c4d..dbac603be3 100644 --- a/system/build.gradle.kts +++ b/system/build.gradle.kts @@ -5,6 +5,7 @@ plugins { alias(libs.plugins.dagger.hilt.android) alias(libs.plugins.kotlin.serialization) alias(libs.plugins.kotlin.parcelize) + alias(libs.plugins.jlleitschuh.gradle.ktlint) } android { diff --git a/system/src/main/java/io/github/sds100/keymapper/system/AndroidSystemFeatureAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/AndroidSystemFeatureAdapter.kt index 0e92208654..6f0c9ed824 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/AndroidSystemFeatureAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/AndroidSystemFeatureAdapter.kt @@ -2,17 +2,18 @@ package io.github.sds100.keymapper.system import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.sds100.keymapper.system.permissions.SystemFeatureAdapter import javax.inject.Inject import javax.inject.Singleton -import io.github.sds100.keymapper.system.permissions.SystemFeatureAdapter @Singleton class AndroidSystemFeatureAdapter @Inject constructor( - @ApplicationContext private val context: Context + @ApplicationContext private val context: Context, ) : SystemFeatureAdapter { private val ctx = context.applicationContext - override fun hasSystemFeature(feature: String): Boolean = ctx.packageManager.hasSystemFeature(feature) + override fun hasSystemFeature(feature: String): Boolean = + ctx.packageManager.hasSystemFeature(feature) override fun getSystemFeatures(): List { return ctx.packageManager.systemAvailableFeatures.map { it.name } diff --git a/system/src/main/java/io/github/sds100/keymapper/system/JobSchedulerHelper.kt b/system/src/main/java/io/github/sds100/keymapper/system/JobSchedulerHelper.kt index c0eb5e1777..7bf619688a 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/JobSchedulerHelper.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/JobSchedulerHelper.kt @@ -74,7 +74,9 @@ object JobSchedulerHelper { ) val historyContentUri = JobInfo.TriggerContentUri( - Settings.Secure.getUriFor(AndroidInputMethodAdapter.SETTINGS_SECURE_SUBTYPE_HISTORY_KEY), + Settings.Secure.getUriFor( + AndroidInputMethodAdapter.SETTINGS_SECURE_SUBTYPE_HISTORY_KEY, + ), JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS, ) diff --git a/system/src/main/java/io/github/sds100/keymapper/system/SystemHiltModule.kt b/system/src/main/java/io/github/sds100/keymapper/system/SystemHiltModule.kt index 65460856f8..a151730aa5 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/SystemHiltModule.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/SystemHiltModule.kt @@ -82,7 +82,9 @@ abstract class SystemHiltModule { @Singleton @Binds - abstract fun providePackageManagerAdapter(impl: AndroidPackageManagerAdapter): PackageManagerAdapter + abstract fun providePackageManagerAdapter( + impl: AndroidPackageManagerAdapter, + ): PackageManagerAdapter @Singleton @Binds @@ -182,9 +184,13 @@ abstract class SystemHiltModule { @Singleton @Binds - abstract fun provideNotificationReceiverAdapter(impl: NotificationReceiverAdapterImpl): NotificationReceiverAdapter + abstract fun provideNotificationReceiverAdapter( + impl: NotificationReceiverAdapterImpl, + ): NotificationReceiverAdapter @Singleton @Binds - abstract fun provideSystemFeatureAdapter(impl: AndroidSystemFeatureAdapter): SystemFeatureAdapter + abstract fun provideSystemFeatureAdapter( + impl: AndroidSystemFeatureAdapter, + ): SystemFeatureAdapter } diff --git a/system/src/main/java/io/github/sds100/keymapper/system/accessibility/AccessibilityServiceState.kt b/system/src/main/java/io/github/sds100/keymapper/system/accessibility/AccessibilityServiceState.kt index f762a294b5..dfaf87c5f9 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/accessibility/AccessibilityServiceState.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/accessibility/AccessibilityServiceState.kt @@ -1,6 +1,5 @@ package io.github.sds100.keymapper.system.accessibility - enum class AccessibilityServiceState { ENABLED, CRASHED, diff --git a/system/src/main/java/io/github/sds100/keymapper/system/airplanemode/AndroidAirplaneModeAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/airplanemode/AndroidAirplaneModeAdapter.kt index d4360093a2..00cbe0cff3 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/airplanemode/AndroidAirplaneModeAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/airplanemode/AndroidAirplaneModeAdapter.kt @@ -11,17 +11,17 @@ import io.github.sds100.keymapper.common.utils.SettingsUtils import io.github.sds100.keymapper.common.utils.Success import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionManager import io.github.sds100.keymapper.system.root.SuAdapter -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch import javax.inject.Inject import javax.inject.Singleton +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch @Singleton class AndroidAirplaneModeAdapter @Inject constructor( @ApplicationContext private val ctx: Context, private val systemBridgeConnectionManager: SystemBridgeConnectionManager, private val suAdapter: SuAdapter, - private val coroutineScope: CoroutineScope + private val coroutineScope: CoroutineScope, ) : AirplaneModeAdapter { override suspend fun enable(): KMResult<*> { @@ -57,7 +57,9 @@ class AndroidAirplaneModeAdapter @Inject constructor( private fun broadcastAirplaneModeChanged(enabled: Boolean) { coroutineScope.launch { - suAdapter.execute("am broadcast -a android.intent.action.AIRPLANE_MODE --ez state $enabled") + suAdapter.execute( + "am broadcast -a android.intent.action.AIRPLANE_MODE --ez state $enabled", + ) } } } diff --git a/system/src/main/java/io/github/sds100/keymapper/system/apps/ActivityInfo.kt b/system/src/main/java/io/github/sds100/keymapper/system/apps/ActivityInfo.kt index 3504e71246..733f072aa6 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/apps/ActivityInfo.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/apps/ActivityInfo.kt @@ -3,7 +3,4 @@ package io.github.sds100.keymapper.system.apps import kotlinx.serialization.Serializable @Serializable -data class ActivityInfo( - val activityName: String, - val packageName: String, -) +data class ActivityInfo(val activityName: String, val packageName: String) diff --git a/system/src/main/java/io/github/sds100/keymapper/system/apps/AndroidAppShortcutAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/apps/AndroidAppShortcutAdapter.kt index 69072392a7..bb629806ca 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/apps/AndroidAppShortcutAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/apps/AndroidAppShortcutAdapter.kt @@ -19,11 +19,11 @@ import io.github.sds100.keymapper.common.utils.KMResult import io.github.sds100.keymapper.common.utils.State import io.github.sds100.keymapper.common.utils.Success import io.github.sds100.keymapper.common.utils.success -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow import java.util.UUID import javax.inject.Inject import javax.inject.Singleton +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow @Singleton class AndroidAppShortcutAdapter @Inject constructor( @@ -106,7 +106,8 @@ class AndroidAppShortcutAdapter @Inject constructor( } } - override fun createShortcutResultIntent(shortcut: ShortcutInfoCompat): Intent = ShortcutManagerCompat.createShortcutResultIntent(ctx, shortcut) + override fun createShortcutResultIntent(shortcut: ShortcutInfoCompat): Intent = + ShortcutManagerCompat.createShortcutResultIntent(ctx, shortcut) override fun getShortcutName(info: AppShortcutInfo): KMResult { try { diff --git a/system/src/main/java/io/github/sds100/keymapper/system/apps/AndroidPackageManagerAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/apps/AndroidPackageManagerAdapter.kt index e1d2c0546b..40fea585c5 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/apps/AndroidPackageManagerAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/apps/AndroidPackageManagerAdapter.kt @@ -29,6 +29,9 @@ import io.github.sds100.keymapper.common.utils.KMResult import io.github.sds100.keymapper.common.utils.State import io.github.sds100.keymapper.common.utils.Success import io.github.sds100.keymapper.common.utils.success +import java.io.IOException +import javax.inject.Inject +import javax.inject.Singleton import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow @@ -38,9 +41,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import java.io.IOException -import javax.inject.Inject -import javax.inject.Singleton @Singleton class AndroidPackageManagerAdapter @Inject constructor( @@ -234,7 +234,8 @@ class AndroidPackageManagerAdapter @Inject constructor( } } - @SuppressLint("UnspecifiedImmutableFlag") // only specify the flag on SDK 23+. SDK 31 is first to enforce it. + // only specify the flag on SDK 23+. SDK 31 is first to enforce it. + @SuppressLint("UnspecifiedImmutableFlag") override fun openApp(packageName: String): KMResult<*> { val leanbackIntent = packageManager.getLeanbackLaunchIntentForPackage(packageName) val normalIntent = packageManager.getLaunchIntentForPackage(packageName) diff --git a/system/src/main/java/io/github/sds100/keymapper/system/apps/AppShortcutInfo.kt b/system/src/main/java/io/github/sds100/keymapper/system/apps/AppShortcutInfo.kt index 2b2bbc45e7..6c7c824303 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/apps/AppShortcutInfo.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/apps/AppShortcutInfo.kt @@ -1,6 +1,3 @@ package io.github.sds100.keymapper.system.apps -data class AppShortcutInfo( - val packageName: String, - val activityName: String, -) +data class AppShortcutInfo(val packageName: String, val activityName: String) diff --git a/system/src/main/java/io/github/sds100/keymapper/system/apps/KeyMapShortcutActivityIntentBuilder.kt b/system/src/main/java/io/github/sds100/keymapper/system/apps/KeyMapShortcutActivityIntentBuilder.kt index b25a2cac13..47a766b5bc 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/apps/KeyMapShortcutActivityIntentBuilder.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/apps/KeyMapShortcutActivityIntentBuilder.kt @@ -4,8 +4,5 @@ import android.content.Intent import android.os.Bundle interface KeyMapShortcutActivityIntentBuilder { - fun build( - intentAction: String, - intentExtras: Bundle, - ): Intent + fun build(intentAction: String, intentExtras: Bundle): Intent } diff --git a/system/src/main/java/io/github/sds100/keymapper/system/apps/PackageInfo.kt b/system/src/main/java/io/github/sds100/keymapper/system/apps/PackageInfo.kt index d5fedd7bdb..f6c4dfad47 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/apps/PackageInfo.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/apps/PackageInfo.kt @@ -1,7 +1,5 @@ package io.github.sds100.keymapper.system.apps - - data class PackageInfo( val packageName: String, val activities: List, diff --git a/system/src/main/java/io/github/sds100/keymapper/system/apps/PackageManagerAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/apps/PackageManagerAdapter.kt index d06cdcf040..191f1fb174 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/apps/PackageManagerAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/apps/PackageManagerAdapter.kt @@ -9,7 +9,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.callbackFlow - interface PackageManagerAdapter { val onPackagesChanged: Flow val installedPackages: StateFlow>> diff --git a/system/src/main/java/io/github/sds100/keymapper/system/apps/TrampolineActivity.kt b/system/src/main/java/io/github/sds100/keymapper/system/apps/TrampolineActivity.kt index 89fc8b24fa..eed1857cd9 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/apps/TrampolineActivity.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/apps/TrampolineActivity.kt @@ -51,4 +51,4 @@ class TrampolineActivity : ComponentActivity() { finish() } -} \ No newline at end of file +} diff --git a/system/src/main/java/io/github/sds100/keymapper/system/bluetooth/AndroidBluetoothAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/bluetooth/AndroidBluetoothAdapter.kt index 838a455be7..547c6a5d1a 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/bluetooth/AndroidBluetoothAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/bluetooth/AndroidBluetoothAdapter.kt @@ -16,19 +16,19 @@ import io.github.sds100.keymapper.common.utils.KMError import io.github.sds100.keymapper.common.utils.KMResult import io.github.sds100.keymapper.common.utils.Success import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionManager +import javax.inject.Inject +import javax.inject.Singleton import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import timber.log.Timber -import javax.inject.Inject -import javax.inject.Singleton @Singleton class AndroidBluetoothAdapter @Inject constructor( @ApplicationContext private val context: Context, private val coroutineScope: CoroutineScope, - private val systemBridgeConnectionManager: SystemBridgeConnectionManager + private val systemBridgeConnectionManager: SystemBridgeConnectionManager, ) : io.github.sds100.keymapper.system.bluetooth.BluetoothAdapter { private val bluetoothManager: BluetoothManager? = context.getSystemService() @@ -153,7 +153,6 @@ class AndroidBluetoothAdapter @Inject constructor( adapter.enable() return Success(Unit) } - } override fun disable(): KMResult<*> { diff --git a/system/src/main/java/io/github/sds100/keymapper/system/camera/AndroidCameraAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/camera/AndroidCameraAdapter.kt index ff90c12619..7c9def425d 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/camera/AndroidCameraAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/camera/AndroidCameraAdapter.kt @@ -10,18 +10,17 @@ import dagger.hilt.android.qualifiers.ApplicationContext import io.github.sds100.keymapper.common.utils.KMError import io.github.sds100.keymapper.common.utils.KMResult import io.github.sds100.keymapper.common.utils.Success +import javax.inject.Inject +import javax.inject.Singleton import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update import timber.log.Timber -import javax.inject.Inject -import javax.inject.Singleton @Singleton -class AndroidCameraAdapter @Inject constructor( - @ApplicationContext private val context: Context -) : CameraAdapter { +class AndroidCameraAdapter @Inject constructor(@ApplicationContext private val context: Context) : + CameraAdapter { private val ctx = context.applicationContext private val cameraManager: CameraManager by lazy { ctx.getSystemService()!! } @@ -197,7 +196,6 @@ class AndroidCameraAdapter @Inject constructor( lens: CameraLens, strengthPercent: Float? = null, ): KMResult<*> { - try { val cameraId = getFlashlightCameraIdForLens(lens) val flashInfo = getFlashInfo(lens) @@ -210,7 +208,10 @@ class AndroidCameraAdapter @Inject constructor( } // try to find a camera with a flash - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && enabled && flashInfo.supportsVariableStrength) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && + enabled && + flashInfo.supportsVariableStrength + ) { val strength = if (strengthPercent == null) { flashInfo.defaultStrength } else { diff --git a/system/src/main/java/io/github/sds100/keymapper/system/clipboard/AndroidClipboardAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/clipboard/AndroidClipboardAdapter.kt index ed4051ef21..a46d0d994e 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/clipboard/AndroidClipboardAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/clipboard/AndroidClipboardAdapter.kt @@ -10,7 +10,7 @@ import javax.inject.Singleton @Singleton class AndroidClipboardAdapter @Inject constructor( - @ApplicationContext private val context: Context + @ApplicationContext private val context: Context, ) : ClipboardAdapter { private val ctx = context.applicationContext diff --git a/system/src/main/java/io/github/sds100/keymapper/system/devices/AndroidDevicesAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/devices/AndroidDevicesAdapter.kt index 72bd687201..923dc8972d 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/devices/AndroidDevicesAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/devices/AndroidDevicesAdapter.kt @@ -19,6 +19,8 @@ import io.github.sds100.keymapper.common.utils.ifIsData import io.github.sds100.keymapper.system.bluetooth.BluetoothDeviceInfo import io.github.sds100.keymapper.system.permissions.Permission import io.github.sds100.keymapper.system.permissions.PermissionAdapter +import javax.inject.Inject +import javax.inject.Singleton import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -28,8 +30,6 @@ import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import javax.inject.Inject -import javax.inject.Singleton @Singleton class AndroidDevicesAdapter @Inject constructor( @@ -75,7 +75,9 @@ class AndroidDevicesAdapter @Inject constructor( override fun onInputDeviceAdded(deviceId: Int) { coroutineScope.launch { val device = InputDevice.getDevice(deviceId) ?: return@launch - onInputDeviceConnect.emit(InputDeviceUtils.createInputDeviceInfo(device)) + onInputDeviceConnect.emit( + InputDeviceUtils.createInputDeviceInfo(device), + ) updateInputDevices() } diff --git a/system/src/main/java/io/github/sds100/keymapper/system/display/AndroidDisplayAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/display/AndroidDisplayAdapter.kt index 9067b25632..d234b680aa 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/display/AndroidDisplayAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/display/AndroidDisplayAdapter.kt @@ -18,6 +18,8 @@ import io.github.sds100.keymapper.common.utils.SettingsUtils import io.github.sds100.keymapper.common.utils.SizeKM import io.github.sds100.keymapper.common.utils.Success import io.github.sds100.keymapper.common.utils.getRealDisplaySize +import javax.inject.Inject +import javax.inject.Singleton import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow @@ -25,8 +27,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.updateAndGet import kotlinx.coroutines.launch -import javax.inject.Inject -import javax.inject.Singleton @Singleton class AndroidDisplayAdapter @Inject constructor( diff --git a/system/src/main/java/io/github/sds100/keymapper/system/display/DisplayAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/display/DisplayAdapter.kt index a38fe5a177..f8792d6fd4 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/display/DisplayAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/display/DisplayAdapter.kt @@ -1,8 +1,8 @@ package io.github.sds100.keymapper.system.display import io.github.sds100.keymapper.common.utils.KMResult -import io.github.sds100.keymapper.common.utils.SizeKM import io.github.sds100.keymapper.common.utils.Orientation +import io.github.sds100.keymapper.common.utils.SizeKM import kotlinx.coroutines.flow.Flow interface DisplayAdapter { diff --git a/system/src/main/java/io/github/sds100/keymapper/system/files/AndroidFileAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/files/AndroidFileAdapter.kt index 18a9a553e6..04a151647b 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/files/AndroidFileAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/files/AndroidFileAdapter.kt @@ -15,19 +15,19 @@ import io.github.sds100.keymapper.common.BuildConfigProvider import io.github.sds100.keymapper.common.utils.KMError import io.github.sds100.keymapper.common.utils.KMResult import io.github.sds100.keymapper.common.utils.Success -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import net.lingala.zip4j.ZipFile import java.io.File import java.io.InputStream import java.util.UUID import javax.inject.Inject import javax.inject.Singleton +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import net.lingala.zip4j.ZipFile @Singleton class AndroidFileAdapter @Inject constructor( @ApplicationContext private val context: Context, - private val buildConfigProvider: BuildConfigProvider + private val buildConfigProvider: BuildConfigProvider, ) : FileAdapter { private val ctx = context.applicationContext private val contentResolver: ContentResolver = ctx.contentResolver diff --git a/system/src/main/java/io/github/sds100/keymapper/system/files/DocumentFileWrapper.kt b/system/src/main/java/io/github/sds100/keymapper/system/files/DocumentFileWrapper.kt index 1bf21fb57e..45a26f5be2 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/files/DocumentFileWrapper.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/files/DocumentFileWrapper.kt @@ -15,13 +15,13 @@ import com.anggrayudi.storage.media.FileDescription import io.github.sds100.keymapper.common.utils.KMError import io.github.sds100.keymapper.common.utils.KMResult import io.github.sds100.keymapper.common.utils.Success -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext import java.io.File import java.io.InputStream import java.io.OutputStream import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext class DocumentFileWrapper(val file: DocumentFile, context: Context) : IFile { @@ -90,51 +90,53 @@ class DocumentFileWrapper(val file: DocumentFile, context: Context) : IFile { } } - override suspend fun copyTo(directory: IFile, fileName: String?): KMResult<*> = withContext(Dispatchers.Default) { - suspendCoroutine { continuation -> - val callback = object : FileCallback() { - override fun onCompleted(result: Any) { - super.onCompleted(result) + override suspend fun copyTo(directory: IFile, fileName: String?): KMResult<*> = + withContext(Dispatchers.Default) { + suspendCoroutine { continuation -> + val callback = object : FileCallback() { + override fun onCompleted(result: Any) { + super.onCompleted(result) - continuation.resume(Success(Unit)) - } + continuation.resume(Success(Unit)) + } - override fun onFailed(errorCode: ErrorCode) { - super.onFailed(errorCode) - - val error = when (errorCode) { - ErrorCode.STORAGE_PERMISSION_DENIED -> KMError.StoragePermissionDenied - ErrorCode.CANNOT_CREATE_FILE_IN_TARGET -> KMError.CannotCreateFileInTarget( - directory.uri, - ) - - ErrorCode.SOURCE_FILE_NOT_FOUND -> KMError.SourceFileNotFound(this@DocumentFileWrapper.uri) - ErrorCode.TARGET_FILE_NOT_FOUND -> KMError.TargetFileNotFound(directory.uri) - ErrorCode.TARGET_FOLDER_NOT_FOUND -> KMError.TargetDirectoryNotFound( - directory.uri, - ) - - ErrorCode.UNKNOWN_IO_ERROR -> KMError.UnknownIOError - ErrorCode.CANCELED -> KMError.FileOperationCancelled - ErrorCode.TARGET_FOLDER_CANNOT_HAVE_SAME_PATH_WITH_SOURCE_FOLDER -> KMError.TargetDirectoryMatchesSourceDirectory - ErrorCode.NO_SPACE_LEFT_ON_TARGET_PATH -> KMError.NoSpaceLeftOnTarget( - directory.uri, - ) + override fun onFailed(errorCode: ErrorCode) { + super.onFailed(errorCode) + + val error = when (errorCode) { + ErrorCode.STORAGE_PERMISSION_DENIED -> KMError.StoragePermissionDenied + ErrorCode.CANNOT_CREATE_FILE_IN_TARGET -> + KMError.CannotCreateFileInTarget(directory.uri) + + ErrorCode.SOURCE_FILE_NOT_FOUND -> + KMError.SourceFileNotFound(this@DocumentFileWrapper.uri) + ErrorCode.TARGET_FILE_NOT_FOUND -> + KMError.TargetFileNotFound(directory.uri) + ErrorCode.TARGET_FOLDER_NOT_FOUND -> + KMError.TargetDirectoryNotFound(directory.uri) + + ErrorCode.UNKNOWN_IO_ERROR -> KMError.UnknownIOError + ErrorCode.CANCELED -> KMError.FileOperationCancelled + ErrorCode.TARGET_FOLDER_CANNOT_HAVE_SAME_PATH_WITH_SOURCE_FOLDER -> + KMError.TargetDirectoryMatchesSourceDirectory + ErrorCode.NO_SPACE_LEFT_ON_TARGET_PATH -> KMError.NoSpaceLeftOnTarget( + directory.uri, + ) + } + + continuation.resume(error) } - continuation.resume(error) + override fun onStart(file: Any, workerThread: Thread): Long = 0 } - override fun onStart(file: Any, workerThread: Thread): Long = 0 - } + val fileDescription = if (fileName == null) { + null + } else { + FileDescription(fileName) + } - val fileDescription = if (fileName == null) { - null - } else { - FileDescription(fileName) + file.copyFileTo(ctx, directory.path, fileDescription, callback) } - - file.copyFileTo(ctx, directory.path, fileDescription, callback) } - } } diff --git a/system/src/main/java/io/github/sds100/keymapper/system/foldable/AndroidFoldableAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/foldable/AndroidFoldableAdapter.kt index 39c87139ed..9f96f21ed1 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/foldable/AndroidFoldableAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/foldable/AndroidFoldableAdapter.kt @@ -9,12 +9,12 @@ import android.os.Build import androidx.annotation.RequiresApi import androidx.core.content.getSystemService import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject +import javax.inject.Singleton import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import timber.log.Timber -import javax.inject.Inject -import javax.inject.Singleton @RequiresApi(Build.VERSION_CODES.R) @Singleton diff --git a/system/src/main/java/io/github/sds100/keymapper/system/inputevents/KMEvdevEvent.kt b/system/src/main/java/io/github/sds100/keymapper/system/inputevents/KMEvdevEvent.kt index 424f869477..fa88349920 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/inputevents/KMEvdevEvent.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/inputevents/KMEvdevEvent.kt @@ -9,7 +9,7 @@ data class KMEvdevEvent( val value: Int, val androidCode: Int, val timeSec: Long, - val timeUsec: Long + val timeUsec: Long, ) : KMInputEvent { companion object { diff --git a/system/src/main/java/io/github/sds100/keymapper/system/inputevents/KMInputEvent.kt b/system/src/main/java/io/github/sds100/keymapper/system/inputevents/KMInputEvent.kt index 1e12eaed08..73ac5637f9 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/inputevents/KMInputEvent.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/inputevents/KMInputEvent.kt @@ -1,3 +1,3 @@ package io.github.sds100.keymapper.system.inputevents -sealed interface KMInputEvent \ No newline at end of file +sealed interface KMInputEvent diff --git a/system/src/main/java/io/github/sds100/keymapper/system/inputevents/KeyEventUtils.kt b/system/src/main/java/io/github/sds100/keymapper/system/inputevents/KeyEventUtils.kt index 1331dcd998..9c3a04080c 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/inputevents/KeyEventUtils.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/inputevents/KeyEventUtils.kt @@ -390,7 +390,9 @@ object KeyEventUtils { KeyEvent.KEYCODE_ALT_RIGHT -> KeyEvent.META_ALT_RIGHT_ON.withFlag(KeyEvent.META_ALT_ON) KeyEvent.KEYCODE_SHIFT_LEFT -> KeyEvent.META_SHIFT_LEFT_ON.withFlag(KeyEvent.META_SHIFT_ON) - KeyEvent.KEYCODE_SHIFT_RIGHT -> KeyEvent.META_SHIFT_RIGHT_ON.withFlag(KeyEvent.META_SHIFT_ON) + KeyEvent.KEYCODE_SHIFT_RIGHT -> KeyEvent.META_SHIFT_RIGHT_ON.withFlag( + KeyEvent.META_SHIFT_ON, + ) KeyEvent.KEYCODE_SYM -> KeyEvent.META_SYM_ON @@ -472,4 +474,3 @@ object KeyEventUtils { scanCode == Scancode.KEY_POWER2 } } - diff --git a/system/src/main/java/io/github/sds100/keymapper/system/inputevents/Scancode.kt b/system/src/main/java/io/github/sds100/keymapper/system/inputevents/Scancode.kt index bc2d90fcd3..3e009a07ab 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/inputevents/Scancode.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/inputevents/Scancode.kt @@ -632,4 +632,4 @@ object Scancode { const val BTN_TRIGGER_HAPPY38 = 0x2e5 const val BTN_TRIGGER_HAPPY39 = 0x2e6 const val BTN_TRIGGER_HAPPY40 = 0x2e7 -} \ No newline at end of file +} diff --git a/system/src/main/java/io/github/sds100/keymapper/system/inputmethod/AndroidInputMethodAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/inputmethod/AndroidInputMethodAdapter.kt index 11d23b6b02..e1c6b70e18 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/inputmethod/AndroidInputMethodAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/inputmethod/AndroidInputMethodAdapter.kt @@ -128,7 +128,9 @@ class AndroidInputMethodAdapter @Inject constructor( * :com.google.android.inputmethod.latin/com.android.inputmethod.latin.LatinIME;1891618174 */ private fun getImeHistory(): List { - val ids = getSubtypeHistoryString(ctx) + val subtypeString = getSubtypeHistoryString(ctx) ?: return emptyList() + + val ids = subtypeString .split(':') .map { it.split(';')[0] } @@ -156,7 +158,7 @@ class AndroidInputMethodAdapter @Inject constructor( } } - private fun getSubtypeHistoryString(ctx: Context): String = Settings.Secure.getString( + private fun getSubtypeHistoryString(ctx: Context): String? = Settings.Secure.getString( ctx.contentResolver, SETTINGS_SECURE_SUBTYPE_HISTORY_KEY, ) diff --git a/system/src/main/java/io/github/sds100/keymapper/system/inputmethod/KeyEventRelayServiceWrapper.kt b/system/src/main/java/io/github/sds100/keymapper/system/inputmethod/KeyEventRelayServiceWrapper.kt index a6d1e94c93..f694d32661 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/inputmethod/KeyEventRelayServiceWrapper.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/inputmethod/KeyEventRelayServiceWrapper.kt @@ -19,7 +19,7 @@ import javax.inject.Singleton @Singleton class KeyEventRelayServiceWrapperImpl @Inject constructor( @ApplicationContext private val ctx: Context, - private val buildConfigProvider: BuildConfigProvider + private val buildConfigProvider: BuildConfigProvider, ) : KeyEventRelayServiceWrapper { private val keyEventRelayServiceLock: Any = Any() @@ -27,10 +27,7 @@ class KeyEventRelayServiceWrapperImpl @Inject constructor( private val serviceConnection: ServiceConnection = object : ServiceConnection { - override fun onServiceConnected( - name: ComponentName?, - service: IBinder?, - ) { + override fun onServiceConnected(name: ComponentName?, service: IBinder?) { synchronized(keyEventRelayServiceLock) { keyEventRelayService = IKeyEventRelayService.Stub.asInterface(service) } @@ -95,7 +92,7 @@ class KeyEventRelayServiceWrapperImpl @Inject constructor( val component = ComponentName( buildConfigProvider.packageName, - "io.github.sds100.keymapper.api.KeyEventRelayService" + "io.github.sds100.keymapper.api.KeyEventRelayService", ) relayServiceIntent.setComponent(component) val isSuccess = @@ -127,4 +124,4 @@ interface KeyEventRelayServiceWrapper { fun unregisterClient(id: String) fun sendKeyEvent(event: KeyEvent, targetPackageName: String, callbackId: String): Boolean fun sendMotionEvent(event: MotionEvent, targetPackageName: String, callbackId: String): Boolean -} \ No newline at end of file +} diff --git a/system/src/main/java/io/github/sds100/keymapper/system/inputmethod/KeyMapperImeService.kt b/system/src/main/java/io/github/sds100/keymapper/system/inputmethod/KeyMapperImeService.kt index 94ee5d311c..fa3b64167c 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/inputmethod/KeyMapperImeService.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/inputmethod/KeyMapperImeService.kt @@ -20,8 +20,8 @@ import io.github.sds100.keymapper.api.IKeyEventRelayServiceCallback import io.github.sds100.keymapper.common.BuildConfigProvider import io.github.sds100.keymapper.system.accessibility.AccessibilityServiceAdapter import io.github.sds100.keymapper.system.accessibility.AccessibilityServiceEvent -import timber.log.Timber import javax.inject.Inject +import timber.log.Timber /** * DO NOT MOVE. Must stay in this package so the user's input method settings are not reset @@ -142,7 +142,7 @@ class KeyMapperImeService : InputMethodService() { keyEventRelayServiceWrapper.registerClient( CALLBACK_ID_INPUT_METHOD, - keyEventReceiverCallback + keyEventReceiverCallback, ) } @@ -164,8 +164,8 @@ class KeyMapperImeService : InputMethodService() { serviceAdapter.sendAsync( AccessibilityServiceEvent.OnKeyMapperImeStartInput( attribute = attribute, - restarting = restarting - ) + restarting = restarting, + ), ) } } diff --git a/system/src/main/java/io/github/sds100/keymapper/system/intents/IntentAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/intents/IntentAdapter.kt index ebb6d120d1..972cb202b5 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/intents/IntentAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/intents/IntentAdapter.kt @@ -10,7 +10,8 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class IntentAdapterImpl @Inject constructor(@ApplicationContext private val context: Context) : IntentAdapter { +class IntentAdapterImpl @Inject constructor(@ApplicationContext private val context: Context) : + IntentAdapter { private val ctx = context.applicationContext override fun send( diff --git a/system/src/main/java/io/github/sds100/keymapper/system/intents/IntentExtraModel.kt b/system/src/main/java/io/github/sds100/keymapper/system/intents/IntentExtraModel.kt index 46f4ec46d8..d136aee215 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/intents/IntentExtraModel.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/intents/IntentExtraModel.kt @@ -1,7 +1,7 @@ package io.github.sds100.keymapper.system.intents -import kotlinx.serialization.Serializable import java.util.UUID +import kotlinx.serialization.Serializable @Serializable data class IntentExtraModel( diff --git a/system/src/main/java/io/github/sds100/keymapper/system/leanback/LeanbackAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/leanback/LeanbackAdapter.kt index 059208e275..6ebc826a6e 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/leanback/LeanbackAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/leanback/LeanbackAdapter.kt @@ -9,9 +9,8 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class LeanbackAdapterImpl @Inject constructor( - @ApplicationContext context: Context, -) : LeanbackAdapter { +class LeanbackAdapterImpl @Inject constructor(@ApplicationContext context: Context) : + LeanbackAdapter { private val ctx = context.applicationContext override fun isTvDevice(): Boolean { diff --git a/system/src/main/java/io/github/sds100/keymapper/system/lock/AndroidLockScreenAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/lock/AndroidLockScreenAdapter.kt index 07b30cb2e5..6b11487316 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/lock/AndroidLockScreenAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/lock/AndroidLockScreenAdapter.kt @@ -11,10 +11,10 @@ import androidx.core.content.getSystemService import dagger.hilt.android.qualifiers.ApplicationContext import io.github.sds100.keymapper.common.utils.KMResult import io.github.sds100.keymapper.common.utils.Success +import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update -import javax.inject.Inject class AndroidLockScreenAdapter @Inject constructor(@ApplicationContext private val ctx: Context) : LockScreenAdapter { @@ -28,7 +28,11 @@ class AndroidLockScreenAdapter @Inject constructor(@ApplicationContext private v context ?: return when (intent.action) { - Intent.ACTION_SCREEN_ON, Intent.ACTION_SCREEN_OFF, Intent.ACTION_USER_PRESENT, Intent.ACTION_USER_UNLOCKED -> { + Intent.ACTION_SCREEN_ON, + Intent.ACTION_SCREEN_OFF, + Intent.ACTION_USER_PRESENT, + Intent.ACTION_USER_UNLOCKED, + -> { isLockedFlow.update { isLocked() } diff --git a/system/src/main/java/io/github/sds100/keymapper/system/lock/LockScreenAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/lock/LockScreenAdapter.kt index e5d5c44095..1b91b62c98 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/lock/LockScreenAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/lock/LockScreenAdapter.kt @@ -3,7 +3,6 @@ package io.github.sds100.keymapper.system.lock import io.github.sds100.keymapper.common.utils.KMResult import kotlinx.coroutines.flow.Flow - interface LockScreenAdapter { fun secureLockDevice(): KMResult<*> diff --git a/system/src/main/java/io/github/sds100/keymapper/system/media/AndroidMediaAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/media/AndroidMediaAdapter.kt index d764a2945e..d7461da2a3 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/media/AndroidMediaAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/media/AndroidMediaAdapter.kt @@ -12,25 +12,25 @@ import android.os.Build import android.view.KeyEvent import androidx.annotation.RequiresApi import androidx.core.content.getSystemService +import dagger.hilt.android.qualifiers.ApplicationContext import io.github.sds100.keymapper.common.utils.KMError import io.github.sds100.keymapper.common.utils.KMResult import io.github.sds100.keymapper.common.utils.Success import io.github.sds100.keymapper.system.volume.VolumeStream +import java.io.FileNotFoundException +import javax.inject.Inject +import javax.inject.Singleton import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import java.io.FileNotFoundException -import dagger.hilt.android.qualifiers.ApplicationContext -import javax.inject.Inject -import javax.inject.Singleton @Singleton class AndroidMediaAdapter @Inject constructor( @ApplicationContext private val context: Context, - private val coroutineScope: CoroutineScope + private val coroutineScope: CoroutineScope, ) : MediaAdapter { private val ctx = context.applicationContext @@ -49,7 +49,9 @@ class AndroidMediaAdapter @Inject constructor( private val audioPlaybackCallback by lazy { @RequiresApi(Build.VERSION_CODES.O) object : AudioManager.AudioPlaybackCallback() { - override fun onPlaybackConfigChanged(configs: MutableList?) { + override fun onPlaybackConfigChanged( + configs: MutableList?, + ) { audioVolumeControlStreams.update { getActiveAudioVolumeStreams() } } } @@ -72,21 +74,29 @@ class AndroidMediaAdapter @Inject constructor( } } - override fun fastForward(packageName: String?): KMResult<*> = sendMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, packageName) + override fun fastForward(packageName: String?): KMResult<*> = + sendMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, packageName) - override fun rewind(packageName: String?): KMResult<*> = sendMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_REWIND, packageName) + override fun rewind(packageName: String?): KMResult<*> = + sendMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_REWIND, packageName) - override fun play(packageName: String?): KMResult<*> = sendMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY, packageName) + override fun play(packageName: String?): KMResult<*> = + sendMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY, packageName) - override fun pause(packageName: String?): KMResult<*> = sendMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PAUSE, packageName) + override fun pause(packageName: String?): KMResult<*> = + sendMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PAUSE, packageName) - override fun playPause(packageName: String?): KMResult<*> = sendMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, packageName) + override fun playPause(packageName: String?): KMResult<*> = + sendMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, packageName) - override fun previousTrack(packageName: String?): KMResult<*> = sendMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PREVIOUS, packageName) + override fun previousTrack(packageName: String?): KMResult<*> = + sendMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PREVIOUS, packageName) - override fun nextTrack(packageName: String?): KMResult<*> = sendMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_NEXT, packageName) + override fun nextTrack(packageName: String?): KMResult<*> = + sendMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_NEXT, packageName) - override fun stop(packageName: String?): KMResult<*> = sendMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_STOP, packageName) + override fun stop(packageName: String?): KMResult<*> = + sendMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_STOP, packageName) override fun stopFileMedia(): KMResult<*> { synchronized(mediaPlayerLock) { @@ -148,7 +158,9 @@ class AndroidMediaAdapter @Inject constructor( mediaPlayer = MediaPlayer().apply { val usage = when (stream) { VolumeStream.ACCESSIBILITY -> AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY - else -> throw Exception("Don't know how to convert volume stream to audio usage attribute") + else -> throw Exception( + "Don't know how to convert volume stream to audio usage attribute", + ) } setAudioAttributes( diff --git a/system/src/main/java/io/github/sds100/keymapper/system/media/MediaAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/media/MediaAdapter.kt index 79e16d53dc..833058281e 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/media/MediaAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/media/MediaAdapter.kt @@ -6,7 +6,6 @@ import io.github.sds100.keymapper.common.utils.KMResult import io.github.sds100.keymapper.system.volume.VolumeStream import kotlinx.coroutines.flow.Flow - interface MediaAdapter { /** diff --git a/system/src/main/java/io/github/sds100/keymapper/system/network/AndroidNetworkAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/network/AndroidNetworkAdapter.kt index 27dc8de180..1ecd1b5116 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/network/AndroidNetworkAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/network/AndroidNetworkAdapter.kt @@ -26,6 +26,8 @@ import io.github.sds100.keymapper.common.utils.KMResult import io.github.sds100.keymapper.common.utils.Success import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionManager import io.github.sds100.keymapper.system.root.SuAdapter +import javax.inject.Inject +import javax.inject.Singleton import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -39,14 +41,12 @@ import okhttp3.RequestBody.Companion.toRequestBody import okio.IOException import okio.use import timber.log.Timber -import javax.inject.Inject -import javax.inject.Singleton @Singleton class AndroidNetworkAdapter @Inject constructor( @ApplicationContext private val context: Context, private val suAdapter: SuAdapter, - private val systemBridgeConnManager: SystemBridgeConnectionManager + private val systemBridgeConnManager: SystemBridgeConnectionManager, ) : NetworkAdapter { private val ctx = context.applicationContext private val wifiManager: WifiManager by lazy { ctx.getSystemService()!! } @@ -101,7 +101,7 @@ class AndroidNetworkAdapter @Inject constructor( override fun onCapabilitiesChanged( network: Network, - networkCapabilities: NetworkCapabilities + networkCapabilities: NetworkCapabilities, ) { super.onCapabilitiesChanged(network, networkCapabilities) diff --git a/system/src/main/java/io/github/sds100/keymapper/system/network/NetworkAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/network/NetworkAdapter.kt index 27862c975e..1440caa651 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/network/NetworkAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/network/NetworkAdapter.kt @@ -3,7 +3,6 @@ package io.github.sds100.keymapper.system.network import io.github.sds100.keymapper.common.utils.KMResult import kotlinx.coroutines.flow.Flow - interface NetworkAdapter { val connectedWifiSSIDFlow: Flow val isWifiConnected: Flow diff --git a/system/src/main/java/io/github/sds100/keymapper/system/nfc/AndroidNfcAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/nfc/AndroidNfcAdapter.kt index d96b12a4e6..a57a5c3462 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/nfc/AndroidNfcAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/nfc/AndroidNfcAdapter.kt @@ -9,15 +9,15 @@ import io.github.sds100.keymapper.common.utils.Constants import io.github.sds100.keymapper.common.utils.KMResult import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionManager import io.github.sds100.keymapper.system.root.SuAdapter -import kotlinx.coroutines.runBlocking import javax.inject.Inject import javax.inject.Singleton +import kotlinx.coroutines.runBlocking @Singleton class AndroidNfcAdapter @Inject constructor( @ApplicationContext private val context: Context, private val suAdapter: SuAdapter, - private val systemBridgeConnectionManager: SystemBridgeConnectionManager + private val systemBridgeConnectionManager: SystemBridgeConnectionManager, ) : NfcAdapter { private val ctx = context.applicationContext diff --git a/system/src/main/java/io/github/sds100/keymapper/system/nfc/NfcAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/nfc/NfcAdapter.kt index e155ffd1e4..284caa2bcc 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/nfc/NfcAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/nfc/NfcAdapter.kt @@ -2,7 +2,6 @@ package io.github.sds100.keymapper.system.nfc import io.github.sds100.keymapper.common.utils.KMResult - interface NfcAdapter { fun isEnabled(): Boolean fun enable(): KMResult<*> diff --git a/system/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationChannelModel.kt b/system/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationChannelModel.kt index f716f1882b..50aa8d0771 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationChannelModel.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationChannelModel.kt @@ -1,8 +1,3 @@ package io.github.sds100.keymapper.system.notifications - -data class NotificationChannelModel( - val id: String, - val name: String, - val importance: Int, -) +data class NotificationChannelModel(val id: String, val name: String, val importance: Int) diff --git a/system/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationModel.kt b/system/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationModel.kt index 2b0e1c1f9a..efc1cd8926 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationModel.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationModel.kt @@ -4,7 +4,6 @@ import androidx.annotation.DrawableRes import androidx.core.app.NotificationCompat import io.github.sds100.keymapper.common.notifications.KMNotificationAction - data class NotificationModel( val id: Int, val channel: String, @@ -34,6 +33,5 @@ data class NotificationModel( val bigTextStyle: Boolean = false, val silent: Boolean = false, val showIndeterminateProgress: Boolean = false, - val timeout: Long? = null + val timeout: Long? = null, ) - diff --git a/system/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationReceiver.kt b/system/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationReceiver.kt index 6d8e80bc5c..974b2948c5 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationReceiver.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationReceiver.kt @@ -11,13 +11,15 @@ import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint import io.github.sds100.keymapper.system.media.AndroidMediaAdapter +import javax.inject.Inject import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import timber.log.Timber -import javax.inject.Inject @AndroidEntryPoint -class NotificationReceiver : NotificationListenerService(), LifecycleOwner { +class NotificationReceiver : + NotificationListenerService(), + LifecycleOwner { private val mediaSessionManager: MediaSessionManager by lazy { getSystemService()!! } private val notificationListenerComponent by lazy { @@ -37,7 +39,6 @@ class NotificationReceiver : NotificationListenerService(), LifecycleOwner { private var lastNotificationKey: String? = null - @Inject lateinit var serviceAdapter: NotificationReceiverAdapterImpl @@ -52,7 +53,9 @@ class NotificationReceiver : NotificationListenerService(), LifecycleOwner { serviceAdapter.eventsToService .onEach { event -> when (event) { - NotificationServiceEvent.DismissLastNotification -> cancelNotification(lastNotificationKey) + NotificationServiceEvent.DismissLastNotification -> cancelNotification( + lastNotificationKey, + ) NotificationServiceEvent.DismissAllNotifications -> cancelAllNotifications() else -> Unit } diff --git a/system/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationReceiverAdapterImpl.kt b/system/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationReceiverAdapterImpl.kt index 401479513e..e1b8578207 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationReceiverAdapterImpl.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationReceiverAdapterImpl.kt @@ -18,13 +18,13 @@ import io.github.sds100.keymapper.common.utils.withFlag import io.github.sds100.keymapper.system.JobSchedulerHelper import io.github.sds100.keymapper.system.SystemError import io.github.sds100.keymapper.system.permissions.Permission +import javax.inject.Inject +import javax.inject.Singleton import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import javax.inject.Inject -import javax.inject.Singleton @Singleton class NotificationReceiverAdapterImpl @Inject constructor( @@ -41,7 +41,7 @@ class NotificationReceiverAdapterImpl @Inject constructor( init { // use job scheduler because there is there is a much shorter delay when the app is in the background if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - JobSchedulerHelper.observeEnabledNotificationListeners(ctx) + JobSchedulerHelper.observeEnabledNotificationListeners(ctx) } else { val uri = Settings.Secure.getUriFor("enabled_notification_listeners") val observer = object : ContentObserver(Handler(Looper.getMainLooper())) { diff --git a/system/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationRemoteInput.kt b/system/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationRemoteInput.kt index 9e536e8de4..f7ceff6139 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationRemoteInput.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationRemoteInput.kt @@ -9,5 +9,5 @@ import io.github.sds100.keymapper.common.notifications.KMNotificationAction */ data class NotificationRemoteInput( val intentAction: KMNotificationAction.IntentAction, - val text: String -) \ No newline at end of file + val text: String, +) diff --git a/system/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationServiceEvent.kt b/system/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationServiceEvent.kt index 42843285eb..cfc6fe15bb 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationServiceEvent.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationServiceEvent.kt @@ -9,5 +9,4 @@ sealed class NotificationServiceEvent { @Serializable data object DismissAllNotifications : NotificationServiceEvent() - -} \ No newline at end of file +} diff --git a/system/src/main/java/io/github/sds100/keymapper/system/notifications/ObserveNotificationListenersJob.kt b/system/src/main/java/io/github/sds100/keymapper/system/notifications/ObserveNotificationListenersJob.kt index d01039e34d..bab2ebb599 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/notifications/ObserveNotificationListenersJob.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/notifications/ObserveNotificationListenersJob.kt @@ -10,12 +10,12 @@ import javax.inject.Inject @AndroidEntryPoint class ObserveNotificationListenersJob : JobService() { - + @Inject lateinit var permissionAdapter: AndroidPermissionAdapter - + override fun onStartJob(params: JobParameters?): Boolean { - permissionAdapter.onPermissionsChanged() + permissionAdapter.onPermissionsChanged() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { JobSchedulerHelper.observeEnabledNotificationListeners(applicationContext) diff --git a/system/src/main/java/io/github/sds100/keymapper/system/permissions/AndroidPermissionAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/permissions/AndroidPermissionAdapter.kt index f280d50a2f..6f33664c9e 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/permissions/AndroidPermissionAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/permissions/AndroidPermissionAdapter.kt @@ -20,7 +20,6 @@ import io.github.sds100.keymapper.common.BuildConfigProvider import io.github.sds100.keymapper.common.utils.Constants import io.github.sds100.keymapper.common.utils.KMError import io.github.sds100.keymapper.common.utils.KMResult -import io.github.sds100.keymapper.common.utils.firstBlocking import io.github.sds100.keymapper.common.utils.getIdentifier import io.github.sds100.keymapper.common.utils.onFailure import io.github.sds100.keymapper.common.utils.onSuccess @@ -29,12 +28,14 @@ import io.github.sds100.keymapper.common.utils.then import io.github.sds100.keymapper.data.Keys import io.github.sds100.keymapper.data.repositories.PreferenceRepository import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionManager -import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionState +import io.github.sds100.keymapper.sysbridge.manager.isConnected import io.github.sds100.keymapper.sysbridge.utils.SystemBridgeError import io.github.sds100.keymapper.system.DeviceAdmin import io.github.sds100.keymapper.system.notifications.NotificationReceiverAdapter import io.github.sds100.keymapper.system.root.SuAdapter import io.github.sds100.keymapper.system.shizuku.ShizukuAdapter +import javax.inject.Inject +import javax.inject.Singleton import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow @@ -54,8 +55,6 @@ import rikka.shizuku.Shizuku import rikka.shizuku.ShizukuBinderWrapper import rikka.shizuku.SystemServiceHelper import timber.log.Timber -import javax.inject.Inject -import javax.inject.Singleton @Singleton class AndroidPermissionAdapter @Inject constructor( @@ -66,7 +65,7 @@ class AndroidPermissionAdapter @Inject constructor( private val preferenceRepository: PreferenceRepository, private val buildConfigProvider: BuildConfigProvider, private val systemBridgeConnectionManager: SystemBridgeConnectionManager, - private val shizukuAdapter: ShizukuAdapter + private val shizukuAdapter: ShizukuAdapter, ) : PermissionAdapter { companion object { const val REQUEST_CODE_SHIZUKU_PERMISSION = 1 @@ -153,7 +152,7 @@ class AndroidPermissionAdapter @Inject constructor( val isSystemBridgeConnected = Build.VERSION.SDK_INT >= Constants.SYSTEM_BRIDGE_MIN_API && - systemBridgeConnectionManager.connectionState.firstBlocking() is SystemBridgeConnectionState.Connected + systemBridgeConnectionManager.isConnected() if (isSystemBridgeConnected) { result = systemBridgeConnectionManager.run { bridge -> @@ -169,8 +168,11 @@ class AndroidPermissionAdapter @Inject constructor( val userId = Process.myUserHandle()!!.getIdentifier() PermissionManagerApis.grantPermission( - shizukuPermissionManager, buildConfigProvider.packageName, - permissionName, deviceId, userId + shizukuPermissionManager, + buildConfigProvider.packageName, + permissionName, + deviceId, + userId, ) if (ContextCompat.checkSelfPermission(ctx, permissionName) == PERMISSION_GRANTED) { @@ -302,6 +304,13 @@ class AndroidPermissionAdapter @Inject constructor( } else { true } + + Permission.READ_LOGS -> { + ContextCompat.checkSelfPermission( + ctx, + Manifest.permission.READ_LOGS, + ) == PERMISSION_GRANTED + } } override fun isGrantedFlow(permission: Permission): Flow = channelFlow { diff --git a/system/src/main/java/io/github/sds100/keymapper/system/permissions/Permission.kt b/system/src/main/java/io/github/sds100/keymapper/system/permissions/Permission.kt index 468b91aba3..f243a54b0c 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/permissions/Permission.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/permissions/Permission.kt @@ -17,4 +17,5 @@ enum class Permission { ANSWER_PHONE_CALL, FIND_NEARBY_DEVICES, POST_NOTIFICATIONS, + READ_LOGS, } diff --git a/system/src/main/java/io/github/sds100/keymapper/system/permissions/PermissionAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/permissions/PermissionAdapter.kt index 62427cf3ce..cefb5c2a30 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/permissions/PermissionAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/permissions/PermissionAdapter.kt @@ -3,7 +3,6 @@ package io.github.sds100.keymapper.system.permissions import io.github.sds100.keymapper.common.utils.KMResult import kotlinx.coroutines.flow.Flow - interface PermissionAdapter { val onPermissionsUpdate: Flow fun isGranted(permission: Permission): Boolean diff --git a/system/src/main/java/io/github/sds100/keymapper/system/permissions/SystemFeatureAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/permissions/SystemFeatureAdapter.kt index dd7474a841..287c36e7a0 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/permissions/SystemFeatureAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/permissions/SystemFeatureAdapter.kt @@ -1,6 +1,5 @@ package io.github.sds100.keymapper.system.permissions - interface SystemFeatureAdapter { fun hasSystemFeature(feature: String): Boolean fun getSystemFeatures(): List diff --git a/system/src/main/java/io/github/sds100/keymapper/system/phone/AndroidPhoneAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/phone/AndroidPhoneAdapter.kt index 934c8635ff..eadc547009 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/phone/AndroidPhoneAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/phone/AndroidPhoneAdapter.kt @@ -24,6 +24,8 @@ import dagger.hilt.android.qualifiers.ApplicationContext import io.github.sds100.keymapper.common.utils.KMError import io.github.sds100.keymapper.common.utils.KMResult import io.github.sds100.keymapper.common.utils.Success +import javax.inject.Inject +import javax.inject.Singleton import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.channels.Channel @@ -31,8 +33,6 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withTimeout import timber.log.Timber -import javax.inject.Inject -import javax.inject.Singleton @Singleton class AndroidPhoneAdapter @Inject constructor( @@ -111,7 +111,7 @@ class AndroidPhoneAdapter @Inject constructor( ctx, broadcastReceiver, this, - ContextCompat.RECEIVER_EXPORTED + ContextCompat.RECEIVER_EXPORTED, ) } } @@ -150,7 +150,7 @@ class AndroidPhoneAdapter @Inject constructor( private fun hasAnswerPhoneCallsPermission(): Boolean { return ActivityCompat.checkSelfPermission( ctx, - Manifest.permission.ANSWER_PHONE_CALLS + Manifest.permission.ANSWER_PHONE_CALLS, ) == PackageManager.PERMISSION_GRANTED } @@ -181,7 +181,7 @@ class AndroidPhoneAdapter @Inject constructor( ctx, 0, Intent(ACTION_SMS_SENT_RESULT), - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, ) try { @@ -234,7 +234,9 @@ class AndroidPhoneAdapter @Inject constructor( TelephonyManager.CALL_STATE_IDLE -> return CallState.NONE TelephonyManager.CALL_STATE_OFFHOOK -> return CallState.IN_PHONE_CALL TelephonyManager.CALL_STATE_RINGING -> return CallState.RINGING - else -> throw IllegalArgumentException("Don't know how to convert that call state $sdkCallState") + else -> throw IllegalArgumentException( + "Don't know how to convert that call state $sdkCallState", + ) } } } diff --git a/system/src/main/java/io/github/sds100/keymapper/system/phone/PhoneAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/phone/PhoneAdapter.kt index d1b6e47239..5229852025 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/phone/PhoneAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/phone/PhoneAdapter.kt @@ -3,7 +3,6 @@ package io.github.sds100.keymapper.system.phone import io.github.sds100.keymapper.common.utils.KMResult import kotlinx.coroutines.flow.Flow - interface PhoneAdapter { val callStateFlow: Flow diff --git a/system/src/main/java/io/github/sds100/keymapper/system/popup/AndroidToastAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/popup/AndroidToastAdapter.kt index b4e7e7cf0a..a9086a8810 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/popup/AndroidToastAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/popup/AndroidToastAdapter.kt @@ -7,9 +7,8 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class AndroidToastAdapter @Inject constructor( - @ApplicationContext private val ctx: Context, -) : ToastAdapter { +class AndroidToastAdapter @Inject constructor(@ApplicationContext private val ctx: Context) : + ToastAdapter { override fun show(message: String) { Toast.makeText(ctx, message, Toast.LENGTH_SHORT).show() } diff --git a/system/src/main/java/io/github/sds100/keymapper/system/popup/ToastAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/popup/ToastAdapter.kt index 4cce00be35..5db5b1fdcc 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/popup/ToastAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/popup/ToastAdapter.kt @@ -1,6 +1,5 @@ package io.github.sds100.keymapper.system.popup - interface ToastAdapter { fun show(message: String) } diff --git a/system/src/main/java/io/github/sds100/keymapper/system/power/AndroidPowerAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/power/AndroidPowerAdapter.kt index 10b6d5f619..bd9f0174ee 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/power/AndroidPowerAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/power/AndroidPowerAdapter.kt @@ -8,17 +8,16 @@ import android.os.BatteryManager import android.os.Build import androidx.core.content.ContextCompat import androidx.core.content.getSystemService -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject import javax.inject.Singleton +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow @Singleton -class AndroidPowerAdapter @Inject constructor( - @ApplicationContext private val context: Context -) : PowerAdapter { +class AndroidPowerAdapter @Inject constructor(@ApplicationContext private val context: Context) : + PowerAdapter { private val ctx: Context = context.applicationContext private val batteryManager: BatteryManager by lazy { ctx.getSystemService()!! } diff --git a/system/src/main/java/io/github/sds100/keymapper/system/power/PowerAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/power/PowerAdapter.kt index ddaf4bb5f5..5d3dbef9a5 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/power/PowerAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/power/PowerAdapter.kt @@ -2,7 +2,6 @@ package io.github.sds100.keymapper.system.power import kotlinx.coroutines.flow.StateFlow - interface PowerAdapter { val isCharging: StateFlow } diff --git a/system/src/main/java/io/github/sds100/keymapper/system/ringtones/RingtoneAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/ringtones/RingtoneAdapter.kt index 136fae35bc..798db85cce 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/ringtones/RingtoneAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/ringtones/RingtoneAdapter.kt @@ -13,7 +13,8 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class AndroidRingtoneAdapter @Inject constructor(@ApplicationContext private val ctx: Context) : RingtoneAdapter { +class AndroidRingtoneAdapter @Inject constructor(@ApplicationContext private val ctx: Context) : + RingtoneAdapter { private val ringtoneManager: RingtoneManager by lazy { RingtoneManager(ctx).apply { diff --git a/system/src/main/java/io/github/sds100/keymapper/system/root/SuAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/root/SuAdapter.kt index 97f911ba8c..d0ce05af92 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/root/SuAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/root/SuAdapter.kt @@ -5,19 +5,36 @@ import io.github.sds100.keymapper.system.shell.BaseShellAdapter import io.github.sds100.keymapper.system.shell.ShellAdapter import javax.inject.Inject import javax.inject.Singleton +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import timber.log.Timber @Singleton -class SuAdapterImpl @Inject constructor() : +class SuAdapterImpl @Inject constructor(private val coroutineScope: CoroutineScope) : BaseShellAdapter(), SuAdapter { - override val isRootGranted: MutableStateFlow = MutableStateFlow(getIsRooted()) + override val isRootGranted: MutableStateFlow = MutableStateFlow(false) + + private var invalidateJob: Job? = null + + init { + invalidateJob?.cancel() + invalidateJob = coroutineScope.launch { + invalidateIsRooted() + } + } override fun requestPermission() { - invalidateIsRooted() + invalidateJob?.cancel() + invalidateJob = coroutineScope.launch { + invalidateIsRooted() + } } override fun prepareCommand(command: String): Array { @@ -25,7 +42,7 @@ class SuAdapterImpl @Inject constructor() : return arrayOf("su", "-c", command) } - fun invalidateIsRooted() { + private suspend fun invalidateIsRooted() { try { // Close the shell so a new one is started without root permission. val isRooted = getIsRooted() @@ -41,10 +58,12 @@ class SuAdapterImpl @Inject constructor() : } } - private fun getIsRooted(): Boolean { - Shell.getShell().waitAndClose() - val isRooted = Shell.isAppGrantedRoot() ?: false - return isRooted + // Must execute on a separate thread so it doesn't block the Main thread. + private suspend fun getIsRooted(): Boolean { + return withContext(Dispatchers.IO) { + Shell.getShell().waitAndClose() + Shell.isAppGrantedRoot() ?: false + } } } diff --git a/system/src/main/java/io/github/sds100/keymapper/system/shell/BaseShellAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/shell/BaseShellAdapter.kt index 289399166f..4d9652f0e7 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/shell/BaseShellAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/shell/BaseShellAdapter.kt @@ -5,6 +5,8 @@ import io.github.sds100.keymapper.common.utils.KMError import io.github.sds100.keymapper.common.utils.KMResult import io.github.sds100.keymapper.common.utils.Success import io.github.sds100.keymapper.common.utils.success +import java.io.IOException +import java.io.InterruptedIOException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.coroutineScope @@ -13,58 +15,54 @@ import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.launch import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.withTimeoutOrNull -import java.io.IOException -import java.io.InterruptedIOException -abstract class BaseShellAdapter() : ShellAdapter { +abstract class BaseShellAdapter : ShellAdapter { abstract fun prepareCommand(command: String): Array - override suspend fun execute( - command: String, - timeoutMillis: Long, - ): KMResult = coroutineScope { - try { - val process = ProcessBuilder() - .command(*prepareCommand(command)) - // Redirect stderr to stdout - .redirectErrorStream(true) - .start() - - val stdoutReader = process.inputStream.bufferedReader() - var stdout = "" - + override suspend fun execute(command: String, timeoutMillis: Long): KMResult = + coroutineScope { try { - val readStdoutJob = launch(Dispatchers.IO) { - stdout = stdoutReader.readText() - } + val process = ProcessBuilder() + .command(*prepareCommand(command)) + // Redirect stderr to stdout + .redirectErrorStream(true) + .start() + + val stdoutReader = process.inputStream.bufferedReader() + var stdout = "" + + try { + val readStdoutJob = launch(Dispatchers.IO) { + stdout = stdoutReader.readText() + } - val exitCode = withTimeoutOrNull(timeoutMillis) { - // This is required so that the blocking process code is interrupted when - // the coroutine is cancelled by the timeout. - runInterruptible(Dispatchers.IO) { - process.waitFor() + val exitCode = withTimeoutOrNull(timeoutMillis) { + // This is required so that the blocking process code is interrupted when + // the coroutine is cancelled by the timeout. + runInterruptible(Dispatchers.IO) { + process.waitFor() + } } - } - readStdoutJob.cancel() + readStdoutJob.cancel() - if (exitCode == null) { - KMError.ShellCommandTimeout(timeoutMillis, stdout) - } else { - Success(ShellResult(stdout, exitCode)) + if (exitCode == null) { + KMError.ShellCommandTimeout(timeoutMillis, stdout) + } else { + Success(ShellResult(stdout, exitCode)) + } + } finally { + stdoutReader.close() + process.destroy() } - } finally { - stdoutReader.close() - process.destroy() + } catch (e: IOException) { + KMError.Exception(e) } - } catch (e: IOException) { - KMError.Exception(e) } - } override suspend fun executeWithStreamingOutput( command: String, - timeoutMillis: Long + timeoutMillis: Long, ): Flow> = callbackFlow { try { val process = ProcessBuilder() @@ -109,12 +107,10 @@ abstract class BaseShellAdapter() : ShellAdapter { } readStdoutJob.cancel() - } finally { process.destroy() stdoutReader.close() } - } catch (e: IOException) { trySend(KMError.Exception(e)) } finally { @@ -122,4 +118,4 @@ abstract class BaseShellAdapter() : ShellAdapter { awaitClose {} } } -} \ No newline at end of file +} diff --git a/system/src/main/java/io/github/sds100/keymapper/system/shell/ShellAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/shell/ShellAdapter.kt index a2090d3c7c..36515b0631 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/shell/ShellAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/shell/ShellAdapter.kt @@ -5,13 +5,10 @@ import io.github.sds100.keymapper.common.utils.KMResult import kotlinx.coroutines.flow.Flow interface ShellAdapter { - suspend fun execute( - command: String, - timeoutMillis: Long = 10000L - ): KMResult + suspend fun execute(command: String, timeoutMillis: Long = 10000L): KMResult suspend fun executeWithStreamingOutput( command: String, - timeoutMillis: Long + timeoutMillis: Long, ): Flow> } diff --git a/system/src/main/java/io/github/sds100/keymapper/system/shell/StandardShellAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/shell/StandardShellAdapter.kt index 5d11a0bb7b..209f4b6c44 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/shell/StandardShellAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/shell/StandardShellAdapter.kt @@ -9,4 +9,4 @@ class StandardShellAdapter @Inject constructor() : BaseShellAdapter() { // Execute through sh -c to properly handle multi-line commands and shell syntax return arrayOf("sh", "-c", command) } -} \ No newline at end of file +} diff --git a/system/src/main/java/io/github/sds100/keymapper/system/shizuku/ShizukuAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/shizuku/ShizukuAdapter.kt index d3c1aabf02..7a54b549e6 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/shizuku/ShizukuAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/shizuku/ShizukuAdapter.kt @@ -2,7 +2,6 @@ package io.github.sds100.keymapper.system.shizuku import kotlinx.coroutines.flow.StateFlow - interface ShizukuAdapter { val isInstalled: StateFlow val isStarted: StateFlow diff --git a/system/src/main/java/io/github/sds100/keymapper/system/shizuku/ShizukuAdapterImpl.kt b/system/src/main/java/io/github/sds100/keymapper/system/shizuku/ShizukuAdapterImpl.kt index b9db5d2541..c8149dca74 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/shizuku/ShizukuAdapterImpl.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/shizuku/ShizukuAdapterImpl.kt @@ -3,6 +3,8 @@ package io.github.sds100.keymapper.system.shizuku import io.github.sds100.keymapper.system.apps.PackageManagerAdapter import io.github.sds100.keymapper.system.apps.isAppInstalledFlow import io.github.sds100.keymapper.system.permissions.AndroidPermissionAdapter +import javax.inject.Inject +import javax.inject.Singleton import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -10,8 +12,6 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn import rikka.shizuku.Shizuku -import javax.inject.Inject -import javax.inject.Singleton @Singleton class ShizukuAdapterImpl @Inject constructor( diff --git a/system/src/main/java/io/github/sds100/keymapper/system/shizuku/ShizukuUtils.kt b/system/src/main/java/io/github/sds100/keymapper/system/shizuku/ShizukuUtils.kt index 856129b345..7074fee4ab 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/shizuku/ShizukuUtils.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/shizuku/ShizukuUtils.kt @@ -2,7 +2,6 @@ package io.github.sds100.keymapper.system.shizuku import android.os.Build - object ShizukuUtils { const val SHIZUKU_PACKAGE = "moe.shizuku.privileged.api" diff --git a/system/src/main/java/io/github/sds100/keymapper/system/url/AndroidOpenUrlAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/url/AndroidOpenUrlAdapter.kt index 6c29a4cb36..fd3de33411 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/url/AndroidOpenUrlAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/url/AndroidOpenUrlAdapter.kt @@ -2,14 +2,13 @@ package io.github.sds100.keymapper.system.url import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.sds100.keymapper.common.utils.KMResult import javax.inject.Inject import javax.inject.Singleton -import io.github.sds100.keymapper.common.utils.KMResult @Singleton -class AndroidOpenUrlAdapter @Inject constructor( - @ApplicationContext private val context: Context -) : OpenUrlAdapter { +class AndroidOpenUrlAdapter @Inject constructor(@ApplicationContext private val context: Context) : + OpenUrlAdapter { private val ctx = context.applicationContext diff --git a/system/src/main/java/io/github/sds100/keymapper/system/url/OpenUrlAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/url/OpenUrlAdapter.kt index 3fa4ec3c2e..f2977fae0f 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/url/OpenUrlAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/url/OpenUrlAdapter.kt @@ -2,7 +2,6 @@ package io.github.sds100.keymapper.system.url import io.github.sds100.keymapper.common.utils.KMResult - interface OpenUrlAdapter { fun openUrl(url: String): KMResult<*> } diff --git a/system/src/main/java/io/github/sds100/keymapper/system/url/UrlUtils.kt b/system/src/main/java/io/github/sds100/keymapper/system/url/UrlUtils.kt index 68a5e58afe..6496c257c2 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/url/UrlUtils.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/url/UrlUtils.kt @@ -8,8 +8,6 @@ import io.github.sds100.keymapper.common.utils.KMError import io.github.sds100.keymapper.common.utils.KMResult import io.github.sds100.keymapper.common.utils.success - - object UrlUtils { fun openUrl(ctx: Context, url: String): KMResult<*> { Intent(Intent.ACTION_VIEW, url.toUri()).apply { diff --git a/system/src/main/java/io/github/sds100/keymapper/system/vibrator/AndroidVibratorAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/vibrator/AndroidVibratorAdapter.kt index 51159df53f..30e14f32a6 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/vibrator/AndroidVibratorAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/vibrator/AndroidVibratorAdapter.kt @@ -11,9 +11,8 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class AndroidVibratorAdapter @Inject constructor( - @ApplicationContext private val context: Context -) : VibratorAdapter { +class AndroidVibratorAdapter @Inject constructor(@ApplicationContext private val context: Context) : + VibratorAdapter { private val vibrator: Vibrator? = context.getSystemService() override fun vibrate(duration: Long) { diff --git a/system/src/main/java/io/github/sds100/keymapper/system/vibrator/VibratorAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/vibrator/VibratorAdapter.kt index 57f55f1559..ac717776b1 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/vibrator/VibratorAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/vibrator/VibratorAdapter.kt @@ -1,6 +1,5 @@ package io.github.sds100.keymapper.system.vibrator - interface VibratorAdapter { fun vibrate(duration: Long) } diff --git a/system/src/main/java/io/github/sds100/keymapper/system/volume/AndroidVolumeAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/volume/AndroidVolumeAdapter.kt index 34376eab8a..ff871199ca 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/volume/AndroidVolumeAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/volume/AndroidVolumeAdapter.kt @@ -17,14 +17,14 @@ import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionManage import io.github.sds100.keymapper.sysbridge.manager.isConnected import io.github.sds100.keymapper.system.SystemError import io.github.sds100.keymapper.system.permissions.Permission -import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton +import timber.log.Timber @Singleton class AndroidVolumeAdapter @Inject constructor( @ApplicationContext private val context: Context, - private val systemBridgeConnectionManager: SystemBridgeConnectionManager + private val systemBridgeConnectionManager: SystemBridgeConnectionManager, ) : VolumeAdapter { private val ctx = context.applicationContext @@ -39,7 +39,7 @@ class AndroidVolumeAdapter @Inject constructor( val ringerModeSdk = if (systemBridgeConnectionManager.isConnected()) { SettingsUtils.getGlobalSetting( ctx, - Settings.Global.MODE_RINGER + Settings.Global.MODE_RINGER, ) ?: 0 } else { audioManager.ringerMode @@ -49,7 +49,9 @@ class AndroidVolumeAdapter @Inject constructor( AudioManager.RINGER_MODE_NORMAL -> RingerMode.NORMAL AudioManager.RINGER_MODE_VIBRATE -> RingerMode.VIBRATE AudioManager.RINGER_MODE_SILENT -> RingerMode.SILENT - else -> throw Exception("Don't know how to convert this ringer moder ${audioManager.ringerMode}") + else -> throw Exception( + "Don't know how to convert this ringer moder ${audioManager.ringerMode}", + ) } } @@ -99,7 +101,9 @@ class AndroidVolumeAdapter @Inject constructor( systemBridgeConnectionManager.run { systemBridge -> systemBridge.setRingerMode(sdkMode) }.otherwise { - Timber.e("Failed to set ringer mode with System Bridge, falling back to AudioManager") + Timber.e( + "Failed to set ringer mode with System Bridge, falling back to AudioManager", + ) audioManager.ringerMode = sdkMode Success(Unit) @@ -116,13 +120,11 @@ class AndroidVolumeAdapter @Inject constructor( override fun enableDndMode(dndMode: DndMode): KMResult<*> { notificationManager.setInterruptionFilter(dndMode.convert()) return Success(Unit) - } override fun disableDndMode(): KMResult<*> { notificationManager.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL) return Success(Unit) - } override fun isDndEnabled(): Boolean = diff --git a/system/src/main/java/io/github/sds100/keymapper/system/volume/RingerMode.kt b/system/src/main/java/io/github/sds100/keymapper/system/volume/RingerMode.kt index 5460db38ec..2548902c5e 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/volume/RingerMode.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/volume/RingerMode.kt @@ -1,6 +1,5 @@ package io.github.sds100.keymapper.system.volume - enum class RingerMode { NORMAL, VIBRATE, diff --git a/system/src/main/java/io/github/sds100/keymapper/system/volume/VolumeAdapter.kt b/system/src/main/java/io/github/sds100/keymapper/system/volume/VolumeAdapter.kt index d6bf4f30ac..7bae48cc79 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/volume/VolumeAdapter.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/volume/VolumeAdapter.kt @@ -2,7 +2,6 @@ package io.github.sds100.keymapper.system.volume import io.github.sds100.keymapper.common.utils.KMResult - interface VolumeAdapter { val ringerMode: RingerMode diff --git a/system/src/main/java/io/github/sds100/keymapper/system/volume/VolumeStream.kt b/system/src/main/java/io/github/sds100/keymapper/system/volume/VolumeStream.kt index fc243f7130..e7081a4fd3 100644 --- a/system/src/main/java/io/github/sds100/keymapper/system/volume/VolumeStream.kt +++ b/system/src/main/java/io/github/sds100/keymapper/system/volume/VolumeStream.kt @@ -1,6 +1,5 @@ package io.github.sds100.keymapper.system.volume - enum class VolumeStream { ALARM, DTMF, diff --git a/systemstubs/build.gradle.kts b/systemstubs/build.gradle.kts index 9c067452a1..07a1908d86 100644 --- a/systemstubs/build.gradle.kts +++ b/systemstubs/build.gradle.kts @@ -1,6 +1,7 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.android) + alias(libs.plugins.jlleitschuh.gradle.ktlint) } android { diff --git a/systemstubs/src/main/java/android/app/ActivityTaskManagerApis.kt b/systemstubs/src/main/java/android/app/ActivityTaskManagerApis.kt index a980d7dcc4..ac3a80e5f6 100644 --- a/systemstubs/src/main/java/android/app/ActivityTaskManagerApis.kt +++ b/systemstubs/src/main/java/android/app/ActivityTaskManagerApis.kt @@ -9,14 +9,14 @@ object ActivityTaskManagerApis { maxNum: Int, filterOnlyVisibleRecents: Boolean, keepIntentExtra: Boolean, - displayId: Int + displayId: Int, ): MutableList? { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { return activityTaskManager.getTasks( maxNum, filterOnlyVisibleRecents, keepIntentExtra, - displayId + displayId, ) } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { return activityTaskManager.getTasks(maxNum, filterOnlyVisibleRecents, keepIntentExtra) @@ -24,4 +24,4 @@ object ActivityTaskManagerApis { return activityTaskManager.getTasks(maxNum) } } -} \ No newline at end of file +} diff --git a/systemstubs/src/main/java/android/nfc/NfcAdapterApis.kt b/systemstubs/src/main/java/android/nfc/NfcAdapterApis.kt index 3f7dce07c9..d3f52096ea 100644 --- a/systemstubs/src/main/java/android/nfc/NfcAdapterApis.kt +++ b/systemstubs/src/main/java/android/nfc/NfcAdapterApis.kt @@ -18,4 +18,4 @@ object NfcAdapterApis { adapter.disable(saveState) } } -} \ No newline at end of file +} diff --git a/systemstubs/src/main/java/android/permission/PermissionManagerApis.kt b/systemstubs/src/main/java/android/permission/PermissionManagerApis.kt index c5983b5a34..8ef810c186 100644 --- a/systemstubs/src/main/java/android/permission/PermissionManagerApis.kt +++ b/systemstubs/src/main/java/android/permission/PermissionManagerApis.kt @@ -8,7 +8,7 @@ object PermissionManagerApis { packageName: String, permission: String, deviceId: Int, - userId: Int + userId: Int, ) { // In revisions of Android 14 the method to grant permissions changed // so try them all. @@ -51,4 +51,4 @@ object PermissionManagerApis { ) } } -} \ No newline at end of file +}