Setup:
-Ich erstelle ein Release-APK mit Gradle in GitHub Actions mit einem Release-Keystore.
-Der Workflow kopiert das APK über SCP auf einen Server.
-SHA256 des APK in GitHub Aktionen:
Code: Select all
78b718df0e56ce5c6f3673c4a2ce277dc83d001544234cf6b00648709828048c
Code: Select all
78b718df0e56ce5c6f3673c4a2ce277dc83d001544234cf6b00648709828048c
Code: Select all
{
"android.app.extra.PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME": "com.example.kios_app/.KioskDeviceAdminReceiver",
"android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION": "http://example.com/downloads/app-release.apk",
"android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM": "78b718df0e56ce5c6f3673c4a2ce277dc83d001544234cf6b00648709828048c",
"android.app.extra.PROVISIONING_ADMIN_EXTRAS_BUNDLE": {"server_url": "http://example.com/api"},
"android.app.extra.PROVISIONING_SKIP_ENCRYPTION": true,
"android.app.extra.PROVISIONING_WIFI_SSID": "MySSID",
"android.app.extra.PROVISIONING_WIFI_PASSWORD": "MyPassword",
"android.app.extra.PROVISIONING_WIFI_SECURITY_TYPE": "WPA"
}
Code: Select all
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="deviceManagement" />
Code: Select all
package com.example.kios_app
import android.app.admin.DeviceAdminReceiver
import android.app.admin.DevicePolicyManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.UserManager
import android.provider.Settings
import android.util.Log
class KioskDeviceAdminReceiver : DeviceAdminReceiver() {
companion object {
private const val TAG = "KioskDeviceAdmin"
const val REQUEST_CODE_ENABLE_ADMIN = 1001
fun getComponentName(context: Context): ComponentName =
ComponentName(context.applicationContext, KioskDeviceAdminReceiver::class.java)
}
override fun onEnabled(context: Context, intent: Intent) {
super.onEnabled(context, intent)
Log.d(TAG, "Device Admin Enabled")
setupDevicePolicies(context)
}
override fun onReceive(context: Context, intent: Intent) {
super.onReceive(context, intent)
Log.d(TAG, "onReceive: ${intent.action}")
when (intent.action) {
ACTION_DEVICE_ADMIN_ENABLED -> {
Log.d(TAG, "Device admin enabled")
setupDevicePolicies(context)
}
ACTION_PROFILE_PROVISIONING_COMPLETE -> {
Log.d(TAG, "Profile provisioning complete")
completeDeviceOwnerSetup(context)
}
}
}
override fun onProfileProvisioningComplete(context: Context, intent: Intent) {
super.onProfileProvisioningComplete(context, intent)
Log.d(TAG, "🎯 Profile Provisioning Complete - Device Owner mode activated")
completeDeviceOwnerSetup(context)
}
private fun setupDevicePolicies(context: Context) {
try {
val dpm = context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val admin = getComponentName(context)
dpm.setLockTaskPackages(admin, arrayOf(context.packageName))
if (dpm.isDeviceOwnerApp(context.packageName)) {
setupDeviceOwnerPolicies(dpm, admin, context)
Log.d(TAG, "🚀 Device Owner policies applied")
} else {
Log.d(TAG, "ℹ️ Regular Device Admin mode")
}
} catch (e: Exception) {
Log.e(TAG, "Error setting up device policies", e)
}
}
private fun completeDeviceOwnerSetup(context: Context) {
try {
val dpm = context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
if (dpm.isDeviceOwnerApp(context.packageName)) {
Log.d(TAG, "Device Owner confirmed")
val intent = Intent(context, MainActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
}
context.startActivity(intent)
} else {
Log.w(TAG, "Not Device Owner after provisioning")
}
} catch (e: Exception) {
Log.e(TAG, "Error completing device owner setup", e)
}
}
private fun setupDeviceOwnerPolicies(dpm: DevicePolicyManager, admin: ComponentName, context: Context) {
try {
dpm.setPasswordMinimumLength(admin, 0)
dpm.setPasswordQuality(admin, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED)
dpm.setSecureSetting(admin, Settings.Secure.INSTALL_NON_MARKET_APPS, "1")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
dpm.addUserRestriction(admin, UserManager.DISALLOW_SAFE_BOOT)
dpm.addUserRestriction(admin, UserManager.DISALLOW_FACTORY_RESET)
dpm.addUserRestriction(admin, UserManager.DISALLOW_ADD_USER)
dpm.addUserRestriction(admin, UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA)
}
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
dpm.setStatusBarDisabled(admin, true)
}
} catch (e: Exception) {
Log.w(TAG, "Cannot disable status bar: ${e.message}")
}
dpm.addUserRestriction(admin, UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS)
Log.d(TAG, "Device Owner policies setup complete")
} catch (e: Exception) {
Log.e(TAG, "Error setting device owner policies", e)
}
}
override fun onDisableRequested(context: Context, intent: Intent): CharSequence {
return "Disabling device administration will take it out of kiosk mode."
}
override fun onDisabled(context: Context, intent: Intent) {
super.onDisabled(context, intent)
Log.w(TAG, "Device Admin Disabled")
}
}
-APK auf dem Server ist identisch mit dem lokal erstellten (SHA256 stimmt überein).
-Download mit curl -L inklusive Zeitstempel zur Umgehung des Cachings erzeugt immer noch die gleiche Prüfsumme.
-APK ist mit dem Release-Keystore signiert.
-Gerät schlägt fehl Bereitstellung mit Prüfsummenkonflikt.
Fragen:
- Warum beschwert sich das Gerät über einen Prüfsummenkonflikt, obwohl SHA256 übereinstimmt?
- Könnte dies durch HTTP-Caching, Signierung oder QR-Bereitstellungsmetadaten verursacht werden?
- Wie kann ich das Gerät sicherstellen Akzeptiert das APK für die Bereitstellung ohne Prüfsummenfehler?
-Android 13/14-Gerät
-Gradle 8.13
-GitHub Actions Runner: ubuntu-latest
-Nginx, das das APK bereitstellt
Mobile version