mirror of https://github.com/x13a/Wasted.git
add option to wipe eSIM
add option to limit the maximum number of failed password attempts add option to disable code receiver add app panic shortcut
This commit is contained in:
parent
547578d8e8
commit
d64d290187
|
@ -8,11 +8,14 @@ Lock device and wipe data on panic trigger.
|
||||||
|
|
||||||
<img src="https://user-images.githubusercontent.com/53379023/147702625-7aef3d46-b819-40df-86e9-c3de418f2b58.png" width="30%" height="30%">
|
<img src="https://user-images.githubusercontent.com/53379023/147702625-7aef3d46-b819-40df-86e9-c3de418f2b58.png" width="30%" height="30%">
|
||||||
|
|
||||||
You can use [PanicKit](https://guardianproject.info/code/panickit/), Tile or send broadcast message
|
You can use [PanicKit](https://guardianproject.info/code/panickit/), tile, shortcut or send
|
||||||
with authentication code. On trigger, using
|
broadcast message with authentication code. On trigger, using
|
||||||
[Device Administration API](https://developer.android.com/guide/topics/admin/device-admin), it
|
[Device Administration API](https://developer.android.com/guide/topics/admin/device-admin), it
|
||||||
locks device and optionally runs wipe.
|
locks device and optionally runs wipe.
|
||||||
|
|
||||||
|
Also you can limit the maximum number of failed password attempts. After the limit exceeded
|
||||||
|
device will be wiped.
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
Broadcast message:
|
Broadcast message:
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
| ------- | ------------------ |
|
| ------- | ------------------ |
|
||||||
| 1.1.x | :white_check_mark: |
|
| 1.2.x | :white_check_mark: |
|
||||||
| < 1.1 | :x: |
|
| < 1.2 | :x: |
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
|
|
@ -10,8 +10,8 @@ android {
|
||||||
applicationId "me.lucky.wasted"
|
applicationId "me.lucky.wasted"
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 31
|
targetSdk 31
|
||||||
versionCode 10
|
versionCode 12
|
||||||
versionName "1.1.3"
|
versionName "1.2.0"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,17 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".ShortcutActivity"
|
||||||
|
android:noHistory="true"
|
||||||
|
android:exported="true"
|
||||||
|
android:theme="@android:style/Theme.NoDisplay">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="me.lucky.wasted.action.TRIGGER" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".QSTileService"
|
android:name=".QSTileService"
|
||||||
android:icon="@android:drawable/ic_lock_lock"
|
android:icon="@android:drawable/ic_lock_lock"
|
||||||
|
|
|
@ -6,20 +6,25 @@ import android.content.Intent
|
||||||
|
|
||||||
class CodeReceiver : BroadcastReceiver() {
|
class CodeReceiver : BroadcastReceiver() {
|
||||||
companion object {
|
companion object {
|
||||||
private const val TRIGGER = "me.lucky.wasted.action.TRIGGER"
|
const val KEY = "code"
|
||||||
}
|
const val ACTION = "me.lucky.wasted.action.TRIGGER"
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
fun panic(context: Context, intent: Intent) {
|
||||||
val prefs by lazy { Preferences(context) }
|
val prefs = Preferences(context)
|
||||||
val code = prefs.code
|
val code = prefs.code
|
||||||
if (!prefs.isServiceEnabled ||
|
if (!prefs.isServiceEnabled ||
|
||||||
code == "" ||
|
code == "" ||
|
||||||
intent.action != TRIGGER ||
|
intent.action != ACTION ||
|
||||||
intent.getStringExtra("code") != code) return
|
intent.getStringExtra(KEY) != code) return
|
||||||
val admin = DeviceAdmin(context)
|
val admin = DeviceAdminManager(context)
|
||||||
try {
|
try {
|
||||||
admin.dpm.lockNow()
|
admin.lockNow()
|
||||||
if (prefs.doWipe) admin.wipeData()
|
if (prefs.isWipeData) admin.wipeData()
|
||||||
} catch (exc: SecurityException) {}
|
} catch (exc: SecurityException) {}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
panic(context, intent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
package me.lucky.wasted
|
|
||||||
|
|
||||||
import android.app.admin.DevicePolicyManager
|
|
||||||
import android.content.ComponentName
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Build
|
|
||||||
|
|
||||||
class DeviceAdmin(ctx: Context) {
|
|
||||||
val dpm by lazy {
|
|
||||||
ctx.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
|
|
||||||
}
|
|
||||||
val deviceAdmin by lazy { ComponentName(ctx, DeviceAdminReceiver::class.java) }
|
|
||||||
|
|
||||||
fun remove() = dpm.removeActiveAdmin(deviceAdmin)
|
|
||||||
fun isActive(): Boolean = dpm.isAdminActive(deviceAdmin)
|
|
||||||
|
|
||||||
fun wipeData() {
|
|
||||||
dpm.wipeData(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
|
||||||
DevicePolicyManager.WIPE_SILENTLY else 0)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
package me.lucky.wasted
|
||||||
|
|
||||||
|
import android.app.admin.DevicePolicyManager
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
|
||||||
|
class DeviceAdminManager(private val ctx: Context) {
|
||||||
|
private val dpm by lazy {
|
||||||
|
ctx.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
|
||||||
|
}
|
||||||
|
private val deviceAdmin by lazy { ComponentName(ctx, DeviceAdminReceiver::class.java) }
|
||||||
|
private val prefs by lazy { Preferences(ctx) }
|
||||||
|
|
||||||
|
fun remove() = dpm.removeActiveAdmin(deviceAdmin)
|
||||||
|
fun isActive(): Boolean = dpm.isAdminActive(deviceAdmin)
|
||||||
|
fun getCurrentFailedPasswordAttempts(): Int = dpm.currentFailedPasswordAttempts
|
||||||
|
fun lockNow() = dpm.lockNow()
|
||||||
|
|
||||||
|
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) {
|
||||||
|
flags = flags.or(DevicePolicyManager.WIPE_EUICC)
|
||||||
|
}
|
||||||
|
dpm.wipeData(flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun makeRequestIntent(): Intent {
|
||||||
|
return Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN)
|
||||||
|
.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, deviceAdmin)
|
||||||
|
.putExtra(
|
||||||
|
DevicePolicyManager.EXTRA_ADD_EXPLANATION,
|
||||||
|
ctx.getString(R.string.device_admin_description),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,30 @@
|
||||||
package me.lucky.wasted
|
package me.lucky.wasted
|
||||||
|
|
||||||
import android.app.admin.DeviceAdminReceiver
|
import android.app.admin.DeviceAdminReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.UserHandle
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
|
||||||
class DeviceAdminReceiver : DeviceAdminReceiver()
|
class DeviceAdminReceiver : DeviceAdminReceiver() {
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
override fun onPasswordFailed(context: Context, intent: Intent, user: UserHandle) {
|
||||||
|
super.onPasswordFailed(context, intent, user)
|
||||||
|
onPasswordFailedInternal(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPasswordFailed(context: Context, intent: Intent) {
|
||||||
|
super.onPasswordFailed(context, intent)
|
||||||
|
onPasswordFailedInternal(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onPasswordFailedInternal(ctx: Context) {
|
||||||
|
val prefs = Preferences(ctx)
|
||||||
|
if (!prefs.isServiceEnabled || prefs.maxFailedPasswordAttempts == 0) return
|
||||||
|
val admin = DeviceAdminManager(ctx)
|
||||||
|
if (admin.getCurrentFailedPasswordAttempts() >= prefs.maxFailedPasswordAttempts)
|
||||||
|
admin.wipeData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
package me.lucky.wasted
|
package me.lucky.wasted
|
||||||
|
|
||||||
import android.app.admin.DevicePolicyManager
|
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.SeekBar
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
@ -17,7 +18,8 @@ open class MainActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
|
|
||||||
private val prefs by lazy { Preferences(this) }
|
private val prefs by lazy { Preferences(this) }
|
||||||
private val admin by lazy { DeviceAdmin(this) }
|
private val admin by lazy { DeviceAdminManager(this) }
|
||||||
|
private val shortcut by lazy { ShortcutManager(this) }
|
||||||
|
|
||||||
private val requestAdminPolicy =
|
private val requestAdminPolicy =
|
||||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
|
@ -51,23 +53,54 @@ open class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private fun init() {
|
private fun init() {
|
||||||
if (prefs.code == "") prefs.code = makeCode()
|
if (prefs.code == "") prefs.code = makeCode()
|
||||||
|
updateCodeColorState()
|
||||||
binding.apply {
|
binding.apply {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) wipeESIM.visibility = View.GONE
|
||||||
code.text = prefs.code
|
code.text = prefs.code
|
||||||
wipeDataCheckBox.isChecked = prefs.doWipe
|
wipeData.isChecked = prefs.isWipeData
|
||||||
|
wipeESIM.isChecked = prefs.isWipeESIM
|
||||||
|
wipeESIM.isEnabled = wipeData.isChecked
|
||||||
|
maxFailedPasswordAttempts.progress = prefs.maxFailedPasswordAttempts
|
||||||
toggle.isChecked = prefs.isServiceEnabled
|
toggle.isChecked = prefs.isServiceEnabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setup() {
|
private fun setup() {
|
||||||
binding.apply {
|
binding.apply {
|
||||||
|
code.setOnClickListener {
|
||||||
|
prefs.isCodeEnabled = !prefs.isCodeEnabled
|
||||||
|
updateCodeColorState()
|
||||||
|
setCodeReceiverState(
|
||||||
|
this@MainActivity,
|
||||||
|
prefs.isServiceEnabled && prefs.isCodeEnabled,
|
||||||
|
)
|
||||||
|
}
|
||||||
code.setOnLongClickListener {
|
code.setOnLongClickListener {
|
||||||
prefs.code = makeCode()
|
prefs.code = makeCode()
|
||||||
code.text = prefs.code
|
code.text = prefs.code
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
wipeDataCheckBox.setOnCheckedChangeListener { _, isChecked ->
|
wipeData.setOnCheckedChangeListener { _, isChecked ->
|
||||||
prefs.doWipe = isChecked
|
prefs.isWipeData = isChecked
|
||||||
|
wipeESIM.isEnabled = isChecked
|
||||||
}
|
}
|
||||||
|
wipeESIM.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
prefs.isWipeESIM = isChecked
|
||||||
|
}
|
||||||
|
maxFailedPasswordAttempts.setOnSeekBarChangeListener(
|
||||||
|
object : SeekBar.OnSeekBarChangeListener {
|
||||||
|
|
||||||
|
override fun onProgressChanged(
|
||||||
|
seekBar: SeekBar?,
|
||||||
|
progress: Int,
|
||||||
|
fromUser: Boolean,
|
||||||
|
) {
|
||||||
|
prefs.maxFailedPasswordAttempts = progress
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
|
||||||
|
override fun onStopTrackingTouch(seekBar: SeekBar?) {}
|
||||||
|
})
|
||||||
toggle.setOnCheckedChangeListener { _, isChecked ->
|
toggle.setOnCheckedChangeListener { _, isChecked ->
|
||||||
when (isChecked) {
|
when (isChecked) {
|
||||||
true -> if (!admin.isActive()) requestAdmin() else setOn()
|
true -> if (!admin.isActive()) requestAdmin() else setOn()
|
||||||
|
@ -77,31 +110,29 @@ open class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateCodeColorState() {
|
||||||
|
binding.code.setBackgroundColor(getColor(
|
||||||
|
if (prefs.isCodeEnabled) R.color.code_receiver_on else R.color.code_receiver_off
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
private fun setOn() {
|
private fun setOn() {
|
||||||
prefs.isServiceEnabled = true
|
prefs.isServiceEnabled = true
|
||||||
setControlReceiverState(this, true)
|
setCodeReceiverState(this, prefs.isCodeEnabled)
|
||||||
|
shortcut.push()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setOff() {
|
private fun setOff() {
|
||||||
admin.remove()
|
admin.remove()
|
||||||
setControlReceiverState(this, false)
|
setCodeReceiverState(this, false)
|
||||||
|
shortcut.remove()
|
||||||
prefs.isServiceEnabled = false
|
prefs.isServiceEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun requestAdmin() {
|
private fun requestAdmin() = requestAdminPolicy.launch(admin.makeRequestIntent())
|
||||||
val intent = Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN).apply {
|
|
||||||
putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, admin.deviceAdmin)
|
|
||||||
putExtra(
|
|
||||||
DevicePolicyManager.EXTRA_ADD_EXPLANATION,
|
|
||||||
getString(R.string.device_admin_description),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
requestAdminPolicy.launch(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun makeCode(): String = UUID.randomUUID().toString()
|
private fun makeCode(): String = UUID.randomUUID().toString()
|
||||||
|
|
||||||
private fun setControlReceiverState(ctx: Context, value: Boolean) {
|
private fun setCodeReceiverState(ctx: Context, value: Boolean) {
|
||||||
ctx.packageManager.setComponentEnabledSetting(
|
ctx.packageManager.setComponentEnabledSetting(
|
||||||
ComponentName(ctx, CodeReceiver::class.java),
|
ComponentName(ctx, CodeReceiver::class.java),
|
||||||
if (value) PackageManager.COMPONENT_ENABLED_STATE_ENABLED else
|
if (value) PackageManager.COMPONENT_ENABLED_STATE_ENABLED else
|
||||||
|
|
|
@ -31,18 +31,17 @@ class PanicConnectionActivity : MainActivity() {
|
||||||
} catch (exc: PackageManager.NameNotFoundException) {}
|
} catch (exc: PackageManager.NameNotFoundException) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
AlertDialog.Builder(this).apply {
|
AlertDialog.Builder(this)
|
||||||
setTitle(getString(R.string.panic_app_dialog_title))
|
.setTitle(getString(R.string.panic_app_dialog_title))
|
||||||
setMessage(String.format(getString(R.string.panic_app_dialog_message), app))
|
.setMessage(String.format(getString(R.string.panic_app_dialog_message), app))
|
||||||
setNegativeButton(R.string.allow) { _, _ ->
|
.setNegativeButton(R.string.allow) { _, _ ->
|
||||||
PanicResponder.setTriggerPackageName(this@PanicConnectionActivity)
|
PanicResponder.setTriggerPackageName(this@PanicConnectionActivity)
|
||||||
setResult(RESULT_OK)
|
setResult(RESULT_OK)
|
||||||
}
|
}
|
||||||
setPositiveButton(R.string.cancel) { _, _ ->
|
.setPositiveButton(R.string.cancel) { _, _ ->
|
||||||
setResult(RESULT_CANCELED)
|
setResult(RESULT_CANCELED)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
show()
|
.show()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,19 +8,19 @@ import info.guardianproject.panic.PanicResponder
|
||||||
|
|
||||||
class PanicResponderActivity : AppCompatActivity() {
|
class PanicResponderActivity : AppCompatActivity() {
|
||||||
private val prefs by lazy { Preferences(this) }
|
private val prefs by lazy { Preferences(this) }
|
||||||
private val admin by lazy { DeviceAdmin(this) }
|
private val admin by lazy { DeviceAdminManager(this) }
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
if (!Panic.isTriggerIntent(intent) || !prefs.isServiceEnabled) {
|
if (!Panic.isTriggerIntent(intent) || !prefs.isServiceEnabled) {
|
||||||
finish()
|
finishAndRemoveTask()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
admin.dpm.lockNow()
|
admin.lockNow()
|
||||||
if (PanicResponder.receivedTriggerFromConnectedApp(this) &&
|
if (PanicResponder.receivedTriggerFromConnectedApp(this) &&
|
||||||
prefs.doWipe) admin.wipeData()
|
prefs.isWipeData) admin.wipeData()
|
||||||
} catch (exc: SecurityException) {}
|
} catch (exc: SecurityException) {}
|
||||||
finish()
|
finishAndRemoveTask()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,10 @@ class Preferences(ctx: Context) {
|
||||||
companion object {
|
companion object {
|
||||||
private const val SERVICE_ENABLED = "service_enabled"
|
private const val SERVICE_ENABLED = "service_enabled"
|
||||||
private const val CODE = "code"
|
private const val CODE = "code"
|
||||||
private const val DO_WIPE = "do_wipe"
|
private const val CODE_ENABLED = "code_enabled"
|
||||||
|
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 val mk = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
|
private val mk = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
|
||||||
|
@ -29,7 +32,19 @@ class Preferences(ctx: Context) {
|
||||||
get() = prefs.getString(CODE, "")
|
get() = prefs.getString(CODE, "")
|
||||||
set(value) = prefs.edit { putString(CODE, value) }
|
set(value) = prefs.edit { putString(CODE, value) }
|
||||||
|
|
||||||
var doWipe: Boolean
|
var isCodeEnabled: Boolean
|
||||||
get() = prefs.getBoolean(DO_WIPE, false)
|
get() = prefs.getBoolean(CODE_ENABLED, false)
|
||||||
set(value) = prefs.edit { putBoolean(DO_WIPE, value) }
|
set(value) = prefs.edit { putBoolean(CODE_ENABLED, value) }
|
||||||
|
|
||||||
|
var isWipeData: Boolean
|
||||||
|
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) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import kotlin.concurrent.timerTask
|
||||||
@RequiresApi(Build.VERSION_CODES.N)
|
@RequiresApi(Build.VERSION_CODES.N)
|
||||||
class QSTileService : TileService() {
|
class QSTileService : TileService() {
|
||||||
private val prefs by lazy { Preferences(this) }
|
private val prefs by lazy { Preferences(this) }
|
||||||
private val admin by lazy { DeviceAdmin(this) }
|
private val admin by lazy { DeviceAdminManager(this) }
|
||||||
private val counter = AtomicInteger(0)
|
private val counter = AtomicInteger(0)
|
||||||
private var timer: Timer? = null
|
private var timer: Timer? = null
|
||||||
|
|
||||||
|
@ -26,9 +26,9 @@ class QSTileService : TileService() {
|
||||||
override fun onClick() {
|
override fun onClick() {
|
||||||
super.onClick()
|
super.onClick()
|
||||||
if (!prefs.isServiceEnabled) return
|
if (!prefs.isServiceEnabled) return
|
||||||
if (!prefs.doWipe) {
|
if (!prefs.isWipeData) {
|
||||||
try {
|
try {
|
||||||
admin.dpm.lockNow()
|
admin.lockNow()
|
||||||
} catch (exc: SecurityException) {}
|
} catch (exc: SecurityException) {}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ class QSTileService : TileService() {
|
||||||
timer = Timer()
|
timer = Timer()
|
||||||
timer?.schedule(timerTask {
|
timer?.schedule(timerTask {
|
||||||
try {
|
try {
|
||||||
admin.dpm.lockNow()
|
admin.lockNow()
|
||||||
admin.wipeData()
|
admin.wipeData()
|
||||||
} catch (exc: SecurityException) {}
|
} catch (exc: SecurityException) {}
|
||||||
}, 2000)
|
}, 2000)
|
||||||
|
@ -53,9 +53,7 @@ class QSTileService : TileService() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun update(tileState: Int) {
|
private fun update(tileState: Int) {
|
||||||
qsTile.apply {
|
qsTile.state = tileState
|
||||||
state = tileState
|
qsTile.updateTile()
|
||||||
updateTile()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package me.lucky.wasted
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
|
||||||
|
class ShortcutActivity : AppCompatActivity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
CodeReceiver.panic(this, intent)
|
||||||
|
finishAndRemoveTask()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package me.lucky.wasted
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.core.content.pm.ShortcutInfoCompat
|
||||||
|
import androidx.core.content.pm.ShortcutManagerCompat
|
||||||
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
|
|
||||||
|
class ShortcutManager(private val ctx: Context) {
|
||||||
|
companion object {
|
||||||
|
private const val SHORTCUT_ID = "panic"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val prefs by lazy { Preferences(ctx) }
|
||||||
|
|
||||||
|
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(CodeReceiver.ACTION)
|
||||||
|
.setClass(ctx, ShortcutActivity::class.java)
|
||||||
|
.putExtra(CodeReceiver.KEY, prefs.code)
|
||||||
|
)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun remove() = ShortcutManagerCompat.removeDynamicShortcuts(ctx, arrayListOf(SHORTCUT_ID))
|
||||||
|
}
|
|
@ -16,20 +16,75 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:id="@+id/scrollView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginVertical="16dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/toggle"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/description">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/code"
|
android:id="@+id/code"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:background="@color/code_receiver_off"
|
||||||
android:background="#FFD600"
|
|
||||||
android:padding="16dp"
|
android:padding="16dp"
|
||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
android:textColor="@color/black"
|
android:textColor="@color/black"
|
||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold" />
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
<CheckBox
|
||||||
app:layout_constraintTop_toBottomOf="@+id/description" />
|
android:id="@+id/wipeData"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layoutDirection="rtl"
|
||||||
|
android:text="@string/wipe_data_check_box"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/wipeESIM"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:layoutDirection="rtl"
|
||||||
|
android:text="@string/wipe_esim_check_box"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/divider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginVertical="16dp"
|
||||||
|
android:background="?android:attr/listDivider" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/description2"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/max_failed_password_attempts_description" />
|
||||||
|
|
||||||
|
<SeekBar
|
||||||
|
android:id="@+id/maxFailedPasswordAttempts"
|
||||||
|
style="@style/Widget.AppCompat.SeekBar.Discrete"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:max="10"
|
||||||
|
android:progress="0" />
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
<ToggleButton
|
<ToggleButton
|
||||||
android:id="@+id/toggle"
|
android:id="@+id/toggle"
|
||||||
|
@ -41,16 +96,4 @@
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
<CheckBox
|
|
||||||
android:id="@+id/wipeDataCheckBox"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="16dp"
|
|
||||||
android:layoutDirection="rtl"
|
|
||||||
android:text="@string/wipe_data_check_box"
|
|
||||||
android:textSize="16sp"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/code" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -7,4 +7,6 @@
|
||||||
<color name="teal_700">#FF018786</color>
|
<color name="teal_700">#FF018786</color>
|
||||||
<color name="black">#FF000000</color>
|
<color name="black">#FF000000</color>
|
||||||
<color name="white">#FFFFFFFF</color>
|
<color name="white">#FFFFFFFF</color>
|
||||||
|
<color name="code_receiver_on">#FFD600</color>
|
||||||
|
<color name="code_receiver_off">#E13741</color>
|
||||||
</resources>
|
</resources>
|
|
@ -1,14 +1,17 @@
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Wasted</string>
|
<string name="app_name">Wasted</string>
|
||||||
<string name="description">Turn on Wasted to lock device on panic trigger. You can use PanicKit, Tile or send broadcast message with this authentication code.</string>
|
<string name="description">Turn on Wasted to lock device on panic trigger. You can use PanicKit, tile, shortcut or send broadcast message with this authentication code.</string>
|
||||||
<string name="device_admin_label">Wasted</string>
|
<string name="device_admin_label">Wasted</string>
|
||||||
<string name="device_admin_description">Allow Wasted to lock device and optionally wipe data on panic trigger</string>
|
<string name="device_admin_description">Allow Wasted to lock device and optionally wipe data on panic trigger</string>
|
||||||
<string name="service_unavailable_toast">Admin service unavailable</string>
|
<string name="service_unavailable_toast">Admin service unavailable</string>
|
||||||
<string name="wipe_data_check_box">Wipe data</string>
|
<string name="wipe_data_check_box">Wipe data</string>
|
||||||
<string name="panic_app_dialog_title">Confirm Panic App</string>
|
<string name="wipe_esim_check_box">Wipe 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_dialog_message">Are you sure that you want to allow %1$s to trigger destructive panic actions\?</string>
|
||||||
<string name="panic_app_unknown_app">an unknown app</string>
|
<string name="panic_app_unknown_app">an unknown app</string>
|
||||||
<string name="allow">Allow</string>
|
<string name="allow">Allow</string>
|
||||||
<string name="cancel">Cancel</string>
|
<string name="cancel">Cancel</string>
|
||||||
<string name="tile_label">Unlock device</string>
|
<string name="tile_label">Unlock device</string>
|
||||||
|
<string name="shortcut_label">Panic</string>
|
||||||
|
<string name="max_failed_password_attempts_description">Also you can limit the maximum number of failed password attempts. After the limit exceeded device will be wiped.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
|
<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<uses-policies>
|
<uses-policies>
|
||||||
<force-lock />
|
<force-lock />
|
||||||
|
<watch-login />
|
||||||
<wipe-data />
|
<wipe-data />
|
||||||
</uses-policies>
|
</uses-policies>
|
||||||
</device-admin>
|
</device-admin>
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
add option to wipe eSIM
|
||||||
|
add option to limit the maximum number of failed password attempts
|
||||||
|
add option to disable code receiver
|
||||||
|
add app panic shortcut
|
|
@ -1,4 +1,7 @@
|
||||||
Lock device and wipe data on panic trigger.
|
Lock device and wipe data on panic trigger.
|
||||||
|
|
||||||
You can use PanicKit, Tile or send broadcast message with authentication code.
|
You can use PanicKit, tile, shortcut or send broadcast message with authentication code.
|
||||||
On trigger, using Device Administration API, it locks device and optionally runs wipe.
|
On trigger, using Device Administration API, it locks device and optionally runs wipe.
|
||||||
|
|
||||||
|
Also you can limit the maximum number of failed password attempts.
|
||||||
|
After the limit exceeded device will be wiped.
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 105 KiB |
Loading…
Reference in New Issue