Skip to content

Commit

Permalink
direct boot aware
Browse files Browse the repository at this point in the history
  • Loading branch information
lucky committed Jun 29, 2022
1 parent 4812468 commit 3182199
Show file tree
Hide file tree
Showing 13 changed files with 77 additions and 63 deletions.
5 changes: 3 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.sentry"
minSdk 23
targetSdk 32
versionCode 3
versionName "1.0.2"
versionCode 4
versionName "1.0.3"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down Expand Up @@ -48,4 +48,5 @@ dependencies {
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'
}
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@

<receiver
android:name=".DeviceAdminReceiver"
android:description="@string/device_admin_description"
android:permission="android.permission.BIND_DEVICE_ADMIN"
android:directBootAware="true"
android:exported="true">
<meta-data android:name="android.app.device_admin"
android:resource="@xml/device_admin" />
Expand Down
4 changes: 0 additions & 4 deletions app/src/main/java/me/lucky/sentry/DeviceAdminManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,4 @@ class DeviceAdminManager(private val ctx: Context) {
fun makeRequestIntent() =
Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN)
.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, deviceAdmin)
.putExtra(
DevicePolicyManager.EXTRA_ADD_EXPLANATION,
ctx.getString(R.string.device_admin_description),
)
}
23 changes: 7 additions & 16 deletions app/src/main/java/me/lucky/sentry/DeviceAdminReceiver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,21 @@ package me.lucky.sentry
import android.app.admin.DeviceAdminReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.UserHandle
import android.widget.Toast
import android.os.UserManager

class DeviceAdminReceiver : DeviceAdminReceiver() {
override fun onPasswordFailed(context: Context, intent: Intent, user: UserHandle) {
super.onPasswordFailed(context, intent, user)
val prefs = Preferences(context)
val prefs = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N &&
context.getSystemService(UserManager::class.java)?.isUserUnlocked != true)

PreferencesDirectBoot(context) else Preferences(context)
val maxFailedPasswordAttempts = prefs.maxFailedPasswordAttempts
if (!prefs.isEnabled || maxFailedPasswordAttempts <= 0) return
val admin = DeviceAdminManager(context)
if (admin.getCurrentFailedPasswordAttempts() >= maxFailedPasswordAttempts)
admin.wipeData()
}

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()
}

override fun onEnabled(context: Context, intent: Intent) {
super.onEnabled(context, intent)
DeviceAdminManager(context)
.setMaximumFailedPasswordsForWipe(Preferences(context)
.maxFailedPasswordAttempts.shl(1))
try { admin.wipeData() } catch (exc: SecurityException) {}
}
}
19 changes: 8 additions & 11 deletions app/src/main/java/me/lucky/sentry/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import me.lucky.sentry.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var prefs: Preferences
private lateinit var prefs: PreferencesProxy
private lateinit var admin: DeviceAdminManager

private val registerForDeviceAdmin =
Expand All @@ -34,8 +34,11 @@ class MainActivity : AppCompatActivity() {
}

private fun init() {
prefs = Preferences(this)
prefs = PreferencesProxy(this)
prefs.clone()
admin = DeviceAdminManager(this)
if (prefs.isEnabled && prefs.maxFailedPasswordAttempts > 0)
try { admin.setMaximumFailedPasswordsForWipe(0) } catch (exc: SecurityException) {}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
!packageManager.hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN))
hideSecureLockScreenRequired()
Expand All @@ -51,11 +54,7 @@ class MainActivity : AppCompatActivity() {
private fun setup() {
binding.apply {
maxFailedPasswordAttempts.addOnChangeListener { _, value, _ ->
val num = value.toInt()
prefs.maxFailedPasswordAttempts = num
try {
admin.setMaximumFailedPasswordsForWipe(num.shl(1))
} catch (exc: SecurityException) {}
prefs.maxFailedPasswordAttempts = value.toInt()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
usbDataSignaling.setOnCheckedChangeListener { _, isChecked ->
Expand Down Expand Up @@ -97,13 +96,11 @@ class MainActivity : AppCompatActivity() {

private fun setOff() {
prefs.isEnabled = false
admin.remove()
try { admin.remove() } catch (exc: SecurityException) {}
}

private fun update() {
binding.apply {
usbDataSignaling.isChecked = isUsbDataSignalingEnabled()
}
binding.usbDataSignaling.isChecked = isUsbDataSignalingEnabled()
if (prefs.isEnabled && !admin.isActive())
Snackbar.make(
binding.toggle,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class NotificationListenerService : NotificationListenerService() {
}

private fun deinit() {
unregisterReceiver(lockReceiver)
try { unregisterReceiver(lockReceiver) } catch (exc: IllegalArgumentException) {}
}

override fun onListenerConnected() {
Expand All @@ -59,9 +59,8 @@ class NotificationListenerService : NotificationListenerService() {

@RequiresApi(Build.VERSION_CODES.S)
private fun setUsbDataSignalingEnabled(ctx: Context, enabled: Boolean) {
try {
DeviceAdminManager(ctx).setUsbDataSignalingEnabled(enabled)
} catch (exc: Exception) {}
try { DeviceAdminManager(ctx).setUsbDataSignalingEnabled(enabled) }
catch (exc: Exception) {}
}
}
}
55 changes: 50 additions & 5 deletions app/src/main/java/me/lucky/sentry/Preferences.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package me.lucky.sentry

import android.content.Context
import android.os.Build
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) : Prefs {
companion object {
private const val ENABLED = "enabled"
private const val MAX_FAILED_PASSWORD_ATTEMPTS = "max_failed_password_attempts"
const val ENABLED = "enabled"
const val MAX_FAILED_PASSWORD_ATTEMPTS = "max_failed_password_attempts"

private const val FILE_NAME = "sec_shared_prefs"
// migration
Expand All @@ -24,11 +26,54 @@ class Preferences(ctx: Context) {
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
)

var isEnabled: Boolean
override var isEnabled: Boolean
get() = prefs.getBoolean(ENABLED, prefs.getBoolean(SERVICE_ENABLED, false))
set(value) = prefs.edit { putBoolean(ENABLED, value) }

var maxFailedPasswordAttempts: Int
override var maxFailedPasswordAttempts: Int
get() = prefs.getInt(MAX_FAILED_PASSWORD_ATTEMPTS, 0)
set(value) = prefs.edit { putInt(MAX_FAILED_PASSWORD_ATTEMPTS, value) }
}

class PreferencesProxy(ctx: Context) {
private val prefs = Preferences(ctx)
private val prefsdb = PreferencesDirectBoot(ctx)

fun clone() {
prefsdb.isEnabled = prefs.isEnabled
prefsdb.maxFailedPasswordAttempts = prefsdb.maxFailedPasswordAttempts
}

var isEnabled: Boolean
get() = prefs.isEnabled
set(value) {
prefs.isEnabled = value
prefsdb.isEnabled = value
}

var maxFailedPasswordAttempts: Int
get() = prefs.maxFailedPasswordAttempts
set(value) {
prefs.maxFailedPasswordAttempts = value
prefsdb.maxFailedPasswordAttempts = value
}
}

interface Prefs {
var isEnabled: Boolean
var maxFailedPasswordAttempts: Int
}

class PreferencesDirectBoot(ctx: Context) : Prefs {
private val context = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
ctx.createDeviceProtectedStorageContext() else ctx
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)

override var isEnabled: Boolean
get() = prefs.getBoolean(Preferences.ENABLED, false)
set(value) = prefs.edit { putBoolean(Preferences.ENABLED, value) }

override var maxFailedPasswordAttempts: Int
get() = prefs.getInt(Preferences.MAX_FAILED_PASSWORD_ATTEMPTS, 0)
set(value) = prefs.edit { putInt(Preferences.MAX_FAILED_PASSWORD_ATTEMPTS, value) }
}
18 changes: 3 additions & 15 deletions app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,13 @@
android:padding="32dp"
tools:context=".MainActivity">

<TextView
android:id="@+id/description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/description"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<ScrollView
android:id="@+id/scrollView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginVertical="16dp"
android:padding="16dp"
app:layout_constraintBottom_toTopOf="@+id/toggle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/description">
app:layout_constraintTop_toTopOf="parent">

<LinearLayout
android:layout_width="match_parent"
Expand Down
2 changes: 0 additions & 2 deletions app/src/main/res/values-ru/strings.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Sentry</string>
<string name="description">Включите приложение, чтобы применить дополнительные политики безопасности. Вы должны выдать разрешение на администрирование устройства.</string>
<string name="device_admin_description">Разрешите приложению стирать данные устройства и отключать передачу данных по USB.</string>
<string name="service_unavailable_popup">Администратор устройства недоступен</string>
<string name="usb_data_signaling">Передача данных по USB</string>
<string name="usb_data_signaling_description">Когда отключено, соединения по USB (кроме зарядки) запрещены.</string>
Expand Down
2 changes: 0 additions & 2 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Sentry</string>
<string name="description">Turn on Sentry to enforce security policies of your device. You have to grant device administration permission.</string>
<string name="device_admin_description">Allow Sentry to wipe a device and disable USB data connections.</string>
<string name="service_unavailable_popup">Device admin unavailable</string>
<string name="usb_data_signaling">USB data signaling</string>
<string name="usb_data_signaling_description">When disabled, USB data connections (except from charging functions) are prohibited.</string>
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
plugins {
id 'com.android.application' version '7.2.1' apply false
id 'com.android.library' version '7.2.1' apply false
id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
id 'org.jetbrains.kotlin.android' version '1.7.0' apply false
}

task clean(type: Delete) {
Expand Down
1 change: 1 addition & 0 deletions fastlane/metadata/android/en-US/changelogs/4.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
direct boot aware
Binary file modified fastlane/metadata/android/en-US/images/phoneScreenshots/1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 3182199

Please sign in to comment.