Skip to content

Commit 368ec9e

Browse files
author
lucky
committed
1.1
1 parent 8ee1f31 commit 368ec9e

29 files changed

+785
-158
lines changed

PRIVACY.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
# Privacy Policy
22

3-
The app has nothing to store, but settings.
3+
The app may store package names of apps without internet permission in internal database if
4+
Monitor > Internet is checked.

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ Tiny app to enforce security policies of your device.
2121
It can:
2222
* limit the maximum number of failed password attempts
2323
* disable USB data connections (Android 12, USB HAL 1.3, Device Owner)
24+
* notify on failed password attempt
25+
* notify when an app without Internet permission got it after an update
2426

2527
Also you can grant it device & app notifications permission to turn off USB data connections
2628
automatically on screen off.
@@ -29,7 +31,7 @@ automatically on screen off.
2931

3032
* DEVICE_ADMIN - limit the maximum number of failed password attempts
3133
* DEVICE_OWNER - disable USB data connections
32-
* NOTIFICATION_LISTENER - receive lock events (optional)
34+
* NOTIFICATION_LISTENER - receive lock/package events
3335

3436
## Example
3537

SECURITY.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44

55
| Version | Supported |
66
| ------- | ------------------ |
7-
| 1.0.x | :white_check_mark: |
8-
| < 1.0 | :x: |
7+
| 1.1.x | :white_check_mark: |
8+
| < 1.1 | :x: |
99

1010
## Reporting a Vulnerability
1111

12-
Contact: mailto:44uaanjm0@relay.firefox.com
12+
Contact: 44uaanjm0@relay.firefox.com

app/build.gradle

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
plugins {
22
id 'com.android.application'
33
id 'org.jetbrains.kotlin.android'
4+
id 'kotlin-kapt'
45
}
56

67
android {
@@ -10,10 +11,16 @@ android {
1011
applicationId "me.lucky.sentry"
1112
minSdk 23
1213
targetSdk 32
13-
versionCode 6
14-
versionName "1.0.5"
14+
versionCode 7
15+
versionName "1.1.0"
1516

1617
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18+
19+
kapt {
20+
arguments {
21+
arg("room.schemaLocation", "$projectDir/schemas")
22+
}
23+
}
1724
}
1825

1926
buildTypes {
@@ -50,4 +57,9 @@ dependencies {
5057
implementation 'androidx.security:security-crypto:1.0.0'
5158
implementation 'androidx.preference:preference-ktx:1.2.0'
5259
implementation 'androidx.biometric:biometric:1.1.0'
60+
implementation 'androidx.drawerlayout:drawerlayout:1.1.1'
61+
62+
def room_version = "2.4.2"
63+
implementation "androidx.room:room-runtime:$room_version"
64+
kapt "androidx.room:room-compiler:$room_version"
5365
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"formatVersion": 1,
3+
"database": {
4+
"version": 1,
5+
"identityHash": "70aadd9a8960bce26cb4a67e42c1d104",
6+
"entities": [
7+
{
8+
"tableName": "package",
9+
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL)",
10+
"fields": [
11+
{
12+
"fieldPath": "uid",
13+
"columnName": "uid",
14+
"affinity": "INTEGER",
15+
"notNull": true
16+
},
17+
{
18+
"fieldPath": "name",
19+
"columnName": "name",
20+
"affinity": "TEXT",
21+
"notNull": true
22+
}
23+
],
24+
"primaryKey": {
25+
"columnNames": [
26+
"uid"
27+
],
28+
"autoGenerate": true
29+
},
30+
"indices": [
31+
{
32+
"name": "index_package_name",
33+
"unique": true,
34+
"columnNames": [
35+
"name"
36+
],
37+
"orders": [],
38+
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_package_name` ON `${TABLE_NAME}` (`name`)"
39+
}
40+
],
41+
"foreignKeys": []
42+
}
43+
],
44+
"views": [],
45+
"setupQueries": [
46+
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
47+
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '70aadd9a8960bce26cb4a67e42c1d104')"
48+
]
49+
}
50+
}

app/src/main/AndroidManifest.xml

+5-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
xmlns:tools="http://schemas.android.com/tools"
44
package="me.lucky.sentry">
55

6-
<uses-feature android:name="android.software.device_admin" android:required="true" />
6+
<uses-feature android:name="android.software.device_admin" android:required="false" />
7+
<uses-permission
8+
android:name="android.permission.QUERY_ALL_PACKAGES"
9+
tools:ignore="QueryAllPackagesPermission" />
710

811
<application
912
android:allowBackup="false"
@@ -27,7 +30,7 @@
2730
</activity>
2831

2932
<receiver
30-
android:name=".DeviceAdminReceiver"
33+
android:name=".admin.DeviceAdminReceiver"
3134
android:permission="android.permission.BIND_DEVICE_ADMIN"
3235
android:directBootAware="true"
3336
android:exported="true">
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package me.lucky.sentry
2+
3+
import android.content.Context
4+
import androidx.room.*
5+
6+
@Database(entities = [Package::class], version = 1)
7+
abstract class AppDatabase : RoomDatabase() {
8+
abstract fun packageDao(): PackageDao
9+
10+
companion object {
11+
@Volatile private var instance: AppDatabase? = null
12+
private const val DATABASE_NAME = "app.db"
13+
14+
fun getInstance(context: Context) =
15+
instance ?: synchronized(this) {
16+
instance ?: buildDatabase(context).also { instance = it }
17+
}
18+
19+
private fun buildDatabase(context: Context) = Room
20+
.databaseBuilder(context.applicationContext, AppDatabase::class.java, DATABASE_NAME)
21+
.allowMainThreadQueries()
22+
.build()
23+
}
24+
}
25+
26+
@Dao
27+
interface PackageDao {
28+
@Insert
29+
fun insert(obj: Package)
30+
31+
@Query("DELETE FROM package WHERE name = :name")
32+
fun delete(name: String)
33+
34+
@Query("SELECT * FROM package WHERE name = :name")
35+
fun select(name: String): Package?
36+
37+
@Query("DELETE FROM package")
38+
fun deleteAll()
39+
}
40+
41+
@Entity(
42+
indices = [Index(value = ["name"], unique = true)],
43+
tableName = "package",
44+
)
45+
data class Package(
46+
@PrimaryKey(autoGenerate = true) val uid: Int,
47+
@ColumnInfo(name = "name") val name: String,
48+
)
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,54 @@
11
package me.lucky.sentry
22

3-
import android.content.SharedPreferences
4-
import android.os.Build
53
import android.os.Bundle
6-
import androidx.activity.result.contract.ActivityResultContracts
74
import androidx.appcompat.app.AppCompatActivity
85
import androidx.biometric.BiometricManager
96
import androidx.biometric.BiometricPrompt
107
import androidx.core.content.ContextCompat
11-
import com.google.android.material.snackbar.Snackbar
8+
import androidx.fragment.app.Fragment
129

1310
import me.lucky.sentry.databinding.ActivityMainBinding
11+
import me.lucky.sentry.fragment.MainFragment
12+
import me.lucky.sentry.fragment.MonitorFragment
1413

1514
class MainActivity : AppCompatActivity() {
1615
private lateinit var binding: ActivityMainBinding
17-
private lateinit var prefs: Preferences
18-
private lateinit var prefsdb: Preferences
19-
private lateinit var admin: DeviceAdminManager
20-
21-
private val registerForDeviceAdmin =
22-
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
23-
if (it.resultCode != RESULT_OK) setOff() else setOn()
24-
}
25-
26-
private val prefsListener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
27-
prefs.copyTo(prefsdb, key)
28-
}
2916

3017
override fun onCreate(savedInstanceState: Bundle?) {
3118
super.onCreate(savedInstanceState)
3219
binding = ActivityMainBinding.inflate(layoutInflater)
3320
setContentView(binding.root)
34-
init1()
3521
if (initBiometric()) return
36-
init2()
22+
init()
3723
setup()
3824
}
3925

40-
override fun onStart() {
41-
super.onStart()
42-
prefs.registerListener(prefsListener)
43-
update()
26+
private fun init() {
27+
replaceFragment(MainFragment())
4428
}
4529

46-
override fun onStop() {
47-
super.onStop()
48-
prefs.unregisterListener(prefsListener)
30+
private fun setup() = binding.apply {
31+
appBar.setNavigationOnClickListener {
32+
drawer.open()
33+
}
34+
navigation.setNavigationItemSelectedListener {
35+
replaceFragment(getFragment(it.itemId))
36+
it.isChecked = true
37+
drawer.close()
38+
true
39+
}
40+
}
41+
42+
private fun replaceFragment(f: Fragment) =
43+
supportFragmentManager
44+
.beginTransaction()
45+
.replace(binding.fragment.id, f)
46+
.commit()
47+
48+
private fun getFragment(id: Int) = when (id) {
49+
R.id.nav_main -> MainFragment()
50+
R.id.nav_monitor -> MonitorFragment()
51+
else -> MainFragment()
4952
}
5053

5154
private fun initBiometric(): Boolean {
@@ -71,7 +74,7 @@ class MainActivity : AppCompatActivity() {
7174

7275
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
7376
super.onAuthenticationSucceeded(result)
74-
init2()
77+
init()
7578
setup()
7679
}
7780
})
@@ -84,63 +87,4 @@ class MainActivity : AppCompatActivity() {
8487
} catch (exc: Exception) { return false }
8588
return true
8689
}
87-
88-
private fun init1() {
89-
prefs = Preferences(this)
90-
prefsdb = Preferences(this, encrypted = false)
91-
prefs.copyTo(prefsdb)
92-
admin = DeviceAdminManager(this)
93-
}
94-
95-
private fun init2() {
96-
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S ||
97-
!admin.canUsbDataSignalingBeDisabled() ||
98-
!admin.isDeviceOwner())
99-
disableUsbDataSignaling()
100-
binding.apply {
101-
maxFailedPasswordAttempts.value = prefs.maxFailedPasswordAttempts.toFloat()
102-
usbDataSignaling.isChecked = isUsbDataSignalingEnabled()
103-
toggle.isChecked = prefs.isEnabled
104-
}
105-
}
106-
107-
private fun setup() = binding.apply {
108-
maxFailedPasswordAttempts.addOnChangeListener { _, value, _ ->
109-
prefs.maxFailedPasswordAttempts = value.toInt()
110-
}
111-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
112-
usbDataSignaling.setOnCheckedChangeListener { _, isChecked ->
113-
try { admin.setUsbDataSignalingEnabled(isChecked) } catch (exc: Exception) {
114-
Snackbar.make(
115-
usbDataSignaling,
116-
R.string.usb_data_signaling_change_failed_popup,
117-
Snackbar.LENGTH_SHORT,
118-
).show()
119-
usbDataSignaling.isChecked = !isChecked
120-
}
121-
}
122-
toggle.setOnCheckedChangeListener { _, isChecked ->
123-
if (isChecked) requestAdmin() else setOff()
124-
}
125-
}
126-
127-
private fun disableUsbDataSignaling() { binding.usbDataSignaling.isEnabled = false }
128-
129-
private fun setOn() {
130-
prefs.isEnabled = true
131-
binding.toggle.isChecked = true
132-
}
133-
134-
private fun setOff() {
135-
prefs.isEnabled = false
136-
try { admin.remove() } catch (exc: SecurityException) {}
137-
binding.toggle.isChecked = false
138-
}
139-
140-
private fun update() { binding.usbDataSignaling.isChecked = isUsbDataSignalingEnabled() }
141-
142-
private fun isUsbDataSignalingEnabled() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
143-
admin.isUsbDataSignalingEnabled() else true
144-
145-
private fun requestAdmin() = registerForDeviceAdmin.launch(admin.makeRequestIntent())
14690
}

0 commit comments

Comments
 (0)