1. The Activity cannot pop up in the background

According to the feedback from my classmates, some pages in Xiaomi mobile phone could not be started normally. I went to check the code and found that it was normal to start the Activity, but it really could not be opened, which was very weird. This phenomenon only occurs in Xiaomi phones, so it must be related to the MIUI system of Xiaomi phones. After investigation, it is found that the permission of “background pop-up interface” in Xiaomi phones is rejected by default, so the Activity cannot be started in background services or other background operations.

In the application permission management of Xiaomi mobile phone, there is a “Background pop-up interface permission”, which limits the Activity Activity when the APP is in the background. This permission is closed by default, which can be seen in the permission management page of Xiaomi system:It’s normal at this timestartActivity()Method could not bring up the Activity, filter in LogMIUILOGViewing system logs:We can see that there is a permission to deny specific instructions:

ExtraActivityManagerService: MIUILOG- Permission Denied Activity
Copy the code

It can be seen that the Activity was rejected for permission reasons, and then we saw their statement in xiaomi’s official forum:

2. Solutions

Since May 2019, Xiaomi has enabled this permission judgment, so previously normal pop-ups can no longer pop up. We tried some ways to bypass this permission judgment, such as starting a foreground Service to jump, or jumping in Toast, but we could not bypass this permission judgment. Therefore, we could not bypass this permission judgment, and we had to find a way to solve it head-on.

2.1 Business negotiation, let Xiaomi MIUI enable this permission by default

After the installation of common apps, this permission is closed by default. Of course, some large apps have the ability to negotiate with Xiaomi, and Xiaomi will enable this permission by default in the system Settings. For example, “Sogou Input Method” is enabled by default.

However, only companies with enough influence can negotiate with Xiaomi, and their various conditions should be met so that the system can open this permission for us by default, which cannot be done by our ordinary APP. Therefore, this method is not applicable to our ordinary APP.

2.2 Determine permissions and enable permissions through codes

In the face of ordinary permission requests, the general processing scheme is like this: first determine whether you will have this permission, if not, request to enable this permission. However, the difficulty for xiaomi is that there is no relevant API to judge whether it has the permission or not, and there is no API to apply for the permission. Therefore, this road is blocked. Of course, we can call some things of his system layer through methods like reflection, but this is not very reliable, and the research and development cost is relatively large, so it can be said that there is no direct solution.

Is there no solution to this problem? After a series of discussions, I solved the problem in a roundabout way and came to the final available solution:

  1. Check whether the Activity to be started is successfully started. If not, the permission is not obtained.
  2. A pop-up window prompts the user to enable the permission. (Because there is no background popup permission, it is not possible to jump to the system permission setting page directly, so pop-up window prompt)

The difficulty here is to determine whether the Activity has been successfully opened, as for the pop-up guide to customize the boot content. The next section explains how to determine if an Activity is started.

3. Check whether the Activity is successfully started

Here, too, various solutions have been tried, such as:

3.1 in each ActivityOnCreate()Method (eventually abandoned)

When startActivity () is followed by a 0.5s countdown logic, send a broadcast in the OnCreate() method of the Activity to be started to remove the countdown. If it is not cancelled, it has not been started successfully. This needs to be done in each Activity, too cumbersome, so give up.

3.2 Obtaining the Activity judgment from the top of the Activity stack (finally abandoned)

When startActivity () after a 0.5s countdown logic, and then through the Activity stack management to get the top of the stack Activity, judge whether successfully opened. This avoids processing each Activity. For example, we can check the common method like this:

public static String getTopActivity(Context context) {
    String packageName ;
    if (Build.VERSION.SDK_INT > 21) { 
    // Version 5.0 and later
        List<ActivityManager.AppTask> tasks = mActivityManager.getAppTasks();
        if (null! = tasks && tasks.size() >0) { 
            for(ActivityManager.AppTask task:tasks){ packageName = task.getTaskInfo().baseIntent.getComponent().getPackageName(); lable = getPackageManager().getApplicationLabel(getPackageManager().getApplicationInfo(packageName,PackageManager.GET_META_DATA) ).toString();//Log.i(TAG,packageName + lable); }}}else{ 
        // Before 5.0 // get the running task stack (one application occupies one task stack) the most recently used task stack is first
        // 1 indicates the maximum capacity set for the collection
        List<RunningTaskInfo> infos = am.getRunningTasks(1); 
        // Get the package name of the top Activity(the Activity the user is currently operating on) in the most recently run task stack
        packageName = mActivityManager.getRunningTasks(1).get(0).topActivity.getPackageName();
        //Log.i(TAG,packageName); 
    }
    return packageName ;
}
Copy the code

The problem is that these methods are also failed after the Android 5.1, there are other methods, online. Use usageStatsManager queryUsageStats to obtain additional permissions, so also is not reasonable. Ultimately, this approach does not achieve usability.

3.3 Self-built tool classes to complete permission judgment and popup boot processing, determine whether the Activity is opened through their own management of activities on the top of the stack (the final solution)

The utility class handles the startActivity() method uniformly and starts a 0.5s countdown. Manage the activities on the top of the stack in the Application itself to judge whether the activities on the top of the stack are successfully opened. If not, the missile window will be displayed.

In this way, both the start Activity and permission judgment are handled in a tool class. Later, only need to call this tool class, it will realize the start Activity, judge permission, and permission pop-up boot. At the same time, it can manage the Activity stack by itself, which solves the problem of not being able to judge the start of the Activity on each Android version.

Start by listening to the lifecycle of all activities in the Application to record the activities at the top of the stack:

// called in Application's OnCreate() method
private void registerLifecycle(a) {
    mApplication.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
                    @Override
                    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}@Override
                    public void onActivityStarted(Activity activity) {}@Override
                    public void onActivityResumed(Activity activity) {
                        // This is the name of the Activity at the top of the stack
                        CustomActivityManager.setTopActivity(activity);
                    }
    
                    @Override
                    public void onActivityPaused(Activity activity) {}@Override
                    public void onActivityStopped(Activity activity) {
                        // Clear the top of the stack Activity
                        CustomActivityManager.clearTopActivity();
                    }
    
                    @Override
                    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}@Override
                    public void onActivityDestroyed(Activity activity) {}}); }public class CustomActivityManager {
    private static final String SP_KEY_ACTIVITY_STACK_TOP = "sp_key_activity_stack_top";

    public static String getTopActivity(a) {
        // Get the top Activity name from SP
    }

    public static void setTopActivity(Activity topActivity) {
        if(topActivity ! =null) {
            // Put the Activity name at the top of the stack into SP}}public static void clearTopActivity(a) {
        // clear the SP data}}Copy the code

Then do the same in the utility class:

public class ActivityStartCheckUtils {
    private static final int TIME_DELAY = 600;
    private static ActivityStartCheckUtils sInstance;
    private boolean mPostDelayIsRunning;
    private String mClassName;
    private IBinder mToken;
    private PermissionGuideDialog mDialog;
    private Handler mHhandler = new Handler();


    private ActivityStartCheckUtils(a) {}public static ActivityStartCheckUtils getInstance(a) {
        if (sInstance == null) {
            synchronized (ActivityStartCheckUtils.class) {
                if (sInstance == null) {
                    sInstance = newActivityStartCheckUtils(); }}}return sInstance;
    }

    // Here is the judgment logic after the countdown
    private Runnable mRunnable = new Runnable() {
        @Override
        public void run(a) {
            mPostDelayIsRunning = false;
            // Determine if the Activity to be started is already at the top of the stack
            if(! isActivityOnTop()) {// context, if you can get the context for your project
                Context context = MyApplication.getAppContext();// The getAppContext needs to be modified itself
                if(context ! =null&& mToken ! =null) {
                    if (mDialog == null) {
                        // Custom Dialog, this code does not need to paste
                        mDialog = new PermissionGuideDialog(context, mToken);
                    }
                    mDialog.setCancelable(false); mDialog.show(); }}}};public void startActivity(Context context, Intent intent, String className, IBinder token) {
        if (context == null || intent == null || TextUtils.isEmpty(className)) {
            return;
        }
        context.startActivity(intent);
        if (token == null) {
            return;
        }
        mToken = token;
        mClassName = className;
        if (mPostDelayIsRunning) {
            mHhandler.removeCallbacks(mRunnable);
        }
        mPostDelayIsRunning = true;
        
        mHhandler.postDelayed(mRunnable, TIME_DELAY);
    }

    private boolean isActivityOnTop(a) {
        boolean result = false;
        String topActivityName = CustomActivityManager.getTopActivity();
        if(! TextUtils.isEmpty(topActivityName)) {if (topActivityName.contains(mClassName)) {
                result = true; }}returnresult; }}Copy the code

Call the startActivity() method of the utility class when you finally use it:

Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setClass(context, EmptyActivity.class);

ActivityStartCheckUtils.getInstance().startActivity(context, intent, emptyActivityClassName, token);
Copy the code

4. To summarize

Xiaomi background pop-up permission problem is solved through the curve to save the country, because there is no direct API to call. Here is encapsulated into a tool class, any place that needs to add permission judgment only need to call the method of the tool class, so as to realize unified management, and convenient to call, so this is the scheme we finally adopt.

If you have a better plan welcome to come to exchange.