Skip to content

Commit 3a83bc2

Browse files
author
lucky
committed
1.1.1
1 parent 00888e5 commit 3a83bc2

File tree

21 files changed

+216
-41
lines changed

21 files changed

+216
-41
lines changed

README.md

-4
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,6 @@ Enforce security policies.
66
src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
77
alt="Get it on F-Droid"
88
height="80">](https://f-droid.org/packages/me.lucky.sentry/)
9-
[<img
10-
src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png"
11-
alt="Get it on Google Play"
12-
height="80">](https://play.google.com/store/apps/details?id=me.lucky.sentry)
139

1410
<img
1511
src="fastlane/metadata/android/en-US/images/phoneScreenshots/1.png"

SECURITY.md

-12
This file was deleted.

app/build.gradle

+10-5
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ android {
1111
applicationId "me.lucky.sentry"
1212
minSdk 23
1313
targetSdk 32
14-
versionCode 7
15-
versionName "1.1.0"
14+
versionCode 8
15+
versionName "1.1.1"
1616

1717
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
1818

@@ -47,19 +47,24 @@ android {
4747

4848
dependencies {
4949
implementation 'androidx.core:core-ktx:1.8.0'
50-
implementation 'androidx.appcompat:appcompat:1.4.2'
50+
implementation 'androidx.appcompat:appcompat:1.5.0'
5151
implementation 'com.google.android.material:material:1.6.1'
5252
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
5353
testImplementation 'junit:junit:4.13.2'
5454
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
5555
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
5656

5757
implementation 'androidx.security:security-crypto:1.0.0'
58-
implementation 'androidx.preference:preference-ktx:1.2.0'
58+
// https://issuetracker.google.com/issues/238425626
59+
implementation('androidx.preference:preference-ktx:1.2.0') {
60+
exclude group: 'androidx.lifecycle', module:'lifecycle-viewmodel'
61+
exclude group: 'androidx.lifecycle', module:'lifecycle-viewmodel-ktx'
62+
}
5963
implementation 'androidx.biometric:biometric:1.1.0'
6064
implementation 'androidx.drawerlayout:drawerlayout:1.1.1'
65+
implementation 'info.guardianproject.panic:panic:1.0'
6166

62-
def room_version = "2.4.2"
67+
def room_version = "2.4.3"
6368
implementation "androidx.room:room-runtime:$room_version"
6469
kapt "androidx.room:room-compiler:$room_version"
6570
}

app/src/main/AndroidManifest.xml

+22
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,27 @@
5454
</meta-data>
5555
</service>
5656

57+
<activity
58+
android:name=".panic.PanicResponderActivity"
59+
android:noHistory="true"
60+
android:exported="true"
61+
android:theme="@android:style/Theme.NoDisplay">
62+
<intent-filter>
63+
<action android:name="info.guardianproject.panic.action.TRIGGER" />
64+
<category android:name="android.intent.category.DEFAULT" />
65+
</intent-filter>
66+
</activity>
67+
68+
<activity
69+
android:name=".panic.PanicConnectionActivity"
70+
android:noHistory="true"
71+
android:exported="true">
72+
<intent-filter>
73+
<action android:name="info.guardianproject.panic.action.CONNECT" />
74+
<action android:name="info.guardianproject.panic.action.DISCONNECT" />
75+
<category android:name="android.intent.category.DEFAULT" />
76+
</intent-filter>
77+
</activity>
78+
5779
</application>
5880
</manifest>

app/src/main/java/me/lucky/sentry/MainActivity.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import me.lucky.sentry.databinding.ActivityMainBinding
1111
import me.lucky.sentry.fragment.MainFragment
1212
import me.lucky.sentry.fragment.MonitorFragment
1313

14-
class MainActivity : AppCompatActivity() {
14+
open class MainActivity : AppCompatActivity() {
1515
private lateinit var binding: ActivityMainBinding
1616

1717
override fun onCreate(savedInstanceState: Bundle?) {

app/src/main/java/me/lucky/sentry/NotificationManager.kt

+3-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package me.lucky.sentry
22

33
import android.content.Context
44
import android.content.pm.PackageManager
5+
import android.os.SystemClock
56
import androidx.core.app.NotificationChannelCompat
67
import androidx.core.app.NotificationCompat
78
import androidx.core.app.NotificationManagerCompat
@@ -11,8 +12,6 @@ class NotificationManager(private val ctx: Context) {
1112
private const val CHANNEL_PASSWORD_ID = "monitor_password"
1213
private const val CHANNEL_INTERNET_ID = "monitor_internet"
1314
private const val GROUP_KEY = "alert"
14-
private const val NOTIFICATION_PASSWORD_ID = 1000
15-
private const val NOTIFICATION_INTERNET_ID = 1001
1615
}
1716

1817
private val manager = NotificationManagerCompat.from(ctx)
@@ -32,7 +31,7 @@ class NotificationManager(private val ctx: Context) {
3231

3332
fun notifyInternet(packageName: String) =
3433
manager.notify(
35-
NOTIFICATION_INTERNET_ID,
34+
SystemClock.uptimeMillis().toInt(),
3635
buildNotification(NotificationCompat.Builder(ctx, CHANNEL_INTERNET_ID)
3736
.setContentText(formatInternetText(packageName))),)
3837

@@ -49,7 +48,7 @@ class NotificationManager(private val ctx: Context) {
4948

5049
fun notifyPassword() =
5150
manager.notify(
52-
NOTIFICATION_PASSWORD_ID,
51+
SystemClock.uptimeMillis().toInt(),
5352
buildNotification(NotificationCompat.Builder(ctx, CHANNEL_PASSWORD_ID)
5453
.setContentText(ctx.getString(R.string.notification_password_text))
5554
.setSilent(true)),)

app/src/main/java/me/lucky/sentry/Preferences.kt

+6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ class Preferences(ctx: Context, encrypted: Boolean = true) {
1212
companion object {
1313
private const val ENABLED = "enabled"
1414
private const val MAX_FAILED_PASSWORD_ATTEMPTS = "max_failed_password_attempts"
15+
private const val MAX_FAILED_PASSWORD_ATTEMPTS_WARNING =
16+
"max_failed_password_attempts_warning"
1517
private const val USB_DATA_SIGNALING_CTL_ENABLED = "usb_data_signaling_ctl_enabled"
1618
private const val MONITOR = "monitor"
1719

@@ -43,6 +45,10 @@ class Preferences(ctx: Context, encrypted: Boolean = true) {
4345
get() = prefs.getInt(MAX_FAILED_PASSWORD_ATTEMPTS, 0)
4446
set(value) = prefs.edit { putInt(MAX_FAILED_PASSWORD_ATTEMPTS, value) }
4547

48+
var isMaxFailedPasswordAttemptsWarningChecked: Boolean
49+
get() = prefs.getBoolean(MAX_FAILED_PASSWORD_ATTEMPTS_WARNING, false)
50+
set(value) = prefs.edit { putBoolean(MAX_FAILED_PASSWORD_ATTEMPTS_WARNING, value) }
51+
4652
var isUsbDataSignalingCtlEnabled: Boolean
4753
get() = prefs.getBoolean(USB_DATA_SIGNALING_CTL_ENABLED, false)
4854
set(value) = prefs.edit { putBoolean(USB_DATA_SIGNALING_CTL_ENABLED, value) }

app/src/main/java/me/lucky/sentry/admin/DeviceAdminManager.kt

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ class DeviceAdminManager(private val ctx: Context) {
1515
fun getCurrentFailedPasswordAttempts() = dpm?.currentFailedPasswordAttempts ?: 0
1616
fun isDeviceOwner() = dpm?.isDeviceOwnerApp(ctx.packageName) ?: false
1717

18+
fun setMaximumFailedPasswordsForWipe(num: Int) =
19+
dpm?.setMaximumFailedPasswordsForWipe(deviceAdmin, num)
20+
1821
@RequiresApi(Build.VERSION_CODES.S)
1922
fun canUsbDataSignalingBeDisabled() = dpm?.canUsbDataSignalingBeDisabled() ?: false
2023

app/src/main/java/me/lucky/sentry/admin/DeviceAdminReceiver.kt

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class DeviceAdminReceiver : DeviceAdminReceiver() {
1818
|| context.getSystemService(UserManager::class.java)?.isUserUnlocked == true)
1919
if (prefs.monitor.and(Monitor.PASSWORD.value) != 0)
2020
NotificationManager(context).notifyPassword()
21+
if (prefs.isMaxFailedPasswordAttemptsWarningChecked) return
2122
val maxFailedPasswordAttempts = prefs.maxFailedPasswordAttempts
2223
if (!prefs.isEnabled || maxFailedPasswordAttempts <= 0) return
2324
val admin = DeviceAdminManager(context)

app/src/main/java/me/lucky/sentry/fragment/MainFragment.kt

+22-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import android.view.LayoutInflater
99
import android.view.View
1010
import android.view.ViewGroup
1111
import androidx.activity.result.contract.ActivityResultContracts
12+
import androidx.core.widget.doAfterTextChanged
1213
import androidx.fragment.app.Fragment
1314
import com.google.android.material.snackbar.Snackbar
1415

@@ -52,7 +53,9 @@ class MainFragment : Fragment() {
5253
prefsdb = Preferences(ctx, encrypted = false)
5354
prefs.copyTo(prefsdb)
5455
binding.apply {
55-
maxFailedPasswordAttempts.value = prefs.maxFailedPasswordAttempts.toFloat()
56+
maxFailedPasswordAttempts.editText?.setText(prefs.maxFailedPasswordAttempts.toString())
57+
maxFailedPasswordAttemptsWarning.isChecked =
58+
prefs.isMaxFailedPasswordAttemptsWarningChecked
5659
val canChangeUsbDataSignaling = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
5760
admin.canUsbDataSignalingBeDisabled() &&
5861
admin.isDeviceOwner()
@@ -65,8 +68,18 @@ class MainFragment : Fragment() {
6568
}
6669

6770
private fun setup() = binding.apply {
68-
maxFailedPasswordAttempts.addOnChangeListener { _, value, _ ->
69-
prefs.maxFailedPasswordAttempts = value.toInt()
71+
maxFailedPasswordAttempts.editText?.doAfterTextChanged {
72+
val i = it?.toString()?.toIntOrNull() ?: return@doAfterTextChanged
73+
prefs.maxFailedPasswordAttempts = i
74+
if (prefs.isMaxFailedPasswordAttemptsWarningChecked)
75+
try { admin.setMaximumFailedPasswordsForWipe(i) } catch (exc: SecurityException) {}
76+
}
77+
maxFailedPasswordAttemptsWarning.setOnCheckedChangeListener { _, isChecked ->
78+
prefs.isMaxFailedPasswordAttemptsWarningChecked = isChecked
79+
try {
80+
admin.setMaximumFailedPasswordsForWipe(
81+
if (isChecked) prefs.maxFailedPasswordAttempts else 0)
82+
} catch (exc: SecurityException) {}
7083
}
7184
usbDataSignaling.setOnCheckedChangeListener { _, isChecked ->
7285
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return@setOnCheckedChangeListener
@@ -88,6 +101,12 @@ class MainFragment : Fragment() {
88101
}
89102

90103
private fun setOn() {
104+
try {
105+
admin.setMaximumFailedPasswordsForWipe(
106+
if (prefs.isMaxFailedPasswordAttemptsWarningChecked) prefs.maxFailedPasswordAttempts
107+
else 0
108+
)
109+
} catch (exc: SecurityException) {}
91110
prefs.isEnabled = true
92111
binding.toggle.isChecked = true
93112
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package me.lucky.sentry.panic
2+
3+
import android.content.pm.PackageManager
4+
import android.os.Bundle
5+
import com.google.android.material.dialog.MaterialAlertDialogBuilder
6+
7+
import info.guardianproject.panic.PanicResponder
8+
import me.lucky.sentry.MainActivity
9+
import me.lucky.sentry.R
10+
11+
class PanicConnectionActivity : MainActivity() {
12+
override fun onCreate(savedInstanceState: Bundle?) {
13+
super.onCreate(savedInstanceState)
14+
if (PanicResponder.checkForDisconnectIntent(this)) {
15+
finish()
16+
return
17+
}
18+
val sender = PanicResponder.getConnectIntentSender(this)
19+
val packageName = PanicResponder.getTriggerPackageName(this)
20+
if (sender != "" && sender != packageName) showOptInDialog() else finish()
21+
}
22+
23+
private fun showOptInDialog() {
24+
var app: CharSequence = getString(R.string.panic_app_unknown_app)
25+
val packageName = callingActivity?.packageName
26+
if (packageName != null) {
27+
try {
28+
app = packageManager
29+
.getApplicationLabel(packageManager.getApplicationInfo(packageName, 0))
30+
} catch (exc: PackageManager.NameNotFoundException) {}
31+
}
32+
MaterialAlertDialogBuilder(this)
33+
.setTitle(R.string.panic_app_dialog_title)
34+
.setMessage(getString(R.string.panic_app_dialog_message, app))
35+
.setNegativeButton(R.string.allow) { _, _ ->
36+
PanicResponder.setTriggerPackageName(this)
37+
setResult(RESULT_OK)
38+
finish()
39+
}
40+
.setPositiveButton(android.R.string.cancel) { _, _ ->
41+
setResult(RESULT_CANCELED)
42+
finish()
43+
}
44+
.show()
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package me.lucky.sentry.panic
2+
3+
import android.os.Bundle
4+
import androidx.appcompat.app.AppCompatActivity
5+
6+
import info.guardianproject.panic.Panic
7+
import info.guardianproject.panic.PanicResponder
8+
import me.lucky.sentry.AppDatabase
9+
10+
class PanicResponderActivity : AppCompatActivity() {
11+
override fun onCreate(savedInstanceState: Bundle?) {
12+
super.onCreate(savedInstanceState)
13+
if (!Panic.isTriggerIntent(intent)) {
14+
finishAndRemoveTask()
15+
return
16+
}
17+
if (PanicResponder.receivedTriggerFromConnectedApp(this))
18+
AppDatabase.getInstance(this).packageDao().deleteAll()
19+
finishAndRemoveTask()
20+
}
21+
}

app/src/main/res/layout/fragment_main.xml

+26-6
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,39 @@
2222
android:layout_height="wrap_content"
2323
android:orientation="vertical">
2424

25-
<com.google.android.material.slider.Slider
25+
<com.google.android.material.textfield.TextInputLayout
2626
android:id="@+id/maxFailedPasswordAttempts"
2727
android:layout_width="match_parent"
2828
android:layout_height="wrap_content"
29-
android:valueFrom="0"
30-
android:valueTo="10"
31-
android:stepSize="1.0" />
29+
app:helperTextEnabled="true"
30+
app:helperText="@string/max_failed_password_attempts_helper_text"
31+
android:hint="@string/max_failed_password_attempts_hint">
32+
33+
<com.google.android.material.textfield.TextInputEditText
34+
android:inputType="number"
35+
android:layout_width="match_parent"
36+
android:layout_height="wrap_content" />
37+
38+
</com.google.android.material.textfield.TextInputLayout>
39+
40+
<Space
41+
android:layout_width="match_parent"
42+
android:layout_height="wrap_content"
43+
android:layout_marginVertical="5dp" />
44+
45+
<CheckBox
46+
android:id="@+id/maxFailedPasswordAttemptsWarning"
47+
android:layout_width="match_parent"
48+
android:layout_height="wrap_content"
49+
android:layoutDirection="rtl"
50+
android:textAppearance="?attr/textAppearanceBodyLarge"
51+
android:text="@string/max_failed_password_attempts_warning" />
3252

3353
<TextView
3454
android:layout_width="match_parent"
3555
android:layout_height="wrap_content"
36-
android:textAppearance="?attr/textAppearanceBodySmall"
37-
android:text="@string/max_failed_password_attempts_description" />
56+
android:text="@string/max_failed_password_attempts_warning_description"
57+
android:textAppearance="?attr/textAppearanceBodySmall" />
3858

3959
<Space
4060
android:layout_width="match_parent"

app/src/main/res/values-de/strings.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<string name="monitor">Überwachung</string>
1414
<string name="main">Start</string>
1515
<string name="authentication">Authentifizierung</string>
16-
<string name="max_failed_password_attempts_description">Maximale Anzahl von fehlgeschlagenen Passwortversuchen.</string>
16+
<string name="max_failed_password_attempts_helper_text">Maximale Anzahl von fehlgeschlagenen Passwortversuchen.</string>
1717
<string name="usb_data_signaling_change_failed_popup">USB-Datensignalisierungsrichtlinie konnte nicht geändert werden</string>
1818
<string name="usb_data_signaling_ctl_description">Schalten Sie die USB-Datensignalisierung bei ausgeschaltetem Bildschirm AUS und bei entsperrtem Bildschirm EIN.</string>
1919
<string name="usb_data_signaling_description">Wenn diese Funktion deaktiviert ist, sind USB-Datenverbindungen (mit Ausnahme von Ladefunktionen) verboten.</string>

app/src/main/res/values-ru/strings.xml

+16-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,20 @@
33
<string name="app_name">Sentry</string>
44
<string name="usb_data_signaling">Передача данных по USB</string>
55
<string name="usb_data_signaling_description">Когда отключено, соединения по USB (кроме зарядки) запрещены.</string>
6+
<string name="usb_data_signaling_ctl">Контроллер</string>
7+
<string name="usb_data_signaling_ctl_description">Выключите сигнализацию данных USB на выключенном экране и включите ее при разблокировке.</string>
68
<string name="usb_data_signaling_change_failed_popup">Не удалось изменить политику передачи данных по USB</string>
7-
<string name="max_failed_password_attempts_description">Максимальное количество неудачных попыток ввода пароля.</string>
8-
</resources>
9+
<string name="max_failed_password_attempts_helper_text">Максимальное количество неудачных попыток ввода пароля.</string>
10+
<string name="authentication">Аутентификация</string>
11+
<string name="main">Главная</string>
12+
<string name="monitor">Экран</string>
13+
<string name="monitor_password">Пароль</string>
14+
<string name="monitor_password_description">При неудачной попытке ввода пароля вы получите уведомление.</string>
15+
<string name="monitor_internet">Интернет</string>
16+
<string name="monitor_internet_description">Если какое-либо приложение получит ИНТЕРНЕТ разрешение после обновления, вы получите уведомление.</string>
17+
<string name="notification_title">ВНИМАНИЕ</string>
18+
<string name="notification_password_text">Обнаружена неудачная попытка вввода пароля!</string>
19+
<string name="notification_internet_text">%1$s (%2$s) получил ИНТЕРНЕТ разрешение!</string>
20+
<string name="unknown_app">Неизвестное приложение</string>
21+
<string name="goto_button">НАЙТИ</string>
22+
</resources>

0 commit comments

Comments
 (0)