Skip to content

Commit

Permalink
1.1
Browse files Browse the repository at this point in the history
  • Loading branch information
lucky committed Jul 21, 2022
1 parent 8ee1f31 commit 368ec9e
Show file tree
Hide file tree
Showing 29 changed files with 785 additions and 158 deletions.
3 changes: 2 additions & 1 deletion PRIVACY.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Privacy Policy

The app has nothing to store, but settings.
The app may store package names of apps without internet permission in internal database if
Monitor > Internet is checked.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ Tiny app to enforce security policies of your device.
It can:
* limit the maximum number of failed password attempts
* disable USB data connections (Android 12, USB HAL 1.3, Device Owner)
* notify on failed password attempt
* notify when an app without Internet permission got it after an update

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

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

## Example

Expand Down
6 changes: 3 additions & 3 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

| Version | Supported |
| ------- | ------------------ |
| 1.0.x | :white_check_mark: |
| < 1.0 | :x: |
| 1.1.x | :white_check_mark: |
| < 1.1 | :x: |

## Reporting a Vulnerability

Contact: mailto:44uaanjm0@relay.firefox.com
Contact: 44uaanjm0@relay.firefox.com
16 changes: 14 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
}

android {
Expand All @@ -10,10 +11,16 @@ android {
applicationId "me.lucky.sentry"
minSdk 23
targetSdk 32
versionCode 6
versionName "1.0.5"
versionCode 7
versionName "1.1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

kapt {
arguments {
arg("room.schemaLocation", "$projectDir/schemas")
}
}
}

buildTypes {
Expand Down Expand Up @@ -50,4 +57,9 @@ dependencies {
implementation 'androidx.security:security-crypto:1.0.0'
implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.biometric:biometric:1.1.0'
implementation 'androidx.drawerlayout:drawerlayout:1.1.1'

def room_version = "2.4.2"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
}
50 changes: 50 additions & 0 deletions app/schemas/me.lucky.sentry.AppDatabase/1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "70aadd9a8960bce26cb4a67e42c1d104",
"entities": [
{
"tableName": "package",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"uid"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_package_name",
"unique": true,
"columnNames": [
"name"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_package_name` ON `${TABLE_NAME}` (`name`)"
}
],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '70aadd9a8960bce26cb4a67e42c1d104')"
]
}
}
7 changes: 5 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
xmlns:tools="http://schemas.android.com/tools"
package="me.lucky.sentry">

<uses-feature android:name="android.software.device_admin" android:required="true" />
<uses-feature android:name="android.software.device_admin" android:required="false" />
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />

<application
android:allowBackup="false"
Expand All @@ -27,7 +30,7 @@
</activity>

<receiver
android:name=".DeviceAdminReceiver"
android:name=".admin.DeviceAdminReceiver"
android:permission="android.permission.BIND_DEVICE_ADMIN"
android:directBootAware="true"
android:exported="true">
Expand Down
48 changes: 48 additions & 0 deletions app/src/main/java/me/lucky/sentry/Database.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package me.lucky.sentry

import android.content.Context
import androidx.room.*

@Database(entities = [Package::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun packageDao(): PackageDao

companion object {
@Volatile private var instance: AppDatabase? = null
private const val DATABASE_NAME = "app.db"

fun getInstance(context: Context) =
instance ?: synchronized(this) {
instance ?: buildDatabase(context).also { instance = it }
}

private fun buildDatabase(context: Context) = Room
.databaseBuilder(context.applicationContext, AppDatabase::class.java, DATABASE_NAME)
.allowMainThreadQueries()
.build()
}
}

@Dao
interface PackageDao {
@Insert
fun insert(obj: Package)

@Query("DELETE FROM package WHERE name = :name")
fun delete(name: String)

@Query("SELECT * FROM package WHERE name = :name")
fun select(name: String): Package?

@Query("DELETE FROM package")
fun deleteAll()
}

@Entity(
indices = [Index(value = ["name"], unique = true)],
tableName = "package",
)
data class Package(
@PrimaryKey(autoGenerate = true) val uid: Int,
@ColumnInfo(name = "name") val name: String,
)
114 changes: 29 additions & 85 deletions app/src/main/java/me/lucky/sentry/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,51 +1,54 @@
package me.lucky.sentry

import android.content.SharedPreferences
import android.os.Build
import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import com.google.android.material.snackbar.Snackbar
import androidx.fragment.app.Fragment

import me.lucky.sentry.databinding.ActivityMainBinding
import me.lucky.sentry.fragment.MainFragment
import me.lucky.sentry.fragment.MonitorFragment

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

private val registerForDeviceAdmin =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode != RESULT_OK) setOff() else setOn()
}

private val prefsListener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
prefs.copyTo(prefsdb, key)
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
init1()
if (initBiometric()) return
init2()
init()
setup()
}

override fun onStart() {
super.onStart()
prefs.registerListener(prefsListener)
update()
private fun init() {
replaceFragment(MainFragment())
}

override fun onStop() {
super.onStop()
prefs.unregisterListener(prefsListener)
private fun setup() = binding.apply {
appBar.setNavigationOnClickListener {
drawer.open()
}
navigation.setNavigationItemSelectedListener {
replaceFragment(getFragment(it.itemId))
it.isChecked = true
drawer.close()
true
}
}

private fun replaceFragment(f: Fragment) =
supportFragmentManager
.beginTransaction()
.replace(binding.fragment.id, f)
.commit()

private fun getFragment(id: Int) = when (id) {
R.id.nav_main -> MainFragment()
R.id.nav_monitor -> MonitorFragment()
else -> MainFragment()
}

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

override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
init2()
init()
setup()
}
})
Expand All @@ -84,63 +87,4 @@ class MainActivity : AppCompatActivity() {
} catch (exc: Exception) { return false }
return true
}

private fun init1() {
prefs = Preferences(this)
prefsdb = Preferences(this, encrypted = false)
prefs.copyTo(prefsdb)
admin = DeviceAdminManager(this)
}

private fun init2() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S ||
!admin.canUsbDataSignalingBeDisabled() ||
!admin.isDeviceOwner())
disableUsbDataSignaling()
binding.apply {
maxFailedPasswordAttempts.value = prefs.maxFailedPasswordAttempts.toFloat()
usbDataSignaling.isChecked = isUsbDataSignalingEnabled()
toggle.isChecked = prefs.isEnabled
}
}

private fun setup() = binding.apply {
maxFailedPasswordAttempts.addOnChangeListener { _, value, _ ->
prefs.maxFailedPasswordAttempts = value.toInt()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
usbDataSignaling.setOnCheckedChangeListener { _, isChecked ->
try { admin.setUsbDataSignalingEnabled(isChecked) } catch (exc: Exception) {
Snackbar.make(
usbDataSignaling,
R.string.usb_data_signaling_change_failed_popup,
Snackbar.LENGTH_SHORT,
).show()
usbDataSignaling.isChecked = !isChecked
}
}
toggle.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) requestAdmin() else setOff()
}
}

private fun disableUsbDataSignaling() { binding.usbDataSignaling.isEnabled = false }

private fun setOn() {
prefs.isEnabled = true
binding.toggle.isChecked = true
}

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

private fun update() { binding.usbDataSignaling.isChecked = isUsbDataSignalingEnabled() }

private fun isUsbDataSignalingEnabled() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
admin.isUsbDataSignalingEnabled() else true

private fun requestAdmin() = registerForDeviceAdmin.launch(admin.makeRequestIntent())
}
Loading

0 comments on commit 368ec9e

Please sign in to comment.