remove all preferences migrations, you have to reconfigure Wasted !!
.CodeReceiver renamed to .TriggerReceiver !!
remove max failed password attempts, migrate to Sentry
add copy authentication code with a long click
add Chinese Simplified, German, Spanish, Turkish, Ukrainian translations
update Italian translation
under the hood improvements

Thanks to:
Alex Ortiz
Alexander Sergevich
Noah Fisker
SuperM
TolDYuThad
Wong Anny
Giovanni Donisi (@gdonisi + @giovannidonisi)
This commit is contained in:
lucky 2022-06-24 06:39:31 +03:00
parent b7463e3910
commit e46277a3a0
39 changed files with 346 additions and 243 deletions

View File

@ -0,0 +1,10 @@
name: "Validate Gradle Wrapper"
on: [push, pull_request]
jobs:
validation:
name: "Validation"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: gradle/wrapper-validation-action@v1

View File

@ -1,6 +1,6 @@
# Wasted # Wasted
Lock a device and wipe its data on danger. Lock a device and wipe its data on emergency.
[<img [<img
src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
@ -22,8 +22,8 @@ message with authentication code. On trigger, using
locks a device and optionally runs wipe. locks a device and optionally runs wipe.
Also you can: Also you can:
* limit the maximum number of failed password attempts
* wipe a device when it was not unlocked for N days * wipe a device when it was not unlocked for N days
* wipe a device using a duress password (companion app: [Duress](https://github.com/x13a/Duress))
The app works in `Work Profile` too. Use [Shelter](https://github.com/PeterCxy/Shelter) to install 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 risky apps and `Wasted` in it. Then you can wipe this profile data with one click without wiping
@ -43,7 +43,7 @@ Broadcast:
```sh ```sh
$ adb shell am broadcast \ $ adb shell am broadcast \
-a me.lucky.wasted.action.TRIGGER \ -a me.lucky.wasted.action.TRIGGER \
-n me.lucky.wasted/.CodeReceiver \ -n me.lucky.wasted/.TriggerReceiver \
-e code "b49a6576-0c27-4f03-b96b-da53501022ba" -e code "b49a6576-0c27-4f03-b96b-da53501022ba"
``` ```

View File

@ -10,8 +10,8 @@ android {
applicationId "me.lucky.wasted" applicationId "me.lucky.wasted"
minSdk 23 minSdk 23
targetSdk 32 targetSdk 32
versionCode 24 versionCode 25
versionName "1.3.4" versionName "1.4.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
@ -39,10 +39,10 @@ android {
} }
dependencies { dependencies {
implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'com.google.android.material:material:1.5.0' implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3' implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

View File

@ -42,7 +42,7 @@
</receiver> </receiver>
<receiver <receiver
android:name=".CodeReceiver" android:name=".TriggerReceiver"
android:enabled="false" android:enabled="false"
android:exported="true" android:exported="true"
tools:ignore="ExportedReceiver"> tools:ignore="ExportedReceiver">

View File

@ -13,8 +13,7 @@ class DeviceAdminManager(private val ctx: Context) {
private val prefs by lazy { Preferences(ctx) } private val prefs by lazy { Preferences(ctx) }
fun remove() = dpm?.removeActiveAdmin(deviceAdmin) fun remove() = dpm?.removeActiveAdmin(deviceAdmin)
fun isActive(): Boolean = dpm?.isAdminActive(deviceAdmin) ?: false fun isActive() = dpm?.isAdminActive(deviceAdmin) ?: false
fun getCurrentFailedPasswordAttempts(): Int = dpm?.currentFailedPasswordAttempts ?: 0
fun lockNow() { if (!lockPrivilegedNow()) dpm?.lockNow() } fun lockNow() { if (!lockPrivilegedNow()) dpm?.lockNow() }
@ -31,25 +30,20 @@ class DeviceAdminManager(private val ctx: Context) {
return ok return ok
} }
fun setMaximumFailedPasswordsForWipe(num: Int) {
dpm?.setMaximumFailedPasswordsForWipe(deviceAdmin, num)
}
fun wipeData() { fun wipeData() {
var flags = 0 var flags = 0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
flags = flags.or(DevicePolicyManager.WIPE_SILENTLY) flags = flags.or(DevicePolicyManager.WIPE_SILENTLY)
if (prefs.isWipeESIM && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) if (prefs.isWipeEmbeddedSim && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
flags = flags.or(DevicePolicyManager.WIPE_EUICC) flags = flags.or(DevicePolicyManager.WIPE_EUICC)
dpm?.wipeData(flags) dpm?.wipeData(flags)
} }
fun makeRequestIntent(): Intent { fun makeRequestIntent() =
return Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN) Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN)
.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, deviceAdmin) .putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, deviceAdmin)
.putExtra( .putExtra(
DevicePolicyManager.EXTRA_ADD_EXPLANATION, DevicePolicyManager.EXTRA_ADD_EXPLANATION,
ctx.getString(R.string.device_admin_description), ctx.getString(R.string.device_admin_description),
) )
} }
}

View File

@ -3,30 +3,12 @@ package me.lucky.wasted
import android.app.admin.DeviceAdminReceiver import android.app.admin.DeviceAdminReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.UserHandle
import android.widget.Toast import android.widget.Toast
class DeviceAdminReceiver : DeviceAdminReceiver() { class DeviceAdminReceiver : DeviceAdminReceiver() {
override fun onPasswordFailed(context: Context, intent: Intent, user: UserHandle) {
super.onPasswordFailed(context, intent, user)
val prefs = Preferences(context)
val maxFailedPasswordAttempts = prefs.maxFailedPasswordAttempts
if (!prefs.isServiceEnabled || maxFailedPasswordAttempts <= 0) return
val admin = DeviceAdminManager(context)
if (admin.getCurrentFailedPasswordAttempts() >= maxFailedPasswordAttempts)
admin.wipeData()
}
override fun onDisabled(context: Context, intent: Intent) { override fun onDisabled(context: Context, intent: Intent) {
super.onDisabled(context, intent) super.onDisabled(context, intent)
if (Preferences(context).isServiceEnabled) if (Preferences(context).isEnabled)
Toast.makeText(context, R.string.service_unavailable_popup, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.service_unavailable_popup, Toast.LENGTH_SHORT).show()
} }
override fun onEnabled(context: Context, intent: Intent) {
super.onEnabled(context, intent)
DeviceAdminManager(context)
.setMaximumFailedPasswordsForWipe(Preferences(context)
.maxFailedPasswordAttempts.shl(1))
}
} }

View File

@ -54,7 +54,8 @@ class ForegroundService : Service() {
private class UnlockReceiver : BroadcastReceiver() { private class UnlockReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context?, intent: Intent?) {
if (context?.getSystemService(KeyguardManager::class.java)?.isDeviceSecure != true || if (intent?.action != Intent.ACTION_USER_PRESENT ||
context?.getSystemService(KeyguardManager::class.java)?.isDeviceSecure != true ||
!Preferences(context).isWipeOnInactivity) return !Preferences(context).isWipeOnInactivity) return
Thread(Runner(context, goAsync())).start() Thread(Runner(context, goAsync())).start()
} }
@ -64,9 +65,9 @@ class ForegroundService : Service() {
private val pendingResult: PendingResult, private val pendingResult: PendingResult,
) : Runnable { ) : Runnable {
override fun run() { override fun run() {
val manager = WipeJobManager(ctx) val job = WipeJobManager(ctx)
var delay = 1000L var delay = 1000L
while (manager.schedule() != JobScheduler.RESULT_SUCCESS) { while (job.schedule() != JobScheduler.RESULT_SUCCESS) {
Thread.sleep(delay) Thread.sleep(delay)
delay = delay.shl(1) delay = delay.shl(1)
} }

View File

@ -1,6 +1,8 @@
package me.lucky.wasted package me.lucky.wasted
import android.app.job.JobScheduler import android.app.job.JobScheduler
import android.content.ClipData
import android.content.ClipboardManager
import android.content.ComponentName import android.content.ComponentName
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
@ -13,15 +15,22 @@ import androidx.core.content.ContextCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import java.util.* import java.util.*
import kotlin.concurrent.timerTask
import me.lucky.wasted.databinding.ActivityMainBinding import me.lucky.wasted.databinding.ActivityMainBinding
open class MainActivity : AppCompatActivity() { open class MainActivity : AppCompatActivity() {
companion object {
private const val CLIPBOARD_CLEAR_DELAY = 30_000L
}
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
private lateinit var prefs: Preferences private lateinit var prefs: Preferences
private lateinit var admin: DeviceAdminManager private lateinit var admin: DeviceAdminManager
private val shortcut by lazy { ShortcutManager(this) } private val shortcut by lazy { ShortcutManager(this) }
private val job by lazy { WipeJobManager(this) } private val job by lazy { WipeJobManager(this) }
private var clipboardManager: ClipboardManager? = null
private var clipboardClearTask: Timer? = null
private val registerForDeviceAdmin = private val registerForDeviceAdmin =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
@ -45,7 +54,7 @@ open class MainActivity : AppCompatActivity() {
} }
private fun update() { private fun update() {
if (prefs.isServiceEnabled && !admin.isActive()) if (prefs.isEnabled && !admin.isActive())
Snackbar.make( Snackbar.make(
binding.toggle, binding.toggle,
R.string.service_unavailable_popup, R.string.service_unavailable_popup,
@ -56,35 +65,32 @@ open class MainActivity : AppCompatActivity() {
private fun init() { private fun init() {
prefs = Preferences(this) prefs = Preferences(this)
admin = DeviceAdminManager(this) admin = DeviceAdminManager(this)
clipboardManager = getSystemService(ClipboardManager::class.java)
NotificationManager(this).createNotificationChannels() NotificationManager(this).createNotificationChannels()
if (prefs.code == "") prefs.code = makeCode() if (prefs.authenticationCode.isEmpty()) prefs.authenticationCode = makeAuthenticationCode()
updateCodeColorState() updateCodeColorState()
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) hideESIM() if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) hideEmbeddedSim()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
!packageManager.hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)) !packageManager.hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN))
hideSecureLockScreenRequired() hideSecureLockScreenRequired()
binding.apply { binding.apply {
code.text = prefs.code authenticationCode.text = prefs.authenticationCode
wipeData.isChecked = prefs.isWipeData wipeData.isChecked = prefs.isWipeData
wipeESIM.isChecked = prefs.isWipeESIM wipeEmbeddedSim.isChecked = prefs.isWipeEmbeddedSim
wipeESIM.isEnabled = wipeData.isChecked wipeEmbeddedSim.isEnabled = wipeData.isChecked
maxFailedPasswordAttempts.value = prefs.maxFailedPasswordAttempts.toFloat()
wipeOnInactivitySwitch.isChecked = prefs.isWipeOnInactivity wipeOnInactivitySwitch.isChecked = prefs.isWipeOnInactivity
toggle.isChecked = prefs.isServiceEnabled toggle.isChecked = prefs.isEnabled
} }
} }
private fun hideESIM() { private fun hideEmbeddedSim() {
binding.wipeESIMSpace.visibility = View.GONE binding.wipeSpace.visibility = View.GONE
binding.wipeESIM.visibility = View.GONE binding.wipeEmbeddedSim.visibility = View.GONE
} }
private fun hideSecureLockScreenRequired() { private fun hideSecureLockScreenRequired() {
binding.apply { binding.apply {
divider.visibility = View.GONE divider.visibility = View.GONE
maxFailedPasswordAttempts.visibility = View.GONE
maxFailedPasswordAttemptsDescription.visibility = View.GONE
wipeOnInactivitySpace.visibility = View.GONE
wipeOnInactivitySwitch.visibility = View.GONE wipeOnInactivitySwitch.visibility = View.GONE
wipeOnInactivityDescription.visibility = View.GONE wipeOnInactivityDescription.visibility = View.GONE
} }
@ -92,30 +98,31 @@ open class MainActivity : AppCompatActivity() {
private fun setup() { private fun setup() {
binding.apply { binding.apply {
code.setOnClickListener { authenticationCode.setOnClickListener {
showTriggersSettings() showTriggersSettings()
} }
code.setOnLongClickListener { authenticationCode.setOnLongClickListener {
prefs.code = makeCode() clipboardManager
code.text = prefs.code ?.setPrimaryClip(ClipData.newPlainText("", prefs.authenticationCode))
if (clipboardManager != null) {
scheduleClipboardClear()
Snackbar.make(
authenticationCode,
R.string.copied_popup,
Snackbar.LENGTH_SHORT,
).show()
}
true true
} }
wipeData.setOnCheckedChangeListener { _, isChecked -> wipeData.setOnCheckedChangeListener { _, isChecked ->
prefs.isWipeData = isChecked prefs.isWipeData = isChecked
wipeESIM.isEnabled = isChecked wipeEmbeddedSim.isEnabled = isChecked
} }
wipeESIM.setOnCheckedChangeListener { _, isChecked -> wipeEmbeddedSim.setOnCheckedChangeListener { _, isChecked ->
prefs.isWipeESIM = isChecked prefs.isWipeEmbeddedSim = isChecked
}
maxFailedPasswordAttempts.addOnChangeListener { _, value, _ ->
val num = value.toInt()
prefs.maxFailedPasswordAttempts = num
try {
admin.setMaximumFailedPasswordsForWipe(num.shl(1))
} catch (exc: SecurityException) {}
} }
wipeOnInactivitySwitch.setOnCheckedChangeListener { _, isChecked -> wipeOnInactivitySwitch.setOnCheckedChangeListener { _, isChecked ->
setWipeOnInactivityComponentsState(prefs.isServiceEnabled && isChecked) setWipeOnInactivityState(prefs.isEnabled && isChecked)
prefs.isWipeOnInactivity = isChecked prefs.isWipeOnInactivity = isChecked
} }
wipeOnInactivitySwitch.setOnLongClickListener { wipeOnInactivitySwitch.setOnLongClickListener {
@ -123,31 +130,21 @@ open class MainActivity : AppCompatActivity() {
true true
} }
toggle.setOnCheckedChangeListener { _, isChecked -> toggle.setOnCheckedChangeListener { _, isChecked ->
when (isChecked) { if (isChecked) requestAdmin() else setOff()
true -> if (!admin.isActive()) requestAdmin() else setOn()
false -> setOff()
}
}
toggle.setOnLongClickListener {
if (!toggle.isChecked) return@setOnLongClickListener false
showPanicDialog()
true
} }
} }
} }
private fun showPanicDialog() { private fun scheduleClipboardClear() {
MaterialAlertDialogBuilder(this) clipboardClearTask?.cancel()
.setTitle(R.string.dialog_confirm_panic_title) clipboardClearTask = Timer()
.setMessage(R.string.dialog_confirm_panic_message) clipboardClearTask?.schedule(timerTask {
.setPositiveButton(R.string.yes) { _, _ -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
try { clipboardManager?.clearPrimaryClip()
admin.lockNow() } else {
if (prefs.isWipeData) admin.wipeData() clipboardManager?.setPrimaryClip(ClipData.newPlainText("", ""))
} catch (exc: SecurityException) {}
} }
.setNegativeButton(android.R.string.cancel, null) }, CLIPBOARD_CLEAR_DELAY)
.show()
} }
private fun showTriggersSettings() { private fun showTriggersSettings() {
@ -172,25 +169,25 @@ open class MainActivity : AppCompatActivity() {
} }
.setPositiveButton(android.R.string.ok) { _, _ -> .setPositiveButton(android.R.string.ok) { _, _ ->
prefs.triggers = triggers prefs.triggers = triggers
setTriggersState(prefs.isServiceEnabled) setTriggersState(prefs.isEnabled)
} }
.show() .show()
} }
private fun showWipeOnInactivitySettings() { private fun showWipeOnInactivitySettings() {
val items = resources.getStringArray(R.array.wipe_on_inactivity_days) val items = resources.getStringArray(R.array.wipe_on_inactivity_days)
var days = prefs.wipeOnInactivityDays var days = prefs.wipeOnInactivityCount / 24 / 60
var checked = items.indexOf(days.toString()) var checked = items.indexOf(days.toString())
if (checked == -1) checked = items if (checked == -1) checked = items
.indexOf(Preferences.DEFAULT_WIPE_ON_INACTIVITY_DAYS.toString()) .indexOf((Preferences.DEFAULT_WIPE_ON_INACTIVITY_COUNT / 24 / 60).toString())
MaterialAlertDialogBuilder(this) MaterialAlertDialogBuilder(this)
.setTitle(R.string.wipe_on_inactivity_days) .setTitle(R.string.wipe_on_inactivity_days)
.setSingleChoiceItems(items, checked) { _, which -> .setSingleChoiceItems(items, checked) { _, which ->
days = items[which].toInt() days = items[which].toInt()
} }
.setPositiveButton(android.R.string.ok) { _, _ -> .setPositiveButton(android.R.string.ok) { _, _ ->
prefs.wipeOnInactivityDays = days prefs.wipeOnInactivityCount = days * 24 * 60
if (prefs.isServiceEnabled && prefs.isWipeOnInactivity) { if (prefs.isEnabled && prefs.isWipeOnInactivity) {
if (job.schedule() == JobScheduler.RESULT_FAILURE) if (job.schedule() == JobScheduler.RESULT_FAILURE)
showWipeJobScheduleFailedPopup() showWipeJobScheduleFailedPopup()
} }
@ -199,18 +196,14 @@ open class MainActivity : AppCompatActivity() {
} }
private fun updateCodeColorState() { private fun updateCodeColorState() {
binding.code.setBackgroundColor(getColor( binding.authenticationCode.setBackgroundColor(getColor(
if (prefs.triggers != 0) R.color.code_on else R.color.code_off if (prefs.triggers != 0) R.color.code_on else R.color.code_off
)) ))
} }
private fun setOn() { private fun setOn() {
if (!setWipeOnInactivityComponentsState(prefs.isWipeOnInactivity)) { prefs.isEnabled = true
binding.toggle.isChecked = false setWipeOnInactivityState(prefs.isWipeOnInactivity)
showWipeJobScheduleFailedPopup()
return
}
prefs.isServiceEnabled = true
setTriggersState(true) setTriggersState(true)
} }
@ -220,13 +213,13 @@ open class MainActivity : AppCompatActivity() {
setPanicKitState(triggers.and(Trigger.PANIC_KIT.value) != 0) setPanicKitState(triggers.and(Trigger.PANIC_KIT.value) != 0)
setTileState(triggers.and(Trigger.TILE.value) != 0) setTileState(triggers.and(Trigger.TILE.value) != 0)
shortcut.setState(triggers.and(Trigger.SHORTCUT.value) != 0) shortcut.setState(triggers.and(Trigger.SHORTCUT.value) != 0)
setCodeReceiverState(triggers.and(Trigger.BROADCAST.value) != 0) setTriggerReceiverState(triggers.and(Trigger.BROADCAST.value) != 0)
setNotificationListenerState(triggers.and(Trigger.NOTIFICATION.value) != 0) setNotificationListenerState(triggers.and(Trigger.NOTIFICATION.value) != 0)
} else { } else {
setPanicKitState(false) setPanicKitState(false)
setTileState(false) setTileState(false)
shortcut.setState(false) shortcut.setState(false)
setCodeReceiverState(false) setTriggerReceiverState(false)
setNotificationListenerState(false) setNotificationListenerState(false)
} }
updateCodeColorState() updateCodeColorState()
@ -241,16 +234,18 @@ open class MainActivity : AppCompatActivity() {
} }
private fun setOff() { private fun setOff() {
prefs.isServiceEnabled = false prefs.isEnabled = false
setWipeOnInactivityComponentsState(false) setWipeOnInactivityState(false)
setTriggersState(false) setTriggersState(false)
try {
admin.remove() admin.remove()
} catch (exc: SecurityException) {}
} }
private fun requestAdmin() = registerForDeviceAdmin.launch(admin.makeRequestIntent()) private fun requestAdmin() = registerForDeviceAdmin.launch(admin.makeRequestIntent())
private fun makeCode() = UUID.randomUUID().toString() private fun makeAuthenticationCode() = UUID.randomUUID().toString()
private fun setCodeReceiverState(value: Boolean) = private fun setTriggerReceiverState(value: Boolean) =
setComponentState(CodeReceiver::class.java, value) setComponentState(TriggerReceiver::class.java, value)
private fun setRestartReceiverState(value: Boolean) = private fun setRestartReceiverState(value: Boolean) =
setComponentState(RestartReceiver::class.java, value) setComponentState(RestartReceiver::class.java, value)
private fun setTileState(value: Boolean) { private fun setTileState(value: Boolean) {
@ -265,6 +260,11 @@ open class MainActivity : AppCompatActivity() {
setComponentState(PanicResponderActivity::class.java, value) setComponentState(PanicResponderActivity::class.java, value)
} }
private fun setWipeOnInactivityState(value: Boolean) {
job.setState(value)
setForegroundState(value)
}
private fun setComponentState(cls: Class<*>, value: Boolean) { private fun setComponentState(cls: Class<*>, value: Boolean) {
packageManager.setComponentEnabledSetting( packageManager.setComponentEnabledSetting(
ComponentName(this, cls), ComponentName(this, cls),
@ -281,12 +281,8 @@ open class MainActivity : AppCompatActivity() {
} }
} }
private fun setWipeOnInactivityComponentsState(value: Boolean): Boolean { private fun setForegroundState(value: Boolean) {
val result = job.setState(value)
if (result) {
setForegroundServiceState(value) setForegroundServiceState(value)
setRestartReceiverState(value) setRestartReceiverState(value)
} }
return result
}
} }

View File

@ -22,11 +22,11 @@ class NotificationListenerService : NotificationListenerService() {
override fun onNotificationPosted(sbn: StatusBarNotification?) { override fun onNotificationPosted(sbn: StatusBarNotification?) {
super.onNotificationPosted(sbn) super.onNotificationPosted(sbn)
if (sbn == null || if (sbn == null ||
!prefs.isServiceEnabled || !prefs.isEnabled ||
prefs.triggers.and(Trigger.NOTIFICATION.value) == 0) return prefs.triggers.and(Trigger.NOTIFICATION.value) == 0) return
val code = prefs.code val code = prefs.authenticationCode
if (code == "" || assert(code.isNotEmpty())
sbn.notification.extras[Notification.EXTRA_TEXT]?.toString() != code) return if (sbn.notification.extras[Notification.EXTRA_TEXT]?.toString() != code) return
cancelAllNotifications() cancelAllNotifications()
try { try {
admin.lockNow() admin.lockNow()

View File

@ -13,7 +13,7 @@ class PanicResponderActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
if (!Panic.isTriggerIntent(intent) || if (!Panic.isTriggerIntent(intent) ||
!prefs.isServiceEnabled || !prefs.isEnabled ||
prefs.triggers.and(Trigger.PANIC_KIT.value) == 0) prefs.triggers.and(Trigger.PANIC_KIT.value) == 0)
{ {
finishAndRemoveTask() finishAndRemoveTask()

View File

@ -7,26 +7,18 @@ import androidx.security.crypto.MasterKeys
class Preferences(ctx: Context) { class Preferences(ctx: Context) {
companion object { companion object {
const val DEFAULT_WIPE_ON_INACTIVITY_DAYS = 7 const val DEFAULT_WIPE_ON_INACTIVITY_COUNT = 7 * 24 * 60
private const val SERVICE_ENABLED = "service_enabled" private const val ENABLED = "enabled"
private const val CODE = "code" private const val AUTHENTICATION_CODE = "authentication_code"
private const val WIPE_DATA = "wipe_data" private const val WIPE_DATA = "wipe_data"
private const val WIPE_ESIM = "wipe_esim" private const val WIPE_EMBEDDED_SIM = "wipe_embedded_sim"
private const val MAX_FAILED_PASSWORD_ATTEMPTS = "max_failed_password_attempts"
private const val WIPE_ON_INACTIVITY = "wipe_on_inactivity" private const val WIPE_ON_INACTIVITY = "wipe_on_inactivity"
private const val TRIGGERS = "triggers" private const val TRIGGERS = "triggers"
private const val WIPE_ON_INACTIVITY_DAYS = "wipe_on_inactivity_days" private const val WIPE_ON_INACTIVITY_COUNT = "wipe_on_inactivity_count"
private const val FILE_NAME = "sec_shared_prefs" private const val FILE_NAME = "sec_shared_prefs"
// migration
private const val DO_WIPE = "do_wipe"
private const val CODE_ENABLED = "code_enabled"
private const val WIPE_ON_INACTIVE = "wipe_on_inactive"
private const val WIPE_ON_INACTIVE_DAYS = "wipe_on_inactive_days"
private const val LAUNCHERS = "launchers"
} }
private val mk = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC) private val mk = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
@ -38,49 +30,33 @@ class Preferences(ctx: Context) {
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
) )
var isServiceEnabled: Boolean var isEnabled: Boolean
get() = prefs.getBoolean(SERVICE_ENABLED, false) get() = prefs.getBoolean(ENABLED, false)
set(value) = prefs.edit { putBoolean(SERVICE_ENABLED, value) } set(value) = prefs.edit { putBoolean(ENABLED, value) }
var triggers: Int var triggers: Int
get() = prefs.getInt( get() = prefs.getInt(TRIGGERS, 0)
TRIGGERS,
prefs.getInt(
LAUNCHERS,
if (prefs.getBoolean(CODE_ENABLED, false)) Trigger.BROADCAST.value else 0
),
)
set(value) = prefs.edit { putInt(TRIGGERS, value) } set(value) = prefs.edit { putInt(TRIGGERS, value) }
var code: String var authenticationCode: String
get() = prefs.getString(CODE, "") ?: "" get() = prefs.getString(AUTHENTICATION_CODE, "") ?: ""
set(value) = prefs.edit { putString(CODE, value) } set(value) = prefs.edit { putString(AUTHENTICATION_CODE, value) }
var isWipeData: Boolean var isWipeData: Boolean
get() = prefs.getBoolean(WIPE_DATA, prefs.getBoolean(DO_WIPE, false)) get() = prefs.getBoolean(WIPE_DATA, false)
set(value) = prefs.edit { putBoolean(WIPE_DATA, value) } set(value) = prefs.edit { putBoolean(WIPE_DATA, value) }
var isWipeESIM: Boolean var isWipeEmbeddedSim: Boolean
get() = prefs.getBoolean(WIPE_ESIM, false) get() = prefs.getBoolean(WIPE_EMBEDDED_SIM, false)
set(value) = prefs.edit { putBoolean(WIPE_ESIM, value) } set(value) = prefs.edit { putBoolean(WIPE_EMBEDDED_SIM, value) }
var maxFailedPasswordAttempts: Int
get() = prefs.getInt(MAX_FAILED_PASSWORD_ATTEMPTS, 0)
set(value) = prefs.edit { putInt(MAX_FAILED_PASSWORD_ATTEMPTS, value) }
var isWipeOnInactivity: Boolean var isWipeOnInactivity: Boolean
get() = prefs.getBoolean( get() = prefs.getBoolean(WIPE_ON_INACTIVITY, false)
WIPE_ON_INACTIVITY,
prefs.getBoolean(WIPE_ON_INACTIVE, false),
)
set(value) = prefs.edit { putBoolean(WIPE_ON_INACTIVITY, value) } set(value) = prefs.edit { putBoolean(WIPE_ON_INACTIVITY, value) }
var wipeOnInactivityDays: Int var wipeOnInactivityCount: Int
get() = prefs.getInt( get() = prefs.getInt(WIPE_ON_INACTIVITY_COUNT, DEFAULT_WIPE_ON_INACTIVITY_COUNT)
WIPE_ON_INACTIVITY_DAYS, set(value) = prefs.edit { putInt(WIPE_ON_INACTIVITY_COUNT, value) }
prefs.getInt(WIPE_ON_INACTIVE_DAYS, DEFAULT_WIPE_ON_INACTIVITY_DAYS),
)
set(value) = prefs.edit { putInt(WIPE_ON_INACTIVITY_DAYS, value) }
} }
enum class Trigger(val value: Int) { enum class Trigger(val value: Int) {

View File

@ -7,11 +7,10 @@ import androidx.core.content.ContextCompat
class RestartReceiver : BroadcastReceiver() { class RestartReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context?, intent: Intent?) {
if (context == null if (intent?.action != Intent.ACTION_BOOT_COMPLETED &&
|| (intent?.action != Intent.ACTION_BOOT_COMPLETED intent?.action != Intent.ACTION_MY_PACKAGE_REPLACED) return
&& intent?.action != Intent.ACTION_MY_PACKAGE_REPLACED)) return val prefs = Preferences(context ?: return)
val prefs = Preferences(context) if (!prefs.isEnabled || !prefs.isWipeOnInactivity) return
if (!prefs.isServiceEnabled || !prefs.isWipeOnInactivity) return
ContextCompat.startForegroundService( ContextCompat.startForegroundService(
context.applicationContext, context.applicationContext,
Intent(context.applicationContext, ForegroundService::class.java), Intent(context.applicationContext, ForegroundService::class.java),

View File

@ -10,7 +10,7 @@ class ShortcutActivity : AppCompatActivity() {
finishAndRemoveTask() finishAndRemoveTask()
return return
} }
CodeReceiver.panic(this, intent) TriggerReceiver.panic(this, intent)
finishAndRemoveTask() finishAndRemoveTask()
} }
} }

View File

@ -20,9 +20,9 @@ class ShortcutManager(private val ctx: Context) {
.setShortLabel(ctx.getString(R.string.shortcut_label)) .setShortLabel(ctx.getString(R.string.shortcut_label))
.setIcon(IconCompat.createWithResource(ctx, android.R.drawable.ic_delete)) .setIcon(IconCompat.createWithResource(ctx, android.R.drawable.ic_delete))
.setIntent( .setIntent(
Intent(CodeReceiver.ACTION) Intent(TriggerReceiver.ACTION)
.setClass(ctx, ShortcutActivity::class.java) .setClass(ctx, ShortcutActivity::class.java)
.putExtra(CodeReceiver.KEY, prefs.code) .putExtra(TriggerReceiver.KEY, prefs.authenticationCode)
) )
.build(), .build(),
) )

View File

@ -32,14 +32,14 @@ class TileService : TileService() {
override fun onStartListening() { override fun onStartListening() {
super.onStartListening() super.onStartListening()
update( update(
if (prefs.isServiceEnabled && admin.isActive()) Tile.STATE_INACTIVE if (prefs.isEnabled && admin.isActive()) Tile.STATE_INACTIVE
else Tile.STATE_UNAVAILABLE else Tile.STATE_UNAVAILABLE
) )
} }
override fun onClick() { override fun onClick() {
super.onClick() super.onClick()
if (!prefs.isServiceEnabled || prefs.triggers.and(Trigger.TILE.value) == 0) return if (!prefs.isEnabled || prefs.triggers.and(Trigger.TILE.value) == 0) return
if (!prefs.isWipeData) { if (!prefs.isWipeData) {
try { try {
admin.lockNow() admin.lockNow()

View File

@ -4,7 +4,7 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
class CodeReceiver : BroadcastReceiver() { class TriggerReceiver : BroadcastReceiver() {
companion object { companion object {
const val KEY = "code" const val KEY = "code"
const val ACTION = "me.lucky.wasted.action.TRIGGER" const val ACTION = "me.lucky.wasted.action.TRIGGER"
@ -12,9 +12,10 @@ class CodeReceiver : BroadcastReceiver() {
fun panic(context: Context, intent: Intent?) { fun panic(context: Context, intent: Intent?) {
if (intent?.action != ACTION) return if (intent?.action != ACTION) return
val prefs = Preferences(context) val prefs = Preferences(context)
if (!prefs.isServiceEnabled) return if (!prefs.isEnabled) return
val code = prefs.code val code = prefs.authenticationCode
if (code == "" || intent.getStringExtra(KEY) != code) return assert(code.isNotEmpty())
if (intent.getStringExtra(KEY) != code) return
val admin = DeviceAdminManager(context) val admin = DeviceAdminManager(context)
try { try {
admin.lockNow() admin.lockNow()
@ -24,8 +25,7 @@ class CodeReceiver : BroadcastReceiver() {
} }
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context?, intent: Intent?) {
if (context == null || if (Preferences(context ?: return).triggers.and(Trigger.BROADCAST.value) == 0) return
Preferences(context).triggers.and(Trigger.BROADCAST.value) == 0) return
panic(context, intent) panic(context, intent)
} }
} }

View File

@ -16,7 +16,7 @@ class WipeJobManager(private val ctx: Context) {
fun schedule(): Int { fun schedule(): Int {
return scheduler?.schedule( return scheduler?.schedule(
JobInfo.Builder(JOB_ID, ComponentName(ctx, WipeJobService::class.java)) JobInfo.Builder(JOB_ID, ComponentName(ctx, WipeJobService::class.java))
.setMinimumLatency(TimeUnit.DAYS.toMillis(prefs.wipeOnInactivityDays.toLong())) .setMinimumLatency(TimeUnit.MINUTES.toMillis(prefs.wipeOnInactivityCount.toLong()))
.setBackoffCriteria(0, JobInfo.BACKOFF_POLICY_LINEAR) .setBackoffCriteria(0, JobInfo.BACKOFF_POLICY_LINEAR)
.setPersisted(true) .setPersisted(true)
.build() .build()

View File

@ -6,7 +6,7 @@ import android.app.job.JobService
class WipeJobService : JobService() { class WipeJobService : JobService() {
override fun onStartJob(params: JobParameters?): Boolean { override fun onStartJob(params: JobParameters?): Boolean {
val prefs = Preferences(this) val prefs = Preferences(this)
if (!prefs.isServiceEnabled || !prefs.isWipeOnInactivity) return false if (!prefs.isEnabled || !prefs.isWipeOnInactivity) return false
try { try {
DeviceAdminManager(this).wipeData() DeviceAdminManager(this).wipeData()
} catch (exc: SecurityException) {} } catch (exc: SecurityException) {}

View File

@ -32,7 +32,7 @@
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
android:id="@+id/code" android:id="@+id/authenticationCode"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@color/code_off" android:background="@color/code_off"
@ -57,13 +57,13 @@
android:textSize="16sp" /> android:textSize="16sp" />
<Space <Space
android:id="@+id/wipeESIMSpace" android:id="@+id/wipeSpace"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginVertical="2dp" /> android:layout_marginVertical="2dp" />
<CheckBox <CheckBox
android:id="@+id/wipeESIM" android:id="@+id/wipeEmbeddedSim"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp" android:layout_marginHorizontal="16dp"
@ -78,27 +78,6 @@
android:layout_marginVertical="8dp" android:layout_marginVertical="8dp"
android:background="?android:attr/listDivider" /> android:background="?android:attr/listDivider" />
<com.google.android.material.slider.Slider
android:id="@+id/maxFailedPasswordAttempts"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:contentDescription="@string/max_failed_password_attempts_content_description"
android:valueFrom="0"
android:valueTo="10"
android:stepSize="1.0" />
<TextView
android:id="@+id/maxFailedPasswordAttemptsDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/max_failed_password_attempts_description" />
<Space
android:id="@+id/wipeOnInactivitySpace"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="8dp" />
<com.google.android.material.switchmaterial.SwitchMaterial <com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/wipeOnInactivitySwitch" android:id="@+id/wipeOnInactivitySwitch"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Wasted</string>
<string name="description">Aktiviere Wasted, um dein Gerät im Notfall zu sperren. Du kannst hierfür PanicKit, Tile und Shortcut verwenden, oder eine Nachricht mit diesem Authentifizierungscode auf dein Gerät senden.</string>
<string name="device_admin_label">Wasted</string>
<string name="device_admin_description">Wasted erlauben das Gerät zu sperren und optional die Daten in einem Notfall zu löschen</string>
<string name="service_unavailable_popup">Geräteadministrator nicht verfügbar</string>
<string name="wipe_data_check_box">Daten löschen</string>
<string name="wipe_esim_check_box">eSim löschen</string>
<string name="panic_app_dialog_title">Bestätige Panik App</string>
<string name="panic_app_dialog_message">Bist du sicher, dass du %1$s erlauben willst, destruktive Aktionen auslösen zu lassen\?</string>
<string name="panic_app_unknown_app">eine unbekannte App</string>
<string name="allow">Zulassen</string>
<string name="tile_label">Flugmodus</string>
<string name="shortcut_label">Panik</string>
<string name="wipe_on_inactivity_switch">Bei Inaktivität löschen</string>
<string name="wipe_on_inactivity_description">Den Speicher des Geräts löschen, wenn es für N Tage nicht entsperrt wurde.</string>
<string name="wipe_on_inactivity_days">Tage</string>
<string name="notification_channel_default_name">Standard</string>
<string name="wipe_job_description">Den Speicher bei Inaktivität löschen</string>
<string name="wipe_job_schedule_failed_popup">Fehler bei der Durchführung des Wipes</string>
<string name="foreground_service_description">Receive events which require foreground service</string>
<string name="foreground_service_notification_title">Guard</string>
<string name="triggers_array_panic_kit">PanicKit</string>
<string name="triggers_array_tile">Tile</string>
<string name="triggers_array_shortcut">Shortcut</string>
<string name="triggers_array_broadcast">Broadcast</string>
<string name="triggers_array_notification">Benachrichtigung</string>
<string name="notification_listener_service_label">Viper</string>
<string name="notification_listener_service_description">Benachrichtigungen nach dem Authentifizierungscode absuchen</string>
<string name="triggers">Auslöser</string>
<string name="copied_popup">Kopiert</string>
</resources>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Wasted</string>
<string name="description">Active Wasted para bloquear un dispositivo en caso de emergencia. Puede usar PanicKit, title, acceso directo o enviar un mensaje con este código de autenticación.</string>
<string name="device_admin_label">Wasted</string>
<string name="device_admin_description">Permita que Wasted bloquee un dispositivo y, opcionalmente, borre sus datos en caso de emergencia</string>
<string name="service_unavailable_popup">Administrador de dispositivos no disponible</string>
<string name="wipe_data_check_box">Borrar datos</string>
<string name="wipe_esim_check_box">Borrar eSIM</string>
<string name="panic_app_dialog_title">Confirmar aplicación de pánico</string>
<string name="panic_app_dialog_message">¿Está seguro de que desea permitir que %1$s active acciones de pánico destructivas\?</string>
<string name="panic_app_unknown_app">una aplicación desconocida</string>
<string name="allow">Permitir</string>
<string name="tile_label">Modo avión</string>
<string name="shortcut_label">Pánico</string>
<string name="wipe_on_inactivity_switch">Borrar por inactividad</string>
<string name="wipe_on_inactivity_description">Limpia un dispositivo cuando no fue desbloqueado por N días.</string>
<string name="wipe_on_inactivity_days">Días</string>
<string name="notification_channel_default_name">Predeterminado</string>
<string name="wipe_job_description">Borrar un dispositivo por inactividad</string>
<string name="wipe_job_schedule_failed_popup">Error al programar un trabajo de borrado</string>
<string name="foreground_service_description">Recibir eventos que requieren servicio de primer plano</string>
<string name="foreground_service_notification_title">Guardia</string>
<string name="triggers_array_panic_kit">PanicKit</string>
<string name="triggers_array_tile">Título</string>
<string name="triggers_array_shortcut">Acceso directo</string>
<string name="triggers_array_broadcast">Transmisión</string>
<string name="triggers_array_notification">Notificación</string>
<string name="notification_listener_service_label">Viper</string>
<string name="notification_listener_service_description">Escanear notificaciones para el código de autenticación</string>
<string name="triggers">Activadores</string>
<string name="copied_popup">Copiado</string>
</resources>

View File

@ -13,8 +13,6 @@
<string name="allow">Consenti</string> <string name="allow">Consenti</string>
<string name="tile_label">Modalità aereo</string> <string name="tile_label">Modalità aereo</string>
<string name="shortcut_label">Panico</string> <string name="shortcut_label">Panico</string>
<string name="max_failed_password_attempts_description">Numero massimo di tentativi di password falliti.</string>
<string name="max_failed_password_attempts_content_description">Numero</string>
<string name="wipe_on_inactivity_switch">Cancella in caso di inattività</string> <string name="wipe_on_inactivity_switch">Cancella in caso di inattività</string>
<string name="wipe_on_inactivity_description">Cancella i dati quando il dispositivo non viene sbloccato per N giorni.</string> <string name="wipe_on_inactivity_description">Cancella i dati quando il dispositivo non viene sbloccato per N giorni.</string>
<string name="wipe_on_inactivity_days">Giorni</string> <string name="wipe_on_inactivity_days">Giorni</string>
@ -23,9 +21,6 @@
<string name="wipe_job_schedule_failed_popup">Impossibile pianificare un processo di cancellazione</string> <string name="wipe_job_schedule_failed_popup">Impossibile pianificare un processo di cancellazione</string>
<string name="foreground_service_description">Ricevi eventi che richiedono il servizio in primo piano</string> <string name="foreground_service_description">Ricevi eventi che richiedono il servizio in primo piano</string>
<string name="foreground_service_notification_title">Guardia</string> <string name="foreground_service_notification_title">Guardia</string>
<string name="dialog_confirm_panic_title">Attivare il panico\?</string>
<string name="dialog_confirm_panic_message">Questo bloccherà un dispositivo e, facoltativamente, cancellerà i suoi dati.</string>
<string name="yes"></string>
<string name="triggers_array_panic_kit">PanicKit</string> <string name="triggers_array_panic_kit">PanicKit</string>
<string name="triggers_array_tile">Toggle</string> <string name="triggers_array_tile">Toggle</string>
<string name="triggers_array_shortcut">Scorciatoia</string> <string name="triggers_array_shortcut">Scorciatoia</string>
@ -34,4 +29,5 @@
<string name="notification_listener_service_label">Viper</string> <string name="notification_listener_service_label">Viper</string>
<string name="notification_listener_service_description">Scansiona le notifiche per il codice di autenticazione</string> <string name="notification_listener_service_description">Scansiona le notifiche per il codice di autenticazione</string>
<string name="triggers">Attivatori</string> <string name="triggers">Attivatori</string>
<string name="copied_popup">Copiato</string>
</resources> </resources>

View File

@ -13,8 +13,6 @@
<string name="allow">Разрешить</string> <string name="allow">Разрешить</string>
<string name="tile_label">Режим полета</string> <string name="tile_label">Режим полета</string>
<string name="shortcut_label">Тревога</string> <string name="shortcut_label">Тревога</string>
<string name="max_failed_password_attempts_description">Максимальное количество неудачных попыток ввода пароля.</string>
<string name="max_failed_password_attempts_content_description">Количество</string>
<string name="wipe_on_inactivity_switch">Стереть при неактивности</string> <string name="wipe_on_inactivity_switch">Стереть при неактивности</string>
<string name="wipe_on_inactivity_description">Стереть данные когда устройство не разблокируется N дней.</string> <string name="wipe_on_inactivity_description">Стереть данные когда устройство не разблокируется N дней.</string>
<string name="wipe_on_inactivity_days">Дней</string> <string name="wipe_on_inactivity_days">Дней</string>
@ -23,9 +21,6 @@
<string name="wipe_job_schedule_failed_popup">Не удалось запланировать сервис стирания данных</string> <string name="wipe_job_schedule_failed_popup">Не удалось запланировать сервис стирания данных</string>
<string name="foreground_service_description">Получать события для которых требуется сервис на переднем плане</string> <string name="foreground_service_description">Получать события для которых требуется сервис на переднем плане</string>
<string name="foreground_service_notification_title">Охрана</string> <string name="foreground_service_notification_title">Охрана</string>
<string name="dialog_confirm_panic_title">Активировать тревогу\?</string>
<string name="dialog_confirm_panic_message">Это заблокирует устройство и возможно сотрёт его данные.</string>
<string name="yes">Да</string>
<string name="triggers_array_panic_kit">Тревожная кнопка</string> <string name="triggers_array_panic_kit">Тревожная кнопка</string>
<string name="triggers_array_tile">Плитка</string> <string name="triggers_array_tile">Плитка</string>
<string name="triggers_array_shortcut">Ярлык</string> <string name="triggers_array_shortcut">Ярлык</string>
@ -34,4 +29,5 @@
<string name="notification_listener_service_label">Вайпер</string> <string name="notification_listener_service_label">Вайпер</string>
<string name="notification_listener_service_description">Сканирует уведомления на наличие кода аутентификации</string> <string name="notification_listener_service_description">Сканирует уведомления на наличие кода аутентификации</string>
<string name="triggers">Триггеры</string> <string name="triggers">Триггеры</string>
<string name="copied_popup">Скопировано</string>
</resources> </resources>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Wasted</string>
<string name="description">Turn on Wasted to lock a device on emergency. You can use PanicKit, tile, shortcut or send a message with this authentication code.</string>
<string name="device_admin_label">Wasted</string>
<string name="device_admin_description">Allow Wasted to lock a device and optionally wipe its data on emergency</string>
<string name="service_unavailable_popup">Cihaz yöneticisi mevcut değil</string>
<string name="wipe_data_check_box">Verileri sil</string>
<string name="wipe_esim_check_box">eSIM\'i sil</string>
<string name="panic_app_dialog_title">Panik uygulamasını onayla</string>
<string name="panic_app_dialog_message">Yıkıcı panik eylemlerini tetiklemek için %1$s\'e izin vermek istediğinizden emin misiniz\?</string>
<string name="panic_app_unknown_app">bilinmeyen bir uygulama</string>
<string name="allow">İzin ver</string>
<string name="tile_label">Uçak modu</string>
<string name="shortcut_label">Panik</string>
<string name="wipe_on_inactivity_switch">Kullanılmadığında sil</string>
<string name="wipe_on_inactivity_description">N gün boyunca kilidi açılmamış bir cihazı silin.</string>
<string name="wipe_on_inactivity_days">Gün</string>
<string name="notification_channel_default_name">Varsayılan</string>
<string name="wipe_job_description">Cihaz kullanılmadığında siler</string>
<string name="wipe_job_schedule_failed_popup">Silme işi zamanlanamadı</string>
<string name="foreground_service_description">Ön plan hizmeti gerektiren olayları al</string>
<string name="foreground_service_notification_title">Guard</string>
<string name="triggers_array_panic_kit">PanicKit</string>
<string name="triggers_array_tile">Tile</string>
<string name="triggers_array_shortcut">Kısayol</string>
<string name="triggers_array_broadcast">Yayın</string>
<string name="triggers_array_notification">Bildirim</string>
<string name="notification_listener_service_label">Viper</string>
<string name="notification_listener_service_description">Kimlik doğrulama kodu için bildirimleri tara</string>
<string name="triggers">Tetikleyiciler</string>
<string name="copied_popup">Kopyalandı</string>
</resources>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Wasted</string>
<string name="description">Turn on Wasted to lock a device on emergency. You can use PanicKit, tile, shortcut or send a message with this authentication code.</string>
<string name="device_admin_label">Wasted</string>
<string name="device_admin_description">Allow Wasted to lock a device and optionally wipe its data on emergency</string>
<string name="service_unavailable_popup">Адміністратор пристрою недоступний</string>
<string name="wipe_data_check_box">Стерти дані</string>
<string name="wipe_esim_check_box">Стерти дані eSIM</string>
<string name="panic_app_dialog_title">Confirm panic app</string>
<string name="panic_app_dialog_message">Are you sure that you want to allow %1$s to trigger destructive panic actions\?</string>
<string name="panic_app_unknown_app">невідомий додаток</string>
<string name="allow">Allow</string>
<string name="tile_label">Режим польоту</string>
<string name="shortcut_label">Тривога</string>
<string name="wipe_on_inactivity_switch">Wipe on inactivity</string>
<string name="wipe_on_inactivity_description">Видалення пристрою, коли його не було розблоковано протягом N днів.</string>
<string name="wipe_on_inactivity_days">Днів</string>
<string name="notification_channel_default_name">За замовчуванням</string>
<string name="wipe_job_description">Wipe a device on inactivity</string>
<string name="wipe_job_schedule_failed_popup">Failed to schedule a wipe job</string>
<string name="foreground_service_description">Receive events which require foreground service</string>
<string name="foreground_service_notification_title">Захисник</string>
<string name="triggers_array_panic_kit">PanicKit</string>
<string name="triggers_array_tile">Tile</string>
<string name="triggers_array_shortcut">Гарячі клавіші</string>
<string name="triggers_array_broadcast">Трансляція</string>
<string name="triggers_array_notification">Сповіщення</string>
<string name="notification_listener_service_label">Знищувач</string>
<string name="notification_listener_service_description">Сканувати сповіщення для коду автентифікації</string>
<string name="triggers">Тріггери</string>
<string name="copied_popup">Скопійовано</string>
</resources>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">自毁</string>
<string name="description">开启自毁,在紧急状况下锁定设备。您可以使用触发器、追踪器、快捷方式或者发送验证码信息。</string>
<string name="device_admin_label">自毁</string>
<string name="device_admin_description">允许自毁锁定设备以及可选择在紧急状况下擦除数据</string>
<string name="service_unavailable_popup">设备管理员不可用</string>
<string name="wipe_data_check_box">擦除数据</string>
<string name="wipe_esim_check_box">擦除eSIM</string>
<string name="panic_app_dialog_title">确认需要使用的紧急应用程序</string>
<string name="panic_app_dialog_message">你是否确定允许%1$s触发紧急自毁行为</string>
<string name="panic_app_unknown_app">未知应用</string>
<string name="allow">允许</string>
<string name="tile_label">飞行模式</string>
<string name="shortcut_label">紧急</string>
<string name="wipe_on_inactivity_switch">在不使用时擦除</string>
<string name="wipe_on_inactivity_description">当设备在N天内没被解锁时擦除</string>
<string name="wipe_on_inactivity_days">天数</string>
<string name="notification_channel_default_name">默认设置</string>
<string name="wipe_job_description">擦除不使用的设备</string>
<string name="wipe_job_schedule_failed_popup">无法制定擦除计划</string>
<string name="foreground_service_description">接收需要前台服务的事件</string>
<string name="foreground_service_notification_title">守卫</string>
<string name="triggers_array_panic_kit">恐慌触发器</string>
<string name="triggers_array_tile">追踪器</string>
<string name="triggers_array_shortcut">快捷方式</string>
<string name="triggers_array_broadcast">广播</string>
<string name="triggers_array_notification">通知</string>
<string name="notification_listener_service_label">哨兵</string>
<string name="notification_listener_service_description">扫描通知中的验证码</string>
<string name="triggers">触发器</string>
<string name="copied_popup">复制成功</string>
</resources>

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">Wasted</string> <string name="app_name">Wasted</string>
<string name="description">Turn on Wasted to lock a device on danger. You can use PanicKit, tile, shortcut or send a message with this authentication code.</string> <string name="description">Turn on Wasted to lock a device on emergency. You can use PanicKit, tile, shortcut or send a message with this authentication code.</string>
<string name="device_admin_label">Wasted</string> <string name="device_admin_label">Wasted</string>
<string name="device_admin_description">Allow Wasted to lock a device and optionally wipe its data on danger</string> <string name="device_admin_description">Allow Wasted to lock a device and optionally wipe its data on emergency</string>
<string name="service_unavailable_popup">Device admin unavailable</string> <string name="service_unavailable_popup">Device admin unavailable</string>
<string name="wipe_data_check_box">Wipe data</string> <string name="wipe_data_check_box">Wipe data</string>
<string name="wipe_esim_check_box">Wipe eSIM</string> <string name="wipe_esim_check_box">Wipe eSIM</string>
@ -13,8 +13,6 @@
<string name="allow">Allow</string> <string name="allow">Allow</string>
<string name="tile_label">Airplane mode</string> <string name="tile_label">Airplane mode</string>
<string name="shortcut_label">Panic</string> <string name="shortcut_label">Panic</string>
<string name="max_failed_password_attempts_description">Maximum number of failed password attempts.</string>
<string name="max_failed_password_attempts_content_description">Count</string>
<string name="wipe_on_inactivity_switch">Wipe on inactivity</string> <string name="wipe_on_inactivity_switch">Wipe on inactivity</string>
<string name="wipe_on_inactivity_description">Wipe a device when it was not unlocked for N days.</string> <string name="wipe_on_inactivity_description">Wipe a device when it was not unlocked for N days.</string>
<string name="wipe_on_inactivity_days">Days</string> <string name="wipe_on_inactivity_days">Days</string>
@ -22,10 +20,7 @@
<string name="wipe_job_description">Wipe a device on inactivity</string> <string name="wipe_job_description">Wipe a device on inactivity</string>
<string name="wipe_job_schedule_failed_popup">Failed to schedule a wipe job</string> <string name="wipe_job_schedule_failed_popup">Failed to schedule a wipe job</string>
<string name="foreground_service_description">Receive events which require foreground service</string> <string name="foreground_service_description">Receive events which require foreground service</string>
<string name="foreground_service_notification_title">Sentry</string> <string name="foreground_service_notification_title">Guard</string>
<string name="dialog_confirm_panic_title">Activate panic\?</string>
<string name="dialog_confirm_panic_message">This will lock a device and optionally wipe its data.</string>
<string name="yes">Yes</string>
<string name="triggers_array_panic_kit">PanicKit</string> <string name="triggers_array_panic_kit">PanicKit</string>
<string name="triggers_array_tile">Tile</string> <string name="triggers_array_tile">Tile</string>
<string name="triggers_array_shortcut">Shortcut</string> <string name="triggers_array_shortcut">Shortcut</string>
@ -34,4 +29,5 @@
<string name="notification_listener_service_label">Viper</string> <string name="notification_listener_service_label">Viper</string>
<string name="notification_listener_service_description">Scan notifications for the authentication code</string> <string name="notification_listener_service_description">Scan notifications for the authentication code</string>
<string name="triggers">Triggers</string> <string name="triggers">Triggers</string>
<string name="copied_popup">Copied</string>
</resources> </resources>

View File

@ -1,9 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<device-admin> <device-admin>
<support-transfer-ownership />
<uses-policies> <uses-policies>
<force-lock /> <force-lock />
<watch-login />
<wipe-data /> <wipe-data />
</uses-policies> </uses-policies>
</device-admin> </device-admin>

View File

@ -5,8 +5,8 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.1.1' classpath 'com.android.tools.build:gradle:7.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.0"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files

View File

@ -0,0 +1,16 @@
remove all preferences migrations, you have to reconfigure Wasted !!
.CodeReceiver renamed to .TriggerReceiver !!
remove max failed password attempts, migrate to Sentry
add copy authentication code with a long click
add Chinese Simplified, German, Spanish, Turkish, Ukrainian translations
update Italian translation
under the hood improvements
Thanks to:
Alex Ortiz
Alexander Sergevich
Noah Fisker
SuperM
TolDYuThad
Wong Anny
Giovanni Donisi (@gdonisi + @giovannidonisi)

View File

@ -1,11 +1,11 @@
Lock a device and wipe its data on danger. Lock a device and wipe its data on emergency.
You can use PanicKit, tile, shortcut or send a message with authentication code. On trigger, using You can use PanicKit, tile, shortcut or send a message with authentication code. On trigger, using
Device Administration API, it locks a device and optionally runs wipe. Device Administration API, it locks a device and optionally runs wipe.
Also you can: Also you can:
* limit the maximum number of failed password attempts
* wipe a device when it was not unlocked for N days * wipe a device when it was not unlocked for N days
* wipe a device using a duress password (companion app: [Duress](https://github.com/x13a/Duress))
The app works in Work Profile too. Use Shelter to install risky apps and Wasted in it. Then you can 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. wipe this profile data with one click without wiping the whole device.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

View File

@ -1 +1 @@
Lock a device and wipe its data on danger Lock a device and wipe its data on emergency

View File

@ -5,7 +5,6 @@ un codice di autenticazione. All'attivazione, utilizzando l'API di amministrazio
del dispositivo, blocca un dispositivo e facoltativamente esegue la cancellazione. del dispositivo, blocca un dispositivo e facoltativamente esegue la cancellazione.
Puoi anche: Puoi anche:
* limitare il numero massimo di tentativi di password falliti
* cancellare i dati quando un dispositivo non è stato sbloccato per N giorni * cancellare i dati quando un dispositivo non è stato sbloccato per N giorni
L'app funziona anche nel Profilo di Lavoro. Usa Shelter per installarci dentro le app pericolose e Wasted. L'app funziona anche nel Profilo di Lavoro. Usa Shelter per installarci dentro le app pericolose e Wasted.

View File

@ -5,8 +5,8 @@
заблокирует устройство и при необходимости запустит очистку данных. заблокирует устройство и при необходимости запустит очистку данных.
Также вы можете: Также вы можете:
* ограничить максимальное количество неудачных попыток ввода пароля
* стереть данные, если устройство не было разблокировано в течение определённого количества дней * стереть данные, если устройство не было разблокировано в течение определённого количества дней
* стереть данные, используя пароль под принуждением (приложение: https://github.com/x13a/Duress)
Приложение работает и в рабочем профиле. Используйте Shelter для установки рискованных приложений и Приложение работает и в рабочем профиле. Используйте Shelter для установки рискованных приложений и
Wasted в него. Тогда вы можете стереть данные этого профиля одним кликом, без удаления данных всего Wasted в него. Тогда вы можете стереть данные этого профиля одним кликом, без удаления данных всего

View File

@ -1,6 +1,6 @@
#Fri Jun 24 03:06:49 MSK 2022
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=f581709a9c35e9cb92e16f585d2c4bc99b2b1a5f85d2badbd3dc6bff59e1e6dd
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME