diff --git a/README.md b/README.md
index 88dc939..6e59fd6 100644
--- a/README.md
+++ b/README.md
@@ -6,10 +6,6 @@ Enforce security policies.
src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/me.lucky.sentry/)
-[
](https://play.google.com/store/apps/details?id=me.lucky.sentry)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/sentry/MainActivity.kt b/app/src/main/java/me/lucky/sentry/MainActivity.kt
index 9a34ae8..d79f343 100644
--- a/app/src/main/java/me/lucky/sentry/MainActivity.kt
+++ b/app/src/main/java/me/lucky/sentry/MainActivity.kt
@@ -11,7 +11,7 @@ import me.lucky.sentry.databinding.ActivityMainBinding
import me.lucky.sentry.fragment.MainFragment
import me.lucky.sentry.fragment.MonitorFragment
-class MainActivity : AppCompatActivity() {
+open class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
diff --git a/app/src/main/java/me/lucky/sentry/NotificationManager.kt b/app/src/main/java/me/lucky/sentry/NotificationManager.kt
index 8c4469b..322ccb1 100644
--- a/app/src/main/java/me/lucky/sentry/NotificationManager.kt
+++ b/app/src/main/java/me/lucky/sentry/NotificationManager.kt
@@ -2,6 +2,7 @@ package me.lucky.sentry
import android.content.Context
import android.content.pm.PackageManager
+import android.os.SystemClock
import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
@@ -11,8 +12,6 @@ class NotificationManager(private val ctx: Context) {
private const val CHANNEL_PASSWORD_ID = "monitor_password"
private const val CHANNEL_INTERNET_ID = "monitor_internet"
private const val GROUP_KEY = "alert"
- private const val NOTIFICATION_PASSWORD_ID = 1000
- private const val NOTIFICATION_INTERNET_ID = 1001
}
private val manager = NotificationManagerCompat.from(ctx)
@@ -32,7 +31,7 @@ class NotificationManager(private val ctx: Context) {
fun notifyInternet(packageName: String) =
manager.notify(
- NOTIFICATION_INTERNET_ID,
+ SystemClock.uptimeMillis().toInt(),
buildNotification(NotificationCompat.Builder(ctx, CHANNEL_INTERNET_ID)
.setContentText(formatInternetText(packageName))),)
@@ -49,7 +48,7 @@ class NotificationManager(private val ctx: Context) {
fun notifyPassword() =
manager.notify(
- NOTIFICATION_PASSWORD_ID,
+ SystemClock.uptimeMillis().toInt(),
buildNotification(NotificationCompat.Builder(ctx, CHANNEL_PASSWORD_ID)
.setContentText(ctx.getString(R.string.notification_password_text))
.setSilent(true)),)
diff --git a/app/src/main/java/me/lucky/sentry/Preferences.kt b/app/src/main/java/me/lucky/sentry/Preferences.kt
index 260f2a9..7bf569c 100644
--- a/app/src/main/java/me/lucky/sentry/Preferences.kt
+++ b/app/src/main/java/me/lucky/sentry/Preferences.kt
@@ -12,6 +12,8 @@ class Preferences(ctx: Context, encrypted: Boolean = true) {
companion object {
private const val ENABLED = "enabled"
private const val MAX_FAILED_PASSWORD_ATTEMPTS = "max_failed_password_attempts"
+ private const val MAX_FAILED_PASSWORD_ATTEMPTS_WARNING =
+ "max_failed_password_attempts_warning"
private const val USB_DATA_SIGNALING_CTL_ENABLED = "usb_data_signaling_ctl_enabled"
private const val MONITOR = "monitor"
@@ -43,6 +45,10 @@ class Preferences(ctx: Context, encrypted: Boolean = true) {
get() = prefs.getInt(MAX_FAILED_PASSWORD_ATTEMPTS, 0)
set(value) = prefs.edit { putInt(MAX_FAILED_PASSWORD_ATTEMPTS, value) }
+ var isMaxFailedPasswordAttemptsWarningChecked: Boolean
+ get() = prefs.getBoolean(MAX_FAILED_PASSWORD_ATTEMPTS_WARNING, false)
+ set(value) = prefs.edit { putBoolean(MAX_FAILED_PASSWORD_ATTEMPTS_WARNING, value) }
+
var isUsbDataSignalingCtlEnabled: Boolean
get() = prefs.getBoolean(USB_DATA_SIGNALING_CTL_ENABLED, false)
set(value) = prefs.edit { putBoolean(USB_DATA_SIGNALING_CTL_ENABLED, value) }
diff --git a/app/src/main/java/me/lucky/sentry/admin/DeviceAdminManager.kt b/app/src/main/java/me/lucky/sentry/admin/DeviceAdminManager.kt
index cf0fce7..89eefbe 100644
--- a/app/src/main/java/me/lucky/sentry/admin/DeviceAdminManager.kt
+++ b/app/src/main/java/me/lucky/sentry/admin/DeviceAdminManager.kt
@@ -15,6 +15,9 @@ class DeviceAdminManager(private val ctx: Context) {
fun getCurrentFailedPasswordAttempts() = dpm?.currentFailedPasswordAttempts ?: 0
fun isDeviceOwner() = dpm?.isDeviceOwnerApp(ctx.packageName) ?: false
+ fun setMaximumFailedPasswordsForWipe(num: Int) =
+ dpm?.setMaximumFailedPasswordsForWipe(deviceAdmin, num)
+
@RequiresApi(Build.VERSION_CODES.S)
fun canUsbDataSignalingBeDisabled() = dpm?.canUsbDataSignalingBeDisabled() ?: false
diff --git a/app/src/main/java/me/lucky/sentry/admin/DeviceAdminReceiver.kt b/app/src/main/java/me/lucky/sentry/admin/DeviceAdminReceiver.kt
index beb1314..14b96db 100644
--- a/app/src/main/java/me/lucky/sentry/admin/DeviceAdminReceiver.kt
+++ b/app/src/main/java/me/lucky/sentry/admin/DeviceAdminReceiver.kt
@@ -18,6 +18,7 @@ class DeviceAdminReceiver : DeviceAdminReceiver() {
|| context.getSystemService(UserManager::class.java)?.isUserUnlocked == true)
if (prefs.monitor.and(Monitor.PASSWORD.value) != 0)
NotificationManager(context).notifyPassword()
+ if (prefs.isMaxFailedPasswordAttemptsWarningChecked) return
val maxFailedPasswordAttempts = prefs.maxFailedPasswordAttempts
if (!prefs.isEnabled || maxFailedPasswordAttempts <= 0) return
val admin = DeviceAdminManager(context)
diff --git a/app/src/main/java/me/lucky/sentry/fragment/MainFragment.kt b/app/src/main/java/me/lucky/sentry/fragment/MainFragment.kt
index ff21ea6..d3778a1 100644
--- a/app/src/main/java/me/lucky/sentry/fragment/MainFragment.kt
+++ b/app/src/main/java/me/lucky/sentry/fragment/MainFragment.kt
@@ -9,6 +9,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
+import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.Fragment
import com.google.android.material.snackbar.Snackbar
@@ -52,7 +53,9 @@ class MainFragment : Fragment() {
prefsdb = Preferences(ctx, encrypted = false)
prefs.copyTo(prefsdb)
binding.apply {
- maxFailedPasswordAttempts.value = prefs.maxFailedPasswordAttempts.toFloat()
+ maxFailedPasswordAttempts.editText?.setText(prefs.maxFailedPasswordAttempts.toString())
+ maxFailedPasswordAttemptsWarning.isChecked =
+ prefs.isMaxFailedPasswordAttemptsWarningChecked
val canChangeUsbDataSignaling = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
admin.canUsbDataSignalingBeDisabled() &&
admin.isDeviceOwner()
@@ -65,8 +68,18 @@ class MainFragment : Fragment() {
}
private fun setup() = binding.apply {
- maxFailedPasswordAttempts.addOnChangeListener { _, value, _ ->
- prefs.maxFailedPasswordAttempts = value.toInt()
+ maxFailedPasswordAttempts.editText?.doAfterTextChanged {
+ val i = it?.toString()?.toIntOrNull() ?: return@doAfterTextChanged
+ prefs.maxFailedPasswordAttempts = i
+ if (prefs.isMaxFailedPasswordAttemptsWarningChecked)
+ try { admin.setMaximumFailedPasswordsForWipe(i) } catch (exc: SecurityException) {}
+ }
+ maxFailedPasswordAttemptsWarning.setOnCheckedChangeListener { _, isChecked ->
+ prefs.isMaxFailedPasswordAttemptsWarningChecked = isChecked
+ try {
+ admin.setMaximumFailedPasswordsForWipe(
+ if (isChecked) prefs.maxFailedPasswordAttempts else 0)
+ } catch (exc: SecurityException) {}
}
usbDataSignaling.setOnCheckedChangeListener { _, isChecked ->
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return@setOnCheckedChangeListener
@@ -88,6 +101,12 @@ class MainFragment : Fragment() {
}
private fun setOn() {
+ try {
+ admin.setMaximumFailedPasswordsForWipe(
+ if (prefs.isMaxFailedPasswordAttemptsWarningChecked) prefs.maxFailedPasswordAttempts
+ else 0
+ )
+ } catch (exc: SecurityException) {}
prefs.isEnabled = true
binding.toggle.isChecked = true
}
diff --git a/app/src/main/java/me/lucky/sentry/panic/PanicConnectionActivity.kt b/app/src/main/java/me/lucky/sentry/panic/PanicConnectionActivity.kt
new file mode 100644
index 0000000..ad5a784
--- /dev/null
+++ b/app/src/main/java/me/lucky/sentry/panic/PanicConnectionActivity.kt
@@ -0,0 +1,46 @@
+package me.lucky.sentry.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.sentry.MainActivity
+import me.lucky.sentry.R
+
+class PanicConnectionActivity : MainActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ if (PanicResponder.checkForDisconnectIntent(this)) {
+ finish()
+ return
+ }
+ val sender = PanicResponder.getConnectIntentSender(this)
+ val packageName = PanicResponder.getTriggerPackageName(this)
+ if (sender != "" && sender != packageName) showOptInDialog() else finish()
+ }
+
+ private fun showOptInDialog() {
+ var app: CharSequence = getString(R.string.panic_app_unknown_app)
+ val packageName = callingActivity?.packageName
+ if (packageName != null) {
+ try {
+ app = packageManager
+ .getApplicationLabel(packageManager.getApplicationInfo(packageName, 0))
+ } catch (exc: PackageManager.NameNotFoundException) {}
+ }
+ MaterialAlertDialogBuilder(this)
+ .setTitle(R.string.panic_app_dialog_title)
+ .setMessage(getString(R.string.panic_app_dialog_message, app))
+ .setNegativeButton(R.string.allow) { _, _ ->
+ PanicResponder.setTriggerPackageName(this)
+ setResult(RESULT_OK)
+ finish()
+ }
+ .setPositiveButton(android.R.string.cancel) { _, _ ->
+ setResult(RESULT_CANCELED)
+ finish()
+ }
+ .show()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/lucky/sentry/panic/PanicResponderActivity.kt b/app/src/main/java/me/lucky/sentry/panic/PanicResponderActivity.kt
new file mode 100644
index 0000000..0a1e542
--- /dev/null
+++ b/app/src/main/java/me/lucky/sentry/panic/PanicResponderActivity.kt
@@ -0,0 +1,21 @@
+package me.lucky.sentry.panic
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+
+import info.guardianproject.panic.Panic
+import info.guardianproject.panic.PanicResponder
+import me.lucky.sentry.AppDatabase
+
+class PanicResponderActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ if (!Panic.isTriggerIntent(intent)) {
+ finishAndRemoveTask()
+ return
+ }
+ if (PanicResponder.receivedTriggerFromConnectedApp(this))
+ AppDatabase.getInstance(this).packageDao().deleteAll()
+ finishAndRemoveTask()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml
index a608534..404aa59 100644
--- a/app/src/main/res/layout/fragment_main.xml
+++ b/app/src/main/res/layout/fragment_main.xml
@@ -22,19 +22,39 @@
android:layout_height="wrap_content"
android:orientation="vertical">
-
+ app:helperTextEnabled="true"
+ app:helperText="@string/max_failed_password_attempts_helper_text"
+ android:hint="@string/max_failed_password_attempts_hint">
+
+
+
+
+
+
+
+
+ android:text="@string/max_failed_password_attempts_warning_description"
+ android:textAppearance="?attr/textAppearanceBodySmall" />
Überwachung
Start
Authentifizierung
- Maximale Anzahl von fehlgeschlagenen Passwortversuchen.
+ Maximale Anzahl von fehlgeschlagenen Passwortversuchen.
USB-Datensignalisierungsrichtlinie konnte nicht geändert werden
Schalten Sie die USB-Datensignalisierung bei ausgeschaltetem Bildschirm AUS und bei entsperrtem Bildschirm EIN.
Wenn diese Funktion deaktiviert ist, sind USB-Datenverbindungen (mit Ausnahme von Ladefunktionen) verboten.
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 26e91bd..082c001 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -3,6 +3,20 @@
Sentry
Передача данных по USB
Когда отключено, соединения по USB (кроме зарядки) запрещены.
+ Контроллер
+ Выключите сигнализацию данных USB на выключенном экране и включите ее при разблокировке.
Не удалось изменить политику передачи данных по USB
- Максимальное количество неудачных попыток ввода пароля.
-
\ No newline at end of file
+ Максимальное количество неудачных попыток ввода пароля.
+ Аутентификация
+ Главная
+ Экран
+ Пароль
+ При неудачной попытке ввода пароля вы получите уведомление.
+ Интернет
+ Если какое-либо приложение получит ИНТЕРНЕТ разрешение после обновления, вы получите уведомление.
+ ВНИМАНИЕ
+ Обнаружена неудачная попытка вввода пароля!
+ %1$s (%2$s) получил ИНТЕРНЕТ разрешение!
+ Неизвестное приложение
+ НАЙТИ
+
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
new file mode 100644
index 0000000..404d2b5
--- /dev/null
+++ b/app/src/main/res/values-uk/strings.xml
@@ -0,0 +1,22 @@
+
+
+ Sentry
+ Передача даних USB
+ Якщо вимкнено, з’єднання даних через USB (за винятком функцій заряджання) заборонено.
+ Контролер
+ Вимкніть передачу даних USB на екрані та увімкніть під час розблокування.
+ Не вдалося змінити політику передачі даних USB
+ Максимальна кількість невдалих спроб пароля.
+ Автентифікація
+ Головна
+ Спостереження
+ Пароль
+ У разі невдалої спроби пароля ви отримаєте сповіщення.
+ Інтернет
+ Якщо якась програма отримає дозвіл ІНТЕРНЕТ після оновлення, ви отримаєте сповіщення.
+ ПОПЕРЕДЖЕННЯ
+ Виявлено невдалу спробу пароля!
+ %1$s (%2$s) отримав дозвіл ІНТЕРНЕТ!
+ Невідомий додаток
+ Доступ до сповіщень
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d5e6de4..bdd9d32 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -6,7 +6,10 @@
Controller
Turn USB data signaling OFF on screen off and turn it ON on unlock.
Failed to change USB data signaling policy
- Maximum number of failed password attempts.
+ Maximum number of failed password attempts.
+ number
+ Warning
+ Show X attempts left.
Authentication
Main
Monitor
@@ -19,4 +22,8 @@
%1$s (%2$s) has got INTERNET permission!
An unknown app
GOTO
+ Confirm panic app
+ Are you sure you want to allow %1$s to trigger destructive panic actions\?
+ an unknown app
+ Allow
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 4711aa7..255b9b3 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
- id 'com.android.application' version '7.2.1' apply false
- id 'com.android.library' version '7.2.1' apply false
+ id 'com.android.application' version '7.2.2' apply false
+ id 'com.android.library' version '7.2.2' apply false
id 'org.jetbrains.kotlin.android' version '1.7.0' apply false
}
diff --git a/data/sentry.svg b/data/icon.svg
similarity index 100%
rename from data/sentry.svg
rename to data/icon.svg
diff --git a/fastlane/metadata/android/en-US/changelogs/8.txt b/fastlane/metadata/android/en-US/changelogs/8.txt
new file mode 100644
index 0000000..fed9f46
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/8.txt
@@ -0,0 +1,6 @@
+any failed password attempts
+optional warning
+panickit responder
+add German translation, thanks to Malte Kiefer (@MalteKiefer)
+add Ukrainian translation, thanks to GNCanva
+update Russian translation, thanks to Photon_Gilbert
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png
index 7a5127a..eb6c674 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png differ