From 31b343b7d85b081ae5068f0a1e4d22cf61f96a79 Mon Sep 17 00:00:00 2001 From: lucky Date: Sun, 3 Jul 2022 05:05:34 +0300 Subject: [PATCH] direct boot aware --- README.md | 1 + app/build.gradle | 6 +- app/src/main/AndroidManifest.xml | 8 +++ .../me/lucky/wasted/DeviceAdminManager.kt | 2 +- .../java/me/lucky/wasted/ForegroundService.kt | 2 +- .../main/java/me/lucky/wasted/MainActivity.kt | 18 ++++-- .../wasted/NotificationListenerService.kt | 2 +- .../me/lucky/wasted/PanicResponderActivity.kt | 2 +- .../main/java/me/lucky/wasted/Preferences.kt | 55 +++++++++++++++---- .../java/me/lucky/wasted/RestartReceiver.kt | 5 +- .../main/java/me/lucky/wasted/TileService.kt | 11 ++-- .../java/me/lucky/wasted/TriggerReceiver.kt | 5 +- .../java/me/lucky/wasted/WipeJobManager.kt | 2 +- .../java/me/lucky/wasted/WipeJobService.kt | 2 +- .../res/drawable/ic_baseline_settings_24.xml | 2 +- app/src/main/res/values-it/strings.xml | 5 +- .../metadata/android/en-US/changelogs/28.txt | 2 + .../android/en-US/full_description.txt | 1 + 18 files changed, 97 insertions(+), 34 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/28.txt diff --git a/README.md b/README.md index 3677543..d3c55f7 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ locks a device and optionally runs wipe. Also you can: * wipe a device when it was not unlocked for N time * wipe a device using a duress password (companion app: [Duress](https://github.com/x13a/Duress)) +* wipe a device on USB connection event (companion app: [USBKill](https://github.com/x13a/USBKill)) The app works in `Work Profile` too. Use [Shelter](https://github.com/PeterCxy/Shelter) to install risky apps and `Wasted` in it. Then you can wipe this profile data with one click without wiping diff --git a/app/build.gradle b/app/build.gradle index 2587e69..d7e69f7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "me.lucky.wasted" minSdk 23 targetSdk 32 - versionCode 27 - versionName "1.4.2" + versionCode 28 + versionName "1.4.3" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -42,11 +42,11 @@ dependencies { implementation 'androidx.core:core-ktx:1.8.0' implementation 'androidx.appcompat:appcompat:1.4.2' implementation 'com.google.android.material:material:1.6.1' - implementation 'androidx.constraintlayout:constraintlayout:2.1.4' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' implementation 'androidx.security:security-crypto:1.0.0' + implementation 'androidx.preference:preference-ktx:1.2.0' implementation 'info.guardianproject.panic:panic:1.0' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b354046..e241719 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -41,6 +41,7 @@ @@ -51,6 +52,7 @@ + @@ -122,6 +129,7 @@ diff --git a/app/src/main/java/me/lucky/wasted/DeviceAdminManager.kt b/app/src/main/java/me/lucky/wasted/DeviceAdminManager.kt index 44a7b68..cb195d5 100644 --- a/app/src/main/java/me/lucky/wasted/DeviceAdminManager.kt +++ b/app/src/main/java/me/lucky/wasted/DeviceAdminManager.kt @@ -10,7 +10,7 @@ import java.lang.Exception class DeviceAdminManager(private val ctx: Context) { private val dpm = ctx.getSystemService(DevicePolicyManager::class.java) private val deviceAdmin by lazy { ComponentName(ctx, DeviceAdminReceiver::class.java) } - private val prefs by lazy { Preferences(ctx) } + private val prefs by lazy { Preferences.new(ctx) } fun remove() = dpm?.removeActiveAdmin(deviceAdmin) fun isActive() = dpm?.isAdminActive(deviceAdmin) ?: false diff --git a/app/src/main/java/me/lucky/wasted/ForegroundService.kt b/app/src/main/java/me/lucky/wasted/ForegroundService.kt index ca8976b..ef0c85b 100644 --- a/app/src/main/java/me/lucky/wasted/ForegroundService.kt +++ b/app/src/main/java/me/lucky/wasted/ForegroundService.kt @@ -59,7 +59,7 @@ class ForegroundService : Service() { private var unlocked = false override fun onReceive(context: Context?, intent: Intent?) { - if (!Preferences(context ?: return).isWipeOnInactivity) return + if (!Preferences.new(context ?: return).isWipeOnInactivity) return when (intent?.action) { Intent.ACTION_USER_PRESENT -> { if (context.getSystemService(KeyguardManager::class.java) diff --git a/app/src/main/java/me/lucky/wasted/MainActivity.kt b/app/src/main/java/me/lucky/wasted/MainActivity.kt index df8425a..77b478c 100644 --- a/app/src/main/java/me/lucky/wasted/MainActivity.kt +++ b/app/src/main/java/me/lucky/wasted/MainActivity.kt @@ -1,9 +1,6 @@ package me.lucky.wasted -import android.content.ClipData -import android.content.ClipboardManager -import android.content.ComponentName -import android.content.Intent +import android.content.* import android.content.pm.PackageManager import android.os.Build import android.os.Bundle @@ -32,6 +29,7 @@ open class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private lateinit var prefs: Preferences + private lateinit var prefsdb: Preferences private lateinit var admin: DeviceAdminManager private val shortcut by lazy { ShortcutManager(this) } private val job by lazy { WipeJobManager(this) } @@ -40,6 +38,10 @@ open class MainActivity : AppCompatActivity() { private var clipboardManager: ClipboardManager? = null private var clipboardClearTask: Timer? = null + private val prefsListener = SharedPreferences.OnSharedPreferenceChangeListener { _, key -> + prefs.copyTo(prefsdb, key) + } + private val registerForDeviceAdmin = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { when (it.resultCode) { @@ -68,9 +70,15 @@ open class MainActivity : AppCompatActivity() { override fun onStart() { super.onStart() + prefs.registerListener(prefsListener) update() } + override fun onStop() { + super.onStop() + prefs.unregisterListener(prefsListener) + } + private fun update() { if (prefs.isEnabled && !admin.isActive()) Snackbar.make( @@ -82,6 +90,8 @@ open class MainActivity : AppCompatActivity() { private fun init() { prefs = Preferences(this) + prefsdb = Preferences(this, encrypted = false) + prefs.copyTo(prefsdb) admin = DeviceAdminManager(this) clipboardManager = getSystemService(ClipboardManager::class.java) NotificationManager(this).createNotificationChannels() diff --git a/app/src/main/java/me/lucky/wasted/NotificationListenerService.kt b/app/src/main/java/me/lucky/wasted/NotificationListenerService.kt index 6dad04d..c91f95d 100644 --- a/app/src/main/java/me/lucky/wasted/NotificationListenerService.kt +++ b/app/src/main/java/me/lucky/wasted/NotificationListenerService.kt @@ -15,7 +15,7 @@ class NotificationListenerService : NotificationListenerService() { } private fun init() { - prefs = Preferences(this) + prefs = Preferences.new(this) admin = DeviceAdminManager(this) } diff --git a/app/src/main/java/me/lucky/wasted/PanicResponderActivity.kt b/app/src/main/java/me/lucky/wasted/PanicResponderActivity.kt index 3944370..460a49b 100644 --- a/app/src/main/java/me/lucky/wasted/PanicResponderActivity.kt +++ b/app/src/main/java/me/lucky/wasted/PanicResponderActivity.kt @@ -7,7 +7,7 @@ import info.guardianproject.panic.Panic import info.guardianproject.panic.PanicResponder class PanicResponderActivity : AppCompatActivity() { - private val prefs by lazy { Preferences(this) } + private val prefs by lazy { Preferences.new(this) } private val admin by lazy { DeviceAdminManager(this) } override fun onCreate(savedInstanceState: Bundle?) { diff --git a/app/src/main/java/me/lucky/wasted/Preferences.kt b/app/src/main/java/me/lucky/wasted/Preferences.kt index 7d4e05d..3f70fab 100644 --- a/app/src/main/java/me/lucky/wasted/Preferences.kt +++ b/app/src/main/java/me/lucky/wasted/Preferences.kt @@ -1,13 +1,17 @@ package me.lucky.wasted import android.content.Context +import android.content.SharedPreferences +import android.os.Build +import android.os.UserManager import androidx.core.content.edit +import androidx.preference.PreferenceManager import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.MasterKeys -class Preferences(ctx: Context) { +class Preferences(ctx: Context, encrypted: Boolean = true) { companion object { - const val DEFAULT_WIPE_ON_INACTIVITY_COUNT = 7 * 24 * 60 + private const val DEFAULT_WIPE_ON_INACTIVITY_COUNT = 7 * 24 * 60 private const val ENABLED = "enabled" private const val AUTHENTICATION_CODE = "authentication_code" @@ -19,16 +23,28 @@ class Preferences(ctx: Context) { private const val WIPE_ON_INACTIVITY_COUNT = "wipe_on_inactivity_count" private const val FILE_NAME = "sec_shared_prefs" + + fun new(ctx: Context) = Preferences( + ctx, + encrypted = Build.VERSION.SDK_INT < Build.VERSION_CODES.N || + ctx.getSystemService(UserManager::class.java).isUserUnlocked, + ) } - private val mk = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC) - private val prefs = EncryptedSharedPreferences.create( - FILE_NAME, - mk, - ctx, - EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, - EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM, - ) + private val prefs: SharedPreferences = if (encrypted) { + val mk = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC) + EncryptedSharedPreferences.create( + FILE_NAME, + mk, + ctx, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM, + ) + } else { + val context = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + ctx.createDeviceProtectedStorageContext() else ctx + PreferenceManager.getDefaultSharedPreferences(context) + } var isEnabled: Boolean get() = prefs.getBoolean(ENABLED, false) @@ -57,6 +73,25 @@ class Preferences(ctx: Context) { var wipeOnInactivityCount: Int get() = prefs.getInt(WIPE_ON_INACTIVITY_COUNT, DEFAULT_WIPE_ON_INACTIVITY_COUNT) set(value) = prefs.edit { putInt(WIPE_ON_INACTIVITY_COUNT, value) } + + fun registerListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) = + prefs.registerOnSharedPreferenceChangeListener(listener) + + fun unregisterListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) = + prefs.unregisterOnSharedPreferenceChangeListener(listener) + + fun copyTo(dst: Preferences, key: String? = null) = dst.prefs.edit { + for (entry in prefs.all.entries) { + val k = entry.key + if (key != null && k != key) continue + val v = entry.value ?: continue + when (v) { + is Boolean -> putBoolean(k, v) + is Int -> putInt(k, v) + is String -> putString(k, v) + } + } + } } enum class Trigger(val value: Int) { diff --git a/app/src/main/java/me/lucky/wasted/RestartReceiver.kt b/app/src/main/java/me/lucky/wasted/RestartReceiver.kt index 7432a66..10c1da1 100644 --- a/app/src/main/java/me/lucky/wasted/RestartReceiver.kt +++ b/app/src/main/java/me/lucky/wasted/RestartReceiver.kt @@ -7,9 +7,10 @@ import androidx.core.content.ContextCompat class RestartReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { - if (intent?.action != Intent.ACTION_BOOT_COMPLETED && + if (intent?.action != Intent.ACTION_LOCKED_BOOT_COMPLETED && + intent?.action != Intent.ACTION_BOOT_COMPLETED && intent?.action != Intent.ACTION_MY_PACKAGE_REPLACED) return - val prefs = Preferences(context ?: return) + val prefs = Preferences.new(context ?: return) if (!prefs.isEnabled || !prefs.isWipeOnInactivity) return ContextCompat.startForegroundService( context.applicationContext, diff --git a/app/src/main/java/me/lucky/wasted/TileService.kt b/app/src/main/java/me/lucky/wasted/TileService.kt index 5bcbe68..cba326f 100644 --- a/app/src/main/java/me/lucky/wasted/TileService.kt +++ b/app/src/main/java/me/lucky/wasted/TileService.kt @@ -5,7 +5,6 @@ import android.service.quicksettings.Tile import android.service.quicksettings.TileService import androidx.annotation.RequiresApi import java.util.* -import java.util.concurrent.atomic.AtomicInteger import kotlin.concurrent.timerTask @RequiresApi(Build.VERSION_CODES.N) @@ -16,7 +15,7 @@ class TileService : TileService() { private lateinit var prefs: Preferences private lateinit var admin: DeviceAdminManager - private val counter = AtomicInteger() + private var counter = 0 private var timer: Timer? = null override fun onCreate() { @@ -25,7 +24,7 @@ class TileService : TileService() { } private fun init() { - prefs = Preferences(this) + prefs = Preferences.new(this) admin = DeviceAdminManager(this) } @@ -46,7 +45,9 @@ class TileService : TileService() { } catch (exc: SecurityException) {} return } - when (counter.getAndIncrement()) { + val v = counter + counter++ + when (v) { 0 -> { update(Tile.STATE_ACTIVE) timer?.cancel() @@ -61,7 +62,7 @@ class TileService : TileService() { else -> { timer?.cancel() update(Tile.STATE_INACTIVE) - counter.set(0) + counter = 0 } } } diff --git a/app/src/main/java/me/lucky/wasted/TriggerReceiver.kt b/app/src/main/java/me/lucky/wasted/TriggerReceiver.kt index 7e5aa1f..67e11a1 100644 --- a/app/src/main/java/me/lucky/wasted/TriggerReceiver.kt +++ b/app/src/main/java/me/lucky/wasted/TriggerReceiver.kt @@ -11,7 +11,7 @@ class TriggerReceiver : BroadcastReceiver() { fun panic(context: Context, intent: Intent?) { if (intent?.action != ACTION) return - val prefs = Preferences(context) + val prefs = Preferences.new(context) if (!prefs.isEnabled) return val code = prefs.authenticationCode assert(code.isNotEmpty()) @@ -25,7 +25,8 @@ class TriggerReceiver : BroadcastReceiver() { } override fun onReceive(context: Context?, intent: Intent?) { - if (Preferences(context ?: return).triggers.and(Trigger.BROADCAST.value) == 0) return + if (Preferences.new(context ?: return).triggers.and(Trigger.BROADCAST.value) == 0) + return panic(context, intent) } } diff --git a/app/src/main/java/me/lucky/wasted/WipeJobManager.kt b/app/src/main/java/me/lucky/wasted/WipeJobManager.kt index daa08a9..1f8d360 100644 --- a/app/src/main/java/me/lucky/wasted/WipeJobManager.kt +++ b/app/src/main/java/me/lucky/wasted/WipeJobManager.kt @@ -10,7 +10,7 @@ class WipeJobManager(private val ctx: Context) { companion object { private const val JOB_ID = 1000 } - private val prefs by lazy { Preferences(ctx) } + private val prefs by lazy { Preferences.new(ctx) } private val scheduler = ctx.getSystemService(JobScheduler::class.java) fun schedule(): Int { diff --git a/app/src/main/java/me/lucky/wasted/WipeJobService.kt b/app/src/main/java/me/lucky/wasted/WipeJobService.kt index 3e172e5..ae50800 100644 --- a/app/src/main/java/me/lucky/wasted/WipeJobService.kt +++ b/app/src/main/java/me/lucky/wasted/WipeJobService.kt @@ -5,7 +5,7 @@ import android.app.job.JobService class WipeJobService : JobService() { override fun onStartJob(params: JobParameters?): Boolean { - val prefs = Preferences(this) + val prefs = Preferences.new(this) if (!prefs.isEnabled || !prefs.isWipeOnInactivity) return false try { DeviceAdminManager(this).wipeData() diff --git a/app/src/main/res/drawable/ic_baseline_settings_24.xml b/app/src/main/res/drawable/ic_baseline_settings_24.xml index 298a5a1..059b7ea 100644 --- a/app/src/main/res/drawable/ic_baseline_settings_24.xml +++ b/app/src/main/res/drawable/ic_baseline_settings_24.xml @@ -1,4 +1,4 @@ - diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index be0e2a7..939f7ae 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -11,7 +11,10 @@ Modalità aereo Panico Cancella in caso di inattività - Cancella i dati quando il dispositivo non viene sbloccato per N giorni. + Cancella i dati quando il dispositivo non viene sbloccato per N tempo. + tempo + 7d / 48h / 120m + [d]giorni [h]ore [m]minuti Predefinito Guardia PanicKit diff --git a/fastlane/metadata/android/en-US/changelogs/28.txt b/fastlane/metadata/android/en-US/changelogs/28.txt new file mode 100644 index 0000000..1e2f995 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/28.txt @@ -0,0 +1,2 @@ +direct boot aware +update Italian translation, thanks to Giovanni Donisi (@gdonisi + @giovannidonisi) diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index 51c4fbe..0961d50 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -6,6 +6,7 @@ Device Administration API, it locks a device and optionally runs wipe. Also you can: * wipe a device when it was not unlocked for N time * wipe a device using a duress password (companion app: [Duress](https://github.com/x13a/Duress)) +* wipe a device on USB connection event (companion app: [USBKill](https://github.com/x13a/USBKill)) The app works in Work Profile too. Use Shelter to install risky apps and Wasted in it. Then you can wipe this profile data with one click without wiping the whole device.