Provisioning (MDM setup) by QR code - compatible with 16-qpr2#48
Open
MoChahadeh wants to merge 8 commits into
Open
Provisioning (MDM setup) by QR code - compatible with 16-qpr2#48MoChahadeh wants to merge 8 commits into
MoChahadeh wants to merge 8 commits into
Conversation
Add the third-party libraries the MDM QR-provisioning patch depends on: - zxing-android-embedded 4.3.0 (camera UI + decoder) - zxing-core 3.4.1 - jackson-core, jackson-databind, jackson-annotations 2.18.2 Each is declared as a Soong import in libs/Android.bp and pulled into the SetupWizard2 app via static_libs in the root Android.bp. Prebuilt jars/aars are copied verbatim from the Android-15 fork at MoChahadeh/grapheneos-setup-wizard.
Port the new files from MoChahadeh/grapheneos-setup-wizard for the QR-based
MDM enrollment flow. Bug fixes from Phase 4 are folded in as the files are
copied so the diff against the original Headwind patch is minimised:
- AppInstaller: drop FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT and make the install-
callback intent explicit via setPackage(context.packageName); keep
FLAG_MUTABLE which PackageInstaller requires. Use a private namespaced
action ("app.grapheneos.setupwizard.INSTALL_COMPLETE") instead of a bare
"INSTALL_COMPLETE" string. Single 64KiB IO buffer constant shared with the
download path.
- MdmInstallActions: add the missing return after the qrContent==null error
branch (fixes NPE on the next line). Reject non-HTTPS download URLs before
opening the connection. Bound recursion in jsonToPersistableBundle to a
depth of 8 to prevent stack exhaustion via a malicious payload. Drive
download/install from viewModelScope+Dispatchers.IO instead of a singleton
Executor so background work is cancelled when the activity is finished.
All state is now held on MdmInstallViewModel rather than the action object.
- ProvisionActions: drop the deprecated startActivityForResult callers;
helpers now take ActivityResultLaunchers wired in by ProvisionActivity.
Remove the dead commented-out PO factory-reset block. Implement
disableSelfAndFinish() so it actually disables WelcomeActivity (component-
level, not the whole package) after a successful hand-off, preventing the
wizard from re-entering setup. Use the modern getParcelableExtra(Class)
overloads.
- MdmInstallViewModel: rewrite the fork's `object MdmInstallData : ViewModel()`
singleton as a regular class scoped to the activity via `by viewModels()`.
Holds the BroadcastReceiver as a property so MdmInstallActivity can
register/unregister it on the lifecycle.
- MdmInstallActivity: bind to the activity-scoped ViewModel. Move
registerReceiver/unregisterReceiver into onStart/onStop with
Context.RECEIVER_NOT_EXPORTED (the install-status broadcast is private to
this app's package).
- ProvisionActivity: extend ComponentActivity instead of bare Activity, and
register two ActivityResultLauncher fields - one for the provisioning
intent, one for the finalization intent - so the deprecated 3-arg
onActivityResult is no longer needed.
- ConsecutiveTapsGestureDetector, baseline_provisioning{,_glif}.xml,
activity_mdm_install.xml: copied verbatim.
- integers.xml: new resource file holding qr_provision_tap_threshold so the
literal "6" lives in resources rather than code.
- strings.xml: append the patch's MDM-related keys without disturbing any
upstream entries.
Surgical edits on top of the Android-16 base: - AndroidManifest.xml: append the four new permissions (INTERNET, DISPATCH_PROVISIONING_MESSAGE, INSTALL_PACKAGES, MASTER_CLEAR), add android:hardwareAccelerated="true" to <application> (required by ZXing's camera SurfaceView), and register MdmInstallActivity, ProvisionActivity, and com.journeyapps.barcodescanner.CaptureActivity. Preserves the upstream-16 UpdaterSecurityPreviewActivity entry untouched. - res/layout/activity_welcome.xml: add android:id="@+id/root_layout" to the inner LinearLayout so ConsecutiveTapsGestureDetector can hit-test against the content area rather than the GlifLayout root. - WelcomeActivity: wire ConsecutiveTapsGestureDetector to root_layout, forward ACTION_UP events from dispatchTouchEvent, reset the counter on onResume, and route consecutive-tap callbacks to WelcomeActions. - WelcomeActions: cast to androidx.activity.ComponentActivity (already in the classpath via SetupWizardActivity's AppCompatActivity ancestor) and register a ScanContract launcher in handleEntry. The launcher is re- registered on each call so a stale launcher bound to a destroyed activity is not retained across recreation. Tap threshold is read from R.integer.qr_provision_tap_threshold rather than a hardcoded 6. Build-portability fixes also applied in this commit: - MdmInstallActivity: switch `by viewModels()` to a lazy ViewModelProvider lookup so the build only depends on androidx.lifecycle:viewmodel (already used by upstream OemUnlockData/SecurityData/etc.) rather than androidx.activity:activity-ktx. - MdmInstallViewModel: drive background work via a per-ViewModel single- thread Executor with tracked Futures cancelled in onCleared(), instead of viewModelScope+Dispatchers.IO. This avoids pulling in kotlinx.coroutines, which is not currently linked into SetupWizard2. Same lifecycle correctness: when the activity is finished, in-flight download/install threads are interrupted.
…roid16-KnkXr Port Headwind MDM QR-provisioning patch onto Android-16 (16-qpr2)
The MDM-provisioning port (PR #1) added these three signature|privileged permissions to AndroidManifest.xml but missed updating the matching privapp allowlist at etc/permissions/app.grapheneos.setupwizard.xml. For a privileged system_ext app, the platform certificate plus manifest declaration are not sufficient — the permission also has to appear in the privapp allowlist that prebuilt_etc installs to /system_ext/etc/permissions/, otherwise system_server denies it at boot. Symptom on a real GrapheneOS build: the QR scan and download steps work, but PackageInstaller.Session.commit() returns STATUS_PENDING_USER_ACTION instead of silently installing, because checkSelfPermission(INSTALL_PACKAGES) returns PERMISSION_DENIED. The MdmInstallActivity reports "Failed to install the MDM Application! PENDING_USER_ACTION" and the flow dead-ends. INSTALL_PACKAGES fixes the immediate install failure. DISPATCH_PROVISIONING_MESSAGE is needed by the next stage (ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE). MASTER_CLEAR is needed by ProvisionActions's factory-reset fallback path. INTERNET is normal protection level and does not require allowlisting.
…owlist Allowlist INSTALL_PACKAGES + 2 more in privapp permissions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Port the Headwind MDM QR-provisioning patch (upstream PR
GrapheneOS#40,
still open against the Android-15 base) onto GrapheneOS SetupWizard2's
16-qpr2Android-16 branch. The Android-15 fork atMoChahadeh/grapheneos-setup-wizardwas the source of truth for new files. The full execution plan is in
MDM_PORT_PLAN.mdon this branch.The wizard becomes an enrollment entry point: tapping the Welcome screen 6
times reveals a hidden ZXing-based QR scanner, and a Headwind/MobileIron-
formatted QR provisions the device as device-owner via the standard
ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCEintent — noGrapheneOS-specific bypasses.
Three commits, one per phase:
(
libs/Android.bp+ 5 prebuilt jars/aar; +5static_libsentries)(with bug fixes folded in as the files were copied — see below)
WelcomeActivity/WelcomeActions/AndroidManifest.xml/activity_welcome.xmlfor the tap-triggerintegration. Build-portability adjustments also live here (no
androidx.activity:activity-ktx, nokotlinx-coroutines).Bug fixes vs. the original Headwind patch
These were applied as the files were copied from the source fork — the diff
isn't a verbatim port
AppInstaller— dropFLAG_ALLOW_UNSAFE_IMPLICIT_INTENT, setintent.setPackage(packageName)so the install-callback intent is explicit. Action namespaced to
app.grapheneos.setupwizard.INSTALL_COMPLETE.The QR payload is trusted but the URL must travel over TLS — otherwise
an on-path attacker can swap the APK before the post-download checksum
check.
returnafter the null-QR-content error branch inMdmInstallActions.handleEntry(the original fork posted the error thenfell through to a
qrContent!!dereference).MdmInstallActivity.onStart/onStopwithContext.RECEIVER_NOT_EXPORTED.The receiver lives on the ViewModel so the activity can pick it up after
recreation.
MdmInstallActionssingletonExecutorwith a per-ViewModel single-thread
Executor+ trackedFuturelist, all cancelled inMdmInstallViewModel.onCleared(). (viewModelScopewas the plan's firstrecommendation but
kotlinx-coroutinesis not currently linked intoSetupWizard2; the plan also offered the
Executor+Futurepath.)ActivityResultContracteverywhere:ProvisionActivitynow extendsandroidx.activity.ComponentActivitywith tworegisterForActivityResultlaunchers (one provisioning, onefinalization).
MdmInstallActivitykeeps using the existingSetupWizardActivitylauncher pattern. The deprecated 3-argonActivityResultis gone.disableSelfAndFinish()now actually implemented — disablesWelcomeActivitycomponent (not the whole package, so the factory-resetfallback path still works) on successful hand-off.
jsonToPersistableBundle(depth ≤ 8) so amalicious deeply-nested QR payload can't exhaust the stack.
R.integer.qr_provision_tap_thresholdrather than a hardcoded
6.MdmInstallDatasingleton replaced with a regularclass MdmInstallViewModel : ViewModel()scoped to the activity. Statefields (
adminComponentName,downloadLocation, etc.) and theBroadcastReceivermoved onto the ViewModel.WelcomeActions.initQrProvisioning— the fork's
if (barcodeLauncher != null) returnwould skipre-registration on activity recreation and leave a launcher bound to a
destroyed activity.
Compatibility / no-drift notes
UpdaterSecurityPreviewActivity(new in16-qpr2) is preserveduntouched.
Android.bpchange is only astatic_libs:append; theprebuilt_etc { etc_permissions_app.grapheneos.setupwizard }block andthe
required:entry are untouched.OPEN_SECURITY_PREVIEW_SETTINGSline; existing ones are preserved.Static checks completed in the porting environment
xmllintclean on all 7 modified/new XML files (manifest, layouts,drawables, strings, integers).
R.string/R.plurals/R.integer/R.layout/R.drawable/R.idreferences in new code resolve to actual resources.
{/}brace counts match in all 9 new/edited Kotlin files.ScanContract,ScanOptions,CaptureActivity) andJackson classes (
ObjectMapper,JsonNode) verified present in theshipped jars/aar.
kotlinc/m/aaptwere not available in the porting environment,so type-checking and APK build are deferred to reviewer / CI.