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
Lock a device and wipe its data on danger.
Lock a device and wipe its data on emergency.
[<img
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.
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 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
risky apps and `Wasted` in it. Then you can wipe this profile data with one click without wiping
@ -43,7 +43,7 @@ Broadcast:
```sh
$ adb shell am broadcast \
-a me.lucky.wasted.action.TRIGGER \
-n me.lucky.wasted/.CodeReceiver \
-n me.lucky.wasted/.TriggerReceiver \
-e code "b49a6576-0c27-4f03-b96b-da53501022ba"
```

View File

@ -10,8 +10,8 @@ android {
applicationId "me.lucky.wasted"
minSdk 23
targetSdk 32
versionCode 24
versionName "1.3.4"
versionCode 25
versionName "1.4.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@ -39,10 +39,10 @@ android {
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
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'

View File

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

View File

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

View File

@ -3,30 +3,12 @@ package me.lucky.wasted
import android.app.admin.DeviceAdminReceiver
import android.content.Context
import android.content.Intent
import android.os.UserHandle
import android.widget.Toast
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) {
super.onDisabled(context, intent)
if (Preferences(context).isServiceEnabled)
if (Preferences(context).isEnabled)
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() {
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
Thread(Runner(context, goAsync())).start()
}
@ -64,9 +65,9 @@ class ForegroundService : Service() {
private val pendingResult: PendingResult,
) : Runnable {
override fun run() {
val manager = WipeJobManager(ctx)
val job = WipeJobManager(ctx)
var delay = 1000L
while (manager.schedule() != JobScheduler.RESULT_SUCCESS) {
while (job.schedule() != JobScheduler.RESULT_SUCCESS) {
Thread.sleep(delay)
delay = delay.shl(1)
}

View File

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

View File

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

View File

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

View File

@ -7,26 +7,18 @@ import androidx.security.crypto.MasterKeys
class Preferences(ctx: Context) {
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 CODE = "code"
private const val ENABLED = "enabled"
private const val AUTHENTICATION_CODE = "authentication_code"
private const val WIPE_DATA = "wipe_data"
private const val WIPE_ESIM = "wipe_esim"
private const val MAX_FAILED_PASSWORD_ATTEMPTS = "max_failed_password_attempts"
private const val WIPE_EMBEDDED_SIM = "wipe_embedded_sim"
private const val WIPE_ON_INACTIVITY = "wipe_on_inactivity"
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"
// 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)
@ -38,49 +30,33 @@ class Preferences(ctx: Context) {
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
)
var isServiceEnabled: Boolean
get() = prefs.getBoolean(SERVICE_ENABLED, false)
set(value) = prefs.edit { putBoolean(SERVICE_ENABLED, value) }
var isEnabled: Boolean
get() = prefs.getBoolean(ENABLED, false)
set(value) = prefs.edit { putBoolean(ENABLED, value) }
var triggers: Int
get() = prefs.getInt(
TRIGGERS,
prefs.getInt(
LAUNCHERS,
if (prefs.getBoolean(CODE_ENABLED, false)) Trigger.BROADCAST.value else 0
),
)
get() = prefs.getInt(TRIGGERS, 0)
set(value) = prefs.edit { putInt(TRIGGERS, value) }
var code: String
get() = prefs.getString(CODE, "") ?: ""
set(value) = prefs.edit { putString(CODE, value) }
var authenticationCode: String
get() = prefs.getString(AUTHENTICATION_CODE, "") ?: ""
set(value) = prefs.edit { putString(AUTHENTICATION_CODE, value) }
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) }
var isWipeESIM: Boolean
get() = prefs.getBoolean(WIPE_ESIM, false)
set(value) = prefs.edit { putBoolean(WIPE_ESIM, value) }
var maxFailedPasswordAttempts: Int
get() = prefs.getInt(MAX_FAILED_PASSWORD_ATTEMPTS, 0)
set(value) = prefs.edit { putInt(MAX_FAILED_PASSWORD_ATTEMPTS, value) }
var isWipeEmbeddedSim: Boolean
get() = prefs.getBoolean(WIPE_EMBEDDED_SIM, false)
set(value) = prefs.edit { putBoolean(WIPE_EMBEDDED_SIM, value) }
var isWipeOnInactivity: Boolean
get() = prefs.getBoolean(
WIPE_ON_INACTIVITY,
prefs.getBoolean(WIPE_ON_INACTIVE, false),
)
get() = prefs.getBoolean(WIPE_ON_INACTIVITY, false)
set(value) = prefs.edit { putBoolean(WIPE_ON_INACTIVITY, value) }
var wipeOnInactivityDays: Int
get() = prefs.getInt(
WIPE_ON_INACTIVITY_DAYS,
prefs.getInt(WIPE_ON_INACTIVE_DAYS, DEFAULT_WIPE_ON_INACTIVITY_DAYS),
)
set(value) = prefs.edit { putInt(WIPE_ON_INACTIVITY_DAYS, value) }
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) }
}
enum class Trigger(val value: Int) {

View File

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

View File

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

View File

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

View File

@ -32,14 +32,14 @@ class TileService : TileService() {
override fun onStartListening() {
super.onStartListening()
update(
if (prefs.isServiceEnabled && admin.isActive()) Tile.STATE_INACTIVE
if (prefs.isEnabled && admin.isActive()) Tile.STATE_INACTIVE
else Tile.STATE_UNAVAILABLE
)
}
override fun 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) {
try {
admin.lockNow()

View File

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

View File

@ -16,7 +16,7 @@ class WipeJobManager(private val ctx: Context) {
fun schedule(): Int {
return scheduler?.schedule(
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)
.setPersisted(true)
.build()

View File

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

View File

@ -32,7 +32,7 @@
android:orientation="vertical">
<TextView
android:id="@+id/code"
android:id="@+id/authenticationCode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/code_off"
@ -57,13 +57,13 @@
android:textSize="16sp" />
<Space
android:id="@+id/wipeESIMSpace"
android:id="@+id/wipeSpace"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="2dp" />
<CheckBox
android:id="@+id/wipeESIM"
android:id="@+id/wipeEmbeddedSim"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
@ -78,27 +78,6 @@
android:layout_marginVertical="8dp"
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
android:id="@+id/wipeOnInactivitySwitch"
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="tile_label">Modalità aereo</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_description">Cancella i dati quando il dispositivo non viene sbloccato per N 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="foreground_service_description">Ricevi eventi che richiedono il servizio in primo piano</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_tile">Toggle</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_description">Scansiona le notifiche per il codice di autenticazione</string>
<string name="triggers">Attivatori</string>
<string name="copied_popup">Copiato</string>
</resources>

View File

@ -13,8 +13,6 @@
<string name="allow">Разрешить</string>
<string name="tile_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_description">Стереть данные когда устройство не разблокируется N дней.</string>
<string name="wipe_on_inactivity_days">Дней</string>
@ -23,9 +21,6 @@
<string name="wipe_job_schedule_failed_popup">Не удалось запланировать сервис стирания данных</string>
<string name="foreground_service_description">Получать события для которых требуется сервис на переднем плане</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_tile">Плитка</string>
<string name="triggers_array_shortcut">Ярлык</string>
@ -34,4 +29,5 @@
<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">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"?>
<resources>
<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_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="wipe_data_check_box">Wipe data</string>
<string name="wipe_esim_check_box">Wipe eSIM</string>
@ -13,8 +13,6 @@
<string name="allow">Allow</string>
<string name="tile_label">Airplane mode</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_description">Wipe a device when it was not unlocked for N 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_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">Sentry</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="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>
@ -34,4 +29,5 @@
<string name="notification_listener_service_label">Viper</string>
<string name="notification_listener_service_description">Scan notifications for the authentication code</string>
<string name="triggers">Triggers</string>
<string name="copied_popup">Copied</string>
</resources>

View File

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

View File

@ -5,8 +5,8 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10"
classpath 'com.android.tools.build:gradle:7.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.0"
// NOTE: Do not place your application dependencies here; they belong
// 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
Device Administration API, it locks a device and optionally runs wipe.
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 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
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.
Puoi anche:
* limitare il numero massimo di tentativi di password falliti
* 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.

View File

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

View File

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