Engineers working in Android VoIP development or building custom dialers inevitably encounter a severe fragmentation issue on Xiaomi, Redmi, and Poco devices. You declare the standard SYSTEM_ALERT_WINDOW permission, request it via standard APIs, and verify it using Settings.canDrawOverlays(context). The API returns true, yet when a VoIP call comes in while the app is backgrounded, the custom call screen fails to appear.
This silent failure occurs because MIUI implements a secondary, hidden permission overlay strictly for background execution: the "Display pop-up windows while running in the background" permission.
This article details the root cause of this undocumented restriction and provides a production-ready Java implementation to detect and resolve it.
The Root Cause: MIUI's AppOps Customization
In stock Android, the system alert window acts as a global capability flag. If the user grants the Android overlay permission via ACTION_MANAGE_OVERLAY_PERMISSION, the OS allows the application to draw UI elements over other apps, regardless of whether the requesting app is in the foreground or background.
MIUI modifies the Android framework's AppOpsManager. Xiaomi engineers aggressively decoupled the permission into two distinct operational codes (OpCodes) to combat malicious ad-ware and preserve battery life:
- Foreground Overlay: Governed by the standard Android
SYSTEM_ALERT_WINDOWpermission. - Background Overlay: Governed by a proprietary MIUI OpCode (
10021).
Because Settings.canDrawOverlays() only checks the standard AOSP framework policy, it will confidently return true even if MIUI's security center actively blocks the background execution. To the system, the app has the permission; to the proprietary MIUI task manager, the app is blocked from executing the window inflation.
The Solution: Bypassing Standard Framework Checks
To solve this, we must use Java Reflection to query MIUI's custom AppOps codes directly. If the permission is missing, we must route the user to the specific MIUI Security Center screen using a proprietary MIUI popup window intent, rather than the standard Android settings screen.
Step 1: Detecting the MIUI Background Popup Permission
We use reflection to invoke checkOpNoThrow on the AppOpsManager, specifically checking for Xiaomi's internal OpCode 10021.
import android.app.AppOpsManager;
import android.content.Context;
import android.os.Binder;
import android.os.Build;
import android.text.TextUtils;
import java.lang.reflect.Method;
public class MiuiPermissionUtils {
// MIUI's proprietary AppOps code for background popups
private static final int OP_BACKGROUND_POPUP_WINDOW = 10021;
/**
* Checks if the device is running MIUI.
*/
public static boolean isMiui() {
String manufacturer = Build.MANUFACTURER.toLowerCase();
return manufacturer.contains("xiaomi") ||
manufacturer.contains("redmi") ||
manufacturer.contains("poco");
}
/**
* Verifies if the hidden MIUI background popup permission is granted.
*/
public static boolean canDrawOverlaysOnMiui(Context context) {
if (!isMiui()) {
// Fallback to standard Android check for non-MIUI devices
return android.provider.Settings.canDrawOverlays(context);
}
try {
AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
if (manager == null) return false;
Method method = AppOpsManager.class.getDeclaredMethod(
"checkOpNoThrow",
int.class,
int.class,
String.class
);
int result = (int) method.invoke(
manager,
OP_BACKGROUND_POPUP_WINDOW,
Binder.getCallingUid(),
context.getPackageName()
);
return AppOpsManager.MODE_ALLOWED == result;
} catch (Exception e) {
// If reflection fails (e.g., OS update changes internal APIs),
// fail open to prevent permanently locking the user out.
return true;
}
}
}
Step 2: Routing Users to the MIUI Security Center
If canDrawOverlaysOnMiui() returns false, firing Settings.ACTION_MANAGE_OVERLAY_PERMISSION will confuse the user. The standard Android screen will show the toggle as already "Enabled," leaving the user no way to fix the issue.
Instead, we must construct a specific intent targeting com.miui.securitycenter.
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
public class MiuiIntentRouter {
/**
* Attempts to open the specific MIUI permission editor for the app.
*/
public static void requestMiuiPopupPermission(Context context) {
Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR");
intent.setClassName(
"com.miui.securitycenter",
"com.miui.permcenter.permissions.PermissionsEditorActivity"
);
intent.putExtra("extra_pkgname", context.getPackageName());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
// Fallback for older MIUI versions (MIUI V8/V9)
fallbackToMiuiAppDetails(context);
}
}
private static void fallbackToMiuiAppDetails(Context context) {
try {
Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR");
intent.setClassName(
"com.miui.securitycenter",
"com.miui.permcenter.permissions.AppPermissionsEditorActivity"
);
intent.putExtra("extra_pkgname", context.getPackageName());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
// Absolute fallback: Standard Android App Info screen
Intent standardIntent = new Intent(
android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.parse("package:" + context.getPackageName())
);
standardIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(standardIntent);
}
}
}
Deep Dive: Why This Architecture Works
Reflection Over Hardcoded Constants
We utilize reflection for checkOpNoThrow instead of directly referencing AppOps constants. Xiaomi modifies the Android SDK at compile-time for their devices, meaning OP_BACKGROUND_POPUP_WINDOW does not exist in the public Android SDK. Attempting to cast or access it without reflection will result in a NoSuchFieldError crash on non-Xiaomi devices.
The Security Center Fallback Chain
MIUI's architecture is historically volatile. Between MIUI V9, V11, V13, and the new HyperOS, Xiaomi frequently renames the internal classes of their Security Center app. The implementation above utilizes a "graceful degradation" pattern:
- It attempts the modern
PermissionsEditorActivityfirst. - It falls back to the legacy
AppPermissionsEditorActivityif the modern class is absent. - It defaults to standard Android application settings if neither exists, ensuring your app never crashes via
ActivityNotFoundException.
Common Pitfalls and Edge Cases
1. User Onboarding Flow Friction
Because you cannot programmatically toggle this permission, abruptly throwing the user into the MIUI Security Center results in high drop-off rates. Before triggering requestMiuiPopupPermission(), you must render a blocking UI (an educational modal) explaining exactly which toggle the user needs to press. Use screenshots of the MIUI interface within your app to guide them.
2. The HyperOS Transition
Xiaomi is currently replacing MIUI with HyperOS. Early testing indicates that HyperOS maintains the com.miui.securitycenter package name for backward compatibility, and OpCode 10021 remains functional. However, your error-handling blocks must strictly return true on reflection failure. If Xiaomi drops this custom OpCode in a future HyperOS build, failing closed (return false) would trap users in an infinite loop demanding a permission that no longer exists.
3. Combining with Full-Screen Intents
If you are building an Android VoIP development project, do not rely solely on the system alert window. Ensure you are also utilizing standard NotificationManager APIs with setFullScreenIntent(). On heavily optimized MIUI devices, if the overlay fails to render due to intense battery saver modes, the OS will fall back to firing the Full-Screen Intent, guaranteeing the user at least sees standard Android incoming call UI.