An overview of the

A few days ago, the product raised a demand, want to start an Activity of our APP in the background, with the Android version update, and various ROM manufacturers unlimited transformation, this affect the user experience of many functions are limited, there is no way, although it is a rogue function, but take people’s money to eliminate the disaster, And so began the gritty process of research.

Native Android ROM

Start with the Android native ROM. According to the official introduction, the restriction on launching activities in the background only started with Android 10(API 29). Prior to that, native ROMs did not have this restriction. I started an Emulator for Android versions 9(API 28) and 10(API 29), respectively, and found that on API 28 it was possible to start an Activity directly from the background, whereas on API 29 it was limited to starting an Activity directly. There are some unrestricted exceptions to the official restrictions on starting an Activity from the background, and the official recommendation is that background startup is required to show a Notification to the user first rather than starting the Activity directly. The corresponding logic is then processed after the user clicks the Notification. You can also add a full-screen Intent object with setFullScreenIntent when setting your Notification. This method has been tested. You can launch an Activity interface in the background on an Android 10 emulator (requires android.permission. The code is as follows:

object NotificationUtils {
    private const val ID = "channel_1"
    private const val NAME = "notification"

    private var manager: NotificationManager? = null

    private fun getNotificationManagerManager(context: Context): NotificationManager? {
        if (manager == null) {
            manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager
        }
        return manager
    }

    fun sendNotificationFullScreen(context: Context, title: String? , content:String?). {
        if (Build.VERSION.SDK_INT >= 26) {
            clearAllNotification(context)
            val channel = NotificationChannel(ID, NAME, NotificationManager.IMPORTANCE_HIGH)
            channel.setSound(null.null) getNotificationManagerManager(context)? .createNotificationChannel(channel)valnotification = getChannelNotificationQ(context, title, content) getNotificationManagerManager(context)? .notify(1, notification)
        }
    }

    private fun clearAllNotification(context: Context){ getNotificationManagerManager(context)? .cancelAll() }private fun getChannelNotificationQ(context: Context, title: String? , content:String?).: Notification {
        val fullScreenPendingIntent = PendingIntent.getActivity(
            context,
            0,
            DemoActivity.genIntent(context),
            PendingIntent.FLAG_UPDATE_CURRENT
        )
        val notificationBuilder = NotificationCompat.Builder(context, ID)
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setContentTitle(title)
            .setContentText(content)
            .setSound(null)
            .setPriority(NotificationCompat.PRIORITY_MAX)
            .setCategory(Notification.CATEGORY_CALL)
            .setOngoing(true)
            .setFullScreenIntent(fullScreenPendingIntent, true)
        return notificationBuilder.build()
    }
}
Copy the code

Up to now, the overall feeling is good, the current Android native ROM can normally start the Activity interface from the background, whether Android 9 or 10 version, are happy.

Custom roms

The problem is that Android is not a GPL. It uses the Apache open source license, which allows third-party vendors to close the source code after modifying the code. Therefore, it is impossible to know what changes have been made to the ROM source code of the vendors. Some models have added a privilege — a background pop-up interface, such as in MIUI, which is added and disabled by default unless added to their whitelist, as the Mi Open Platform documentation states: This permission is denied by default. By default, applications are not allowed to display pages in the background. Whitelists are provided for special applications, such as music (lyrics), sports, and VOIP (calls). The whitelisted app will be permanently removed from the whitelist if malicious behavior such as promotion occurs.

Check the permissions of the background pop-up interface

On Mi models, the new permissions for this background popup interface are extended to the new permissions in AppOpsService. Check the AppOpsManager source code and see many familiar constants in it:

@SystemService(Context.APP_OPS_SERVICE)
public class AppOpsManager {
    public static final int OP_GPS = 2;
    public static final int OP_READ_CONTACTS = 4;
    // ...
}
Copy the code

We can use AppOpsService to check whether we have background popup permission. What is the OpCode corresponding to this permission? There are, according to people familiar with the access Code is 10021, so you can use AppOpsManager. CheckOpNoThrow or AppOpsManager. NoteOpNoThrow series of methods, such as detecting the existence of the permissions and These methods are all @hide, so we need to use reflection:

fun checkOpNoThrow(context: Context, op: Int): Boolean {
    val ops = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
    try {
        val method: Method = ops.javaClass.getMethod(
            "checkOpNoThrow".Int: :class.javaPrimitiveType, Int: :class.javaPrimitiveType, String::class.java
        )
        val result = method.invoke(ops, op, myUid(), context.packageName) as Int
        return result == AppOpsManager.MODE_ALLOWED
    } catch (e: Exception) {
        e.printStackTrace()
    }
    return false
}

fun noteOpNoThrow(context: Context, op: Int): Int {
    val ops = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
    try {
        val method: Method = ops.javaClass.getMethod(
            "noteOpNoThrow".Int: :class.javaPrimitiveType, Int: :class.javaPrimitiveType, String::class.java
        )
        return method.invoke(ops, op, myUid(), context.packageName) as Int
    } catch (e: Exception) {
        e.printStackTrace()
    }
    return -100
}
Copy the code

In addition, if you want to know the code of other newly added permissions, you can use the above method to traverse the permissions of codes within a certain range (such as 10000~10100), and then use the mobile phone to switch the permissions you want to query. According to the traversal result, you can roughly get the code of corresponding permissions.

Android P background startup permission

Tests on the Mi Max3 found two ways to launch the Activity interface from the background, which is based on the Android 9 MIUI system.

Method one: moveTaskToFront

Instead of starting an Activity directly from the background, this approach takes the idea of switching the application to the foreground before starting the target Activity in the background, and then starting the target Activity if necessary. You can also move an Activity back to the background using the Activity.moveTasktoback method. In tests, this method is invalid on Android 10… But 10 the following version can still salvage once (need to declare the android. Permission. REORDER_TASKS permissions).

Determine if the application is in the background before starting the target Activity, Judgment method can use ActivityManager. GetRunningAppProcesses method or Application. The ActivityLifecycleCallbacks to listen on Taiwan before and after, the two methods have articles online, is not here. Post the code to switch from background to foreground directly:

fun moveToFront(context: Context) {
    val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager activityManager? .getRunningTasks(100)? .forEach { taskInfo ->if(taskInfo.topActivity? .packageName == context.packageName) { Log.d("LLL"."Try to move to front")
            activityManager.moveTaskToFront(taskInfo.id, 0)
            return}}}fun startActivity(activity: Activity, intent: Intent) {
    if(! isRunningForeground(activity)) { Log.d("LLL"."Now is in background")
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
            // TODO can prevent moveToFront from failing
            moveToFront(activity)
            activity.startActivity(intent)
            activity.moveTaskToBack(true)}else {
            NotificationUtils.sendNotificationFullScreen(activity, ""."")}}else {
        Log.d("LLL"."Now is in foreground")
        activity.startActivity(intent)
    }
}
Copy the code

Method two: Hook

Because MIUI system is not open source, so try to study AOSP source code, when the dead horse live horse doctor to see what clues can be found. Start with the activity. startActivity method, If you can read the Activity start source process know Activity. StartActivity or call to Instrumentation. The execStartActivity, then call to AMS related method by Binder, Permission authentication is done in AMS, and natural startup fails if the permissions are not met (Android 10).

/ / APP process
public ActivityResult execStartActivity(Context who, IBinder contextThread, ...) {
    // ...
    // AMS code is called with Binder
    intresult = ActivityManager.getService().startActivity(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target ! =null ? target.mEmbeddedID : null, requestCode, 0.null, options);
    // ...
}

/ / system_server process
// AMS
public final int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
    String resolvedType, IBinder resultTo, String resultWho, int requestCode,
    int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {
    // ...
}
Copy the code

Here if you are interested in system_server, zygote process these can refer to the Android system to start the source code reading – init-Zygote -system_server. Take a look at these parameters:

  • Caller: AMS will use the Binder to call the client APP process to instantiate the Activity object and call back its lifecycle methods after completing related tasks. The Caller’s Binder server is located in the APP process.
  • CallingPackage: This parameter identifies the caller package name.
  • .

Here we can try to Hook some things of the system, the specific code of how to Hook is not given. After the test, it can be successful on Xiaomi devices of Android 9. If you are interested in it, you can study and talk about it by yourself. Or decompile the Mi ROM source code, you can find something from it.

Android Q background startup permission

As mentioned above, the native system also adds the background launch limitation. You can start an Activity from the background on the native Android 10 system by setting the fullScreenIntent notification. AOSP source code can be found in AMS, which describes the startActivity process. After an APP process initiates a request, it calls AMS in system_Server process with Binder across processes. Then call to ActivityStarter. StartActivity method, the restrictions on the background started it here:

// There are more than 20 parameters, hey hey, hey hey
private int startActivity(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
        String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
        IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
        IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
        String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
        SafeActivityOptions options,
        boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity,
        TaskRecord inTask, boolean allowPendingRemoteAnimationRegistryLookup,
        PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) {
    // ...
    booleanabort = ! mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho, requestCode, callingPid, callingUid, callingPackage, ignoreTargetSecurity, inTask ! =null, callerApp, resultRecord, resultStack); abort |= ! mService.mIntentFirewall.checkStartActivity(intent, callingUid, callingPid, resolvedType, aInfo.applicationInfo); abort |= ! mService.getPermissionPolicyInternal().checkStartActivity(intent, callingUid, callingPackage);boolean restrictedBgActivity = false;
    if(! abort) { restrictedBgActivity = shouldAbortBackgroundActivityStart(callingUid, callingPid, callingPackage, realCallingUid, realCallingPid, callerApp, originatingPendingIntent, allowBackgroundActivityStart, intent); }// ...
}
Copy the code

The shouldAbortBackgroundActivityStart call here is new in Android Q, see the method name can start chopper for the background:

boolean shouldAbortBackgroundActivityStart(...). {
    final int callingAppId = UserHandle.getAppId(callingUid);
    if (callingUid == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID
            || callingAppId == Process.NFC_UID) {
        return false;
    }
    if (callingUidHasAnyVisibleWindow || isCallingUidPersistentSystemProcess) {
        return false;
    }
    // don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission
    if (mService.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid)
            == PERMISSION_GRANTED) {
        return false;
    }
    // don't abort if the caller has the same uid as the recents component
    if (mSupervisor.mRecentTasks.isCallerRecents(callingUid)) {
        return false;
    }
    // don't abort if the callingUid is the device owner
    if (mService.isDeviceOwner(callingUid)) {
        return false;
    }
    // don't abort if the callingUid has SYSTEM_ALERT_WINDOW permission
    if (mService.hasSystemAlertWindowPermission(callingUid, callingPid, callingPackage)) {
        Slog.w(TAG, "Background activity start for " + callingPackage
                + " allowed because SYSTEM_ALERT_WINDOW permission is granted.");
        return false;
    }
    // ...
}
Copy the code

In this way, we can see that the restrictions on starting an Activity from the background are corresponding to the restrictions in the official document on starting an Activity from the background. In this way, we can see that the permissions are determined based on the UID and are completed in the system process system_server. It is useless to simply change the package name.

The background Activity page can be successfully popup through full-screen notification on some ROMs that do not have separate restrictions on background startup, such as Xiaomi A3, and a Vivo and a Samsung phone. The specific models are forgotten. It won’t pop up on restricted devices, like the Redmi Note 8 Pro.

For redmi Note 8 Pro this hard bone, kept trying a lot of methods, but in fact is a lottery, because can’t get MIUI source code, then want to change the way of thinking, can try to pull out the relevant framework. Jar package from this phone and decompile it? You might get something! But need a Root phone, this is easy to do, Xiaomi itself can provide a Root development version of the system, so go to the MIUI official website to find, found that this Redmi Note 8 Pro model does not provide development version of the system (tears of laughter), think of it like before is said that the low-end machine Xiaomi no longer provide development version… Well, there’s no other phone to try.

On second thought, whether you can directly download the stable version of the ROM package, decompressed after no tools can get some source code related traces? So I downloaded a rom.zip and decompress it to see that there are only some system image img file and.dat.br file in it. I don’t know much about this part. There will be plenty of time for further research.

conclusion

Native Android ROM

The Android native ROM launches the Activity interface normally from the background, whether Android 9(directly launched) or Android 10 (with full screen notifications).

Custom roms

Check the background pop-up interface permission:

  • Check opCode permissions by reflecting AppOpsManager related methods;
  • OpCode = 10021;
  • Other models can try to get opCode by traversal;

Android P version of Xiaomi:

  • Use Hook related parameters to start the Activity in the background. The code cannot be given due to some reasons. If you need it, please leave a message and tell me.
  • Only xiaomi models have been tested, other models are not necessarily available;
  • In theory, xiaomi below P version should also support;

Android P model:

  • Move the application to the foreground using the moveTaskToFront method;
  • This approach is, after all, the official API, so it’s probably more compatible;
  • If the switch fails, you can try moveTaskToFront a few more times;
  • Theoretically, models below P version should also be supported;

Android Q version models:

  • Start the background Activity through the system full-screen notification;
  • It may fail to debug on some otherwise limited ROMs;

The way MIUI code was decompiled was a matter of conjecture, not action due to time reasons. Well, it seems that brother’s demand cannot be fully realized, temporarily do not know to have done related research (or know) friend can provide some reference idea, although the function of the is a rascal, hey hey, but the code innocence towards a goal demand, thinking about the solution for this, and from all directions to research, I think itself is an interesting and uplifting thing! Students who have done related research are welcome to make suggestions in the comments section.

  • The blog link

If there are mistakes in the content of the article, welcome to point out and make progress together! Feel good to leave a like to go again ha ~