Add foreground keep alive option
This commit is contained in:
parent
54cf7eed35
commit
c191397dc0
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue