Add foreground keep alive option

This commit is contained in:
Nullptr 2022-11-09 13:09:50 +08:00
parent 54cf7eed35
commit c191397dc0
No known key found for this signature in database
8 changed files with 76 additions and 5 deletions

View File

@ -7,6 +7,7 @@
android:name="android.permission.QUERY_ALL_PACKAGES" android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" /> tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" /> <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application <application
android:name=".LSPApplication" android:name=".LSPApplication"

View File

@ -2,12 +2,14 @@ package org.lsposed.lspatch
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.lsposed.hiddenapibypass.HiddenApiBypass import org.lsposed.hiddenapibypass.HiddenApiBypass
import org.lsposed.lspatch.manager.AppBroadcastReceiver import org.lsposed.lspatch.manager.AppBroadcastReceiver
import org.lsposed.lspatch.manager.ModuleService
import org.lsposed.lspatch.util.LSPPackageManager import org.lsposed.lspatch.util.LSPPackageManager
import org.lsposed.lspatch.util.ShizukuApi import org.lsposed.lspatch.util.ShizukuApi
import java.io.File import java.io.File
@ -23,13 +25,14 @@ class LSPApplication : Application() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
HiddenApiBypass.addHiddenApiExemptions(""); HiddenApiBypass.addHiddenApiExemptions("")
lspApp = this lspApp = this
filesDir.mkdir() filesDir.mkdir()
tmpApkDir = cacheDir.resolve("apk").also { it.mkdir() } tmpApkDir = cacheDir.resolve("apk").also { it.mkdir() }
prefs = lspApp.getSharedPreferences("settings", Context.MODE_PRIVATE) prefs = lspApp.getSharedPreferences("settings", Context.MODE_PRIVATE)
ShizukuApi.init() ShizukuApi.init()
AppBroadcastReceiver.register(this) AppBroadcastReceiver.register(this)
startService(Intent(this, ModuleService::class.java))
globalScope.launch { LSPPackageManager.fetchAppList() } globalScope.launch { LSPPackageManager.fetchAppList() }
} }
} }

View File

@ -1,6 +1,8 @@
package org.lsposed.lspatch.config package org.lsposed.lspatch.config
import android.content.Intent
import org.lsposed.lspatch.lspApp import org.lsposed.lspatch.lspApp
import org.lsposed.lspatch.manager.ModuleService
import org.lsposed.lspatch.ui.util.delegateStateOf import org.lsposed.lspatch.ui.util.delegateStateOf
import org.lsposed.lspatch.ui.util.getValue import org.lsposed.lspatch.ui.util.getValue
import org.lsposed.lspatch.ui.util.setValue import org.lsposed.lspatch.ui.util.setValue
@ -12,6 +14,11 @@ object Configs {
private const val PREFS_KEYSTORE_ALIAS_PASSWORD = "keystore_alias_password" private const val PREFS_KEYSTORE_ALIAS_PASSWORD = "keystore_alias_password"
private const val PREFS_STORAGE_DIRECTORY = "storage_directory" private const val PREFS_STORAGE_DIRECTORY = "storage_directory"
private const val PREFS_DETAIL_PATCH_LOGS = "detail_patch_logs" private const val PREFS_DETAIL_PATCH_LOGS = "detail_patch_logs"
private const val PREFS_KEEP_ALIVE = "keep_alive"
enum class KeepAlive {
OFF, FOREGROUND
}
var keyStorePassword by delegateStateOf(lspApp.prefs.getString(PREFS_KEYSTORE_PASSWORD, "123456")!!) { var keyStorePassword by delegateStateOf(lspApp.prefs.getString(PREFS_KEYSTORE_PASSWORD, "123456")!!) {
lspApp.prefs.edit().putString(PREFS_KEYSTORE_PASSWORD, it).apply() lspApp.prefs.edit().putString(PREFS_KEYSTORE_PASSWORD, it).apply()
@ -32,4 +39,9 @@ object Configs {
var detailPatchLogs by delegateStateOf(lspApp.prefs.getBoolean(PREFS_DETAIL_PATCH_LOGS, true)) { var detailPatchLogs by delegateStateOf(lspApp.prefs.getBoolean(PREFS_DETAIL_PATCH_LOGS, true)) {
lspApp.prefs.edit().putBoolean(PREFS_DETAIL_PATCH_LOGS, it).apply() lspApp.prefs.edit().putBoolean(PREFS_DETAIL_PATCH_LOGS, it).apply()
} }
var keepAlive by delegateStateOf(KeepAlive.values()[lspApp.prefs.getInt(PREFS_KEEP_ALIVE, KeepAlive.OFF.ordinal)]) {
lspApp.prefs.edit().putInt(PREFS_KEEP_ALIVE, it.ordinal).apply()
lspApp.startService(Intent(lspApp, ModuleService::class.java))
}
} }

View File

@ -1,9 +1,15 @@
package org.lsposed.lspatch.manager package org.lsposed.lspatch.manager
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service import android.app.Service
import android.content.Intent import android.content.Intent
import android.os.IBinder import android.os.IBinder
import android.util.Log import android.util.Log
import androidx.core.app.NotificationCompat
import org.lsposed.lspatch.config.Configs
import org.lsposed.lspatch.share.Constants
class ModuleService : Service() { class ModuleService : Service() {
@ -11,6 +17,19 @@ class ModuleService : Service() {
private const val TAG = "ModuleService" private const val TAG = "ModuleService"
} }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
intent ?: return START_NOT_STICKY
if (Configs.keepAlive == Configs.KeepAlive.FOREGROUND) {
val channel = NotificationChannel(Constants.MANAGER_PACKAGE_NAME, TAG, NotificationManager.IMPORTANCE_DEFAULT)
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(channel)
startForeground(1, NotificationCompat.Builder(this, Constants.MANAGER_PACKAGE_NAME).build())
} else {
stopForeground(STOP_FOREGROUND_REMOVE)
}
return super.onStartCommand(intent, flags, startId)
}
override fun onBind(intent: Intent): IBinder? { override fun onBind(intent: Intent): IBinder? {
val packageName = intent.getStringExtra("packageName") ?: return null val packageName = intent.getStringExtra("packageName") ?: return null
// TODO: Authentication // TODO: Authentication

View File

@ -12,6 +12,8 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Ballot import androidx.compose.material.icons.outlined.Ballot
import androidx.compose.material.icons.outlined.BugReport
import androidx.compose.material.icons.outlined.HourglassEmpty
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
@ -49,6 +51,7 @@ fun SettingsScreen() {
) { ) {
KeyStore() KeyStore()
DetailPatchLogs() DetailPatchLogs()
KeepAlive()
} }
} }
} }
@ -235,6 +238,37 @@ private fun DetailPatchLogs() {
SettingsSwitch( SettingsSwitch(
modifier = Modifier.clickable { Configs.detailPatchLogs = !Configs.detailPatchLogs }, modifier = Modifier.clickable { Configs.detailPatchLogs = !Configs.detailPatchLogs },
checked = Configs.detailPatchLogs, checked = Configs.detailPatchLogs,
icon = Icons.Outlined.BugReport,
title = stringResource(R.string.settings_detail_patch_logs) title = stringResource(R.string.settings_detail_patch_logs)
) )
} }
@Composable
private fun KeepAlive() {
var expanded by remember { mutableStateOf(false) }
AnywhereDropdown(
expanded = expanded,
onDismissRequest = { expanded = false },
onClick = { expanded = true },
surface = {
val (title, desc) = when (Configs.keepAlive) {
Configs.KeepAlive.OFF -> R.string.settings_keep_alive to R.string.off
Configs.KeepAlive.FOREGROUND -> R.string.settings_keep_alive_foreground to R.string.settings_keep_alive_foreground_desc
}
SettingsItem(
icon = Icons.Outlined.HourglassEmpty,
title = stringResource(title),
desc = stringResource(desc)
)
}
) {
DropdownMenuItem(
text = { Text(stringResource(R.string.off)) },
onClick = { Configs.keepAlive = Configs.KeepAlive.OFF }
)
DropdownMenuItem(
text = { Text(stringResource(R.string.settings_keep_alive_foreground)) },
onClick = { Configs.keepAlive = Configs.KeepAlive.FOREGROUND }
)
}
}

View File

@ -83,8 +83,7 @@ object LSPPackageManager {
var message: String? = null var message: String? = null
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
runCatching { runCatching {
val params = PackageInstaller.SessionParams::class.java.getConstructor(Int::class.javaPrimitiveType) val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
.newInstance(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
var flags = HiddenApiBridge.PackageInstaller_SessionParams_installFlags(params) var flags = HiddenApiBridge.PackageInstaller_SessionParams_installFlags(params)
flags = flags or 0x00000004 /* PackageManager.INSTALL_ALLOW_TEST */ or 0x00000002 /* PackageManager.INSTALL_REPLACE_EXISTING */ flags = flags or 0x00000004 /* PackageManager.INSTALL_ALLOW_TEST */ or 0x00000002 /* PackageManager.INSTALL_REPLACE_EXISTING */
HiddenApiBridge.PackageInstaller_SessionParams_installFlags(params, flags) HiddenApiBridge.PackageInstaller_SessionParams_installFlags(params, flags)

View File

@ -11,6 +11,7 @@
<string name="shizuku_unavailable">Shizuku service not connected</string> <string name="shizuku_unavailable">Shizuku service not connected</string>
<string name="screen_repo">Repo</string> <string name="screen_repo">Repo</string>
<string name="screen_logs">Logs</string> <string name="screen_logs">Logs</string>
<string name="off">Off</string>
<!-- Home Screen --> <!-- Home Screen -->
<string name="home_shizuku_warning">Some functions unavailable</string> <string name="home_shizuku_warning">Some functions unavailable</string>
@ -87,4 +88,7 @@
<string name="settings_keystore_wrong_alias">Wrong alias name</string> <string name="settings_keystore_wrong_alias">Wrong alias name</string>
<string name="settings_keystore_wrong_alias_password">Wrong alias password</string> <string name="settings_keystore_wrong_alias_password">Wrong alias password</string>
<string name="settings_detail_patch_logs">Detail patch logs</string> <string name="settings_detail_patch_logs">Detail patch logs</string>
<string name="settings_keep_alive">Keep alive</string>
<string name="settings_keep_alive_foreground">Foreground</string>
<string name="settings_keep_alive_foreground_desc">Create a notification to keep manager alive</string>
</resources> </resources>

View File

@ -60,7 +60,6 @@ public class RemoteApplicationService implements ILSPApplicationService {
} }
}; };
Log.i(TAG, "Request manager binder"); Log.i(TAG, "Request manager binder");
context.startService(intent);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
context.bindService(intent, Context.BIND_AUTO_CREATE, Executors.newSingleThreadExecutor(), conn); context.bindService(intent, Context.BIND_AUTO_CREATE, Executors.newSingleThreadExecutor(), conn);
} else { } else {
@ -74,7 +73,7 @@ public class RemoteApplicationService implements ILSPApplicationService {
var userHandle = (UserHandle) getUserMethod.invoke(context); var userHandle = (UserHandle) getUserMethod.invoke(context);
bindServiceAsUserMethod.invoke(context, intent, conn, Context.BIND_AUTO_CREATE, handler, userHandle); bindServiceAsUserMethod.invoke(context, intent, conn, Context.BIND_AUTO_CREATE, handler, userHandle);
} }
boolean success = latch.await(5, TimeUnit.SECONDS); boolean success = latch.await(1, TimeUnit.SECONDS);
if (!success) throw new TimeoutException("Bind service timeout"); if (!success) throw new TimeoutException("Bind service timeout");
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InterruptedException | TimeoutException e) { } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InterruptedException | TimeoutException e) {
Toast.makeText(context, "Manager died", Toast.LENGTH_SHORT).show(); Toast.makeText(context, "Manager died", Toast.LENGTH_SHORT).show();