Intune App SDK for Android — Phase 7: App Participation Features
If you've wrestled with MAM policy enforcement in a custom Android app and wondered why a save-to-OneDrive button still works even after enabling App Protection Policy — this is the article you need. Phase 7 of the Intune App SDK integration is where the SDK stops doing the heavy lifting and hands the wheel to you, the developer. The SDK cannot infer your app's save destinations, notification content sensitivity, or backup strategy. You have to wire these up explicitly.
This guide is written for Android engineers who've already completed SDK Phases 1–6 and for Intune architects who need to know what to demand from dev teams during policy validation. We'll cover every App Participation Feature with production-ready Kotlin, a Graph API validation example, and a PowerShell script to audit your App Protection Policy configuration.
getPolicy() call returns a permissive default policy, silently allowing operations that should be blocked.The Big Picture: Where Phase 7 Sits
Retrieving an AppPolicy Instance
Every Phase 7 check starts with one of two MAMPolicyManager calls. Get this right first — everything downstream depends on obtaining the policy for the correct identity.
// Option A — activity-scoped (uses current foreground identity) val policy: AppPolicy = MAMPolicyManager.getPolicy(currentActivity) // Option B — explicit OID (preferred for multi-identity apps) val policy: AppPolicy = MAMPolicyManager.getPolicyForIdentityOID(userOid)
getPolicy() returns a permissive stub — all checks return true (allowed). This means a misconfigured enrollment silently disables all your policy checks. Always verify enrollment state before relying on policy results in QA.Save Location Policy Enforcement
This is the most commonly mis-implemented feature. The SDK has zero visibility into where your Save button sends data — SharePoint, a local path, Box — so you must query the policy before initiating any external save operation.
// In your save handler — e.g., a "Save to OneDrive" button click fun onSaveToOneDriveClicked(activity: Activity, userOid: String) { val policy = MAMPolicyManager.getPolicy(activity) val isSaveAllowed = policy.getIsSaveToLocationAllowedForOID( SaveLocation.ONEDRIVE, userOid // Entra OID of the signed-in user ) if (!isSaveAllowed) { MAMUIHelper.showSharingBlockedDialog(activity) return } // Safe to proceed initiateOneDriveUpload() }
| SaveLocation | Use Case | OID Required |
|---|---|---|
ONEDRIVE | Save to OneDrive consumer or business | ✓ or null |
SHAREPOINT | Save to SharePoint document library | ✓ or null |
BOX | Save to Box cloud storage | ✓ or null |
LOCAL | External device storage (not app-private) | Always null |
PHOTO_LIBRARY | Android local photo storage | Always null |
IMANAGE | Save to iManage DMS | ✓ or null |
EGNYTE | Save to Egnyte | ✓ or null |
ACCOUNT_DOCUMENT | Account-associated multi-identity location | ✓ required |
OTHER | Unlisted locations | Always null |
Open Location Policy Enforcement
The inverse of Save policy — call this before importing or opening a file from an external source. The OpenLocation enum mirrors SaveLocation but with some different members, including the handy convenience method for local files with embedded identity metadata.
// Explicit OID check for SharePoint import val isOpenAllowed = MAMPolicyManager.getPolicy(activity) .getIsOpenFromLocationAllowedForOID(OpenLocation.SHAREPOINT, userOid) // Convenience method — parses file owner OID from file metadata automatically val isLocalFileAllowed = policy.isOpenFromLocalStorageAllowed(file) // Equivalent to: getIsOpenFromLocationAllowedForOID(OpenLocation.LOCAL, fileOwnerOid)
Notification Restriction Policy
This one catches developers off guard because the failure is invisible to end users — a notification simply never shows. The SDK will attempt a best-effort block for single-identity apps if you skip this, but BLOCK_ORG_DATA gets silently upgraded to a full block, meaning your users lose the sanitized notification entirely.
// In your notification dispatch path fun sendOrgNotification(notificationIdentityOid: String, content: NotificationContent) { val restriction = MAMPolicyManager .getPolicyForIdentityOID(notificationIdentityOid) .getNotificationRestriction() when (restriction) { NotificationRestriction.BLOCKED -> { // Swallow entirely — no notification posted return } NotificationRestriction.BLOCK_ORG_DATA -> { // Replace body with sanitized placeholder val sanitized = content.copy( title = getString(R.string.notification_sanitized_title), body = getString(R.string.notification_sanitized_body) ) postNotification(sanitized) } NotificationRestriction.UNRESTRICTED -> { postNotification(content) } } }
getNotificationRestriction(), org data will appear in notifications regardless of policy. This is the most commonly missed Phase 7 requirement in app reviews.Backup Data Protection
Android's Auto Backup can silently exfiltrate MAM-managed data to a user's personal Google account. The SDK provides two compliant paths — use MAMDefaultBackupAgent for most apps, or extend MAMBackupAgentHelper for custom backup logic.
<!-- AndroidManifest.xml — MAM-compliant Auto Backup --> <application android:allowBackup="true" android:fullBackupOnly="true" android:backupAgent="com.microsoft.intune.mam.client.app.backup.MAMDefaultBackupAgent" ... />
// Custom backup agent — extend MAMBackupAgentHelper class MyBackupAgent : MAMBackupAgentHelper() { override fun onCreate() { val helper = FileBackupHelper(this, "my_data_file.db") addHelper("file_backup_key", helper) } }
Screen Capture Restriction (Custom Workflows)
The SDK automatically sets FLAG_SECURE on MAM-managed activities, which blocks the system screenshot mechanism. But if your app has a custom screen capture workflow — in-app recording, watermarked screenshot export, etc. — you must check the policy explicitly.
// Hide custom screenshot capability if policy prohibits it
val isScreenCaptureAllowed = MAMPolicyManager
.getPolicy(currentActivity)
.getIsScreenCaptureAllowed()
screenshotButton.visibility = if (isScreenCaptureAllowed) View.VISIBLE else View.GONEApp Protection Conditional Access Registration
If your tenant uses App Protection CA policies (requiring MAM-compliant apps as a condition of access), the app must explicitly register each account with the SDK enrollment manager. This call belongs in Application.onCreate() or immediately after MSAL token acquisition.
// Register after successful MSAL sign-in MAMEnrollmentManager.getInstance(context).registerAccountForMAM( upn, // e.g. alice@contoso.com aadId, // Entra Object ID (OID) tenantId, // Entra Tenant ID authority // e.g. https://login.microsoftonline.com/<tenantId> ) // SDK Notification — listen for enrollment result MAMNotificationReceiverRegistry.getInstance(context) .registerReceiver( { notification -> when (notification.type) { MAMNotificationType.MAM_ENROLLMENT_RESULT -> { handleEnrollmentResult(notification); true } MAMNotificationType.WIPE_USER_DATA -> { handleWipe(); true } MAMNotificationType.COMPLIANCE_STATUS -> { handleCompliance(notification); true } else -> true } }, MAMNotificationType.MAM_ENROLLMENT_RESULT, MAMNotificationType.WIPE_USER_DATA, MAMNotificationType.COMPLIANCE_STATUS )
Validating Policy Configuration via Graph API
Before testing your app, confirm the App Protection Policy is actually targeting your app and has the expected settings. Use the Intune Graph API endpoint for managed app policies.
GET https://graph.microsoft.com/beta/deviceAppManagement/androidManagedAppProtections // Required scope: DeviceManagementApps.Read.All // Sample response fragment { "value": [ { "id": "T_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "displayName": "Contoso Android MAM Policy", "allowedDataStorageLocations": ["oneDriveForBusiness", "sharePoint"], "notificationRestriction": "blockOrganizationalData", "screenCaptureBlocked": true, "isAssigned": true } ] }
/deviceAppManagement/androidManagedAppProtections/{id}/apps to verify your app's bundle ID is in the target list before running device tests. Saves hours of debugging a policy that was never assigned to the right app.PowerShell: Audit App Protection Policy for Phase 7 Coverage
This script pulls your Android MAM policies and flags any settings that Phase 7 features depend on, so you can catch misconfigurations in CI or during policy reviews.
#Requires -Modules Microsoft.Graph.DeviceManagement.Apps # Audit-MAMAndroidPolicies.ps1 # Checks Phase 7-relevant settings across all Android MAM policies Connect-MgGraph -Scopes "DeviceManagementApps.Read.All" $policies = Invoke-MgGraphRequest -Method "GET" ` -Uri "https://graph.microsoft.com/beta/deviceAppManagement/androidManagedAppProtections" if (-not $policies.value) { Write-Warning "No Android MAM policies found." return } $report = foreach ($policy in $policies.value) { # Fetch detailed policy to get all properties $detail = Invoke-MgGraphRequest -Method "GET" ` -Uri "https://graph.microsoft.com/beta/deviceAppManagement/androidManagedAppProtections/$($policy.id)" [PSCustomObject]@{ PolicyName = $detail.displayName IsAssigned = $detail.isAssigned SaveLocations = ($detail.allowedDataStorageLocations -join ", ") OpenLocations = ($detail.allowedInboundDataTransferSources -join ", ") NotificationRestriction = $detail.notificationRestriction ScreenCaptureBlocked = $detail.screenCaptureBlocked BackupBlocked = $detail.backupBlocked Phase7SavePolicyConfigured = ($null -ne $detail.allowedDataStorageLocations) Phase7NotifPolicyConfigured = ($detail.notificationRestriction -ne "unrestricted") } } $report | Format-Table -AutoSize # Flag any policies where notification restriction is default (unrestricted) $unrestrictedNotif = $report | Where-Object { $_.NotificationRestriction -eq "unrestricted" } if ($unrestrictedNotif) { Write-Warning "⚠ Policies with unrestricted notifications (Phase 7 not enforced):" $unrestrictedNotif | Select-Object PolicyName, NotificationRestriction | Format-Table } # Flag unassigned policies $unassigned = $report | Where-Object { -not $_.IsAssigned } if ($unassigned) { Write-Warning "⚠ Unassigned policies (no users/groups targeted):" $unassigned | Select-Object PolicyName | Format-Table } Write-Host "Audit complete. Review flagged items before releasing the app." -ForegroundColor Cyan
Common Gotchas and Production Lessons
| Issue | Root Cause | Resolution |
|---|---|---|
Policy checks always return true (allowed) | No MAM account enrolled — stub policy returned | Verify MAMEnrollmentManager registration succeeded before calling policy checks |
BLOCK_ORG_DATA notifications fully suppressed | App skipped getNotificationRestriction() — SDK fallback treats it as BLOCKED | Implement the when block in all notification dispatch paths |
| Save to LOCAL is blocked despite policy allowing it | Passing a non-null OID for SaveLocation.LOCAL | Always pass null as OID for LOCAL and PHOTO_LIBRARY |
| Screenshot button still visible after policy change | Checking policy at Activity start only, not on resume | Re-check getIsScreenCaptureAllowed() in onResume() |
| Backup exfiltrates org data | Using default BackupAgent instead of MAM variant | Switch manifest to MAMDefaultBackupAgent or extend MAMBackupAgentHelper |
| App Protection CA fails silently | registerAccountForMAM() not called after MSAL auth | Register immediately post-token acquisition; listen for MAM_ENROLLMENT_RESULT |
getNotificationRestriction(). (3) Manifest uses MAMDefaultBackupAgent or a custom MAMBackupAgentHelper. (4) registerAccountForMAM() is called immediately after MSAL sign-in. (5) Custom screen capture UI is gated on getIsScreenCaptureAllowed().Wrapping Up
Phase 7 is where most MAM integration projects fail audit — not because the SDK is hard to use, but because these checks are easy to skip and easy to miss in testing when your test account happens to have a permissive policy. The golden rule: if data crosses an app boundary in either direction, there is a policy check that belongs there, and it's your code, not the SDK's, that has to make that call.
Run the PowerShell audit script against your tenant policies before every QA cycle, validate your Graph API responses match what your app expects, and don't trust the SDK's fallback behavior for notification policy in production — implement it explicitly every time.