feat: MVP implementation - Kiosk Browser
- Fullscreen WebView with immersive mode - URL whitelist system - Admin PIN-protected settings - Boot receiver for auto-launch - Idle timeout with homepage reset - Lock Task Mode support - Material Design 3 UI Team: Pluto (coordinator), Margaret, Spirit
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "farm.hobbs.kioskbrowser"
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "farm.hobbs.kioskbrowser"
|
||||
minSdk = 21
|
||||
targetSdk = 34
|
||||
versionCode = 1
|
||||
versionName = "1.0.0-mvp"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.material)
|
||||
implementation(libs.androidx.activity)
|
||||
implementation(libs.androidx.constraintlayout)
|
||||
implementation(libs.androidx.webkit)
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.KioskBrowser"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="31">
|
||||
|
||||
<!-- Main Kiosk Activity -->
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask"
|
||||
android:screenOrientation="landscape"
|
||||
android:configChanges="orientation|screenSize|keyboard|keyboardHidden"
|
||||
android:theme="@style/Theme.KioskBrowser.Fullscreen">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.HOME" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- Admin Settings Activity -->
|
||||
<activity
|
||||
android:name=".AdminActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.KioskBrowser" />
|
||||
|
||||
<!-- Boot Receiver - Auto-launch on device start -->
|
||||
<receiver
|
||||
android:name=".BootReceiver"
|
||||
android:exported="true"
|
||||
android:enabled="true">
|
||||
<intent-filter android:priority="999">
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!-- Device Admin Receiver for Lock Task Mode -->
|
||||
<receiver
|
||||
android:name=".KioskDeviceAdminReceiver"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_DEVICE_ADMIN">
|
||||
<meta-data
|
||||
android:name="android.app.device_admin"
|
||||
android:resource="@xml/device_admin" />
|
||||
<intent-filter>
|
||||
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,106 @@
|
||||
package farm.hobbs.kioskbrowser
|
||||
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import farm.hobbs.kioskbrowser.databinding.ActivityAdminBinding
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import android.text.InputType
|
||||
import android.widget.EditText
|
||||
|
||||
class AdminActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var binding: ActivityAdminBinding
|
||||
private val adminPin = "1234" // Default PIN - should be configurable
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// Require PIN before showing settings
|
||||
showPinDialog()
|
||||
}
|
||||
|
||||
private fun showPinDialog() {
|
||||
val input = EditText(this).apply {
|
||||
inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD
|
||||
hint = "Enter Admin PIN"
|
||||
}
|
||||
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle("Admin Access")
|
||||
.setMessage("Enter PIN to access settings")
|
||||
.setView(input)
|
||||
.setPositiveButton("OK") { _, _ ->
|
||||
if (input.text.toString() == getStoredPin()) {
|
||||
setupUI()
|
||||
} else {
|
||||
Toast.makeText(this, "Invalid PIN", Toast.LENGTH_SHORT).show()
|
||||
finish()
|
||||
}
|
||||
}
|
||||
.setNegativeButton("Cancel") { _, _ -> finish() }
|
||||
.setCancelable(false)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun getStoredPin(): String {
|
||||
return runBlocking {
|
||||
dataStore.data.first()[stringPreferencesKey("admin_pin")] ?: adminPin
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupUI() {
|
||||
binding = ActivityAdminBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
loadCurrentSettings()
|
||||
|
||||
binding.btnSave.setOnClickListener {
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
binding.btnExit.setOnClickListener {
|
||||
finish()
|
||||
}
|
||||
|
||||
binding.btnExitKiosk.setOnClickListener {
|
||||
// Exit kiosk mode completely
|
||||
finishAffinity()
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadCurrentSettings() {
|
||||
runBlocking {
|
||||
dataStore.data.first().let { prefs ->
|
||||
binding.etHomepage.setText(
|
||||
prefs[stringPreferencesKey("homepage")] ?: "https://hobbs.farm"
|
||||
)
|
||||
binding.etWhitelist.setText(
|
||||
prefs[stringPreferencesKey("whitelist")] ?: "hobbs.farm"
|
||||
)
|
||||
binding.etIdleTimeout.setText(
|
||||
prefs[stringPreferencesKey("idle_timeout")] ?: "300"
|
||||
)
|
||||
binding.etAdminPin.setText(
|
||||
prefs[stringPreferencesKey("admin_pin")] ?: "1234"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveSettings() {
|
||||
runBlocking {
|
||||
dataStore.edit { prefs ->
|
||||
prefs[stringPreferencesKey("homepage")] = binding.etHomepage.text.toString()
|
||||
prefs[stringPreferencesKey("whitelist")] = binding.etWhitelist.text.toString()
|
||||
prefs[stringPreferencesKey("idle_timeout")] = binding.etIdleTimeout.text.toString()
|
||||
prefs[stringPreferencesKey("admin_pin")] = binding.etAdminPin.text.toString()
|
||||
}
|
||||
}
|
||||
Toast.makeText(this, "Settings saved", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package farm.hobbs.kioskbrowser
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
|
||||
class BootReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action == Intent.ACTION_BOOT_COMPLETED ||
|
||||
intent.action == "android.intent.action.QUICKBOOT_POWERON") {
|
||||
|
||||
val launchIntent = Intent(context, MainActivity::class.java).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
}
|
||||
context.startActivity(launchIntent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package farm.hobbs.kioskbrowser
|
||||
|
||||
import android.app.admin.DeviceAdminReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
|
||||
class KioskDeviceAdminReceiver : DeviceAdminReceiver() {
|
||||
override fun onEnabled(context: Context, intent: Intent) {
|
||||
super.onEnabled(context, intent)
|
||||
}
|
||||
|
||||
override fun onDisabled(context: Context, intent: Intent) {
|
||||
super.onDisabled(context, intent)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
package farm.hobbs.kioskbrowser
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.admin.DevicePolicyManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.view.WindowInsets
|
||||
import android.view.WindowInsetsController
|
||||
import android.view.WindowManager
|
||||
import android.webkit.WebChromeClient
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import farm.hobbs.kioskbrowser.databinding.ActivityMainBinding
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
val Context.dataStore by preferencesDataStore(name = "kiosk_settings")
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
private lateinit var webView: WebView
|
||||
|
||||
private val idleHandler = Handler(Looper.getMainLooper())
|
||||
private var idleTimeoutMs = 300_000L // 5 minutes default
|
||||
private var homepage = "https://hobbs.farm"
|
||||
private var whitelist = listOf("hobbs.farm")
|
||||
|
||||
private val idleRunnable = Runnable {
|
||||
loadHomepage()
|
||||
clearSession()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
setupFullscreen()
|
||||
loadSettings()
|
||||
setupWebView()
|
||||
loadHomepage()
|
||||
|
||||
// Keep screen on
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
}
|
||||
|
||||
private fun setupFullscreen() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
window.setDecorFitsSystemWindows(false)
|
||||
window.insetsController?.let {
|
||||
it.hide(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars())
|
||||
it.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
}
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
window.decorView.systemUiVisibility = (
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
or View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadSettings() {
|
||||
// Load from DataStore
|
||||
runBlocking {
|
||||
dataStore.data.first().let { prefs ->
|
||||
homepage = prefs[stringPreferencesKey("homepage")] ?: homepage
|
||||
whitelist = (prefs[stringPreferencesKey("whitelist")] ?: "hobbs.farm")
|
||||
.split(",").map { it.trim() }
|
||||
idleTimeoutMs = (prefs[stringPreferencesKey("idle_timeout")]?.toLongOrNull()
|
||||
?: 300) * 1000L
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
private fun setupWebView() {
|
||||
webView = binding.webView
|
||||
|
||||
webView.settings.apply {
|
||||
javaScriptEnabled = true
|
||||
domStorageEnabled = true
|
||||
loadWithOverviewMode = true
|
||||
useWideViewPort = true
|
||||
setSupportZoom(true)
|
||||
builtInZoomControls = true
|
||||
displayZoomControls = false
|
||||
}
|
||||
|
||||
webView.webViewClient = object : WebViewClient() {
|
||||
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
|
||||
val url = request.url.toString()
|
||||
return if (isUrlAllowed(url)) {
|
||||
resetIdleTimer()
|
||||
false // Allow loading
|
||||
} else {
|
||||
Toast.makeText(this@MainActivity, "URL blocked", Toast.LENGTH_SHORT).show()
|
||||
true // Block and don't load
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPageFinished(view: WebView?, url: String?) {
|
||||
super.onPageFinished(view, url)
|
||||
resetIdleTimer()
|
||||
}
|
||||
}
|
||||
|
||||
webView.webChromeClient = WebChromeClient()
|
||||
}
|
||||
|
||||
private fun isUrlAllowed(url: String): Boolean {
|
||||
return whitelist.any { domain ->
|
||||
url.contains(domain, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadHomepage() {
|
||||
webView.loadUrl(homepage)
|
||||
}
|
||||
|
||||
private fun clearSession() {
|
||||
webView.clearCache(true)
|
||||
webView.clearHistory()
|
||||
android.webkit.CookieManager.getInstance().removeAllCookies(null)
|
||||
}
|
||||
|
||||
private fun resetIdleTimer() {
|
||||
idleHandler.removeCallbacks(idleRunnable)
|
||||
idleHandler.postDelayed(idleRunnable, idleTimeoutMs)
|
||||
}
|
||||
|
||||
override fun onUserInteraction() {
|
||||
super.onUserInteraction()
|
||||
resetIdleTimer()
|
||||
}
|
||||
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||
// Block back button - require admin PIN to exit
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
if (webView.canGoBack()) {
|
||||
webView.goBack()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return super.onKeyDown(keyCode, event)
|
||||
}
|
||||
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
super.onWindowFocusChanged(hasFocus)
|
||||
if (hasFocus) {
|
||||
setupFullscreen()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
resetIdleTimer()
|
||||
setupFullscreen()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
idleHandler.removeCallbacks(idleRunnable)
|
||||
}
|
||||
|
||||
// Secret admin gesture: triple-tap top-left corner to open settings
|
||||
private var tapCount = 0
|
||||
private val tapHandler = Handler(Looper.getMainLooper())
|
||||
|
||||
fun onAdminAreaTap() {
|
||||
tapCount++
|
||||
tapHandler.removeCallbacksAndMessages(null)
|
||||
tapHandler.postDelayed({ tapCount = 0 }, 500)
|
||||
|
||||
if (tapCount >= 3) {
|
||||
tapCount = 0
|
||||
startActivity(Intent(this, AdminActivity::class.java))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="24dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Kiosk Browser Settings"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="24dp" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Homepage URL"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etHomepage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textUri" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Allowed Domains (comma-separated)"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etWhitelist"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="text" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Idle Timeout (seconds)"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etIdleTimeout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="number" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Admin PIN"
|
||||
android:layout_marginBottom="24dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etAdminPin"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="numberPassword" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSave"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="Save Settings" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnExit"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Back to Kiosk" />
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnExitKiosk"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:backgroundTint="#D32F2F"
|
||||
android:text="Exit Kiosk Mode" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<WebView
|
||||
android:id="@+id/webView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<!-- Invisible admin tap area (top-left corner) -->
|
||||
<View
|
||||
android:id="@+id/adminTapArea"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
android:layout_gravity="start|top"
|
||||
android:onClick="onAdminAreaTap" />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="primary">#1976D2</color>
|
||||
<color name="primary_container">#BBDEFB</color>
|
||||
<color name="white">#FFFFFF</color>
|
||||
<color name="black">#000000</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Kiosk Browser</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="Theme.KioskBrowser" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<item name="colorPrimary">@color/primary</item>
|
||||
<item name="colorPrimaryContainer">@color/primary_container</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.KioskBrowser.Fullscreen" parent="Theme.KioskBrowser">
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-policies>
|
||||
<lock-task />
|
||||
</uses-policies>
|
||||
</device-admin>
|
||||
Reference in New Issue
Block a user