Background processing on Android is a well-documented architectural challenge, but Xiaomi's custom Android skins (MIUI and the newer HyperOS) take aggressive task-killing to another level. If your application relies on WorkManager, foreground services, or push notifications, you have likely encountered the silent termination of your background processes.
To preserve battery life, Xiaomi devices require users to explicitly whitelist applications in a proprietary "AutoStart" menu. Because developers cannot enable AutoStart via API, they must use explicit Intents to deep-link users to the correct, highly-fragmented MIUI settings screens.
This article details the mechanics of Android intent routing required to navigate users directly to the correct permission screens, bypassing the inaccessible MIUI permissions API.
The Root Cause: Why Standard APIs Fail
Android's core philosophy historically allowed apps to start automatically on boot by declaring the RECEIVE_BOOT_COMPLETED permission. Modern Android relies on WorkManager for deferred background tasks.
However, Original Equipment Manufacturers (OEMs) like Xiaomi implement custom power management protocols within their system frameworks. On MIUI, the OS intercepts standard background execution requests and blocks them unless the app is present in the AutoStart database.
The core issue for Kotlin Android dev teams is two-fold:
- No Public Setter API: The internal MIUI permissions API does not expose a programmatic method for third-party applications to toggle the AutoStart flag. Modifying this database requires system-level privileges.
- No Public Getter API: There is no reliable, officially supported API to query if your application has already been granted AutoStart permission.
Consequently, the only viable engineering solution is to prompt the user to enable the permission manually by routing them directly to the precise settings activity.
The Solution: Explicit Android Intent Routing
Because MIUI is heavily fragmented across versions (MIUI 10, 11, 12, 13, 14, and HyperOS), the exact ComponentName of the AutoStart screen frequently changes. A robust solution requires an intent fallback chain.
The strategy is to iterate through a list of known Xiaomi AutoStart intents. If the system's PackageManager resolves the intent, we launch it. If all explicit intents fail, we fall back to the standard Android App Info settings screen.
The Kotlin Implementation
Below is a robust utility object implemented in modern Kotlin. It handles device identification, API 33+ (Tiramisu) PackageManager deprecations, and secure intent resolution.
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.provider.Settings
import java.util.Locale
object XiaomiAutoStartHelper {
// Xiaomi manufactures devices under multiple brand names
private val XIAOMI_MANUFACTURERS = setOf("xiaomi", "poco", "redmi", "blackshark")
// Known fragmented component names for MIUI AutoStart menus
private val AUTO_START_INTENTS = listOf(
Intent().setComponent(
ComponentName(
"com.miui.securitycenter",
"com.miui.permcenter.autostart.AutoStartManagementActivity"
)
),
Intent().setComponent(
ComponentName(
"com.miui.securitycenter",
"com.miui.applicationlock.ConfirmAutoStartActivity"
)
)
)
/**
* Checks if the current hardware is manufactured by Xiaomi or its sub-brands.
*/
fun isXiaomiDevice(): Boolean {
val manufacturer = Build.MANUFACTURER.lowercase(Locale.getDefault())
return manufacturer in XIAOMI_MANUFACTURERS
}
/**
* Attempts to route the user to the AutoStart settings.
* Returns true if a settings screen was successfully launched.
*/
fun requestAutoStartPermission(context: Context): Boolean {
if (!isXiaomiDevice()) return false
for (intent in AUTO_START_INTENTS) {
if (isIntentResolvable(context, intent)) {
// FLAG_ACTIVITY_NEW_TASK is required if context is not an Activity
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
try {
context.startActivity(intent)
return true
} catch (e: SecurityException) {
// Component resolved but OS blocked invocation
continue
}
}
}
// Fallback to native Android App Info screen
return navigateToAppInfo(context)
}
/**
* Safely checks if the OS can resolve the intent without throwing an ActivityNotFoundException.
* Implements modern API 33+ PackageManager flag handling.
*/
private fun isIntentResolvable(context: Context, intent: Intent): Boolean {
val packageManager = context.packageManager
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
packageManager.resolveActivity(
intent,
PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong())
) != null
} else {
@Suppress("DEPRECATION")
packageManager.resolveActivity(
intent,
PackageManager.MATCH_DEFAULT_ONLY
) != null
}
}
/**
* Standard Android fallback to open the App Details Settings.
*/
private fun navigateToAppInfo(context: Context): Boolean {
return try {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.parse("package:${context.packageName}")
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(intent)
true
} catch (e: Exception) {
false
}
}
}
Deep Dive: Why This Architecture Works
Safe Component Resolution
Directly invoking context.startActivity() with a fragmented ComponentName risks an ActivityNotFoundException crashing your application. The isIntentResolvable function queries the OS to confirm the activity exists in the current system image before execution.
Starting with Android 13 (API 33), PackageManager.resolveActivity signature changes require the use of PackageManager.ResolveInfoFlags. The helper class manages this branch seamlessly, suppressing the deprecation warning only on legacy API levels to maintain clean compilation.
Handling Security Exceptions
Occasionally, resolveActivity will return true because the component exists, but the OEM has locked the activity behind internal permissions (android:exported="false" in the system manifest). Wrapping the startActivity call in a try-catch block targeting SecurityException ensures the loop continues to the next viable Android AutoStart intent or falls back to App Info.
Common Pitfalls and UX Constraints
The HyperOS Transition
Xiaomi is actively deprecating MIUI in favor of HyperOS. While early versions of HyperOS maintain backward compatibility with com.miui.securitycenter, future updates may restructure the settings app. By architecting a fallback mechanism to Settings.ACTION_APPLICATION_DETAILS_SETTINGS, your app remains resilient to undocumented OS mutations. Users will be routed to the standard app info page, where the AutoStart toggle is generally located under "Battery & Performance" or "Permissions."
Mitigating User Fatigue
Because the MIUI permissions API does not allow you to verify if the user actually toggled the switch, you must rely on UX heuristics.
Do not prompt the user on every launch. Store a boolean flag in DataStore or SharedPreferences once the routing function returns true.
// Example UX implementation logic
val hasPromptedAutoStart = sharedPrefs.getBoolean("PROMPTED_AUTOSTART", false)
if (!hasPromptedAutoStart && XiaomiAutoStartHelper.isXiaomiDevice()) {
showAutoStartRationaleDialog { userAccepted ->
if (userAccepted) {
XiaomiAutoStartHelper.requestAutoStartPermission(context)
}
// Mark as prompted regardless of outcome to avoid spamming the user
sharedPrefs.edit().putBoolean("PROMPTED_AUTOSTART", true).apply()
}
}
Presenting an educational UI (rationale dialog) before executing the intent significantly increases conversion rates. Users presented abruptly with a foreign system settings screen will likely bounce back to your app without toggling the permission. Provide clear, visual instructions (e.g., a short GIF or annotated image) demonstrating exactly which switch to toggle.