First poc
This commit is contained in:
parent
bfd5a134da
commit
37a8f3c2b7
@ -76,6 +76,18 @@
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="a16x" />
|
||||
<option name="id" value="a16x" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="A16 5G" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
|
@ -4,6 +4,14 @@
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DropdownSelection timestamp="2025-05-02T20:56:34.144725876Z">
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="Default" identifier="serial=192.168.0.192:37353;connection=b4c14d05" />
|
||||
</handle>
|
||||
</Target>
|
||||
</DropdownSelection>
|
||||
<DialogSelection />
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
</component>
|
||||
|
@ -1,4 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||
|
@ -1,4 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings" defaultProject="true" />
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
@ -4,12 +4,47 @@
|
||||
<shared>
|
||||
<config />
|
||||
</shared>
|
||||
<layouts>
|
||||
<layout url="file://$PROJECT_DIR$/app/src/main/res/layout/activity_transparent.xml">
|
||||
<config>
|
||||
<theme>@style/Theme.Transparent</theme>
|
||||
</config>
|
||||
</layout>
|
||||
</layouts>
|
||||
</component>
|
||||
<component name="AutoImportSettings">
|
||||
<option name="autoReloadType" value="NONE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="85e0dc1a-d7e2-4ab4-a8ad-e708732e10e5" name="Changes" comment="" />
|
||||
<list default="true" id="85e0dc1a-d7e2-4ab4-a8ad-e708732e10e5" name="Changes" comment="Improve .gitignore">
|
||||
<change beforePath="$PROJECT_DIR$/.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.gradle/8.11.1/checksums/checksums.lock" beforeDir="false" afterPath="$PROJECT_DIR$/.gradle/8.11.1/checksums/checksums.lock" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.gradle/8.11.1/checksums/md5-checksums.bin" beforeDir="false" afterPath="$PROJECT_DIR$/.gradle/8.11.1/checksums/md5-checksums.bin" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.gradle/8.11.1/checksums/sha1-checksums.bin" beforeDir="false" afterPath="$PROJECT_DIR$/.gradle/8.11.1/checksums/sha1-checksums.bin" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.gradle/8.11.1/executionHistory/executionHistory.bin" beforeDir="false" afterPath="$PROJECT_DIR$/.gradle/8.11.1/executionHistory/executionHistory.bin" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.gradle/8.11.1/executionHistory/executionHistory.lock" beforeDir="false" afterPath="$PROJECT_DIR$/.gradle/8.11.1/executionHistory/executionHistory.lock" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.gradle/8.11.1/fileHashes/fileHashes.bin" beforeDir="false" afterPath="$PROJECT_DIR$/.gradle/8.11.1/fileHashes/fileHashes.bin" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.gradle/8.11.1/fileHashes/fileHashes.lock" beforeDir="false" afterPath="$PROJECT_DIR$/.gradle/8.11.1/fileHashes/fileHashes.lock" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.gradle/8.11.1/fileHashes/resourceHashesCache.bin" beforeDir="false" afterPath="$PROJECT_DIR$/.gradle/8.11.1/fileHashes/resourceHashesCache.bin" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.gradle/buildOutputCleanup/buildOutputCleanup.lock" beforeDir="false" afterPath="$PROJECT_DIR$/.gradle/buildOutputCleanup/buildOutputCleanup.lock" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.gradle/buildOutputCleanup/outputFiles.bin" beforeDir="false" afterPath="$PROJECT_DIR$/.gradle/buildOutputCleanup/outputFiles.bin" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.gradle/config.properties" beforeDir="false" afterPath="$PROJECT_DIR$/.gradle/config.properties" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.gradle/file-system.probe" beforeDir="false" afterPath="$PROJECT_DIR$/.gradle/file-system.probe" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/caches/deviceStreaming.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/caches/deviceStreaming.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/deploymentTargetSelector.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/deploymentTargetSelector.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/vcs.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/vcs.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/app/build.gradle.kts" beforeDir="false" afterPath="$PROJECT_DIR$/app/build.gradle.kts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/app/src/main/AndroidManifest.xml" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/AndroidManifest.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/app/src/main/java/de/polyfish0/pamauth/MainActivity.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/de/polyfish0/pamauth/MainActivity.kt" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/app/src/main/java/de/polyfish0/pamauth/services/PAMServerService.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/de/polyfish0/pamauth/services/PAMServerService.kt" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/app/src/main/res/values/colors.xml" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/res/values/colors.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/app/src/main/res/values/themes.xml" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/res/values/themes.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/build/reports/problems/problems-report.html" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/gradle/libs.versions.toml" beforeDir="false" afterPath="$PROJECT_DIR$/gradle/libs.versions.toml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/local.properties" beforeDir="false" afterPath="$PROJECT_DIR$/local.properties" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
@ -18,7 +53,7 @@
|
||||
<component name="ClangdSettings">
|
||||
<option name="formatViaClangd" value="false" />
|
||||
</component>
|
||||
<component name="ExecutionTargetManager" SELECTED_TARGET="device_and_snapshot_combo_box_target[]" />
|
||||
<component name="ExecutionTargetManager" SELECTED_TARGET="device_and_snapshot_combo_box_target[DeviceId(pluginId=Default, isTemplate=false, identifier=serial=192.168.0.192:37353;connection=b4c14d05)]" />
|
||||
<component name="ExternalProjectsData">
|
||||
<projectState path="$PROJECT_DIR$">
|
||||
<ProjectState />
|
||||
@ -28,13 +63,18 @@
|
||||
<option name="RECENT_TEMPLATES">
|
||||
<list>
|
||||
<option value="Kotlin Class" />
|
||||
<option value="Kotlin Object" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="ProjectColorInfo">{
|
||||
"associatedIndex": 2
|
||||
}</component>
|
||||
<component name="ProjectId" id="2wKOHidxhnHfFSWY1BX7dDJxqEV" />
|
||||
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
@ -44,16 +84,27 @@
|
||||
"Android App.app.executor": "Run",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.cidr.known.project.marker": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"RunOnceActivity.readMode.enableVisualFormatting": "true",
|
||||
"cf.first.check.clang-format": "false",
|
||||
"cidr.known.project.marker": "true",
|
||||
"com.google.services.firebase.aqiPopupShown": "true",
|
||||
"git-widget-placeholder": "master",
|
||||
"kotlin-language-version-configured": "true",
|
||||
"project.structure.last.edited": "Modules",
|
||||
"project.structure.proportion": "0.17",
|
||||
"project.structure.side.proportion": "0.2",
|
||||
"settings.editor.selected.configurable": "preferences.language.Kotlin"
|
||||
"settings.editor.selected.configurable": "editor.preferences.fonts.default"
|
||||
}
|
||||
}]]></component>
|
||||
<component name="RecentsManager">
|
||||
<key name="android.template.-2066939809">
|
||||
<recent name="de.polyfish0.pamauth.activities" />
|
||||
</key>
|
||||
<key name="android.template.2108362692">
|
||||
<recent name="de.polyfish0.pamauth.activities" />
|
||||
</key>
|
||||
</component>
|
||||
<component name="RunManager">
|
||||
<configuration name="app" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
|
||||
<module name="PAM_Auth.app" />
|
||||
@ -137,19 +188,67 @@
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1745786712844</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="Improve .gitignore">
|
||||
<option name="closed" value="true" />
|
||||
<created>1746231445912</created>
|
||||
<option name="number" value="00001" />
|
||||
<option name="presentableId" value="LOCAL-00001" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1746231445912</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00002" summary="Improve .gitignore">
|
||||
<option name="closed" value="true" />
|
||||
<created>1746231473505</created>
|
||||
<option name="number" value="00002" />
|
||||
<option name="presentableId" value="LOCAL-00002" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1746231473505</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00003" summary="Improve .gitignore">
|
||||
<option name="closed" value="true" />
|
||||
<created>1746231483427</created>
|
||||
<option name="number" value="00003" />
|
||||
<option name="presentableId" value="LOCAL-00003" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1746231483427</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00004" summary="Improve .gitignore">
|
||||
<option name="closed" value="true" />
|
||||
<created>1746231495331</created>
|
||||
<option name="number" value="00004" />
|
||||
<option name="presentableId" value="LOCAL-00004" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1746231495331</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="5" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="VcsManagerConfiguration">
|
||||
<MESSAGE value="Improve .gitignore" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="Improve .gitignore" />
|
||||
</component>
|
||||
<component name="XDebuggerManager">
|
||||
<breakpoint-manager>
|
||||
<breakpoints>
|
||||
<line-breakpoint enabled="true" type="kotlin-line">
|
||||
<url>file://$PROJECT_DIR$/app/src/main/java/de/polyfish0/pamauth/services/PAMServerService.kt</url>
|
||||
<line>173</line>
|
||||
<option name="timeStamp" value="9" />
|
||||
</line-breakpoint>
|
||||
</breakpoints>
|
||||
</breakpoint-manager>
|
||||
</component>
|
||||
<component name="play_dynamic_filters_status">
|
||||
<option name="appIdToCheckInfo">
|
||||
<map>
|
||||
<entry key="de.polyfish0.pamauth">
|
||||
<value>
|
||||
<CheckInfo lastCheckTimestamp="1745789513434" />
|
||||
<CheckInfo lastCheckTimestamp="1746209320095" />
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="de.polyfish0.pamauth.test">
|
||||
<value>
|
||||
<CheckInfo lastCheckTimestamp="1745789513434" />
|
||||
<CheckInfo lastCheckTimestamp="1746209320092" />
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
|
@ -2,6 +2,7 @@ plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
kotlin("plugin.serialization") version "2.1.20"
|
||||
}
|
||||
|
||||
android {
|
||||
@ -50,6 +51,13 @@ dependencies {
|
||||
implementation(libs.androidx.ui.tooling.preview)
|
||||
implementation(libs.androidx.material3)
|
||||
implementation(libs.androidx.biometric.ktx)
|
||||
implementation(libs.androidx.activity)
|
||||
implementation(libs.androidx.activity.ktx)
|
||||
implementation(libs.androidx.lifecycle.service)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.material)
|
||||
implementation(libs.androidx.constraintlayout)
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0")
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
|
@ -1,9 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
xmlns:tools="http://schemas.android.com/tools" >
|
||||
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
@ -14,22 +17,30 @@
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.PAMAuth"
|
||||
tools:targetApi="31">
|
||||
<service
|
||||
android:name=".services.PAMServerService"
|
||||
android:foregroundServiceType="dataSync"
|
||||
android:exported="false"/>
|
||||
tools:targetApi="31" >
|
||||
<activity
|
||||
android:name=".activities.TransparentBiometricActivity"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:taskAffinity=""
|
||||
android:finishOnTaskLaunch="true" />
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.PAMAuth">
|
||||
android:theme="@style/Theme.PAMAuth" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".services.PAMServerService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="dataSync" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -1,83 +1,193 @@
|
||||
package de.polyfish0.pamauth
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import android.provider.Settings
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ElevatedButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.net.toUri
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import de.polyfish0.pamauth.services.PAMServerService
|
||||
import de.polyfish0.pamauth.ui.theme.PAMAuthTheme
|
||||
import de.polyfish0.pamauth.viewmodels.PermissionState
|
||||
|
||||
class MainActivity : FragmentActivity() {
|
||||
private lateinit var showNotificationDialog: MutableState<Boolean>
|
||||
private lateinit var showOverlayDialog: MutableState<Boolean>
|
||||
private lateinit var context: Context
|
||||
private lateinit var serviceIntent: Intent
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
serviceIntent = Intent(this, PAMServerService::class.java)
|
||||
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
PAMAuthTheme {
|
||||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
||||
Greeting(
|
||||
name = "Android",
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
)
|
||||
ElevatedButton(
|
||||
modifier = Modifier.padding(innerPadding),
|
||||
onClick = { startForegroundService(Intent(this, PAMServerService::class.java)) }
|
||||
) {
|
||||
Text(text = "Show Biometric")
|
||||
context = LocalContext.current
|
||||
showNotificationDialog = remember { mutableStateOf(false) }
|
||||
showOverlayDialog = remember { mutableStateOf(!Settings.canDrawOverlays(context)) }
|
||||
val isServiceRunning by rememberServiceRunningState()
|
||||
|
||||
RequestPermission("android.permission.POST_NOTIFICATIONS") { result ->
|
||||
showNotificationDialog.value = !result
|
||||
}
|
||||
|
||||
val isRunning = getSharedPreferences("pam", Context.MODE_PRIVATE).getBoolean("serviceRunning", false)
|
||||
if (isRunning) {
|
||||
if (!isServiceActuallyRunning(context)) {
|
||||
getSharedPreferences("pam", Context.MODE_PRIVATE).edit {
|
||||
putBoolean("serviceRunning", false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PAMAuthTheme {
|
||||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
||||
Column(modifier = Modifier.padding(innerPadding)) {
|
||||
Greeting(
|
||||
name = "PAM Server for Linux authentication",
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
)
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||
Text("Service enabled")
|
||||
Switch(
|
||||
checked = isServiceRunning,
|
||||
onCheckedChange = {
|
||||
if(it) {
|
||||
startForegroundService(serviceIntent)
|
||||
}else {
|
||||
stopService(serviceIntent)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CheckOverlayPermission()
|
||||
CheckNotificationsPermission()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showBiometric() {
|
||||
var biometricPrompt = BiometricPrompt(
|
||||
this,
|
||||
ContextCompat.getMainExecutor(this),
|
||||
object : BiometricPrompt.AuthenticationCallback() {
|
||||
override fun onAuthenticationError(errorCode: Int,
|
||||
errString: CharSequence) {
|
||||
super.onAuthenticationError(errorCode, errString)
|
||||
Toast.makeText(applicationContext,
|
||||
"Authentication error: $errString", Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
PermissionState.updatePermission(applicationContext, "android.permission.POST_NOTIFICATIONS")
|
||||
if(this::showOverlayDialog.isInitialized)
|
||||
showOverlayDialog.value = !Settings.canDrawOverlays(applicationContext)
|
||||
}
|
||||
|
||||
override fun onAuthenticationSucceeded(
|
||||
result: BiometricPrompt.AuthenticationResult) {
|
||||
super.onAuthenticationSucceeded(result)
|
||||
Toast.makeText(applicationContext,
|
||||
"Authentication succeeded!", Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
private fun isServiceActuallyRunning(context: Context): Boolean {
|
||||
val manager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
||||
return manager.getRunningServices(Int.MAX_VALUE).any {
|
||||
it.service.className == PAMServerService::class.java.name
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAuthenticationFailed() {
|
||||
super.onAuthenticationFailed()
|
||||
Toast.makeText(applicationContext, "Authentication failed",
|
||||
Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
@Composable
|
||||
fun CheckNotificationsPermission() {
|
||||
if(showNotificationDialog.value and !(PermissionState.rememberPermissionState(context, "android.permission.POST_NOTIFICATIONS").value)) {
|
||||
AlertDialog(
|
||||
title = {
|
||||
Text("Notification permission denied")
|
||||
},
|
||||
text = {
|
||||
Text("This app needs the permission for notifications to function. If the app is not able to open the biometrics menu it sends you a notification which opens it when you tap on it.")
|
||||
},
|
||||
onDismissRequest = {},
|
||||
confirmButton = {
|
||||
Button(onClick = {
|
||||
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||
intent.data = "package:$packageName".toUri()
|
||||
startActivity(intent)
|
||||
}) {
|
||||
Text("Open Settings")
|
||||
}
|
||||
}
|
||||
})
|
||||
biometricPrompt.authenticate(BiometricPrompt.PromptInfo.Builder()
|
||||
.setTitle("PAM Test")
|
||||
.setSubtitle("PAM Subtitle")
|
||||
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
|
||||
.setNegativeButtonText("Abort")
|
||||
.build()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CheckOverlayPermission() {
|
||||
if(showOverlayDialog.value) {
|
||||
AlertDialog(
|
||||
onDismissRequest = {},
|
||||
title = { Text("Overlay permission required") },
|
||||
text = {
|
||||
Text("This app needs the permission to show overlays to work properly.")
|
||||
},
|
||||
confirmButton = {
|
||||
Button(onClick = {
|
||||
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION).apply {
|
||||
data = "package:${context.packageName}".toUri()
|
||||
}
|
||||
startActivityForResult(intent, 100)
|
||||
}) {
|
||||
Text("Settings")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if(resultCode == 100) {
|
||||
showOverlayDialog.value = !Settings.canDrawOverlays(applicationContext)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberServiceRunningState(): State<Boolean> {
|
||||
val state = remember { mutableStateOf(false) }
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
val prefs = getSharedPreferences("pam", Context.MODE_PRIVATE)
|
||||
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
|
||||
if (key == "serviceRunning") {
|
||||
state.value = prefs.getBoolean("serviceRunning", false)
|
||||
}
|
||||
}
|
||||
prefs.registerOnSharedPreferenceChangeListener(listener)
|
||||
state.value = prefs.getBoolean("serviceRunning", false)
|
||||
|
||||
onDispose {
|
||||
prefs.unregisterOnSharedPreferenceChangeListener(listener)
|
||||
}
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,4 +205,23 @@ fun GreetingPreview() {
|
||||
PAMAuthTheme {
|
||||
Greeting("Android")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RequestPermission(permission: String, onResult: (Boolean) -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val launcher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.RequestPermission()
|
||||
) { isGranted ->
|
||||
PermissionState.updatePermission(context, permission)
|
||||
onResult(isGranted)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (!PermissionState.hasPermission(context, permission)) {
|
||||
launcher.launch(permission)
|
||||
} else {
|
||||
onResult(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,35 +1,64 @@
|
||||
package de.polyfish0.pamauth.services
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.app.ForegroundServiceStartNotAllowedException
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.graphics.PixelFormat
|
||||
import android.net.nsd.NsdManager
|
||||
import android.net.nsd.NsdServiceInfo
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
import android.os.Looper
|
||||
import android.os.PowerManager
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.Window
|
||||
import android.view.WindowManager
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.ServiceCompat
|
||||
import androidx.lifecycle.LifecycleService
|
||||
import de.polyfish0.pamauth.R
|
||||
import de.polyfish0.pamauth.activities.TransparentBiometricActivity
|
||||
import de.polyfish0.pamauth.data.PAMRequestData
|
||||
import de.polyfish0.pamauth.utils.CallbackManager
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.net.ServerSocket
|
||||
import java.net.Socket
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
import androidx.core.content.edit
|
||||
|
||||
class PAMServerService : LifecycleService() {
|
||||
private val socketMap = mutableMapOf<String, Socket>()
|
||||
private lateinit var notificationManager: NotificationManager
|
||||
|
||||
class PAMServerService : Service() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
getSharedPreferences("pam", Context.MODE_PRIVATE).edit {
|
||||
putBoolean("serviceRunning", true)
|
||||
}
|
||||
|
||||
try {
|
||||
val channel = NotificationChannel(
|
||||
"PAMServer",
|
||||
"PAM Authentication Server",
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
NotificationManager.IMPORTANCE_MAX
|
||||
).apply {
|
||||
description = "Handles PAM authentication server foreground service"
|
||||
}
|
||||
|
||||
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
|
||||
val notification = NotificationCompat.Builder(this, "PAMServer")
|
||||
@ -37,6 +66,7 @@ class PAMServerService : Service() {
|
||||
.setContentText("PAM Server is running")
|
||||
.setSmallIcon(android.R.drawable.ic_lock_lock)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setOngoing(true)
|
||||
.build()
|
||||
|
||||
ServiceCompat.startForeground(
|
||||
@ -59,11 +89,13 @@ class PAMServerService : Service() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(p0: Intent?): IBinder? {
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
super.onBind(intent)
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
startServer()
|
||||
|
||||
return START_STICKY
|
||||
@ -97,11 +129,19 @@ class PAMServerService : Service() {
|
||||
}
|
||||
}
|
||||
|
||||
var nsdManager = (getSystemService(NSD_SERVICE) as NsdManager).apply {
|
||||
(getSystemService(NSD_SERVICE) as NsdManager).apply {
|
||||
registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, registrationListener)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
getSharedPreferences("pam", Context.MODE_PRIVATE).edit {
|
||||
putBoolean("serviceRunning", false)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
private fun startServer() {
|
||||
Thread {
|
||||
try {
|
||||
@ -111,16 +151,64 @@ class PAMServerService : Service() {
|
||||
}
|
||||
while (true) {
|
||||
val client = serverSocket.accept()
|
||||
val input = client.getInputStream().bufferedReader()
|
||||
val request = input.readLine()
|
||||
Log.d("PAM", request)
|
||||
client.outputStream.write("Hi $request\n".toByteArray())
|
||||
client.outputStream.flush()
|
||||
client.close()
|
||||
|
||||
Thread {
|
||||
val input = client.getInputStream().bufferedReader()
|
||||
val data = Json.decodeFromString<PAMRequestData>(input.readLine())
|
||||
|
||||
val callbackID = Uuid.random().toString()
|
||||
CallbackManager.put(callbackID, ::test)
|
||||
socketMap[callbackID] = client
|
||||
|
||||
val intent = Intent(applicationContext, TransparentBiometricActivity::class.java).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
|
||||
|
||||
putExtra("computerName", data.computerName)
|
||||
putExtra("process", data.process)
|
||||
putExtra("callbackID", callbackID)
|
||||
}
|
||||
if(hasAppTask(applicationContext)) {
|
||||
startActivity(intent)
|
||||
}else {
|
||||
val notification = NotificationCompat.Builder(applicationContext, "PAMServer")
|
||||
.setContentTitle("PAM Authentication request")
|
||||
.setContentText("The computer \"${data.computerName}\" has requested an authentication for the process \"${data.process}\"")
|
||||
.setSmallIcon(android.R.drawable.ic_lock_lock)
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(
|
||||
applicationContext,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
)
|
||||
.setAutoCancel(true)
|
||||
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||
.build()
|
||||
|
||||
notificationManager.notify(callbackID.hashCode(), notification)
|
||||
}
|
||||
|
||||
}.start()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("PAM", "Server error", e)
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
fun test(result: Boolean, callbackID: String) {
|
||||
Thread {
|
||||
val client = socketMap.remove(callbackID)?: throw RuntimeException("No client with callbackID \"$callbackID\" registered")
|
||||
client.getOutputStream().write("Result: $result".toByteArray())
|
||||
client.close()
|
||||
}.start()
|
||||
}
|
||||
|
||||
fun hasAppTask(context: Context): Boolean {
|
||||
val am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
||||
val tasks = am.appTasks
|
||||
return tasks.any { it.taskInfo.baseIntent.component?.packageName == context.packageName }
|
||||
}
|
||||
}
|
@ -7,4 +7,5 @@
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
<color name="transparent">#00000000</color>
|
||||
</resources>
|
@ -1,5 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="Theme.PAMAuth" parent="android:Theme.Material.Light.NoActionBar" />
|
||||
<style name="Theme.Transparent" parent="android:Theme">
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowIsFloating">true</item>
|
||||
<item name="android:backgroundDimEnabled">false</item>
|
||||
</style>
|
||||
</resources>
|
@ -1,5 +1,5 @@
|
||||
[versions]
|
||||
agp = "8.9.1"
|
||||
agp = "8.9.2"
|
||||
kotlin = "2.0.21"
|
||||
coreKtx = "1.10.1"
|
||||
junit = "4.13.2"
|
||||
@ -9,6 +9,9 @@ lifecycleRuntimeKtx = "2.6.1"
|
||||
activityCompose = "1.8.0"
|
||||
composeBom = "2024.09.00"
|
||||
biometricKtx = "1.4.0-alpha02"
|
||||
appcompat = "1.6.1"
|
||||
material = "1.10.0"
|
||||
constraintlayout = "2.1.4"
|
||||
|
||||
[libraries]
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
@ -26,9 +29,14 @@ androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-man
|
||||
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||
androidx-biometric-ktx = { group = "androidx.biometric", name = "biometric", version.ref = "biometricKtx" }
|
||||
androidx-activity = { group = "androidx.activity", name = "activity" }
|
||||
androidx-activity-ktx = { group = "androidx.activity", name = "activity-ktx" }
|
||||
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
||||
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
|
||||
androidx-lifecycle-service = { group = "androidx.lifecycle", name = "lifecycle-service" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||
|
||||
|
@ -1,10 +1,8 @@
|
||||
## This file is automatically generated by Android Studio.
|
||||
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||
#
|
||||
# This file should *NOT* be checked into Version Control Systems,
|
||||
## This file must *NOT* be checked into Version Control Systems,
|
||||
# as it contains information specific to your local configuration.
|
||||
#
|
||||
# Location of the SDK. This is only used by Gradle.
|
||||
# For customization when using a Version Control System, please read the
|
||||
# header note.
|
||||
sdk.dir=/Users/mika/Library/Android/sdk
|
||||
#Tue Apr 29 15:43:07 UTC 2025
|
||||
sdk.dir=/home/mika/Android/Sdk
|
||||
|
Loading…
Reference in New Issue
Block a user