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/) -[Get it on Google Play](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