Remove custom permission, fix client lifecycle and encrypted prefs
Remove USE_LCK_CONTROL permission from service to fix binding failures caused by Android revoking custom permissions on app reinstall. Add auto-cleanup of dead clients via RemoteCallbackList.onCallbackDied and handle corrupted EncryptedSharedPreferences keyset gracefully. Lower SDK minSdk to 32.
This commit is contained in:
@@ -7,12 +7,6 @@
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<permission
|
||||
android:name="com.omixlab.lckcontrol.permission.USE_LCK_CONTROL"
|
||||
android:label="Access LCK Control Service"
|
||||
android:description="@string/permission_use_lck_control_desc"
|
||||
android:protectionLevel="normal" />
|
||||
|
||||
<application
|
||||
android:name=".LckControlApp"
|
||||
android:allowBackup="true"
|
||||
@@ -71,7 +65,6 @@
|
||||
<service
|
||||
android:name=".service.LckControlService"
|
||||
android:exported="true"
|
||||
android:permission="com.omixlab.lckcontrol.permission.USE_LCK_CONTROL"
|
||||
android:foregroundServiceType="connectedDevice">
|
||||
<intent-filter>
|
||||
<action android:name="com.omixlab.lckcontrol.BIND" />
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.omixlab.lckcontrol.data.local
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import androidx.security.crypto.EncryptedSharedPreferences
|
||||
import androidx.security.crypto.MasterKeys
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
@@ -13,13 +15,22 @@ class TokenStore @Inject constructor(
|
||||
) {
|
||||
private val masterKey = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
|
||||
|
||||
private val prefs = EncryptedSharedPreferences.create(
|
||||
"lck_control_tokens",
|
||||
masterKey,
|
||||
context,
|
||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
|
||||
)
|
||||
private val prefs: SharedPreferences = try {
|
||||
createEncryptedPrefs(context)
|
||||
} catch (e: Exception) {
|
||||
Log.w("TokenStore", "Corrupted keyset, clearing and recreating", e)
|
||||
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit().clear().commit()
|
||||
createEncryptedPrefs(context)
|
||||
}
|
||||
|
||||
private fun createEncryptedPrefs(context: Context): SharedPreferences =
|
||||
EncryptedSharedPreferences.create(
|
||||
PREFS_NAME,
|
||||
masterKey,
|
||||
context,
|
||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
|
||||
)
|
||||
|
||||
fun getJwt(): String? =
|
||||
prefs.getString(KEY_JWT, null)
|
||||
@@ -44,6 +55,7 @@ class TokenStore @Inject constructor(
|
||||
fun isLoggedIn(): Boolean = getJwt() != null
|
||||
|
||||
companion object {
|
||||
private const val PREFS_NAME = "lck_control_tokens"
|
||||
private const val KEY_JWT = "session_jwt"
|
||||
private const val KEY_REFRESH_TOKEN = "session_refresh_token"
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ data class ConnectedClient(
|
||||
val clientId: String,
|
||||
val clientName: String,
|
||||
val packageName: String,
|
||||
val callingUid: Int = 0,
|
||||
val activePlanId: String? = null,
|
||||
val connectedAt: Long = System.currentTimeMillis(),
|
||||
)
|
||||
@@ -15,18 +16,33 @@ class ClientTracker {
|
||||
|
||||
private val clients = ConcurrentHashMap<String, ConnectedClient>()
|
||||
|
||||
fun register(clientName: String, packageName: String): String {
|
||||
fun register(clientName: String, packageName: String, callingUid: Int): String {
|
||||
// Remove existing client with same packageName to prevent duplicates on reconnect
|
||||
clients.entries.removeIf { it.value.packageName == packageName }
|
||||
|
||||
val clientId = UUID.randomUUID().toString()
|
||||
clients[clientId] = ConnectedClient(
|
||||
clientId = clientId,
|
||||
clientName = clientName,
|
||||
packageName = packageName,
|
||||
callingUid = callingUid,
|
||||
)
|
||||
return clientId
|
||||
}
|
||||
|
||||
fun unregister(clientId: String): ConnectedClient? = clients.remove(clientId)
|
||||
|
||||
fun unregisterByUid(uid: Int): List<ConnectedClient> {
|
||||
val removed = mutableListOf<ConnectedClient>()
|
||||
clients.entries.removeIf { entry ->
|
||||
if (entry.value.callingUid == uid) {
|
||||
removed.add(entry.value)
|
||||
true
|
||||
} else false
|
||||
}
|
||||
return removed
|
||||
}
|
||||
|
||||
fun setActivePlan(clientId: String, planId: String?) {
|
||||
clients.computeIfPresent(clientId) { _, client ->
|
||||
client.copy(activePlanId = planId)
|
||||
|
||||
@@ -57,7 +57,18 @@ class LckControlService : Service() {
|
||||
|
||||
private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
private val clientTracker = ClientTracker()
|
||||
private val callbacks = RemoteCallbackList<ILckControlCallback>()
|
||||
private val callbacks = object : RemoteCallbackList<ILckControlCallback>() {
|
||||
override fun onCallbackDied(callback: ILckControlCallback, cookie: Any?) {
|
||||
val uid = cookie as? Int ?: return
|
||||
serviceScope.launch {
|
||||
val removed = clientTracker.unregisterByUid(uid)
|
||||
for (client in removed) {
|
||||
Log.d(TAG, "Auto-unregistered client ${client.clientId} (${client.packageName}) - process died")
|
||||
broadcastClientUnregistered(client.clientId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val binder = object : ILckControlService.Stub() {
|
||||
|
||||
@@ -146,7 +157,8 @@ class LckControlService : Service() {
|
||||
// ── Clients ─────────────────────────────────────────
|
||||
|
||||
override fun registerClient(clientName: String, packageName: String): String {
|
||||
val clientId = clientTracker.register(clientName, packageName)
|
||||
val uid = android.os.Binder.getCallingUid()
|
||||
val clientId = clientTracker.register(clientName, packageName, uid)
|
||||
broadcastClientRegistered(clientId)
|
||||
return clientId
|
||||
}
|
||||
@@ -174,7 +186,8 @@ class LckControlService : Service() {
|
||||
// ── Callbacks ───────────────────────────────────────
|
||||
|
||||
override fun registerCallback(callback: ILckControlCallback) {
|
||||
callbacks.register(callback)
|
||||
val uid = android.os.Binder.getCallingUid()
|
||||
callbacks.register(callback, uid)
|
||||
}
|
||||
|
||||
override fun unregisterCallback(callback: ILckControlCallback) {
|
||||
|
||||
@@ -7,7 +7,7 @@ android {
|
||||
compileSdk = 36
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 33
|
||||
minSdk = 32
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
|
||||
Reference in New Issue
Block a user