mirror of https://github.com/x13a/Wasted.git
usb trigger
This commit is contained in:
parent
31b343b7d8
commit
3876640954
14
README.md
14
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
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-feature android:name="android.software.device_admin" android:required="true" />
|
||||
<uses-feature android:name="android.hardware.usb.host" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.usb.accessory" android:required="false" />
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
|
@ -29,7 +31,7 @@
|
|||
</activity>
|
||||
|
||||
<receiver
|
||||
android:name=".DeviceAdminReceiver"
|
||||
android:name=".admin.DeviceAdminReceiver"
|
||||
android:permission="android.permission.BIND_DEVICE_ADMIN"
|
||||
android:exported="true">
|
||||
<meta-data android:name="android.app.device_admin"
|
||||
|
@ -39,32 +41,8 @@
|
|||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".TriggerReceiver"
|
||||
android:directBootAware="true"
|
||||
android:enabled="false"
|
||||
android:exported="true"
|
||||
tools:ignore="ExportedReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="me.lucky.wasted.action.TRIGGER" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<activity
|
||||
android:name=".PanicResponderActivity"
|
||||
android:directBootAware="true"
|
||||
android:noHistory="true"
|
||||
android:enabled="false"
|
||||
android:exported="true"
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
<intent-filter>
|
||||
<action android:name="info.guardianproject.panic.action.TRIGGER" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".PanicConnectionActivity"
|
||||
android:name=".trigger.panic.PanicConnectionActivity"
|
||||
android:noHistory="true"
|
||||
android:enabled="false"
|
||||
android:exported="true">
|
||||
|
@ -76,18 +54,20 @@
|
|||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ShortcutActivity"
|
||||
android:name=".trigger.panic.PanicResponderActivity"
|
||||
android:directBootAware="true"
|
||||
android:noHistory="true"
|
||||
android:enabled="false"
|
||||
android:exported="true"
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
<intent-filter>
|
||||
<action android:name="me.lucky.wasted.action.TRIGGER" />
|
||||
<action android:name="info.guardianproject.panic.action.TRIGGER" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".TileService"
|
||||
android:name=".trigger.tile.TileService"
|
||||
android:directBootAware="true"
|
||||
android:icon="@drawable/ic_baseline_airplanemode_active_24"
|
||||
android:label="@string/tile_label"
|
||||
|
@ -102,21 +82,70 @@
|
|||
android:value="true" />
|
||||
</service>
|
||||
|
||||
<activity
|
||||
android:name=".trigger.shortcut.ShortcutActivity"
|
||||
android:noHistory="true"
|
||||
android:enabled="false"
|
||||
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>
|
||||
|
||||
<receiver
|
||||
android:name=".TriggerReceiver"
|
||||
android:directBootAware="true"
|
||||
android:enabled="false"
|
||||
android:exported="true"
|
||||
tools:ignore="ExportedReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="me.lucky.wasted.action.TRIGGER" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".WipeJobService"
|
||||
android:name=".trigger.notification.NotificationListenerService"
|
||||
android:directBootAware="true"
|
||||
android:enabled="false"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.notification.NotificationListenerService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".trigger.lock.LockJobService"
|
||||
android:directBootAware="true"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE">
|
||||
</service>
|
||||
|
||||
<receiver
|
||||
android:name=".trigger.usb.UsbReceiver"
|
||||
android:directBootAware="true"
|
||||
android:enabled="false"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
||||
<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
|
||||
android:resource="@xml/device_filter" />
|
||||
<meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
|
||||
android:resource="@xml/accessory_filter" />
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".ForegroundService"
|
||||
android:name=".trigger.shared.ForegroundService"
|
||||
android:directBootAware="true"
|
||||
android:exported="false">
|
||||
</service>
|
||||
|
||||
<receiver
|
||||
android:name=".RestartReceiver"
|
||||
android:name=".trigger.shared.RestartReceiver"
|
||||
android:directBootAware="true"
|
||||
android:enabled="false"
|
||||
android:exported="true">
|
||||
|
@ -127,16 +156,5 @@
|
|||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".NotificationListenerService"
|
||||
android:directBootAware="true"
|
||||
android:enabled="false"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.notification.NotificationListenerService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
</application>
|
||||
</manifest>
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package me.lucky.wasted.admin
|
||||
|
||||
import android.app.admin.DeviceAdminReceiver
|
||||
|
||||
class DeviceAdminReceiver : DeviceAdminReceiver()
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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 }
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<vector android:height="24dp" android:tint="#000000"
|
||||
<vector android:height="24dp" android:tint="?attr/colorControlNormal"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z"/>
|
||||
<path android:fillColor="@android:color/white" android:pathData="M3,18h18v-2L3,16v2zM3,13h18v-2L3,11v2zM3,6v2h18L21,6L3,6z"/>
|
||||
</vector>
|
|
@ -1,119 +1,45 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/drawer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="32dp"
|
||||
tools:openDrawer="start"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/scrollView"
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:isScrollContainer="false"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<LinearLayout
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/authenticationCode"
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/appBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/code_off"
|
||||
android:padding="16dp"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:menu="@menu/top"
|
||||
app:navigationIcon="@drawable/ic_baseline_menu_24" />
|
||||
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="8dp"/>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/wipeData"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layoutDirection="rtl"
|
||||
android:text="@string/wipe_data_check_box"
|
||||
android:textSize="16sp" />
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/fragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="32dp"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
|
||||
<Space
|
||||
android:id="@+id/wipeSpace"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="2dp" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/wipeEmbeddedSim"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layoutDirection="rtl"
|
||||
android:text="@string/wipe_embedded_sim_check_box"
|
||||
android:textSize="16sp" />
|
||||
<com.google.android.material.navigation.NavigationView
|
||||
android:id="@+id/navigation"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
app:menu="@menu/nav" />
|
||||
|
||||
<View
|
||||
android:id="@+id/divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginVertical="8dp"
|
||||
android:background="?android:attr/listDivider" />
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/wipeOnInactivitySwitch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:text="@string/wipe_on_inactivity_switch" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/wipeOnInactivityDescription"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/wipe_on_inactivity_description" />
|
||||
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="8dp"/>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/wipeOnInactivityTime"
|
||||
app:helperText="@string/wipe_on_inactivity_time_helper_text"
|
||||
app:helperTextEnabled="true"
|
||||
app:errorEnabled="true"
|
||||
app:endIconMode="custom"
|
||||
app:endIconDrawable="@drawable/ic_baseline_check_circle_24"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/wipe_on_inactivity_time_hint" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
<ToggleButton
|
||||
android:id="@+id/toggle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="12dp"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".fragment.LockFragment">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/time"
|
||||
app:helperText="@string/trigger_lock_time_helper_text"
|
||||
app:helperTextEnabled="true"
|
||||
app:errorEnabled="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/trigger_lock_time_hint" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</FrameLayout>
|
|
@ -0,0 +1,78 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".fragment.MainFragment">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:isScrollContainer="false"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/secret"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/secret_0"
|
||||
android:padding="16dp"
|
||||
android:textAlignment="center"
|
||||
android:textStyle="bold"
|
||||
android:textAppearance="?attr/textAppearanceTitleLarge" />
|
||||
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="8dp"/>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/wipeData"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layoutDirection="rtl"
|
||||
android:text="@string/wipe_data_checkbox"
|
||||
android:textAppearance="?attr/textAppearanceBodyLarge" />
|
||||
|
||||
<Space
|
||||
android:id="@+id/space1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="2dp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/wipeEmbeddedSim"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layoutDirection="rtl"
|
||||
android:text="@string/wipe_embedded_sim_checkbox"
|
||||
android:textAppearance="?attr/textAppearanceBodyLarge" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
<ToggleButton
|
||||
android:id="@+id/toggle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?attr/textAppearanceTitleMedium"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</FrameLayout>
|
|
@ -0,0 +1,147 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".fragment.SettingsFragment">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/panicKit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layoutDirection="rtl"
|
||||
android:textAppearance="?attr/textAppearanceBodyLarge"
|
||||
android:text="@string/trigger_panic_kit" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/trigger_panic_kit_description"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall" />
|
||||
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="5dp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/tile"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layoutDirection="rtl"
|
||||
android:textAppearance="?attr/textAppearanceBodyLarge"
|
||||
android:text="@string/trigger_tile" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/trigger_tile_description"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall" />
|
||||
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="5dp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/shortcut"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layoutDirection="rtl"
|
||||
android:textAppearance="?attr/textAppearanceBodyLarge"
|
||||
android:text="@string/trigger_shortcut" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/trigger_shortcut_description"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall" />
|
||||
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="5dp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/broadcast"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layoutDirection="rtl"
|
||||
android:textAppearance="?attr/textAppearanceBodyLarge"
|
||||
android:text="@string/trigger_broadcast" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/trigger_broadcast_description"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall" />
|
||||
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="5dp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/notification"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layoutDirection="rtl"
|
||||
android:textAppearance="?attr/textAppearanceBodyLarge"
|
||||
android:text="@string/trigger_notification" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/trigger_notification_description"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall" />
|
||||
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="5dp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/lock"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layoutDirection="rtl"
|
||||
android:textAppearance="?attr/textAppearanceBodyLarge"
|
||||
android:text="@string/trigger_lock" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/trigger_lock_description"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall" />
|
||||
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="5dp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/usb"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layoutDirection="rtl"
|
||||
android:textAppearance="?attr/textAppearanceBodyLarge"
|
||||
android:text="@string/trigger_usb" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/trigger_usb_description"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</FrameLayout>
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_main"
|
||||
android:title="@string/main"
|
||||
android:checkable="true"
|
||||
android:checked="true" />
|
||||
|
||||
<group
|
||||
android:id="@+id/nav_group_options"
|
||||
android:checkableBehavior="all">
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_trigger_lock"
|
||||
android:title="@string/trigger_lock" />
|
||||
|
||||
</group>
|
||||
|
||||
</menu>
|
|
@ -3,9 +3,9 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/triggers"
|
||||
android:title="@string/triggers"
|
||||
android:id="@+id/top_settings"
|
||||
android:icon="@drawable/ic_baseline_settings_24"
|
||||
android:title="@string/settings"
|
||||
app:showAsAction="always" />
|
||||
|
||||
</menu>
|
|
@ -1,24 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Wasted</string>
|
||||
<string name="service_unavailable_popup">Geräteadministrator nicht verfügbar</string>
|
||||
<string name="wipe_data_check_box">Daten löschen</string>
|
||||
<string name="wipe_embedded_sim_check_box">eSim löschen</string>
|
||||
<string name="wipe_data_checkbox">Daten löschen</string>
|
||||
<string name="wipe_embedded_sim_checkbox">eSim löschen</string>
|
||||
<string name="panic_app_dialog_title">Bestätige Panik App</string>
|
||||
<string name="panic_app_dialog_message">Bist du sicher, dass du %1$s erlauben willst, destruktive Aktionen auslösen zu lassen\?</string>
|
||||
<string name="panic_app_unknown_app">eine unbekannte App</string>
|
||||
<string name="allow">Zulassen</string>
|
||||
<string name="tile_label">Flugmodus</string>
|
||||
<string name="shortcut_label">Panik</string>
|
||||
<string name="wipe_on_inactivity_switch">Bei Inaktivität löschen</string>
|
||||
<string name="wipe_on_inactivity_description">Den Speicher des Geräts löschen, wenn es für N Tage nicht entsperrt wurde.</string>
|
||||
<string name="trigger_lock_description">Den Speicher des Geräts löschen, wenn es für N Tage nicht entsperrt wurde.</string>
|
||||
<string name="notification_channel_default_name">Standard</string>
|
||||
<string name="foreground_service_notification_title">Guard</string>
|
||||
<string name="triggers_array_panic_kit">PanicKit</string>
|
||||
<string name="triggers_array_tile">Tile</string>
|
||||
<string name="triggers_array_shortcut">Shortcut</string>
|
||||
<string name="triggers_array_broadcast">Broadcast</string>
|
||||
<string name="triggers_array_notification">Benachrichtigung</string>
|
||||
<string name="triggers">Auslöser</string>
|
||||
<string name="trigger_panic_kit">PanicKit</string>
|
||||
<string name="trigger_tile">Tile</string>
|
||||
<string name="trigger_shortcut">Shortcut</string>
|
||||
<string name="trigger_broadcast">Broadcast</string>
|
||||
<string name="trigger_notification">Benachrichtigung</string>
|
||||
<string name="copied_popup">Kopiert</string>
|
||||
</resources>
|
||||
|
|
|
@ -1,24 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Wasted</string>
|
||||
<string name="service_unavailable_popup">Administrador de dispositivos no disponible</string>
|
||||
<string name="wipe_data_check_box">Borrar datos</string>
|
||||
<string name="wipe_embedded_sim_check_box">Borrar eSIM</string>
|
||||
<string name="wipe_data_checkbox">Borrar datos</string>
|
||||
<string name="wipe_embedded_sim_checkbox">Borrar eSIM</string>
|
||||
<string name="panic_app_dialog_title">Confirmar aplicación de pánico</string>
|
||||
<string name="panic_app_dialog_message">¿Está seguro de que desea permitir que %1$s active acciones de pánico destructivas\?</string>
|
||||
<string name="panic_app_unknown_app">una aplicación desconocida</string>
|
||||
<string name="allow">Permitir</string>
|
||||
<string name="tile_label">Modo avión</string>
|
||||
<string name="shortcut_label">Pánico</string>
|
||||
<string name="wipe_on_inactivity_switch">Borrar por inactividad</string>
|
||||
<string name="wipe_on_inactivity_description">Limpia un dispositivo cuando no fue desbloqueado por N días.</string>
|
||||
<string name="trigger_lock_description">Limpia un dispositivo cuando no fue desbloqueado por N días.</string>
|
||||
<string name="notification_channel_default_name">Predeterminado</string>
|
||||
<string name="foreground_service_notification_title">Guardia</string>
|
||||
<string name="triggers_array_panic_kit">PanicKit</string>
|
||||
<string name="triggers_array_tile">Título</string>
|
||||
<string name="triggers_array_shortcut">Acceso directo</string>
|
||||
<string name="triggers_array_broadcast">Transmisión</string>
|
||||
<string name="triggers_array_notification">Notificación</string>
|
||||
<string name="triggers">Activadores</string>
|
||||
<string name="trigger_panic_kit">PanicKit</string>
|
||||
<string name="trigger_tile">Título</string>
|
||||
<string name="trigger_shortcut">Acceso directo</string>
|
||||
<string name="trigger_broadcast">Transmisión</string>
|
||||
<string name="trigger_notification">Notificación</string>
|
||||
<string name="copied_popup">Copiado</string>
|
||||
</resources>
|
||||
|
|
|
@ -1,27 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Wasted</string>
|
||||
<string name="service_unavailable_popup">Amministrazione dispositivo non disponibile</string>
|
||||
<string name="wipe_data_check_box">Cancella i dati</string>
|
||||
<string name="wipe_embedded_sim_check_box">Cancella eSIM</string>
|
||||
<string name="wipe_data_checkbox">Cancella i dati</string>
|
||||
<string name="wipe_embedded_sim_checkbox">Cancella eSIM</string>
|
||||
<string name="panic_app_dialog_title">Conferma app di panico</string>
|
||||
<string name="panic_app_dialog_message">Sei sicuro di voler consentire a %1$s di attivare azioni di panico distruttive\?</string>
|
||||
<string name="panic_app_unknown_app">un\'app sconosciuta</string>
|
||||
<string name="allow">Consenti</string>
|
||||
<string name="tile_label">Modalità aereo</string>
|
||||
<string name="shortcut_label">Panico</string>
|
||||
<string name="wipe_on_inactivity_switch">Cancella in caso di inattività</string>
|
||||
<string name="wipe_on_inactivity_description">Cancella i dati quando il dispositivo non viene sbloccato per N tempo.</string>
|
||||
<string name="wipe_on_inactivity_time_hint">tempo</string>
|
||||
<string name="wipe_on_inactivity_time_error">7d / 48h / 120m</string>
|
||||
<string name="wipe_on_inactivity_time_helper_text">[d]giorni [h]ore [m]minuti</string>
|
||||
<string name="trigger_lock_description">Cancella i dati quando il dispositivo non viene sbloccato per N tempo.</string>
|
||||
<string name="trigger_lock_time_hint">tempo</string>
|
||||
<string name="trigger_lock_time_error">7d / 48h / 120m</string>
|
||||
<string name="trigger_lock_time_helper_text">[d]giorni [h]ore [m]minuti</string>
|
||||
<string name="notification_channel_default_name">Predefinito</string>
|
||||
<string name="foreground_service_notification_title">Guardia</string>
|
||||
<string name="triggers_array_panic_kit">PanicKit</string>
|
||||
<string name="triggers_array_tile">Toggle</string>
|
||||
<string name="triggers_array_shortcut">Scorciatoia</string>
|
||||
<string name="triggers_array_broadcast">Broadcast</string>
|
||||
<string name="triggers_array_notification">Notifica</string>
|
||||
<string name="triggers">Attivatori</string>
|
||||
<string name="trigger_panic_kit">PanicKit</string>
|
||||
<string name="trigger_tile">Toggle</string>
|
||||
<string name="trigger_shortcut">Scorciatoia</string>
|
||||
<string name="trigger_broadcast">Broadcast</string>
|
||||
<string name="trigger_notification">Notifica</string>
|
||||
<string name="copied_popup">Copiato</string>
|
||||
</resources>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<resources>
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.Wasted" parent="Theme.Material3.DayNight">
|
||||
<style name="Theme.Wasted" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_200</item>
|
||||
<item name="colorOnPrimary">@color/black</item>
|
||||
|
|
|
@ -1,24 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Потрачено</string>
|
||||
<string name="service_unavailable_popup">Администратор устройства недоступен</string>
|
||||
<string name="wipe_data_check_box">Стереть данные</string>
|
||||
<string name="wipe_embedded_sim_check_box">Стереть eSIM</string>
|
||||
<string name="wipe_data_checkbox">Стереть данные</string>
|
||||
<string name="wipe_embedded_sim_checkbox">Стереть eSIM</string>
|
||||
<string name="panic_app_dialog_title">Подтвердите приложение тревожной кнопки</string>
|
||||
<string name="panic_app_dialog_message">Вы уверены, что хотите разрешить %1$s запускать действия тревожной кнопки\?</string>
|
||||
<string name="panic_app_unknown_app">неизвестное приложение</string>
|
||||
<string name="allow">Разрешить</string>
|
||||
<string name="tile_label">Режим полета</string>
|
||||
<string name="shortcut_label">Тревога</string>
|
||||
<string name="wipe_on_inactivity_switch">Стереть при неактивности</string>
|
||||
<string name="wipe_on_inactivity_description">Стереть данные когда устройство не разблокируется N дней.</string>
|
||||
<string name="trigger_lock_description">Стереть данные когда устройство не разблокируется N дней.</string>
|
||||
<string name="notification_channel_default_name">По умолчанию</string>
|
||||
<string name="foreground_service_notification_title">Охрана</string>
|
||||
<string name="triggers_array_panic_kit">Тревожная кнопка</string>
|
||||
<string name="triggers_array_tile">Плитка</string>
|
||||
<string name="triggers_array_shortcut">Ярлык</string>
|
||||
<string name="triggers_array_broadcast">Широковещательное сообщение</string>
|
||||
<string name="triggers_array_notification">Уведомление</string>
|
||||
<string name="triggers">Триггеры</string>
|
||||
<string name="trigger_panic_kit">Тревожная кнопка</string>
|
||||
<string name="trigger_tile">Плитка</string>
|
||||
<string name="trigger_shortcut">Ярлык</string>
|
||||
<string name="trigger_broadcast">Широковещательное сообщение</string>
|
||||
<string name="trigger_notification">Уведомление</string>
|
||||
<string name="copied_popup">Скопировано</string>
|
||||
</resources>
|
||||
|
|
|
@ -1,24 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Wasted</string>
|
||||
<string name="service_unavailable_popup">Cihaz yöneticisi mevcut değil</string>
|
||||
<string name="wipe_data_check_box">Verileri sil</string>
|
||||
<string name="wipe_embedded_sim_check_box">eSIM\'i sil</string>
|
||||
<string name="wipe_data_checkbox">Verileri sil</string>
|
||||
<string name="wipe_embedded_sim_checkbox">eSIM\'i sil</string>
|
||||
<string name="panic_app_dialog_title">Panik uygulamasını onayla</string>
|
||||
<string name="panic_app_dialog_message">Yıkıcı panik eylemlerini tetiklemek için %1$s\'e izin vermek istediğinizden emin misiniz\?</string>
|
||||
<string name="panic_app_unknown_app">bilinmeyen bir uygulama</string>
|
||||
<string name="allow">İzin ver</string>
|
||||
<string name="tile_label">Uçak modu</string>
|
||||
<string name="shortcut_label">Panik</string>
|
||||
<string name="wipe_on_inactivity_switch">Kullanılmadığında sil</string>
|
||||
<string name="wipe_on_inactivity_description">N gün boyunca kilidi açılmamış bir cihazı silin.</string>
|
||||
<string name="trigger_lock_description">N gün boyunca kilidi açılmamış bir cihazı silin.</string>
|
||||
<string name="notification_channel_default_name">Varsayılan</string>
|
||||
<string name="foreground_service_notification_title">Guard</string>
|
||||
<string name="triggers_array_panic_kit">PanicKit</string>
|
||||
<string name="triggers_array_tile">Tile</string>
|
||||
<string name="triggers_array_shortcut">Kısayol</string>
|
||||
<string name="triggers_array_broadcast">Yayın</string>
|
||||
<string name="triggers_array_notification">Bildirim</string>
|
||||
<string name="triggers">Tetikleyiciler</string>
|
||||
<string name="trigger_panic_kit">PanicKit</string>
|
||||
<string name="trigger_tile">Tile</string>
|
||||
<string name="trigger_shortcut">Kısayol</string>
|
||||
<string name="trigger_broadcast">Yayın</string>
|
||||
<string name="trigger_notification">Bildirim</string>
|
||||
<string name="copied_popup">Kopyalandı</string>
|
||||
</resources>
|
||||
|
|
|
@ -1,24 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Wasted</string>
|
||||
<string name="service_unavailable_popup">Адміністратор пристрою недоступний</string>
|
||||
<string name="wipe_data_check_box">Стерти дані</string>
|
||||
<string name="wipe_embedded_sim_check_box">Стерти дані eSIM</string>
|
||||
<string name="wipe_data_checkbox">Стерти дані</string>
|
||||
<string name="wipe_embedded_sim_checkbox">Стерти дані eSIM</string>
|
||||
<string name="panic_app_dialog_title">Confirm panic app</string>
|
||||
<string name="panic_app_dialog_message">Are you sure that you want to allow %1$s to trigger destructive panic actions\?</string>
|
||||
<string name="panic_app_unknown_app">невідомий додаток</string>
|
||||
<string name="allow">Allow</string>
|
||||
<string name="tile_label">Режим польоту</string>
|
||||
<string name="shortcut_label">Тривога</string>
|
||||
<string name="wipe_on_inactivity_switch">Wipe on inactivity</string>
|
||||
<string name="wipe_on_inactivity_description">Видалення пристрою, коли його не було розблоковано протягом N днів.</string>
|
||||
<string name="trigger_lock_description">Видалення пристрою, коли його не було розблоковано протягом N днів.</string>
|
||||
<string name="notification_channel_default_name">За замовчуванням</string>
|
||||
<string name="foreground_service_notification_title">Захисник</string>
|
||||
<string name="triggers_array_panic_kit">PanicKit</string>
|
||||
<string name="triggers_array_tile">Tile</string>
|
||||
<string name="triggers_array_shortcut">Гарячі клавіші</string>
|
||||
<string name="triggers_array_broadcast">Трансляція</string>
|
||||
<string name="triggers_array_notification">Сповіщення</string>
|
||||
<string name="triggers">Тріггери</string>
|
||||
<string name="trigger_panic_kit">PanicKit</string>
|
||||
<string name="trigger_tile">Tile</string>
|
||||
<string name="trigger_shortcut">Гарячі клавіші</string>
|
||||
<string name="trigger_broadcast">Трансляція</string>
|
||||
<string name="trigger_notification">Сповіщення</string>
|
||||
<string name="copied_popup">Скопійовано</string>
|
||||
</resources>
|
||||
|
|
|
@ -1,24 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">自毁</string>
|
||||
<string name="service_unavailable_popup">设备管理员不可用</string>
|
||||
<string name="wipe_data_check_box">擦除数据</string>
|
||||
<string name="wipe_embedded_sim_check_box">擦除eSIM</string>
|
||||
<string name="wipe_data_checkbox">擦除数据</string>
|
||||
<string name="wipe_embedded_sim_checkbox">擦除eSIM</string>
|
||||
<string name="panic_app_dialog_title">确认需要使用的紧急应用程序</string>
|
||||
<string name="panic_app_dialog_message">你是否确定允许%1$s触发紧急自毁行为?</string>
|
||||
<string name="panic_app_unknown_app">未知应用</string>
|
||||
<string name="allow">允许</string>
|
||||
<string name="tile_label">飞行模式</string>
|
||||
<string name="shortcut_label">紧急</string>
|
||||
<string name="wipe_on_inactivity_switch">在不使用时擦除</string>
|
||||
<string name="wipe_on_inactivity_description">当设备在N天内没被解锁时擦除</string>
|
||||
<string name="trigger_lock_description">当设备在N天内没被解锁时擦除</string>
|
||||
<string name="notification_channel_default_name">默认设置</string>
|
||||
<string name="foreground_service_notification_title">守卫</string>
|
||||
<string name="triggers_array_panic_kit">恐慌触发器</string>
|
||||
<string name="triggers_array_tile">追踪器</string>
|
||||
<string name="triggers_array_shortcut">快捷方式</string>
|
||||
<string name="triggers_array_broadcast">广播</string>
|
||||
<string name="triggers_array_notification">通知</string>
|
||||
<string name="triggers">触发器</string>
|
||||
<string name="trigger_panic_kit">恐慌触发器</string>
|
||||
<string name="trigger_tile">追踪器</string>
|
||||
<string name="trigger_shortcut">快捷方式</string>
|
||||
<string name="trigger_broadcast">广播</string>
|
||||
<string name="trigger_notification">通知</string>
|
||||
<string name="copied_popup">复制成功</string>
|
||||
</resources>
|
||||
|
|
|
@ -6,6 +6,6 @@
|
|||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
|
||||
<color name="code_on">#FFD600</color>
|
||||
<color name="code_off">#E13741</color>
|
||||
<color name="secret_1">#FFD600</color>
|
||||
<color name="secret_0">#E13741</color>
|
||||
</resources>
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="triggers">
|
||||
<item>@string/triggers_array_panic_kit</item>
|
||||
<item>@string/triggers_array_tile</item>
|
||||
<item>@string/triggers_array_shortcut</item>
|
||||
<item>@string/triggers_array_broadcast</item>
|
||||
<item>@string/triggers_array_notification</item>
|
||||
</string-array>
|
||||
</resources>
|
|
@ -1,27 +1,34 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Wasted</string>
|
||||
<string name="service_unavailable_popup">Device admin unavailable</string>
|
||||
<string name="wipe_data_check_box">Wipe data</string>
|
||||
<string name="wipe_embedded_sim_check_box">Wipe eSIM</string>
|
||||
<string name="wipe_data_checkbox">Wipe data</string>
|
||||
<string name="wipe_embedded_sim_checkbox">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 you want to allow %1$s to trigger destructive panic actions\\?</string>
|
||||
<string name="panic_app_unknown_app">an unknown app</string>
|
||||
<string name="allow">Allow</string>
|
||||
<string name="tile_label">Airplane mode</string>
|
||||
<string name="shortcut_label">Panic</string>
|
||||
<string name="wipe_on_inactivity_switch">Wipe on inactivity</string>
|
||||
<string name="wipe_on_inactivity_description">Wipe a device when it was not unlocked for N time.</string>
|
||||
<string name="wipe_on_inactivity_time_hint">time</string>
|
||||
<string name="wipe_on_inactivity_time_error">7d / 48h / 120m</string>
|
||||
<string name="wipe_on_inactivity_time_helper_text">[d]ays [h]ours [m]inutes</string>
|
||||
<string name="trigger_lock_time_hint">time</string>
|
||||
<string name="trigger_lock_time_error">7d / 48h / 120m</string>
|
||||
<string name="trigger_lock_time_helper_text">How much time to wait. Modifiers: [d]ays [h]ours [m]inutes</string>
|
||||
<string name="notification_channel_default_name">Default</string>
|
||||
<string name="foreground_service_notification_title">Guard</string>
|
||||
<string name="triggers_array_panic_kit">PanicKit</string>
|
||||
<string name="triggers_array_tile">Tile</string>
|
||||
<string name="triggers_array_shortcut">Shortcut</string>
|
||||
<string name="triggers_array_broadcast">Broadcast</string>
|
||||
<string name="triggers_array_notification">Notification</string>
|
||||
<string name="triggers">Triggers</string>
|
||||
<string name="trigger_panic_kit">PanicKit</string>
|
||||
<string name="trigger_tile">Tile</string>
|
||||
<string name="trigger_shortcut">Shortcut</string>
|
||||
<string name="trigger_broadcast">Broadcast</string>
|
||||
<string name="trigger_notification">Notification</string>
|
||||
<string name="trigger_lock">Lock</string>
|
||||
<string name="trigger_usb">USB</string>
|
||||
<string name="copied_popup">Copied</string>
|
||||
</resources>
|
||||
<string name="main">Main</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="trigger_panic_kit_description">Enable panic responder. PanicKit is a collection of tools for creating “panic buttons” that can trigger a system-wide response when the user is in an anxious or dangerous situation. It enables trigger apps and responder apps to safely and easily connect to each other. The user engages with the trigger app when in a panic situation. The responder apps receive that trigger signal, and individually execute the steps that they were configured to do.</string>
|
||||
<string name="trigger_tile_description">Enable tile service. It is a button in quick settings panel when you swipe from the top of the screen. This button will mimic to the airplane mode. It has 2s delay if you will tap it accidentally.</string>
|
||||
<string name="trigger_shortcut_description">Enable icon shortcut. It is a button you will see when you make a long tap on the Wasted icon.</string>
|
||||
<string name="trigger_broadcast_description">Enable broadcast receiver. It is useful to communicate with another Android apps. For example you can fire Wasted from Tasker using this.</string>
|
||||
<string name="trigger_notification_description">Enable device notification listener. It will scan all notifications it has access to for the secret code. When found it will fire. You have to give Wasted necessary permissions in Settings > Notifications > Device and app notifications.</string>
|
||||
<string name="trigger_lock_description">Enable lock job scheduler. It will schedule a job every time you lock a device and cancel it every time you unlock a device. When you do not unlock a device for X time a job will fire.</string>
|
||||
<string name="trigger_usb_description">Enable USB state receiver. When you make a USB data connection while a device is locked it will fire. It must not fire on charger, only on device and accessory.</string>
|
||||
</resources>
|
|
@ -1,6 +1,6 @@
|
|||
<resources>
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.Wasted" parent="Theme.Material3.DayNight">
|
||||
<style name="Theme.Wasted" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_500</item>
|
||||
<item name="colorOnPrimary">@color/white</item>
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<usb-accessory />
|
||||
</resources>
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<usb-device />
|
||||
</resources>
|
|
@ -0,0 +1,5 @@
|
|||
refresh ui
|
||||
"Wipe on inactivity" is now a trigger named "Lock", it will respect "Wipe data/eSIM" checkboxes, you have to re-enable it if you used "Wipe on inactivity" before
|
||||
merge USBKill to Wasted
|
||||
|
||||
there were many internal changes, if you will find bugs please let me know, thx
|
|
@ -1,12 +1,12 @@
|
|||
Lock a device and wipe its data on emergency.
|
||||
|
||||
You can use PanicKit, tile, shortcut or send a message with authentication code. On trigger, using
|
||||
You can use PanicKit, tile, shortcut or send a message with a secret code. On trigger, using
|
||||
Device Administration API, it locks a device and optionally runs wipe.
|
||||
|
||||
Also you can:
|
||||
* wipe a device when it was not unlocked for N time
|
||||
* wipe a device using a duress password (companion app: [Duress](https://github.com/x13a/Duress))
|
||||
* wipe a device on USB connection event (companion app: [USBKill](https://github.com/x13a/USBKill))
|
||||
* 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 to install risky apps and Wasted in it. Then you can
|
||||
wipe this profile data with one click without wiping the whole device.
|
||||
|
@ -15,8 +15,8 @@ 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
|
||||
* FOREGROUND_SERVICE - receive lock and USB state events
|
||||
* RECEIVE_BOOT_COMPLETED - persist lock job and foreground service across reboots
|
||||
|
||||
It is Free Open Source Software.
|
||||
License: GPL-3
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 55 KiB |
Binary file not shown.
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 34 KiB |
Binary file not shown.
After Width: | Height: | Size: 216 KiB |
|
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStoreBase=GRADLE_USER_HOME
|
|
@ -6,4 +6,4 @@ dependencyResolutionManagement {
|
|||
}
|
||||
}
|
||||
rootProject.name = "Wasted"
|
||||
include ':app'
|
||||
include ':app'
|
Loading…
Reference in New Issue