Skip to content

Commit

Permalink
recast option
Browse files Browse the repository at this point in the history
  • Loading branch information
lucky committed Aug 29, 2022
1 parent 6fc8fa3 commit 957bec3
Show file tree
Hide file tree
Showing 20 changed files with 295 additions and 101 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ Lock a device and wipe its data on emergency.
You can use [PanicKit](https://guardianproject.info/code/panickit/), tile, shortcut or send a
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.
locks a device and optionally runs wipe (factory reset). Also it can send a broadcast message
instead of the wipe.

Also you can:
* fire when a device was not unlocked for X time
Expand Down
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ android {
applicationId "me.lucky.wasted"
minSdk 23
targetSdk 32
versionCode 38
versionName "1.5.9"
versionCode 39
versionName "1.5.10"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/me/lucky/wasted/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ open class MainActivity : AppCompatActivity() {
R.id.nav_trigger_notification -> NotificationFragment()
R.id.nav_trigger_lock -> LockFragment()
R.id.nav_trigger_application -> ApplicationFragment()
R.id.top_settings -> SettingsFragment()
R.id.nav_recast -> RecastFragment()
else -> MainFragment()
}

Expand Down
26 changes: 26 additions & 0 deletions app/src/main/java/me/lucky/wasted/Preferences.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ class Preferences(ctx: Context, encrypted: Boolean = true) {
private const val WIPE_DATA = "wipe_data"
private const val WIPE_EMBEDDED_SIM = "wipe_embedded_sim"

private const val RECAST_ENABLED = "recast_enabled"
private const val RECAST_ACTION = "recast_action"
private const val RECAST_RECEIVER = "recast_receiver"
private const val RECAST_EXTRA_KEY = "recast_extra_key"
private const val RECAST_EXTRA_VALUE = "recast_extra_value"

private const val TRIGGERS = "triggers"
private const val TRIGGER_LOCK_COUNT = "trigger_lock_count"
private const val TRIGGER_TILE_DELAY = "trigger_tile_delay"
Expand Down Expand Up @@ -90,6 +96,26 @@ class Preferences(ctx: Context, encrypted: Boolean = true) {
get() = prefs.getInt(TRIGGER_APPLICATION_OPTIONS, 0)
set(value) = prefs.edit { putInt(TRIGGER_APPLICATION_OPTIONS, value) }

var isRecastEnabled: Boolean
get() = prefs.getBoolean(RECAST_ENABLED, false)
set(value) = prefs.edit { putBoolean(RECAST_ENABLED, value) }

var recastAction: String
get() = prefs.getString(RECAST_ACTION, "") ?: ""
set(value) = prefs.edit { putString(RECAST_ACTION, value) }

var recastReceiver: String
get() = prefs.getString(RECAST_RECEIVER, "") ?: ""
set(value) = prefs.edit { putString(RECAST_RECEIVER, value) }

var recastExtraKey: String
get() = prefs.getString(RECAST_EXTRA_KEY, "") ?: ""
set(value) = prefs.edit { putString(RECAST_EXTRA_KEY, value) }

var recastExtraValue: String
get() = prefs.getString(RECAST_EXTRA_VALUE, "") ?: ""
set(value) = prefs.edit { putString(RECAST_EXTRA_VALUE, value) }

fun registerListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) =
prefs.registerOnSharedPreferenceChangeListener(listener)

Expand Down
41 changes: 37 additions & 4 deletions app/src/main/java/me/lucky/wasted/Utils.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package me.lucky.wasted

import android.app.KeyguardManager
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.admin.DeviceAdminManager
import me.lucky.wasted.trigger.notification.NotificationListenerService
import me.lucky.wasted.trigger.panic.PanicConnectionActivity
import me.lucky.wasted.trigger.panic.PanicResponderActivity
Expand All @@ -26,10 +28,10 @@ class Utils(private val ctx: Context) {
}
}

private val shortcut by lazy { ShortcutManager(ctx) }
private val prefs by lazy { Preferences.new(ctx) }

fun setEnabled(enabled: Boolean) {
val triggers = Preferences(ctx).triggers
val triggers = prefs.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)
Expand All @@ -50,6 +52,7 @@ class Utils(private val ctx: Context) {
}

fun setShortcutEnabled(enabled: Boolean) {
val shortcut = ShortcutManager(ctx)
if (!enabled) shortcut.remove()
setComponentEnabled(ShortcutActivity::class.java, enabled)
if (enabled) shortcut.push()
Expand All @@ -63,7 +66,6 @@ class Utils(private val ctx: Context) {

fun updateApplicationEnabled() {
val prefix = "${ctx.packageName}.trigger.application"
val prefs = Preferences(ctx)
val options = prefs.triggerApplicationOptions
val enabled = prefs.isEnabled && prefs.triggers.and(Trigger.APPLICATION.value) != 0
setComponentEnabled(
Expand All @@ -85,7 +87,6 @@ class Utils(private val ctx: Context) {
}

fun updateForegroundRequiredEnabled() {
val prefs = Preferences(ctx)
val enabled = prefs.isEnabled
val triggers = prefs.triggers
val isUSB = triggers.and(Trigger.USB.value) != 0
Expand Down Expand Up @@ -114,4 +115,36 @@ class Utils(private val ctx: Context) {
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP,
)

fun fire(trigger: Trigger, safe: Boolean = true) {
if (!prefs.isEnabled || prefs.triggers.and(trigger.value) == 0) return
val admin = DeviceAdminManager(ctx)
try {
admin.lockNow()
if (prefs.isWipeData && safe) admin.wipeData()
} catch (exc: SecurityException) {}
if (prefs.isRecastEnabled && safe) recast()
}

fun isDeviceLocked() = ctx.getSystemService(KeyguardManager::class.java).isDeviceLocked

private fun recast() {
val action = prefs.recastAction
if (action.isEmpty()) return
ctx.sendBroadcast(Intent(action).apply {
val cls = prefs.recastReceiver.split('/')
val packageName = cls.firstOrNull() ?: ""
if (packageName.isNotEmpty()) {
setPackage(packageName)
if (cls.size == 2)
setClassName(
packageName,
"$packageName.${cls[1].trimStart('.')}",
)
}
addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
val extraKey = prefs.recastExtraKey
if (extraKey.isNotEmpty()) putExtra(extraKey, prefs.recastExtraValue)
})
}
}
76 changes: 76 additions & 0 deletions app/src/main/java/me/lucky/wasted/fragment/RecastFragment.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package me.lucky.wasted.fragment

import android.content.Context
import android.content.SharedPreferences
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 me.lucky.wasted.Preferences
import me.lucky.wasted.databinding.FragmentRecastBinding

class RecastFragment : Fragment() {
private lateinit var binding: FragmentRecastBinding
private lateinit var ctx: Context
private lateinit var prefs: Preferences
private lateinit var prefsdb: Preferences

private val prefsListener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
prefs.copyTo(prefsdb, key)
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
binding = FragmentRecastBinding.inflate(inflater, container, false)
init()
setup()
return binding.root
}

override fun onStart() {
super.onStart()
prefs.registerListener(prefsListener)
}

override fun onStop() {
super.onStop()
prefs.unregisterListener(prefsListener)
}

private fun init() {
ctx = requireContext()
prefs = Preferences(ctx)
prefsdb = Preferences(ctx, encrypted = false)
binding.apply {
enabled.isChecked = prefs.isRecastEnabled
action.editText?.setText(prefs.recastAction)
receiver.editText?.setText(prefs.recastReceiver)
extraKey.editText?.setText(prefs.recastExtraKey)
extraValue.editText?.setText(prefs.recastExtraValue)
}
}

private fun setup() = binding.apply {
enabled.setOnCheckedChangeListener { _, isChecked ->
prefs.isRecastEnabled = isChecked
}
action.editText?.doAfterTextChanged {
prefs.recastAction = it?.toString()?.trim() ?: return@doAfterTextChanged
}
receiver.editText?.doAfterTextChanged {
prefs.recastReceiver = it?.toString()?.trim() ?: return@doAfterTextChanged
}
extraKey.editText?.doAfterTextChanged {
prefs.recastExtraKey = it?.toString()?.trim() ?: return@doAfterTextChanged
}
extraValue.editText?.doAfterTextChanged {
prefs.recastExtraValue = it?.toString()?.trim() ?: return@doAfterTextChanged
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,13 @@ package me.lucky.wasted.trigger.application
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

import me.lucky.wasted.Preferences
import me.lucky.wasted.Trigger
import me.lucky.wasted.admin.DeviceAdminManager
import me.lucky.wasted.Utils

class ApplicationActivity : AppCompatActivity() {
private val prefs by lazy { Preferences(this) }
private val admin by lazy { DeviceAdminManager(this) }

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (!prefs.isEnabled || prefs.triggers.and(Trigger.APPLICATION.value) == 0) {
finishAndRemoveTask()
return
}
try {
admin.lockNow()
if (prefs.isWipeData) admin.wipeData()
} catch (exc: SecurityException) {}
Utils(this).fire(Trigger.APPLICATION)
finishAndRemoveTask()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,23 @@ import android.content.Intent

import me.lucky.wasted.Preferences
import me.lucky.wasted.Trigger
import me.lucky.wasted.admin.DeviceAdminManager
import me.lucky.wasted.Utils

class BroadcastReceiver : BroadcastReceiver() {
companion object {
const val KEY = "code"
const val ACTION = "me.lucky.wasted.action.TRIGGER"

fun panic(context: Context, intent: Intent?) {
fun panic(context: Context, intent: Intent?, trigger: Trigger) {
if (intent?.action != ACTION) return
val prefs = Preferences.new(context)
if (!prefs.isEnabled) return
val secret = prefs.secret
val secret = Preferences.new(context).secret
assert(secret.isNotEmpty())
if (intent.getStringExtra(KEY)?.trim() != secret) return
val admin = DeviceAdminManager(context)
try {
admin.lockNow()
if (prefs.isWipeData) admin.wipeData()
} catch (exc: SecurityException) {}
Utils(context).fire(trigger)
}
}

override fun onReceive(context: Context?, intent: Intent?) {
if (Preferences.new(context ?: return).triggers.and(Trigger.BROADCAST.value) != 0)
panic(context, intent)
panic(context ?: return, intent, Trigger.BROADCAST)
}
}
11 changes: 2 additions & 9 deletions app/src/main/java/me/lucky/wasted/trigger/lock/LockJobService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,12 @@ 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
import me.lucky.wasted.Utils

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) {}
Utils(this).fire(Trigger.LOCK)
return false
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ 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
import me.lucky.wasted.Utils

class NotificationListenerService : NotificationListenerService() {
private lateinit var prefs: Preferences
private lateinit var admin: DeviceAdminManager
private lateinit var utils: Utils

override fun onCreate() {
super.onCreate()
Expand All @@ -20,22 +20,17 @@ class NotificationListenerService : NotificationListenerService() {

private fun init() {
prefs = Preferences.new(this)
admin = DeviceAdminManager(this)
utils = Utils(this)
}

override fun onNotificationPosted(sbn: StatusBarNotification?) {
super.onNotificationPosted(sbn)
if (sbn == null ||
!prefs.isEnabled ||
prefs.triggers.and(Trigger.NOTIFICATION.value) == 0) return
if (sbn == null) return
val secret = prefs.secret
assert(secret.isNotEmpty())
if (sbn.notification.extras[Notification.EXTRA_TEXT]?.toString() != secret) return
if (sbn.notification.extras[Notification.EXTRA_TEXT]?.toString()?.trim() != secret) return
cancelAllNotifications()
try {
admin.lockNow()
if (prefs.isWipeData) admin.wipeData()
} catch (exc: SecurityException) {}
utils.fire(Trigger.NOTIFICATION)
}

override fun onListenerConnected() {
Expand Down
Loading

0 comments on commit 957bec3

Please sign in to comment.