diff --git a/README.md b/README.md
index d3c55f7..9305f01 100644
--- a/README.md
+++ b/README.md
@@ -17,14 +17,14 @@ Lock a device and wipe its data on emergency.
height="30%">
You can use [PanicKit](https://guardianproject.info/code/panickit/), tile, shortcut or send a
-message with authentication code. On trigger, using
+message with a secret code. On trigger, using
[Device Administration API](https://developer.android.com/guide/topics/admin/device-admin), it
locks a device and optionally runs wipe.
Also you can:
-* wipe a device when it was not unlocked for N time
-* wipe a device using a duress password (companion app: [Duress](https://github.com/x13a/Duress))
-* wipe a device on USB connection event (companion app: [USBKill](https://github.com/x13a/USBKill))
+* fire when a device was not unlocked for N time
+* fire when a USB data connection is made while a device is locked
+* fire when a duress password is entered (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
@@ -34,9 +34,9 @@ Only encrypted device may guarantee that the data will not be recoverable.
## Permissions
-* DEVICE_ADMIN - lock and optionally wipe a device
-* FOREGROUND_SERVICE - receive unlock events
-* RECEIVE_BOOT_COMPLETED - persist wipe job across reboots
+* DEVICE_ADMIN - lock and optionally wipe a device
+* FOREGROUND_SERVICE - receive lock and USB state events
+* RECEIVE_BOOT_COMPLETED - persist lock job and foreground service across reboots
## Example
diff --git a/app/build.gradle b/app/build.gradle
index d7e69f7..5e82cf1 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -10,8 +10,8 @@ android {
applicationId "me.lucky.wasted"
minSdk 23
targetSdk 32
- versionCode 28
- versionName "1.4.3"
+ versionCode 29
+ versionName "1.5.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e241719..00bcc17 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,6 +6,8 @@
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -76,18 +54,20 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -127,16 +156,5 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/wasted/Application.kt b/app/src/main/java/me/lucky/wasted/Application.kt
index 67ec508..eb01f5e 100644
--- a/app/src/main/java/me/lucky/wasted/Application.kt
+++ b/app/src/main/java/me/lucky/wasted/Application.kt
@@ -3,10 +3,9 @@ package me.lucky.wasted
import android.app.Application
import com.google.android.material.color.DynamicColors
-@Suppress("unused")
class Application : Application() {
override fun onCreate() {
super.onCreate()
DynamicColors.applyToActivitiesIfAvailable(this)
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/wasted/DeviceAdminReceiver.kt b/app/src/main/java/me/lucky/wasted/DeviceAdminReceiver.kt
deleted file mode 100644
index e6c9705..0000000
--- a/app/src/main/java/me/lucky/wasted/DeviceAdminReceiver.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package me.lucky.wasted
-
-import android.app.admin.DeviceAdminReceiver
-import android.content.Context
-import android.content.Intent
-import android.widget.Toast
-
-class DeviceAdminReceiver : DeviceAdminReceiver() {
- override fun onDisabled(context: Context, intent: Intent) {
- super.onDisabled(context, intent)
- if (Preferences(context).isEnabled)
- Toast.makeText(context, R.string.service_unavailable_popup, Toast.LENGTH_SHORT).show()
- }
-}
diff --git a/app/src/main/java/me/lucky/wasted/ForegroundService.kt b/app/src/main/java/me/lucky/wasted/ForegroundService.kt
deleted file mode 100644
index ef0c85b..0000000
--- a/app/src/main/java/me/lucky/wasted/ForegroundService.kt
+++ /dev/null
@@ -1,93 +0,0 @@
-package me.lucky.wasted
-
-import android.app.KeyguardManager
-import android.app.Service
-import android.app.job.JobScheduler
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
-import android.os.IBinder
-import androidx.core.app.NotificationCompat
-
-class ForegroundService : Service() {
- companion object {
- private const val NOTIFICATION_ID = 1000
- }
-
- private val lockReceiver = LockReceiver()
-
- override fun onCreate() {
- super.onCreate()
- init()
- }
-
- override fun onDestroy() {
- super.onDestroy()
- deinit()
- }
-
- private fun init() {
- registerReceiver(lockReceiver, IntentFilter().apply {
- addAction(Intent.ACTION_USER_PRESENT)
- addAction(Intent.ACTION_SCREEN_OFF)
- })
- }
-
- private fun deinit() {
- unregisterReceiver(lockReceiver)
- }
-
- override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
- super.onStartCommand(intent, flags, startId)
- startForeground(
- NOTIFICATION_ID,
- NotificationCompat.Builder(this, NotificationManager.CHANNEL_DEFAULT_ID)
- .setContentTitle(getString(R.string.foreground_service_notification_title))
- .setSmallIcon(android.R.drawable.ic_delete)
- .setPriority(NotificationCompat.PRIORITY_LOW)
- .build()
- )
- return START_STICKY
- }
-
- override fun onBind(intent: Intent?): IBinder? {
- return null
- }
-
- private class LockReceiver : BroadcastReceiver() {
- private var unlocked = false
-
- override fun onReceive(context: Context?, intent: Intent?) {
- if (!Preferences.new(context ?: return).isWipeOnInactivity) return
- when (intent?.action) {
- Intent.ACTION_USER_PRESENT -> {
- if (context.getSystemService(KeyguardManager::class.java)
- ?.isDeviceSecure != true) return
- unlocked = true
- WipeJobManager(context).cancel()
- }
- Intent.ACTION_SCREEN_OFF -> {
- if (!unlocked) return
- unlocked = false
- Thread(Runner(context, goAsync())).start()
- }
- }
- }
-
- private class Runner(
- private val ctx: Context,
- private val pendingResult: PendingResult,
- ) : Runnable {
- override fun run() {
- val job = WipeJobManager(ctx)
- var delay = 1000L
- while (job.schedule() != JobScheduler.RESULT_SUCCESS) {
- Thread.sleep(delay)
- delay = delay.shl(1)
- }
- pendingResult.finish()
- }
- }
- }
-}
diff --git a/app/src/main/java/me/lucky/wasted/MainActivity.kt b/app/src/main/java/me/lucky/wasted/MainActivity.kt
index 77b478c..6aed5c5 100644
--- a/app/src/main/java/me/lucky/wasted/MainActivity.kt
+++ b/app/src/main/java/me/lucky/wasted/MainActivity.kt
@@ -1,55 +1,23 @@
package me.lucky.wasted
-import android.content.*
-import android.content.pm.PackageManager
-import android.os.Build
+import android.content.SharedPreferences
import android.os.Bundle
-import android.view.Menu
-import android.view.MenuItem
-import android.view.View
-import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
-import androidx.core.content.ContextCompat
-import androidx.core.widget.doAfterTextChanged
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.google.android.material.snackbar.Snackbar
-import java.util.*
-import java.util.regex.Pattern
-import kotlin.concurrent.timerTask
+import androidx.fragment.app.Fragment
import me.lucky.wasted.databinding.ActivityMainBinding
+import me.lucky.wasted.fragment.*
+import me.lucky.wasted.trigger.shared.NotificationManager
open class MainActivity : AppCompatActivity() {
- companion object {
- private const val CLIPBOARD_CLEAR_DELAY = 30_000L
- private const val MODIFIER_DAYS = 'd'
- private const val MODIFIER_HOURS = 'h'
- private const val MODIFIER_MINUTES = 'm'
- }
-
private lateinit var binding: ActivityMainBinding
private lateinit var prefs: Preferences
private lateinit var prefsdb: Preferences
- private lateinit var admin: DeviceAdminManager
- private val shortcut by lazy { ShortcutManager(this) }
- private val job by lazy { WipeJobManager(this) }
- private val wipeOnInactivityTimeRegex by lazy {
- Pattern.compile("^[1-9]\\d*[$MODIFIER_DAYS$MODIFIER_HOURS$MODIFIER_MINUTES]$") }
- private var clipboardManager: ClipboardManager? = null
- private var clipboardClearTask: Timer? = null
private val prefsListener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
prefs.copyTo(prefsdb, key)
}
- private val registerForDeviceAdmin =
- registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
- when (it.resultCode) {
- RESULT_OK -> setOn()
- else -> binding.toggle.isChecked = false
- }
- }
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
@@ -58,260 +26,62 @@ open class MainActivity : AppCompatActivity() {
setup()
}
- override fun onCreateOptionsMenu(menu: Menu?): Boolean {
- menuInflater.inflate(R.menu.main, menu)
- return super.onCreateOptionsMenu(menu)
+ private fun init() {
+ prefs = Preferences(this)
+ prefsdb = Preferences(this, encrypted = false)
+ prefs.copyTo(prefsdb)
+ NotificationManager(this).createNotificationChannels()
+ replaceFragment(MainFragment())
}
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- if (item.itemId == R.id.triggers) showTriggers()
- return super.onOptionsItemSelected(item)
+ private fun setup() {
+ binding.apply {
+ appBar.setNavigationOnClickListener {
+ drawer.open()
+ }
+ appBar.setOnMenuItemClickListener {
+ when (it.itemId) {
+ R.id.top_settings -> {
+ replaceFragment(when (supportFragmentManager.fragments.last()) {
+ is SettingsFragment ->
+ getFragment(navigation.checkedItem?.itemId ?: R.id.nav_main)
+ else -> SettingsFragment()
+ })
+ true
+ }
+ else -> false
+ }
+ }
+ navigation.setNavigationItemSelectedListener {
+ replaceFragment(getFragment(it.itemId))
+ it.isChecked = true
+ drawer.close()
+ true
+ }
+ }
+ }
+
+ private fun replaceFragment(f: Fragment) {
+ supportFragmentManager
+ .beginTransaction()
+ .replace(binding.fragment.id, f)
+ .commit()
+ }
+
+ private fun getFragment(id: Int) = when (id) {
+ R.id.nav_main -> MainFragment()
+ R.id.nav_trigger_lock -> LockFragment()
+ R.id.top_settings -> SettingsFragment()
+ else -> MainFragment()
}
override fun onStart() {
super.onStart()
prefs.registerListener(prefsListener)
- update()
}
override fun onStop() {
super.onStop()
prefs.unregisterListener(prefsListener)
}
-
- private fun update() {
- if (prefs.isEnabled && !admin.isActive())
- Snackbar.make(
- binding.toggle,
- R.string.service_unavailable_popup,
- Snackbar.LENGTH_SHORT,
- ).show()
- }
-
- private fun init() {
- prefs = Preferences(this)
- prefsdb = Preferences(this, encrypted = false)
- prefs.copyTo(prefsdb)
- admin = DeviceAdminManager(this)
- clipboardManager = getSystemService(ClipboardManager::class.java)
- NotificationManager(this).createNotificationChannels()
- if (prefs.authenticationCode.isEmpty()) prefs.authenticationCode = makeAuthenticationCode()
- updateCodeColorState()
- 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 {
- authenticationCode.text = prefs.authenticationCode
- wipeData.isChecked = prefs.isWipeData
- wipeEmbeddedSim.isChecked = prefs.isWipeEmbeddedSim
- wipeEmbeddedSim.isEnabled = wipeData.isChecked
- wipeOnInactivitySwitch.isChecked = prefs.isWipeOnInactivity
- toggle.isChecked = prefs.isEnabled
- }
- initWipeOnInactivityTime()
- }
-
- private fun hideEmbeddedSim() {
- binding.wipeSpace.visibility = View.GONE
- binding.wipeEmbeddedSim.visibility = View.GONE
- }
-
- private fun hideSecureLockScreenRequired() {
- binding.apply {
- divider.visibility = View.GONE
- wipeOnInactivitySwitch.visibility = View.GONE
- wipeOnInactivityDescription.visibility = View.GONE
- }
- }
-
- private fun initWipeOnInactivityTime() {
- val count = prefs.wipeOnInactivityCount
- val time = when {
- count % (24 * 60) == 0 -> "${count / 24 / 60}$MODIFIER_DAYS"
- count % 60 == 0 -> "${count / 60}$MODIFIER_HOURS"
- else -> "$count$MODIFIER_MINUTES"
- }
- binding.wipeOnInactivityTime.editText?.setText(time)
- }
-
- private fun setup() {
- binding.apply {
- authenticationCode.setOnLongClickListener {
- copyAuthenticationCode()
- true
- }
- wipeData.setOnCheckedChangeListener { _, isChecked ->
- prefs.isWipeData = isChecked
- wipeEmbeddedSim.isEnabled = isChecked
- }
- wipeEmbeddedSim.setOnCheckedChangeListener { _, isChecked ->
- prefs.isWipeEmbeddedSim = isChecked
- }
- wipeOnInactivitySwitch.setOnCheckedChangeListener { _, isChecked ->
- setWipeOnInactivityState(prefs.isEnabled && isChecked)
- prefs.isWipeOnInactivity = isChecked
- }
- wipeOnInactivityTime.editText?.doAfterTextChanged {
- if (wipeOnInactivityTimeRegex.matcher(it?.toString() ?: "").matches()) {
- wipeOnInactivityTime.error = null
- } else {
- wipeOnInactivityTime.error = getString(R.string.wipe_on_inactivity_time_error)
- }
- }
- wipeOnInactivityTime.setEndIconOnClickListener {
- if (wipeOnInactivityTime.error != null) return@setEndIconOnClickListener
- val time = wipeOnInactivityTime.editText?.text?.toString() ?: ""
- if (time.length < 2) return@setEndIconOnClickListener
- val modifier = time.last()
- val i: Int
- try {
- i = time.dropLast(1).toInt()
- } catch (exc: NumberFormatException) { return@setEndIconOnClickListener }
- prefs.wipeOnInactivityCount = when (modifier) {
- MODIFIER_DAYS -> i * 24 * 60
- MODIFIER_HOURS -> i * 60
- MODIFIER_MINUTES -> i
- else -> return@setEndIconOnClickListener
- }
- }
- toggle.setOnCheckedChangeListener { _, isChecked ->
- if (isChecked) requestAdmin() else setOff()
- }
- }
- }
-
- private fun copyAuthenticationCode() {
- clipboardManager?.setPrimaryClip(ClipData.newPlainText("", prefs.authenticationCode))
- if (clipboardManager != null) {
- scheduleClipboardClear()
- Snackbar.make(
- binding.authenticationCode,
- R.string.copied_popup,
- Snackbar.LENGTH_SHORT,
- ).show()
- }
- }
-
- 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("", ""))
- }
- }, CLIPBOARD_CLEAR_DELAY)
- }
-
- private fun showTriggers() {
- var triggers = prefs.triggers
- val values = Trigger.values().toMutableList()
- val strings = resources.getStringArray(R.array.triggers).toMutableList()
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
- strings.removeAt(values.indexOf(Trigger.TILE))
- values.remove(Trigger.TILE)
- }
- MaterialAlertDialogBuilder(this)
- .setTitle(R.string.triggers)
- .setMultiChoiceItems(
- strings.toTypedArray(),
- values.map { triggers.and(it.value) != 0 }.toBooleanArray(),
- ) { _, index, isChecked ->
- val flag = values[index]
- triggers = when (isChecked) {
- true -> triggers.or(flag.value)
- false -> triggers.and(flag.value.inv())
- }
- }
- .setPositiveButton(android.R.string.ok) { _, _ ->
- prefs.triggers = triggers
- setTriggersState(prefs.isEnabled)
- }
- .show()
- }
-
- private fun updateCodeColorState() {
- binding.authenticationCode.setBackgroundColor(getColor(
- if (prefs.triggers != 0) R.color.code_on else R.color.code_off
- ))
- }
-
- private fun setOn() {
- prefs.isEnabled = true
- setWipeOnInactivityState(prefs.isWipeOnInactivity)
- setTriggersState(true)
- }
-
- private fun setTriggersState(value: Boolean) {
- if (value) {
- val triggers = prefs.triggers
- setPanicKitState(triggers.and(Trigger.PANIC_KIT.value) != 0)
- setTileState(triggers.and(Trigger.TILE.value) != 0)
- shortcut.setState(triggers.and(Trigger.SHORTCUT.value) != 0)
- setTriggerReceiverState(triggers.and(Trigger.BROADCAST.value) != 0)
- setNotificationListenerState(triggers.and(Trigger.NOTIFICATION.value) != 0)
- } else {
- setPanicKitState(false)
- setTileState(false)
- shortcut.setState(false)
- setTriggerReceiverState(false)
- setNotificationListenerState(false)
- }
- updateCodeColorState()
- }
-
- private fun setOff() {
- prefs.isEnabled = false
- setWipeOnInactivityState(false)
- setTriggersState(false)
- try {
- admin.remove()
- } catch (exc: SecurityException) {}
- }
-
- private fun requestAdmin() = registerForDeviceAdmin.launch(admin.makeRequestIntent())
- 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) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
- setComponentState(TileService::class.java, value)
- }
- private fun setNotificationListenerState(value: Boolean) =
- setComponentState(NotificationListenerService::class.java, value)
-
- private fun setPanicKitState(value: Boolean) {
- setComponentState(PanicConnectionActivity::class.java, value)
- setComponentState(PanicResponderActivity::class.java, value)
- }
-
- private fun setWipeOnInactivityState(value: Boolean) {
- if (!value) job.cancel()
- setForegroundState(value)
- }
-
- private fun setComponentState(cls: Class<*>, value: Boolean) {
- packageManager.setComponentEnabledSetting(
- ComponentName(this, cls),
- if (value) PackageManager.COMPONENT_ENABLED_STATE_ENABLED else
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
- PackageManager.DONT_KILL_APP,
- )
- }
-
- private fun setForegroundServiceState(value: Boolean) {
- Intent(this.applicationContext, ForegroundService::class.java).also {
- if (value) ContextCompat.startForegroundService(this.applicationContext, it)
- else stopService(it)
- }
- }
-
- private fun setForegroundState(value: Boolean) {
- setForegroundServiceState(value)
- setRestartReceiverState(value)
- }
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/wasted/Preferences.kt b/app/src/main/java/me/lucky/wasted/Preferences.kt
index 3f70fab..cf9d7a9 100644
--- a/app/src/main/java/me/lucky/wasted/Preferences.kt
+++ b/app/src/main/java/me/lucky/wasted/Preferences.kt
@@ -11,19 +11,22 @@ import androidx.security.crypto.MasterKeys
class Preferences(ctx: Context, encrypted: Boolean = true) {
companion object {
- private const val DEFAULT_WIPE_ON_INACTIVITY_COUNT = 7 * 24 * 60
+ private const val DEFAULT_TRIGGER_LOCK_COUNT = 7 * 24 * 60
private const val ENABLED = "enabled"
- private const val AUTHENTICATION_CODE = "authentication_code"
+ private const val SECRET = "secret"
private const val WIPE_DATA = "wipe_data"
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_COUNT = "wipe_on_inactivity_count"
+ private const val TRIGGER_LOCK_COUNT = "trigger_lock_count"
private const val FILE_NAME = "sec_shared_prefs"
+ // migration
+ private const val AUTHENTICATION_CODE = "authentication_code"
+ private const val WIPE_ON_INACTIVITY_COUNT = "wipe_on_inactivity_count"
+
fun new(ctx: Context) = Preferences(
ctx,
encrypted = Build.VERSION.SDK_INT < Build.VERSION_CODES.N ||
@@ -54,9 +57,12 @@ class Preferences(ctx: Context, encrypted: Boolean = true) {
get() = prefs.getInt(TRIGGERS, 0)
set(value) = prefs.edit { putInt(TRIGGERS, value) }
- var authenticationCode: String
- get() = prefs.getString(AUTHENTICATION_CODE, "") ?: ""
- set(value) = prefs.edit { putString(AUTHENTICATION_CODE, value) }
+ var secret: String
+ get() = prefs.getString(
+ SECRET,
+ prefs.getString(AUTHENTICATION_CODE, "") ?: "",
+ ) ?: ""
+ set(value) = prefs.edit { putString(SECRET, value) }
var isWipeData: Boolean
get() = prefs.getBoolean(WIPE_DATA, false)
@@ -66,13 +72,12 @@ class Preferences(ctx: Context, encrypted: Boolean = true) {
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, false)
- set(value) = prefs.edit { putBoolean(WIPE_ON_INACTIVITY, 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) }
+ var triggerLockCount: Int
+ get() = prefs.getInt(
+ TRIGGER_LOCK_COUNT,
+ prefs.getInt(WIPE_ON_INACTIVITY_COUNT, DEFAULT_TRIGGER_LOCK_COUNT),
+ )
+ set(value) = prefs.edit { putInt(TRIGGER_LOCK_COUNT, value) }
fun registerListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) =
prefs.registerOnSharedPreferenceChangeListener(listener)
@@ -100,4 +105,6 @@ enum class Trigger(val value: Int) {
SHORTCUT(1 shl 2),
BROADCAST(1 shl 3),
NOTIFICATION(1 shl 4),
-}
+ LOCK(1 shl 5),
+ USB(1 shl 6),
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/wasted/TriggerReceiver.kt b/app/src/main/java/me/lucky/wasted/TriggerReceiver.kt
index 67e11a1..c1c16c8 100644
--- a/app/src/main/java/me/lucky/wasted/TriggerReceiver.kt
+++ b/app/src/main/java/me/lucky/wasted/TriggerReceiver.kt
@@ -5,28 +5,7 @@ import android.content.Context
import android.content.Intent
class TriggerReceiver : BroadcastReceiver() {
- companion object {
- const val KEY = "code"
- const val ACTION = "me.lucky.wasted.action.TRIGGER"
-
- fun panic(context: Context, intent: Intent?) {
- if (intent?.action != ACTION) return
- val prefs = Preferences.new(context)
- if (!prefs.isEnabled) return
- val code = prefs.authenticationCode
- assert(code.isNotEmpty())
- if (intent.getStringExtra(KEY) != code) return
- val admin = DeviceAdminManager(context)
- try {
- admin.lockNow()
- if (prefs.isWipeData) admin.wipeData()
- } catch (exc: SecurityException) {}
- }
- }
-
override fun onReceive(context: Context?, intent: Intent?) {
- if (Preferences.new(context ?: return).triggers.and(Trigger.BROADCAST.value) == 0)
- return
- panic(context, intent)
+ me.lucky.wasted.trigger.broadcast.BroadcastReceiver().onReceive(context, intent)
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/wasted/Utils.kt b/app/src/main/java/me/lucky/wasted/Utils.kt
new file mode 100644
index 0000000..5a865b7
--- /dev/null
+++ b/app/src/main/java/me/lucky/wasted/Utils.kt
@@ -0,0 +1,87 @@
+package me.lucky.wasted
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Build
+import androidx.core.content.ContextCompat
+
+import me.lucky.wasted.trigger.notification.NotificationListenerService
+import me.lucky.wasted.trigger.panic.PanicConnectionActivity
+import me.lucky.wasted.trigger.panic.PanicResponderActivity
+import me.lucky.wasted.trigger.shared.ForegroundService
+import me.lucky.wasted.trigger.shared.RestartReceiver
+import me.lucky.wasted.trigger.shortcut.ShortcutActivity
+import me.lucky.wasted.trigger.shortcut.ShortcutManager
+import me.lucky.wasted.trigger.tile.TileService
+import me.lucky.wasted.trigger.usb.UsbReceiver
+
+class Utils(private val ctx: Context) {
+ companion object {
+ fun setFlag(key: Int, value: Int, enabled: Boolean) =
+ when(enabled) {
+ true -> key.or(value)
+ false -> key.and(value.inv())
+ }
+ }
+
+ private val shortcut by lazy { ShortcutManager(ctx) }
+
+ fun setEnabled(enabled: Boolean) {
+ val triggers = Preferences(ctx).triggers
+ setPanicKitEnabled(enabled && triggers.and(Trigger.PANIC_KIT.value) != 0)
+ setTileEnabled(enabled && triggers.and(Trigger.TILE.value) != 0)
+ setShortcutEnabled(enabled && triggers.and(Trigger.SHORTCUT.value) != 0)
+ setBroadcastEnabled(enabled && triggers.and(Trigger.BROADCAST.value) != 0)
+ setNotificationEnabled(enabled && triggers.and(Trigger.NOTIFICATION.value) != 0)
+ updateForegroundRequiredEnabled()
+ }
+
+ fun setPanicKitEnabled(enabled: Boolean) {
+ setComponentEnabled(PanicConnectionActivity::class.java, enabled)
+ setComponentEnabled(PanicResponderActivity::class.java, enabled)
+ }
+
+ fun setTileEnabled(enabled: Boolean) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
+ setComponentEnabled(TileService::class.java, enabled)
+ }
+
+ fun setShortcutEnabled(enabled: Boolean) {
+ if (!enabled) shortcut.remove()
+ setComponentEnabled(ShortcutActivity::class.java, enabled)
+ if (enabled) shortcut.push()
+ }
+
+ fun setBroadcastEnabled(enabled: Boolean) =
+ setComponentEnabled(TriggerReceiver::class.java, enabled)
+
+ fun setNotificationEnabled(enabled: Boolean) =
+ setComponentEnabled(NotificationListenerService::class.java, enabled)
+
+ fun updateForegroundRequiredEnabled() {
+ val prefs = Preferences(ctx)
+ val enabled = prefs.isEnabled
+ val triggers = prefs.triggers
+ val isLock = triggers.and(Trigger.LOCK.value) != 0
+ val isUSB = triggers.and(Trigger.USB.value) != 0
+ setForegroundEnabled(enabled && (isLock || isUSB))
+ setComponentEnabled(RestartReceiver::class.java, enabled && (isLock || isUSB))
+ setComponentEnabled(UsbReceiver::class.java, enabled && isUSB)
+ }
+
+ private fun setForegroundEnabled(enabled: Boolean) =
+ Intent(ctx.applicationContext, ForegroundService::class.java).also {
+ if (enabled) ContextCompat.startForegroundService(ctx.applicationContext, it)
+ else ctx.stopService(it)
+ }
+
+ private fun setComponentEnabled(cls: Class<*>, enabled: Boolean) =
+ ctx.packageManager.setComponentEnabledSetting(
+ ComponentName(ctx, cls),
+ if (enabled) PackageManager.COMPONENT_ENABLED_STATE_ENABLED else
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP,
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/wasted/WipeJobService.kt b/app/src/main/java/me/lucky/wasted/WipeJobService.kt
deleted file mode 100644
index ae50800..0000000
--- a/app/src/main/java/me/lucky/wasted/WipeJobService.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package me.lucky.wasted
-
-import android.app.job.JobParameters
-import android.app.job.JobService
-
-class WipeJobService : JobService() {
- override fun onStartJob(params: JobParameters?): Boolean {
- val prefs = Preferences.new(this)
- if (!prefs.isEnabled || !prefs.isWipeOnInactivity) return false
- try {
- DeviceAdminManager(this).wipeData()
- } catch (exc: SecurityException) {}
- return false
- }
-
- override fun onStopJob(params: JobParameters?): Boolean {
- return true
- }
-}
diff --git a/app/src/main/java/me/lucky/wasted/DeviceAdminManager.kt b/app/src/main/java/me/lucky/wasted/admin/DeviceAdminManager.kt
similarity index 96%
rename from app/src/main/java/me/lucky/wasted/DeviceAdminManager.kt
rename to app/src/main/java/me/lucky/wasted/admin/DeviceAdminManager.kt
index cb195d5..35a8403 100644
--- a/app/src/main/java/me/lucky/wasted/DeviceAdminManager.kt
+++ b/app/src/main/java/me/lucky/wasted/admin/DeviceAdminManager.kt
@@ -1,4 +1,4 @@
-package me.lucky.wasted
+package me.lucky.wasted.admin
import android.app.admin.DevicePolicyManager
import android.content.ComponentName
@@ -7,6 +7,8 @@ import android.content.Intent
import android.os.Build
import java.lang.Exception
+import me.lucky.wasted.Preferences
+
class DeviceAdminManager(private val ctx: Context) {
private val dpm = ctx.getSystemService(DevicePolicyManager::class.java)
private val deviceAdmin by lazy { ComponentName(ctx, DeviceAdminReceiver::class.java) }
@@ -42,4 +44,4 @@ class DeviceAdminManager(private val ctx: Context) {
fun makeRequestIntent() =
Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN)
.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, deviceAdmin)
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/wasted/admin/DeviceAdminReceiver.kt b/app/src/main/java/me/lucky/wasted/admin/DeviceAdminReceiver.kt
new file mode 100644
index 0000000..2e11a9a
--- /dev/null
+++ b/app/src/main/java/me/lucky/wasted/admin/DeviceAdminReceiver.kt
@@ -0,0 +1,5 @@
+package me.lucky.wasted.admin
+
+import android.app.admin.DeviceAdminReceiver
+
+class DeviceAdminReceiver : DeviceAdminReceiver()
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/wasted/fragment/LockFragment.kt b/app/src/main/java/me/lucky/wasted/fragment/LockFragment.kt
new file mode 100644
index 0000000..3a00582
--- /dev/null
+++ b/app/src/main/java/me/lucky/wasted/fragment/LockFragment.kt
@@ -0,0 +1,74 @@
+package me.lucky.wasted.fragment
+
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.widget.doAfterTextChanged
+import androidx.fragment.app.Fragment
+import java.util.regex.Pattern
+
+import me.lucky.wasted.Preferences
+import me.lucky.wasted.R
+import me.lucky.wasted.databinding.FragmentLockBinding
+
+class LockFragment : Fragment() {
+ companion object {
+ private const val MODIFIER_DAYS = 'd'
+ private const val MODIFIER_HOURS = 'h'
+ private const val MODIFIER_MINUTES = 'm'
+ }
+
+ private lateinit var binding: FragmentLockBinding
+ private lateinit var ctx: Context
+ private lateinit var prefs: Preferences
+ private val lockCountPattern by lazy {
+ Pattern.compile("^[1-9]\\d*[$MODIFIER_DAYS$MODIFIER_HOURS$MODIFIER_MINUTES]$") }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ binding = FragmentLockBinding.inflate(inflater, container, false)
+ init()
+ setup()
+ return binding.root
+ }
+
+ private fun init() {
+ ctx = requireContext()
+ prefs = Preferences(ctx)
+ val count = prefs.triggerLockCount
+ val time = when {
+ count % (24 * 60) == 0 -> "${count / 24 / 60}$MODIFIER_DAYS"
+ count % 60 == 0 -> "${count / 60}$MODIFIER_HOURS"
+ else -> "$count$MODIFIER_MINUTES"
+ }
+ binding.time.editText?.setText(time)
+ }
+
+ private fun setup() = binding.apply {
+ time.editText?.doAfterTextChanged {
+ val str = it?.toString() ?: ""
+ if (!lockCountPattern.matcher(str).matches()) {
+ time.error = ctx.getString(R.string.trigger_lock_time_error)
+ return@doAfterTextChanged
+ }
+ if (str.length < 2) return@doAfterTextChanged
+ val modifier = str.last()
+ val i: Int
+ try {
+ i = str.dropLast(1).toInt()
+ } catch (exc: NumberFormatException) { return@doAfterTextChanged }
+ prefs.triggerLockCount = when (modifier) {
+ MODIFIER_DAYS -> i * 24 * 60
+ MODIFIER_HOURS -> i * 60
+ MODIFIER_MINUTES -> i
+ else -> return@doAfterTextChanged
+ }
+ time.error = null
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/wasted/fragment/MainFragment.kt b/app/src/main/java/me/lucky/wasted/fragment/MainFragment.kt
new file mode 100644
index 0000000..60a2250
--- /dev/null
+++ b/app/src/main/java/me/lucky/wasted/fragment/MainFragment.kt
@@ -0,0 +1,104 @@
+package me.lucky.wasted.fragment
+
+import android.app.Activity
+import android.content.ClipData
+import android.content.ClipboardManager
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.fragment.app.Fragment
+import com.google.android.material.snackbar.Snackbar
+import java.util.*
+
+import me.lucky.wasted.Preferences
+import me.lucky.wasted.R
+import me.lucky.wasted.Utils
+import me.lucky.wasted.admin.DeviceAdminManager
+import me.lucky.wasted.databinding.FragmentMainBinding
+
+class MainFragment : Fragment() {
+ private lateinit var binding: FragmentMainBinding
+ private lateinit var ctx: Context
+ private lateinit var prefs: Preferences
+ private val clipboardManager by lazy { ctx.getSystemService(ClipboardManager::class.java) }
+ private val admin by lazy { DeviceAdminManager(ctx) }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ binding = FragmentMainBinding.inflate(inflater, container, false)
+ init()
+ setup()
+ return binding.root
+ }
+
+ override fun onStart() {
+ super.onStart()
+ updateSecretColor()
+ }
+
+ private fun init() {
+ ctx = requireContext()
+ prefs = Preferences(ctx)
+ if (prefs.secret.isEmpty()) prefs.secret = makeSecret()
+ binding.apply {
+ secret.text = prefs.secret
+ wipeData.isChecked = prefs.isWipeData
+ wipeEmbeddedSim.isChecked = prefs.isWipeEmbeddedSim
+ wipeEmbeddedSim.isEnabled = wipeData.isChecked
+ toggle.isChecked = prefs.isEnabled
+ }
+ }
+
+ private fun setup() = binding.apply {
+ secret.setOnLongClickListener {
+ copySecret()
+ true
+ }
+ wipeData.setOnCheckedChangeListener { _, isChecked ->
+ prefs.isWipeData = isChecked
+ wipeEmbeddedSim.isEnabled = isChecked
+ }
+ wipeEmbeddedSim.setOnCheckedChangeListener { _, isChecked ->
+ prefs.isWipeEmbeddedSim = isChecked
+ }
+ toggle.setOnCheckedChangeListener { _, isChecked ->
+ if (isChecked) requestAdmin() else setOff()
+ }
+ }
+
+ private fun copySecret() {
+ clipboardManager.setPrimaryClip(ClipData.newPlainText("", prefs.secret))
+ Snackbar.make(binding.secret, R.string.copied_popup, Snackbar.LENGTH_SHORT).show()
+ }
+
+ private fun updateSecretColor() = binding.secret.setBackgroundColor(ctx.getColor(
+ if (prefs.triggers != 0) R.color.secret_1 else R.color.secret_0
+ ))
+
+ private fun setOn() {
+ prefs.isEnabled = true
+ Utils(ctx).setEnabled(true)
+ binding.toggle.isChecked = true
+ }
+
+ private fun setOff() {
+ prefs.isEnabled = false
+ Utils(ctx).setEnabled(false)
+ try { admin.remove() } catch (exc: SecurityException) {}
+ binding.toggle.isChecked = false
+ }
+
+ private val registerForDeviceAdmin =
+ registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+ if (it.resultCode == Activity.RESULT_OK) setOn() else setOff()
+ }
+
+ private fun requestAdmin() = registerForDeviceAdmin.launch(admin.makeRequestIntent())
+ private fun makeSecret() = UUID.randomUUID().toString()
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/wasted/fragment/SettingsFragment.kt b/app/src/main/java/me/lucky/wasted/fragment/SettingsFragment.kt
new file mode 100644
index 0000000..3b3d226
--- /dev/null
+++ b/app/src/main/java/me/lucky/wasted/fragment/SettingsFragment.kt
@@ -0,0 +1,80 @@
+package me.lucky.wasted.fragment
+
+import android.content.Context
+import android.os.Build
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+
+import me.lucky.wasted.Preferences
+import me.lucky.wasted.Trigger
+import me.lucky.wasted.Utils
+import me.lucky.wasted.databinding.FragmentSettingsBinding
+
+class SettingsFragment : Fragment() {
+ private lateinit var binding: FragmentSettingsBinding
+ private lateinit var ctx: Context
+ private lateinit var prefs: Preferences
+ private val utils by lazy { Utils(ctx) }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ binding = FragmentSettingsBinding.inflate(inflater, container, false)
+ init()
+ setup()
+ return binding.root
+ }
+
+ private fun init() {
+ ctx = requireContext()
+ prefs = Preferences(ctx)
+ binding.apply {
+ val triggers = prefs.triggers
+ panicKit.isChecked = triggers.and(Trigger.PANIC_KIT.value) != 0
+ tile.isChecked = triggers.and(Trigger.TILE.value) != 0
+ tile.isEnabled = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
+ shortcut.isChecked = triggers.and(Trigger.SHORTCUT.value) != 0
+ broadcast.isChecked = triggers.and(Trigger.BROADCAST.value) != 0
+ notification.isChecked = triggers.and(Trigger.NOTIFICATION.value) != 0
+ lock.isChecked = triggers.and(Trigger.LOCK.value) != 0
+ usb.isChecked = triggers.and(Trigger.USB.value) != 0
+ }
+ }
+
+ private fun setup() = binding.apply {
+ panicKit.setOnCheckedChangeListener { _, isChecked ->
+ prefs.triggers = Utils.setFlag(prefs.triggers, Trigger.PANIC_KIT.value, isChecked)
+ utils.setPanicKitEnabled(isChecked && prefs.isEnabled)
+ }
+ tile.setOnCheckedChangeListener { _, isChecked ->
+ prefs.triggers = Utils.setFlag(prefs.triggers, Trigger.TILE.value, isChecked)
+ utils.setTileEnabled(isChecked && prefs.isEnabled)
+ }
+ shortcut.setOnCheckedChangeListener { _, isChecked ->
+ prefs.triggers = Utils.setFlag(prefs.triggers, Trigger.SHORTCUT.value, isChecked)
+ utils.setShortcutEnabled(isChecked && prefs.isEnabled)
+ }
+ broadcast.setOnCheckedChangeListener { _, isChecked ->
+ prefs.triggers = Utils.setFlag(prefs.triggers, Trigger.BROADCAST.value, isChecked)
+ utils.setBroadcastEnabled(isChecked && prefs.isEnabled)
+ }
+ notification.setOnCheckedChangeListener { _, isChecked ->
+ prefs.triggers =
+ Utils.setFlag(prefs.triggers, Trigger.NOTIFICATION.value, isChecked)
+ utils.setNotificationEnabled(isChecked && prefs.isEnabled)
+ }
+ lock.setOnCheckedChangeListener { _, isChecked ->
+ prefs.triggers = Utils.setFlag(prefs.triggers, Trigger.LOCK.value, isChecked)
+ utils.updateForegroundRequiredEnabled()
+ }
+ usb.setOnCheckedChangeListener { _, isChecked ->
+ prefs.triggers = Utils.setFlag(prefs.triggers, Trigger.USB.value, isChecked)
+ utils.updateForegroundRequiredEnabled()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/wasted/trigger/broadcast/BroadcastReceiver.kt b/app/src/main/java/me/lucky/wasted/trigger/broadcast/BroadcastReceiver.kt
new file mode 100644
index 0000000..db79190
--- /dev/null
+++ b/app/src/main/java/me/lucky/wasted/trigger/broadcast/BroadcastReceiver.kt
@@ -0,0 +1,35 @@
+package me.lucky.wasted.trigger.broadcast
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+
+import me.lucky.wasted.Preferences
+import me.lucky.wasted.Trigger
+import me.lucky.wasted.admin.DeviceAdminManager
+
+class BroadcastReceiver : BroadcastReceiver() {
+ companion object {
+ const val KEY = "code"
+ const val ACTION = "me.lucky.wasted.action.TRIGGER"
+
+ fun panic(context: Context, intent: Intent?) {
+ if (intent?.action != ACTION) return
+ val prefs = Preferences.new(context)
+ if (!prefs.isEnabled) return
+ val secret = prefs.secret
+ assert(secret.isNotEmpty())
+ if (intent.getStringExtra(KEY) != secret) return
+ val admin = DeviceAdminManager(context)
+ try {
+ admin.lockNow()
+ if (prefs.isWipeData) admin.wipeData()
+ } catch (exc: SecurityException) {}
+ }
+ }
+
+ override fun onReceive(context: Context?, intent: Intent?) {
+ if (Preferences.new(context ?: return).triggers.and(Trigger.BROADCAST.value) != 0)
+ panic(context, intent)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/wasted/WipeJobManager.kt b/app/src/main/java/me/lucky/wasted/trigger/lock/LockJobManager.kt
similarity index 77%
rename from app/src/main/java/me/lucky/wasted/WipeJobManager.kt
rename to app/src/main/java/me/lucky/wasted/trigger/lock/LockJobManager.kt
index 1f8d360..767a730 100644
--- a/app/src/main/java/me/lucky/wasted/WipeJobManager.kt
+++ b/app/src/main/java/me/lucky/wasted/trigger/lock/LockJobManager.kt
@@ -1,4 +1,4 @@
-package me.lucky.wasted
+package me.lucky.wasted.trigger.lock
import android.app.job.JobInfo
import android.app.job.JobScheduler
@@ -6,7 +6,9 @@ import android.content.ComponentName
import android.content.Context
import java.util.concurrent.TimeUnit
-class WipeJobManager(private val ctx: Context) {
+import me.lucky.wasted.Preferences
+
+class LockJobManager(private val ctx: Context) {
companion object {
private const val JOB_ID = 1000
}
@@ -15,8 +17,8 @@ class WipeJobManager(private val ctx: Context) {
fun schedule(): Int {
return scheduler?.schedule(
- JobInfo.Builder(JOB_ID, ComponentName(ctx, WipeJobService::class.java))
- .setMinimumLatency(TimeUnit.MINUTES.toMillis(prefs.wipeOnInactivityCount.toLong()))
+ JobInfo.Builder(JOB_ID, ComponentName(ctx, LockJobService::class.java))
+ .setMinimumLatency(TimeUnit.MINUTES.toMillis(prefs.triggerLockCount.toLong()))
.setBackoffCriteria(0, JobInfo.BACKOFF_POLICY_LINEAR)
.setPersisted(true)
.build()
@@ -24,4 +26,4 @@ class WipeJobManager(private val ctx: Context) {
}
fun cancel() = scheduler?.cancel(JOB_ID)
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/wasted/trigger/lock/LockJobService.kt b/app/src/main/java/me/lucky/wasted/trigger/lock/LockJobService.kt
new file mode 100644
index 0000000..66be7a3
--- /dev/null
+++ b/app/src/main/java/me/lucky/wasted/trigger/lock/LockJobService.kt
@@ -0,0 +1,23 @@
+package me.lucky.wasted.trigger.lock
+
+import android.app.job.JobParameters
+import android.app.job.JobService
+
+import me.lucky.wasted.admin.DeviceAdminManager
+import me.lucky.wasted.Preferences
+import me.lucky.wasted.Trigger
+
+class LockJobService : JobService() {
+ override fun onStartJob(params: JobParameters?): Boolean {
+ val prefs = Preferences.new(this)
+ if (!prefs.isEnabled || prefs.triggers.and(Trigger.LOCK.value) == 0) return false
+ val admin = DeviceAdminManager(this)
+ try {
+ admin.lockNow()
+ if (prefs.isWipeData) admin.wipeData()
+ } catch (exc: SecurityException) {}
+ return false
+ }
+
+ override fun onStopJob(params: JobParameters?): Boolean { return true }
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/wasted/NotificationListenerService.kt b/app/src/main/java/me/lucky/wasted/trigger/notification/NotificationListenerService.kt
similarity index 82%
rename from app/src/main/java/me/lucky/wasted/NotificationListenerService.kt
rename to app/src/main/java/me/lucky/wasted/trigger/notification/NotificationListenerService.kt
index c91f95d..8b988dd 100644
--- a/app/src/main/java/me/lucky/wasted/NotificationListenerService.kt
+++ b/app/src/main/java/me/lucky/wasted/trigger/notification/NotificationListenerService.kt
@@ -1,10 +1,14 @@
-package me.lucky.wasted
+package me.lucky.wasted.trigger.notification
import android.app.Notification
import android.os.Build
import android.service.notification.NotificationListenerService
import android.service.notification.StatusBarNotification
+import me.lucky.wasted.admin.DeviceAdminManager
+import me.lucky.wasted.Preferences
+import me.lucky.wasted.Trigger
+
class NotificationListenerService : NotificationListenerService() {
private lateinit var prefs: Preferences
private lateinit var admin: DeviceAdminManager
@@ -24,9 +28,9 @@ class NotificationListenerService : NotificationListenerService() {
if (sbn == null ||
!prefs.isEnabled ||
prefs.triggers.and(Trigger.NOTIFICATION.value) == 0) return
- val code = prefs.authenticationCode
- assert(code.isNotEmpty())
- if (sbn.notification.extras[Notification.EXTRA_TEXT]?.toString() != code) return
+ val secret = prefs.secret
+ assert(secret.isNotEmpty())
+ if (sbn.notification.extras[Notification.EXTRA_TEXT]?.toString() != secret) return
cancelAllNotifications()
try {
admin.lockNow()
@@ -39,4 +43,4 @@ class NotificationListenerService : NotificationListenerService() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
migrateNotificationFilter(0, null)
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/wasted/PanicConnectionActivity.kt b/app/src/main/java/me/lucky/wasted/trigger/panic/PanicConnectionActivity.kt
similarity index 94%
rename from app/src/main/java/me/lucky/wasted/PanicConnectionActivity.kt
rename to app/src/main/java/me/lucky/wasted/trigger/panic/PanicConnectionActivity.kt
index 964df8c..f1b9e6c 100644
--- a/app/src/main/java/me/lucky/wasted/PanicConnectionActivity.kt
+++ b/app/src/main/java/me/lucky/wasted/trigger/panic/PanicConnectionActivity.kt
@@ -1,10 +1,12 @@
-package me.lucky.wasted
+package me.lucky.wasted.trigger.panic
import android.content.pm.PackageManager
import android.os.Bundle
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import info.guardianproject.panic.PanicResponder
+import me.lucky.wasted.MainActivity
+import me.lucky.wasted.R
class PanicConnectionActivity : MainActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
@@ -42,4 +44,4 @@ class PanicConnectionActivity : MainActivity() {
}
.show()
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/wasted/PanicResponderActivity.kt b/app/src/main/java/me/lucky/wasted/trigger/panic/PanicResponderActivity.kt
similarity index 85%
rename from app/src/main/java/me/lucky/wasted/PanicResponderActivity.kt
rename to app/src/main/java/me/lucky/wasted/trigger/panic/PanicResponderActivity.kt
index 460a49b..0b52129 100644
--- a/app/src/main/java/me/lucky/wasted/PanicResponderActivity.kt
+++ b/app/src/main/java/me/lucky/wasted/trigger/panic/PanicResponderActivity.kt
@@ -1,10 +1,13 @@
-package me.lucky.wasted
+package me.lucky.wasted.trigger.panic
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import info.guardianproject.panic.Panic
import info.guardianproject.panic.PanicResponder
+import me.lucky.wasted.admin.DeviceAdminManager
+import me.lucky.wasted.Preferences
+import me.lucky.wasted.Trigger
class PanicResponderActivity : AppCompatActivity() {
private val prefs by lazy { Preferences.new(this) }
@@ -26,4 +29,4 @@ class PanicResponderActivity : AppCompatActivity() {
} catch (exc: SecurityException) {}
finishAndRemoveTask()
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/wasted/trigger/shared/ForegroundService.kt b/app/src/main/java/me/lucky/wasted/trigger/shared/ForegroundService.kt
new file mode 100644
index 0000000..c0583b6
--- /dev/null
+++ b/app/src/main/java/me/lucky/wasted/trigger/shared/ForegroundService.kt
@@ -0,0 +1,128 @@
+package me.lucky.wasted.trigger.shared
+
+import android.app.KeyguardManager
+import android.app.Service
+import android.app.job.JobScheduler
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.IBinder
+import androidx.core.app.NotificationCompat
+
+import me.lucky.wasted.Preferences
+import me.lucky.wasted.R
+import me.lucky.wasted.Trigger
+import me.lucky.wasted.admin.DeviceAdminManager
+import me.lucky.wasted.trigger.lock.LockJobManager
+
+class ForegroundService : Service() {
+ companion object {
+ private const val NOTIFICATION_ID = 1000
+ private const val ACTION_USB_STATE = "android.hardware.usb.action.USB_STATE"
+ }
+
+ private lateinit var prefs: Preferences
+ private lateinit var lockReceiver: LockReceiver
+ private val usbReceiver = UsbReceiver()
+
+ override fun onCreate() {
+ super.onCreate()
+ init()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ deinit()
+ }
+
+ private fun init() {
+ prefs = Preferences.new(this)
+ lockReceiver = LockReceiver(getSystemService(KeyguardManager::class.java).isDeviceLocked)
+ val triggers = prefs.triggers
+ if (triggers.and(Trigger.LOCK.value) != 0)
+ registerReceiver(lockReceiver, IntentFilter().apply {
+ addAction(Intent.ACTION_USER_PRESENT)
+ addAction(Intent.ACTION_SCREEN_OFF)
+ })
+ if (triggers.and(Trigger.USB.value) != 0)
+ registerReceiver(usbReceiver, IntentFilter(ACTION_USB_STATE))
+ }
+
+ private fun deinit() {
+ try {
+ unregisterReceiver(lockReceiver)
+ unregisterReceiver(usbReceiver)
+ } catch (exc: IllegalArgumentException) {}
+ }
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ super.onStartCommand(intent, flags, startId)
+ startForeground(
+ NOTIFICATION_ID,
+ NotificationCompat.Builder(this, NotificationManager.CHANNEL_DEFAULT_ID)
+ .setContentTitle(getString(R.string.foreground_service_notification_title))
+ .setSmallIcon(android.R.drawable.ic_delete)
+ .setPriority(NotificationCompat.PRIORITY_LOW)
+ .build()
+ )
+ return START_STICKY
+ }
+
+ override fun onBind(intent: Intent?): IBinder? { return null }
+
+ private class LockReceiver(private var locked: Boolean) : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ if (Preferences.new(context ?: return).triggers.and(Trigger.LOCK.value) == 0)
+ return
+ when (intent?.action) {
+ Intent.ACTION_USER_PRESENT -> {
+ locked = false
+ LockJobManager(context).cancel()
+ }
+ Intent.ACTION_SCREEN_OFF -> {
+ if (locked) return
+ locked = true
+ Thread(Runner(context, goAsync())).start()
+ }
+ }
+ }
+
+ private class Runner(
+ private val ctx: Context,
+ private val pendingResult: PendingResult,
+ ) : Runnable {
+ override fun run() {
+ val job = LockJobManager(ctx)
+ var delay = 1000L
+ while (job.schedule() != JobScheduler.RESULT_SUCCESS) {
+ Thread.sleep(delay)
+ delay = delay.shl(1)
+ }
+ pendingResult.finish()
+ }
+ }
+ }
+
+ private class UsbReceiver : BroadcastReceiver() {
+ companion object {
+ private const val KEY_1 = "connected"
+ private const val KEY_2 = "host_connected"
+ }
+
+ override fun onReceive(context: Context?, intent: Intent?) {
+ if (intent?.action != ACTION_USB_STATE) return
+ val prefs = Preferences.new(context ?: return)
+ if (!prefs.isEnabled ||
+ prefs.triggers.and(Trigger.USB.value) == 0 ||
+ !context.getSystemService(KeyguardManager::class.java).isDeviceLocked) return
+ val extras = intent.extras ?: return
+ if (!extras.getBoolean(KEY_1) && !extras.getBoolean(KEY_2)) return
+ val admin = DeviceAdminManager(context)
+ try {
+ admin.lockNow()
+ if (prefs.isWipeData) admin.wipeData()
+ } catch (exc: SecurityException) {}
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/wasted/NotificationManager.kt b/app/src/main/java/me/lucky/wasted/trigger/shared/NotificationManager.kt
similarity index 90%
rename from app/src/main/java/me/lucky/wasted/NotificationManager.kt
rename to app/src/main/java/me/lucky/wasted/trigger/shared/NotificationManager.kt
index 23e2c2d..f84e396 100644
--- a/app/src/main/java/me/lucky/wasted/NotificationManager.kt
+++ b/app/src/main/java/me/lucky/wasted/trigger/shared/NotificationManager.kt
@@ -1,9 +1,11 @@
-package me.lucky.wasted
+package me.lucky.wasted.trigger.shared
import android.content.Context
import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationManagerCompat
+import me.lucky.wasted.R
+
class NotificationManager(private val ctx: Context) {
companion object {
const val CHANNEL_DEFAULT_ID = "default"
@@ -17,4 +19,4 @@ class NotificationManager(private val ctx: Context) {
NotificationManagerCompat.IMPORTANCE_LOW,
).setName(ctx.getString(R.string.notification_channel_default_name)).build())
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/wasted/RestartReceiver.kt b/app/src/main/java/me/lucky/wasted/trigger/shared/RestartReceiver.kt
similarity index 70%
rename from app/src/main/java/me/lucky/wasted/RestartReceiver.kt
rename to app/src/main/java/me/lucky/wasted/trigger/shared/RestartReceiver.kt
index 10c1da1..64bde36 100644
--- a/app/src/main/java/me/lucky/wasted/RestartReceiver.kt
+++ b/app/src/main/java/me/lucky/wasted/trigger/shared/RestartReceiver.kt
@@ -1,20 +1,26 @@
-package me.lucky.wasted
+package me.lucky.wasted.trigger.shared
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import androidx.core.content.ContextCompat
+import me.lucky.wasted.Preferences
+import me.lucky.wasted.Trigger
+
class RestartReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action != Intent.ACTION_LOCKED_BOOT_COMPLETED &&
intent?.action != Intent.ACTION_BOOT_COMPLETED &&
intent?.action != Intent.ACTION_MY_PACKAGE_REPLACED) return
val prefs = Preferences.new(context ?: return)
- if (!prefs.isEnabled || !prefs.isWipeOnInactivity) return
+ val triggers = prefs.triggers
+ if (!prefs.isEnabled || (
+ triggers.and(Trigger.LOCK.value) == 0 &&
+ triggers.and(Trigger.USB.value) == 0)) return
ContextCompat.startForegroundService(
context.applicationContext,
Intent(context.applicationContext, ForegroundService::class.java),
)
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/wasted/ShortcutActivity.kt b/app/src/main/java/me/lucky/wasted/trigger/shortcut/ShortcutActivity.kt
similarity index 64%
rename from app/src/main/java/me/lucky/wasted/ShortcutActivity.kt
rename to app/src/main/java/me/lucky/wasted/trigger/shortcut/ShortcutActivity.kt
index 1d86a9c..2208e25 100644
--- a/app/src/main/java/me/lucky/wasted/ShortcutActivity.kt
+++ b/app/src/main/java/me/lucky/wasted/trigger/shortcut/ShortcutActivity.kt
@@ -1,8 +1,12 @@
-package me.lucky.wasted
+package me.lucky.wasted.trigger.shortcut
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
+import me.lucky.wasted.Preferences
+import me.lucky.wasted.Trigger
+import me.lucky.wasted.trigger.broadcast.BroadcastReceiver
+
class ShortcutActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -10,7 +14,7 @@ class ShortcutActivity : AppCompatActivity() {
finishAndRemoveTask()
return
}
- TriggerReceiver.panic(this, intent)
+ BroadcastReceiver.panic(this, intent)
finishAndRemoveTask()
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/wasted/ShortcutManager.kt b/app/src/main/java/me/lucky/wasted/trigger/shortcut/ShortcutManager.kt
similarity index 67%
rename from app/src/main/java/me/lucky/wasted/ShortcutManager.kt
rename to app/src/main/java/me/lucky/wasted/trigger/shortcut/ShortcutManager.kt
index b34058c..fb4689e 100644
--- a/app/src/main/java/me/lucky/wasted/ShortcutManager.kt
+++ b/app/src/main/java/me/lucky/wasted/trigger/shortcut/ShortcutManager.kt
@@ -1,4 +1,4 @@
-package me.lucky.wasted
+package me.lucky.wasted.trigger.shortcut
import android.content.Context
import android.content.Intent
@@ -6,6 +6,10 @@ import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
+import me.lucky.wasted.Preferences
+import me.lucky.wasted.R
+import me.lucky.wasted.trigger.broadcast.BroadcastReceiver
+
class ShortcutManager(private val ctx: Context) {
companion object {
private const val SHORTCUT_ID = "panic"
@@ -13,22 +17,19 @@ class ShortcutManager(private val ctx: Context) {
private val prefs by lazy { Preferences(ctx) }
- private fun push() {
+ fun push() =
ShortcutManagerCompat.pushDynamicShortcut(
ctx,
ShortcutInfoCompat.Builder(ctx, SHORTCUT_ID)
.setShortLabel(ctx.getString(R.string.shortcut_label))
.setIcon(IconCompat.createWithResource(ctx, android.R.drawable.ic_delete))
.setIntent(
- Intent(TriggerReceiver.ACTION)
+ Intent(BroadcastReceiver.ACTION)
.setClass(ctx, ShortcutActivity::class.java)
- .putExtra(TriggerReceiver.KEY, prefs.authenticationCode)
+ .putExtra(BroadcastReceiver.KEY, prefs.secret)
)
.build(),
)
- }
- private fun remove() =
- ShortcutManagerCompat.removeDynamicShortcuts(ctx, arrayListOf(SHORTCUT_ID))
- fun setState(value: Boolean) = if (value) push() else remove()
-}
+ fun remove() = ShortcutManagerCompat.removeDynamicShortcuts(ctx, arrayListOf(SHORTCUT_ID))
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/wasted/TileService.kt b/app/src/main/java/me/lucky/wasted/trigger/tile/TileService.kt
similarity index 92%
rename from app/src/main/java/me/lucky/wasted/TileService.kt
rename to app/src/main/java/me/lucky/wasted/trigger/tile/TileService.kt
index cba326f..75bcea5 100644
--- a/app/src/main/java/me/lucky/wasted/TileService.kt
+++ b/app/src/main/java/me/lucky/wasted/trigger/tile/TileService.kt
@@ -1,4 +1,4 @@
-package me.lucky.wasted
+package me.lucky.wasted.trigger.tile
import android.os.Build
import android.service.quicksettings.Tile
@@ -7,6 +7,10 @@ import androidx.annotation.RequiresApi
import java.util.*
import kotlin.concurrent.timerTask
+import me.lucky.wasted.admin.DeviceAdminManager
+import me.lucky.wasted.Preferences
+import me.lucky.wasted.Trigger
+
@RequiresApi(Build.VERSION_CODES.N)
class TileService : TileService() {
companion object {
@@ -71,4 +75,4 @@ class TileService : TileService() {
qsTile.state = tileState
qsTile.updateTile()
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/wasted/trigger/usb/UsbReceiver.kt b/app/src/main/java/me/lucky/wasted/trigger/usb/UsbReceiver.kt
new file mode 100644
index 0000000..7673f41
--- /dev/null
+++ b/app/src/main/java/me/lucky/wasted/trigger/usb/UsbReceiver.kt
@@ -0,0 +1,27 @@
+package me.lucky.wasted.trigger.usb
+
+import android.app.KeyguardManager
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.hardware.usb.UsbManager
+
+import me.lucky.wasted.Preferences
+import me.lucky.wasted.Trigger
+import me.lucky.wasted.admin.DeviceAdminManager
+
+class UsbReceiver : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ if (intent?.action != UsbManager.ACTION_USB_DEVICE_ATTACHED &&
+ intent?.action != UsbManager.ACTION_USB_ACCESSORY_ATTACHED) return
+ val prefs = Preferences.new(context ?: return)
+ if (!prefs.isEnabled ||
+ prefs.triggers.and(Trigger.USB.value) == 0 ||
+ !context.getSystemService(KeyguardManager::class.java).isDeviceLocked) return
+ val admin = DeviceAdminManager(context)
+ try {
+ admin.lockNow()
+ if (prefs.isWipeData) admin.wipeData()
+ } catch (exc: SecurityException) {}
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_baseline_check_circle_24.xml b/app/src/main/res/drawable/ic_baseline_menu_24.xml
similarity index 54%
rename from app/src/main/res/drawable/ic_baseline_check_circle_24.xml
rename to app/src/main/res/drawable/ic_baseline_menu_24.xml
index b83d1bc..0c86f36 100644
--- a/app/src/main/res/drawable/ic_baseline_check_circle_24.xml
+++ b/app/src/main/res/drawable/ic_baseline_menu_24.xml
@@ -1,5 +1,5 @@
-
-
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 6101854..27419ee 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,119 +1,45 @@
-
-
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true">
-
+ android:layout_height="wrap_content">
-
+ android:layout_height="?attr/actionBarSize"
+ app:menu="@menu/top"
+ app:navigationIcon="@drawable/ic_baseline_menu_24" />
-
+
-
+
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_lock.xml b/app/src/main/res/layout/fragment_lock.xml
new file mode 100644
index 0000000..c18c8db
--- /dev/null
+++ b/app/src/main/res/layout/fragment_lock.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml
new file mode 100644
index 0000000..ff3db33
--- /dev/null
+++ b/app/src/main/res/layout/fragment_main.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml
new file mode 100644
index 0000000..1c11b72
--- /dev/null
+++ b/app/src/main/res/layout/fragment_settings.xml
@@ -0,0 +1,147 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/nav.xml b/app/src/main/res/menu/nav.xml
new file mode 100644
index 0000000..7c0aa2f
--- /dev/null
+++ b/app/src/main/res/menu/nav.xml
@@ -0,0 +1,20 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/top.xml
similarity index 75%
rename from app/src/main/res/menu/main.xml
rename to app/src/main/res/menu/top.xml
index d883b2b..a818682 100644
--- a/app/src/main/res/menu/main.xml
+++ b/app/src/main/res/menu/top.xml
@@ -3,9 +3,9 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index f07546b..08aa4cb 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -1,24 +1,21 @@
Wasted
- Geräteadministrator nicht verfügbar
- Daten löschen
- eSim löschen
+ Daten löschen
+ eSim löschen
Bestätige Panik App
Bist du sicher, dass du %1$s erlauben willst, destruktive Aktionen auslösen zu lassen\?
eine unbekannte App
Zulassen
Flugmodus
Panik
- Bei Inaktivität löschen
- Den Speicher des Geräts löschen, wenn es für N Tage nicht entsperrt wurde.
+ Den Speicher des Geräts löschen, wenn es für N Tage nicht entsperrt wurde.
Standard
Guard
- PanicKit
- Tile
- Shortcut
- Broadcast
- Benachrichtigung
- Auslöser
+ PanicKit
+ Tile
+ Shortcut
+ Broadcast
+ Benachrichtigung
Kopiert
diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml
index d7daf4d..5b18cc5 100644
--- a/app/src/main/res/values-es-rES/strings.xml
+++ b/app/src/main/res/values-es-rES/strings.xml
@@ -1,24 +1,21 @@
Wasted
- Administrador de dispositivos no disponible
- Borrar datos
- Borrar eSIM
+ Borrar datos
+ Borrar eSIM
Confirmar aplicación de pánico
¿Está seguro de que desea permitir que %1$s active acciones de pánico destructivas\?
una aplicación desconocida
Permitir
Modo avión
Pánico
- Borrar por inactividad
- Limpia un dispositivo cuando no fue desbloqueado por N días.
+ Limpia un dispositivo cuando no fue desbloqueado por N días.
Predeterminado
Guardia
- PanicKit
- Título
- Acceso directo
- Transmisión
- Notificación
- Activadores
+ PanicKit
+ Título
+ Acceso directo
+ Transmisión
+ Notificación
Copiado
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 939f7ae..01f81b7 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -1,27 +1,24 @@
Wasted
- Amministrazione dispositivo non disponibile
- Cancella i dati
- Cancella eSIM
+ Cancella i dati
+ Cancella eSIM
Conferma app di panico
Sei sicuro di voler consentire a %1$s di attivare azioni di panico distruttive\?
un\'app sconosciuta
Consenti
Modalità aereo
Panico
- Cancella in caso di inattività
- Cancella i dati quando il dispositivo non viene sbloccato per N tempo.
- tempo
- 7d / 48h / 120m
- [d]giorni [h]ore [m]minuti
+ Cancella i dati quando il dispositivo non viene sbloccato per N tempo.
+ tempo
+ 7d / 48h / 120m
+ [d]giorni [h]ore [m]minuti
Predefinito
Guardia
- PanicKit
- Toggle
- Scorciatoia
- Broadcast
- Notifica
- Attivatori
+ PanicKit
+ Toggle
+ Scorciatoia
+ Broadcast
+ Notifica
Copiato
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
index bef17fd..960ab3c 100644
--- a/app/src/main/res/values-night/themes.xml
+++ b/app/src/main/res/values-night/themes.xml
@@ -1,6 +1,6 @@
-