direct boot aware

This commit is contained in:
lucky 2022-07-03 05:05:34 +03:00
parent 16b79689c6
commit 31b343b7d8
18 changed files with 97 additions and 34 deletions

View File

@ -24,6 +24,7 @@ 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))
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

View File

@ -10,8 +10,8 @@ android {
applicationId "me.lucky.wasted"
minSdk 23
targetSdk 32
versionCode 27
versionName "1.4.2"
versionCode 28
versionName "1.4.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@ -42,11 +42,11 @@ dependencies {
implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation 'androidx.security:security-crypto:1.0.0'
implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'info.guardianproject.panic:panic:1.0'
}

View File

@ -41,6 +41,7 @@
<receiver
android:name=".TriggerReceiver"
android:directBootAware="true"
android:enabled="false"
android:exported="true"
tools:ignore="ExportedReceiver">
@ -51,6 +52,7 @@
<activity
android:name=".PanicResponderActivity"
android:directBootAware="true"
android:noHistory="true"
android:enabled="false"
android:exported="true"
@ -86,6 +88,7 @@
<service
android:name=".TileService"
android:directBootAware="true"
android:icon="@drawable/ic_baseline_airplanemode_active_24"
android:label="@string/tile_label"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
@ -101,20 +104,24 @@
<service
android:name=".WipeJobService"
android:directBootAware="true"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
<service
android:name=".ForegroundService"
android:directBootAware="true"
android:exported="false">
</service>
<receiver
android:name=".RestartReceiver"
android:directBootAware="true"
android:enabled="false"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
@ -122,6 +129,7 @@
<service
android:name=".NotificationListenerService"
android:directBootAware="true"
android:enabled="false"
android:exported="true"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">

View File

@ -10,7 +10,7 @@ import java.lang.Exception
class DeviceAdminManager(private val ctx: Context) {
private val dpm = ctx.getSystemService(DevicePolicyManager::class.java)
private val deviceAdmin by lazy { ComponentName(ctx, DeviceAdminReceiver::class.java) }
private val prefs by lazy { Preferences(ctx) }
private val prefs by lazy { Preferences.new(ctx) }
fun remove() = dpm?.removeActiveAdmin(deviceAdmin)
fun isActive() = dpm?.isAdminActive(deviceAdmin) ?: false

View File

@ -59,7 +59,7 @@ class ForegroundService : Service() {
private var unlocked = false
override fun onReceive(context: Context?, intent: Intent?) {
if (!Preferences(context ?: return).isWipeOnInactivity) return
if (!Preferences.new(context ?: return).isWipeOnInactivity) return
when (intent?.action) {
Intent.ACTION_USER_PRESENT -> {
if (context.getSystemService(KeyguardManager::class.java)

View File

@ -1,9 +1,6 @@
package me.lucky.wasted
import android.content.ClipData
import android.content.ClipboardManager
import android.content.ComponentName
import android.content.Intent
import android.content.*
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
@ -32,6 +29,7 @@ open class MainActivity : AppCompatActivity() {
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) }
@ -40,6 +38,10 @@ open class MainActivity : AppCompatActivity() {
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) {
@ -68,9 +70,15 @@ open class MainActivity : AppCompatActivity() {
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(
@ -82,6 +90,8 @@ open class MainActivity : AppCompatActivity() {
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()

View File

@ -15,7 +15,7 @@ class NotificationListenerService : NotificationListenerService() {
}
private fun init() {
prefs = Preferences(this)
prefs = Preferences.new(this)
admin = DeviceAdminManager(this)
}

View File

@ -7,7 +7,7 @@ import info.guardianproject.panic.Panic
import info.guardianproject.panic.PanicResponder
class PanicResponderActivity : AppCompatActivity() {
private val prefs by lazy { Preferences(this) }
private val prefs by lazy { Preferences.new(this) }
private val admin by lazy { DeviceAdminManager(this) }
override fun onCreate(savedInstanceState: Bundle?) {

View File

@ -1,13 +1,17 @@
package me.lucky.wasted
import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import android.os.UserManager
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
class Preferences(ctx: Context) {
class Preferences(ctx: Context, encrypted: Boolean = true) {
companion object {
const val DEFAULT_WIPE_ON_INACTIVITY_COUNT = 7 * 24 * 60
private const val DEFAULT_WIPE_ON_INACTIVITY_COUNT = 7 * 24 * 60
private const val ENABLED = "enabled"
private const val AUTHENTICATION_CODE = "authentication_code"
@ -19,16 +23,28 @@ class Preferences(ctx: Context) {
private const val WIPE_ON_INACTIVITY_COUNT = "wipe_on_inactivity_count"
private const val FILE_NAME = "sec_shared_prefs"
fun new(ctx: Context) = Preferences(
ctx,
encrypted = Build.VERSION.SDK_INT < Build.VERSION_CODES.N ||
ctx.getSystemService(UserManager::class.java).isUserUnlocked,
)
}
private val mk = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
private val prefs = EncryptedSharedPreferences.create(
FILE_NAME,
mk,
ctx,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
)
private val prefs: SharedPreferences = if (encrypted) {
val mk = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
EncryptedSharedPreferences.create(
FILE_NAME,
mk,
ctx,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
)
} else {
val context = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
ctx.createDeviceProtectedStorageContext() else ctx
PreferenceManager.getDefaultSharedPreferences(context)
}
var isEnabled: Boolean
get() = prefs.getBoolean(ENABLED, false)
@ -57,6 +73,25 @@ class Preferences(ctx: Context) {
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) }
fun registerListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) =
prefs.registerOnSharedPreferenceChangeListener(listener)
fun unregisterListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) =
prefs.unregisterOnSharedPreferenceChangeListener(listener)
fun copyTo(dst: Preferences, key: String? = null) = dst.prefs.edit {
for (entry in prefs.all.entries) {
val k = entry.key
if (key != null && k != key) continue
val v = entry.value ?: continue
when (v) {
is Boolean -> putBoolean(k, v)
is Int -> putInt(k, v)
is String -> putString(k, v)
}
}
}
}
enum class Trigger(val value: Int) {

View File

@ -7,9 +7,10 @@ import androidx.core.content.ContextCompat
class RestartReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action != Intent.ACTION_BOOT_COMPLETED &&
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(context ?: return)
val prefs = Preferences.new(context ?: return)
if (!prefs.isEnabled || !prefs.isWipeOnInactivity) return
ContextCompat.startForegroundService(
context.applicationContext,

View File

@ -5,7 +5,6 @@ import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import androidx.annotation.RequiresApi
import java.util.*
import java.util.concurrent.atomic.AtomicInteger
import kotlin.concurrent.timerTask
@RequiresApi(Build.VERSION_CODES.N)
@ -16,7 +15,7 @@ class TileService : TileService() {
private lateinit var prefs: Preferences
private lateinit var admin: DeviceAdminManager
private val counter = AtomicInteger()
private var counter = 0
private var timer: Timer? = null
override fun onCreate() {
@ -25,7 +24,7 @@ class TileService : TileService() {
}
private fun init() {
prefs = Preferences(this)
prefs = Preferences.new(this)
admin = DeviceAdminManager(this)
}
@ -46,7 +45,9 @@ class TileService : TileService() {
} catch (exc: SecurityException) {}
return
}
when (counter.getAndIncrement()) {
val v = counter
counter++
when (v) {
0 -> {
update(Tile.STATE_ACTIVE)
timer?.cancel()
@ -61,7 +62,7 @@ class TileService : TileService() {
else -> {
timer?.cancel()
update(Tile.STATE_INACTIVE)
counter.set(0)
counter = 0
}
}
}

View File

@ -11,7 +11,7 @@ class TriggerReceiver : BroadcastReceiver() {
fun panic(context: Context, intent: Intent?) {
if (intent?.action != ACTION) return
val prefs = Preferences(context)
val prefs = Preferences.new(context)
if (!prefs.isEnabled) return
val code = prefs.authenticationCode
assert(code.isNotEmpty())
@ -25,7 +25,8 @@ class TriggerReceiver : BroadcastReceiver() {
}
override fun onReceive(context: Context?, intent: Intent?) {
if (Preferences(context ?: return).triggers.and(Trigger.BROADCAST.value) == 0) return
if (Preferences.new(context ?: return).triggers.and(Trigger.BROADCAST.value) == 0)
return
panic(context, intent)
}
}

View File

@ -10,7 +10,7 @@ class WipeJobManager(private val ctx: Context) {
companion object {
private const val JOB_ID = 1000
}
private val prefs by lazy { Preferences(ctx) }
private val prefs by lazy { Preferences.new(ctx) }
private val scheduler = ctx.getSystemService(JobScheduler::class.java)
fun schedule(): Int {

View File

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

View File

@ -1,4 +1,4 @@
<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="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z"/>

View File

@ -11,7 +11,10 @@
<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 giorni.</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="notification_channel_default_name">Predefinito</string>
<string name="foreground_service_notification_title">Guardia</string>
<string name="triggers_array_panic_kit">PanicKit</string>

View File

@ -0,0 +1,2 @@
direct boot aware
update Italian translation, thanks to Giovanni Donisi (@gdonisi + @giovannidonisi)

View File

@ -6,6 +6,7 @@ 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))
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.