Android counterpart to git.oxmc.me/PawletOS/profiled's Linux daemon -- independent implementation, not a port. Same .vconfig profile format and 17 payload types, but implemented against DevicePolicyManager/VpnManager/ WifiManager/WallpaperManager/KeyChain instead of a native NDK Binder daemon, since that's where AOSP actually exposes this functionality. Self-provisions as device owner at first boot (DeviceOwnerProvisioner.kt) to unlock the DevicePolicyManager-gated payload types (cert, pkcs12, passcode, proxy, screensaver lock enforcement). 16 of 17 payload types are real implementations; firewall is a documented platform dead end (no app UID gets CAP_NET_ADMIN). See README's capability matrix for the full per-payload breakdown. Reviewed against the documented @SystemApi/hidden-API surface, not compiled -- no AOSP toolchain available in this environment.
62 lines
2.8 KiB
Kotlin
62 lines
2.8 KiB
Kotlin
package os.pawlet.profiled.payloads
|
|
|
|
import android.content.Context
|
|
import android.provider.Settings
|
|
import android.util.Log
|
|
import os.pawlet.profiled.Fields
|
|
import os.pawlet.profiled.ParsedPayload
|
|
import java.io.File
|
|
|
|
// Android counterpart to platform/linux/FirstBoot.cpp. Same "skip" field,
|
|
// but Android's Setup Wizard skip mechanism is all-or-nothing rather than
|
|
// the pane-by-pane cloud-init module list the Linux side builds:
|
|
// Settings.Global.DEVICE_PROVISIONED + Settings.Secure.USER_SETUP_COMPLETE
|
|
// suppress the entire wizard in one step (the standard mechanism a
|
|
// pre-provisioned device-owner build uses instead of the interactive
|
|
// NFC/QR managed-provisioning flow). So this handler doesn't need — or
|
|
// have — a per-pane skip map the way FirstBoot.cpp's kSkipMap does; a
|
|
// non-empty "skip" list is honored by suppressing SUW entirely, same
|
|
// practical outcome as the Linux side's more granular approach.
|
|
class FirstBootHandler : PayloadHandler {
|
|
|
|
companion object {
|
|
private const val TAG = "PawletProfiled/FirstBoot"
|
|
const val DONE_STAMP = "/data/system/pawletos/firstboot.done"
|
|
}
|
|
|
|
override fun apply(context: Context, payload: ParsedPayload): Boolean {
|
|
val skipJson = Fields.of(payload, "skip")
|
|
|
|
return try {
|
|
Settings.Global.putInt(context.contentResolver, Settings.Global.DEVICE_PROVISIONED, 1)
|
|
Settings.Secure.putInt(context.contentResolver, "user_setup_complete", 1)
|
|
|
|
if (skipJson.contains("software-update")) {
|
|
// Mirrors what the schema's software-update payload would set;
|
|
// if this profile only lists it under first-boot's skip array
|
|
// without its own software-update payload, still suppress
|
|
// BgUpd's install-time nag by writing the same bridge file
|
|
// SoftwareUpdateHandler uses.
|
|
File(SoftwareUpdateHandler.POLICY_PATH).apply { parentFile?.mkdirs() }
|
|
.writeText("""{"autoCheck":true,"autoDownload":true,"autoInstall":false,"deferDays":0}""")
|
|
}
|
|
|
|
File(DONE_STAMP).apply { parentFile?.mkdirs() }.writeText(payload.uuid)
|
|
Log.i(TAG, "suppressed Setup Wizard, stamp written")
|
|
true
|
|
} catch (e: Exception) {
|
|
Log.e(TAG, "failed to apply first-boot config", e)
|
|
false
|
|
}
|
|
}
|
|
|
|
override fun revert(context: Context, payload: ParsedPayload) {
|
|
// Deliberately not un-suppressing Setup Wizard on revert — undoing
|
|
// "the device already went through first boot" isn't a meaningful
|
|
// or safe operation once real accounts/data exist. Only the stamp
|
|
// (an internal bookkeeping file, not user-facing state) is cleared.
|
|
File(DONE_STAMP).delete()
|
|
Log.i(TAG, "cleared first-boot stamp uuid=${payload.uuid}")
|
|
}
|
|
}
|