I'm developing an Android app that should be automatically installed and launched as part of the device setup process using a Mobile Device Management (MDM) solution. The app's package is configured to install as a required app for setup, but it doesn't seem to launch automatically after installation. I have set up the MDM policy to trigger the app installation and launch during the setup process, but it's not behaving as expected.
MDM Policy
Here’s the policy configuration that installs the app and should trigger the auto-launch:
applications: [
{
packageName: 'com.quicksetup.app',
installType: 'REQUIRED_FOR_SETUP',
defaultPermissionPolicy: 'GRANT'
}
],
...
setupActions: [
{
launchApp: {
packageName: 'com.quicksetup.app',
},
title: {
defaultMessage: 'QuickSetup Android App',
},
description: {
defaultMessage: 'QuickSetup Android App for device setup',
}
}
]
MainActivity
The MainActivity checks if it’s launched as part of the setup action:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private var instance: MainActivity? = null
private lateinit var prefsStore: PrefsStoreImpl
fun getContext(): Context? {
return instance
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
instance = this
binding = ActivityMainBinding.inflate(layoutInflater)
val launchedAsSetupAction = intent.getBooleanExtra("com.google.android.apps.work.clouddpc.EXTRA_LAUNCHED_AS_SETUP_ACTION", false)
if (launchedAsSetupAction) {
setResult(Activity.RESULT_OK)
finish()
}
}
override fun onResume() {
super.onResume()
// Request notification permission on Android 13+ devices
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), 1)
}
}
setContentView(binding.root)
instance?.getContext()?.let {
prefsStore = PrefsStoreImpl(it)
}
val navView: BottomNavigationView = binding.navView
val navController = findNavController(R.id.nav_host_fragment_activity_main)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
supportActionBar?.hide()
setupActionBarWithNavController(navController)
navView.setupWithNavController(navController)
instance?.getContext()?.let { context ->
val workManager = WorkManager.getInstance(context)
// One-time worker (runs immediately when the app starts)
val registerWorker = OneTimeWorkRequestBuilder<RegisterWorker>().build()
workManager.enqueue(registerWorker)
// Periodic worker (runs every 15 minutes)
val notificationWorker = PeriodicWorkRequestBuilder<NotificationWorker>(
15, TimeUnit.MINUTES
).build()
val periodicRegisterWorker = PeriodicWorkRequestBuilder<RegisterWorker>(
15, TimeUnit.MINUTES
).build()
workManager.enqueueUniquePeriodicWork(
"NotificationWorker",
ExistingPeriodicWorkPolicy.KEEP,
notificationWorker
)
workManager.enqueueUniquePeriodicWork(
"RegisterWorker",
ExistingPeriodicWorkPolicy.KEEP,
periodicRegisterWorker
)
}
}
override fun onPause() {
super.onPause()
}
override fun onStop() {
super.onStop()
}
}
Android Manifest
Here’s the AndroidManifest.xml file where the app’s main activity is defined with the necessary intent filters:
<manifest xmlns:android=";
xmlns:tools=";>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.QuickSetup"
tools:targetApi="31">
<meta-data android:name="android.content.APP_RESTRICTIONS" android:resource="@xml/app_restrictions"/>
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.QuickSetup"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Problem
The app seems to install successfully every time as part of the MDM policy, but does not auto-launch after completed installation. However I noticed that it installs like anywhere between 1 and 10 minutes after the setup completes and the user reaches the desktop. I’m unsure if this delay is affecting the auto-launch behavior.
The app is designed to assist with SCEP certificate configuration for Wi-Fi networks and requires background functionality (via a periodic worker) to send silent notifications every 15 minutes. However, this background worker will not start until the app is opened for the first time.
Ideally, the app should be launched automatically during the enrollment process, allowing it to initiate the background worker immediately without any user action. I expect that the app can open and close on its first run to trigger the worker, but it should not close itself if opened manually later by the user.
What I've tried
- I’ve removed the finish() call after setting the result for the EXTRA_LAUNCHED_AS_SETUP_ACTION flag to prevent the app from closing immediately.
- I verified that the app’s AndroidManifest.xml has the correct intent filters (MAIN action, LAUNCHER category).
Question
Why is the app not auto-launching immediately after installation during the enrollment process, even though the MDM policy is configured to trigger the launch? Could the delayed installation be preventing the app from launching as expected?