Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 695c54f03c | |||
| 3dc0448942 |
@@ -11,8 +11,8 @@ android {
|
||||
applicationId = "me.khodak.claudeusage"
|
||||
minSdk = 26
|
||||
targetSdk = 34
|
||||
versionCode = 7
|
||||
versionName = "1.6"
|
||||
versionCode = 9
|
||||
versionName = "1.8"
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
|
||||
@@ -43,6 +43,14 @@
|
||||
android:name=".AlarmReceiver"
|
||||
android:exported="false" />
|
||||
|
||||
<receiver
|
||||
android:name=".BootReceiver"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name="androidx.work.impl.background.systemalarm.RescheduleReceiver"
|
||||
android:exported="false"
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package me.khodak.claudeusage
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import me.khodak.claudeusage.data.PreferencesManager
|
||||
|
||||
class BootReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action != Intent.ACTION_BOOT_COMPLETED) return
|
||||
if (!PreferencesManager(context).isLoggedIn()) return
|
||||
UsageUpdateWorker.schedulePeriodicRefresh(context)
|
||||
UsageUpdateWorker.triggerImmediateRefresh(context)
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ class ClaudeUsageWidget : AppWidgetProvider() {
|
||||
ids.forEach { updateWidget(context, manager, it) }
|
||||
if (PreferencesManager(context).isLoggedIn()) {
|
||||
UsageUpdateWorker.schedulePeriodicRefresh(context)
|
||||
UsageUpdateWorker.triggerImmediateRefresh(context)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,10 @@ class MainActivity : AppCompatActivity() {
|
||||
val cached = prefs.getUsageData()
|
||||
updateUI(cached)
|
||||
if (prefs.isLoggedIn()) {
|
||||
refreshUsage()
|
||||
val staleMs = 5 * 60 * 1000L
|
||||
if ((cached?.lastUpdated ?: 0L) < System.currentTimeMillis() - staleMs) {
|
||||
refreshUsage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +82,7 @@ class MainActivity : AppCompatActivity() {
|
||||
val data = try {
|
||||
repo.fetchUsage()
|
||||
} catch (e: Exception) {
|
||||
if (e is kotlinx.coroutines.CancellationException) throw e
|
||||
prefs.getUsageData()?.copy(errorMessage = "Network error")
|
||||
?: UsageData(errorMessage = "Network error")
|
||||
}
|
||||
|
||||
@@ -10,29 +10,22 @@ class PreferencesManager(context: Context) {
|
||||
|
||||
private val gson = Gson()
|
||||
|
||||
private val securePrefs = try {
|
||||
val masterKey = MasterKey.Builder(context)
|
||||
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
||||
.build()
|
||||
EncryptedSharedPreferences.create(
|
||||
context, "claude_secure", masterKey,
|
||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
context.getSharedPreferences("claude_secure_fallback", Context.MODE_PRIVATE)
|
||||
}
|
||||
private val securePrefs = createSecurePrefs(context)
|
||||
|
||||
private val prefs = context.getSharedPreferences("claude_prefs", Context.MODE_PRIVATE)
|
||||
|
||||
fun saveCookies(cookies: String) {
|
||||
securePrefs.edit().putString(KEY_COOKIES, cookies).apply()
|
||||
try {
|
||||
securePrefs.edit().putString(KEY_COOKIES, cookies).apply()
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
|
||||
fun getCookies(): String? = securePrefs.getString(KEY_COOKIES, null)
|
||||
fun getCookies(): String? = try {
|
||||
securePrefs.getString(KEY_COOKIES, null)
|
||||
} catch (_: Exception) { null }
|
||||
|
||||
fun clearSession() {
|
||||
securePrefs.edit().clear().apply()
|
||||
try { securePrefs.edit().clear().apply() } catch (_: Exception) {}
|
||||
prefs.edit().remove(KEY_ORG_ID).remove(KEY_SESSION_START).apply()
|
||||
}
|
||||
|
||||
@@ -84,5 +77,35 @@ class PreferencesManager(context: Context) {
|
||||
private const val KEY_USAGE_DATA = "usage_data"
|
||||
private const val KEY_ACTIVE_WEEK = "active_week"
|
||||
private const val KEY_ACTIVE_MASK = "active_mask"
|
||||
|
||||
private fun createSecurePrefs(context: Context): android.content.SharedPreferences {
|
||||
// First attempt: normal open
|
||||
return try {
|
||||
val masterKey = MasterKey.Builder(context)
|
||||
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
||||
.build()
|
||||
EncryptedSharedPreferences.create(
|
||||
context, "claude_secure", masterKey,
|
||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
||||
)
|
||||
} catch (_: Exception) {
|
||||
// Keystore key was invalidated (e.g., user added/changed biometrics or screen lock).
|
||||
// Delete the stale encrypted file and recreate — user will need to re-login.
|
||||
try {
|
||||
context.deleteSharedPreferences("claude_secure")
|
||||
val masterKey = MasterKey.Builder(context)
|
||||
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
||||
.build()
|
||||
EncryptedSharedPreferences.create(
|
||||
context, "claude_secure", masterKey,
|
||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
||||
)
|
||||
} catch (_: Exception) {
|
||||
context.getSharedPreferences("claude_secure_fallback", Context.MODE_PRIVATE)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user