An overview of the

Then start the Activity demand, the last time the background according to the actual combat | Android background start some methods of the path of practice of the Activity, while on the Android Q version there are still some problems, the ability to start but the background is almost done, Later I unlock the mi ROM source code, found their background to start the implementation of this permission and how to bypass this permission method, found the results of the unexpected simple.. (There will be a chance to write a separate article for this section later).

This article happened after the investigation of background startup. If the Activity page we want to start in the background is in a third-party SDK, and the startActivity of the page also happens in the third-party SDK, Therefore, their startActivity directly does not have the ability to start the background. For some reasons, we cannot ask the SDK to modify the method of starting the Activity. Therefore, we need to find a way to call startActivity without modifying the code of the third-party SDK. Make it capable of launching in the background. The first response is to intercept the startActivity request. See the Android system_server process and the Android-activity startup process. AMS is a thread in the System_server process. It takes care of the actual work of starting the Activity, and when it’s done, Binder calls back to the lifecycle methods of the Activity instance in the APP process. When APP process calls startActivity, the Binder agent of AMS will be obtained by Instrumentation, and then through it to call AMS related methods, we can do Hook interception is this Binder agent object!

The following from each Android version of the system to see the implementation of this process and how we intercept, mainly look at the source code of Android P, although other versions of the process is not the same, but the Hook way is similar.

Android P

Android 8 to Android 9 version of the AOSP access to AMS proxy is the same, APP process in the call context. StartActivity, will come to the Instrumentation in the related method to call the following code:

intresult = ActivityManager.getService().startActivity(whoThread, who.getBasePackageName(), intent, ...) ;Copy the code

Here by Binder cross-process calls the relevant methods in AMS, look at the ActivityManager. GetService () :

/ * *@hide* /
public static IActivityManager getService(a) {
    return IActivityManagerSingleton.get();
}

private static final Singleton<IActivityManager> IActivityManagerSingleton = new Singleton<IActivityManager>() {
    @Override
    protected IActivityManager create(a) {
        / / 1...}};Copy the code

Can see IActivityManagerSingleton is the Singleton instance of type, obviously the Singleton is a lazy loading of Singleton template class:

public abstract class Singleton<T> {
    private T mInstance;

    protected abstract T create(a);

    public final T get(a) {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            returnmInstance; }}}Copy the code

Then can know IActivityManagerSingleton. The get () returns the instance is the create method, given the above 1 to omit the create method of code:

final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
Copy the code

Familiar with Binder classmate see at a glance the am here is a Binder proxy objects, existed ServiceManager. GetService method will certainly exist ServiceManager. The addService method, One is to query Binder services from the ServiceManager, the other is to register services with the ServiceManager. When the system starts the System_server process, refer to the AMS startup process, which will not be described here.

So ActivityManager. GetService () method is returned to the AMS a Binder agent object, used to call AMS related method across processes, so you can through the JDK dynamic proxy approach, Agent. Through the Proxy newProxyInstance method to create am Proxy object, and through the reflection of the way the ActivityManager. GetService () method returns the am replacing our Proxy objects, In App process calls ActivityManager. GetService () method of XXX will be our Proxy intercepted, and then do some processing. JDK dynamic proxy is one of the most common design patterns in Java. If you are not familiar with it, you can refer to JDK dynamic proxy.

This process can be broken down into three steps:

  1. Reflection gets am objects becauseActivityManager.getService()Is a hidden method, so it can be called by reflection to retrieve the original AM object;
  2. Create the Proxy object Proxy.
  3. Replace am objects with proxies by reflection;

We see am object is Singleton (actually is IActivityManagerSingleton) of mInstance properties, so the third step will only by reflection mInstance attribute is set to our Proxy objects can, The following AmsHooker class is an abstract class with different implementations on different Android platforms. It is used to get am objects from different Android platforms and replace AM objects with reflection:

abstract class AmsHooker {
    // Use reflection to replace AM with proxy
    fun hookAms(proxy: Any?). {
        try {
            val hookObj = getHookObj()
            val hookField = getHookField()
            if(hookObj ! =null&& hookField ! =null&& proxy ! =null) {
                hookField.set(hookObj, proxy)
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    / / the IActivityManagerSingleton instance
    protected abstract fun getHookObj(a): Any?

    / / mInstance namely
    protected abstract fun getHookField(a): Field?

    // 即am
    abstract fun getTarget(a): Any?

    // interface to create a Proxy
    abstract fun getInterfaces(a): Array<Class<*>>
}
Copy the code

The implementation on Android P platform is as follows:

class AmsPHooker : AmsHooker() {
    override fun getHookObj(a): Any? {
        val amClass = ReflectUtils.getClass("android.app.ActivityManager")
        / / get IActivityManagerSingleton properties
        return ReflectUtils.readStaticField(amClass, "IActivityManagerSingleton")}override fun getHookField(a): Field? {
        // Get the mInstance Field
        return ReflectUtils.getField(ReflectUtils.getClass("android.util.Singleton"), "mInstance")}override fun getTarget(a): Any? {
        / / ActivityManager. GetService () returns for am
        return ReflectUtils.getClass("android.app.ActivityManager").getDeclaredMethod("getService").invoke(null)}// Get interfaces to create dynamic proxies
    override fun getInterfaces(a): Array<Class<*>> {
        return arrayOf(ReflectUtils.getClass("android.app.IActivityManager"))}}Copy the code

Next create the proxy class (code truncated) :

public class AMSProxy implements InvocationHandler {
    private AmsHooker hooker; // Return different implementations for different Android platforms
    private Object origAm; // The original AM object

    private boolean ensureInit(a) {
        // ...
        hooker = getHooker();
        origAm = hooker.getTarget();
    }

    private AmsHooker getHooker(a) {
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
            return new AmsQHooker();
        } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) {
            return new AmsPHooker();
        } else {
            return newAmsNHooker(); }}@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // ...
    }

    // Create the proxy
    Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                hooker.getInterfaces(), this);
    // Replace the system AM object
    hooker.hookAms(proxy);
}
Copy the code

Above AMSProxy instance as parameters to create a Proxy object Proxy, and use the Proxy objects through hookAms method to replace the am object, so in this process by ActivityManager. GetService () to invoke the related method, The above invoke method is called, where interception can be done:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        if (callback.canIntercept(method, args)) {
            if (callback.autoRemove()) {
                // Restore the AM object
                // ...
            }
            // Intercept the am request and do your own business
            return callback.intercept(origAm, method, args);
        }
        return method.invoke(origAm, args);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}
Copy the code

When code in this process attempts to invoke a method (such as startActivity, etc.) via AM, it is intercepted by the invoke method and then selected by a canIntercept condition we set. It is recommended that the original AM object be restored through hookAms method every time the business requirements for interception are fulfilled to prevent the continuous interception of system requests during the process. The emphasis here is on this process, and it is obvious that replacing am objects with reflection only works for this process.

Android Q

On Android Q, the call in the above Instrumentation becomes the following:

intresult = ActivityTaskManager.getService().startActivity(whoThread, who.getBasePackageName(), intent, ...) ;Copy the code

This becomes a ActivityTaskManager getService () :

/ * *@hide* /
public static IActivityTaskManager getService(a) {
    return IActivityTaskManagerSingleton.get();
}

private static final Singleton<IActivityTaskManager> IActivityTaskManagerSingleton = new Singleton<IActivityTaskManager>() {
    @Override
    protected IActivityTaskManager create(a) {
        final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE);
        returnIActivityTaskManager.Stub.asInterface(b); }};Copy the code

As you can see from ActivityManager to ActivityTaskManager class on Android Q, our AmsQHooker implementation is as follows:

class AmsQHooker : AmsHooker() {
    override fun getHookObj(a): Any? {
        val amClass = ReflectUtils.getClass("android.app.ActivityTaskManager")
        / / get IActivityTaskManagerSingleton properties
        return ReflectUtils.readStaticField(amClass, "IActivityTaskManagerSingleton")}override fun getHookField(a): Field? {
        return ReflectUtils.getField(ReflectUtils.getClass("android.util.Singleton"), "mInstance")}override fun getTarget(a): Any? {
        // Reflective access to getService is forbidden when targeting API 29 and above
        // val getServiceMethod = amClass.getDeclaredMethod("getService")
        return ReflectUtils.getClass("android.util.Singleton").getDeclaredMethod("get").invoke(getHookObj())
    }

    override fun getInterfaces(a): Array<Class<*>> {
        return arrayOf(ReflectUtils.getClass("android.app.IActivityTaskManager"))}}Copy the code

The other steps are the same as for Android P.

Android N

In Android 7.1 and below, the call of Instrumentation is different:

intresult = ActivityManagerNative.getDefault().startActivity(whoThread, who.getBasePackageName(), intent, ...) ;Copy the code

This becomes a ActivityManagerNative getDefault () :

static public IActivityManager getDefault(a) {
    return gDefault.get();
}

private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
    protected IActivityManager create(a) {
        IBinder b = ServiceManager.getService("activity");
        IActivityManager am = asInterface(b);
        returnam; }};Copy the code

As you can see, the Singleton class is used even though the class name and method have changed, so you just need to inherit the AmsHooker function to override the related method:

class AmsNHooker : AmsHooker() {
    override fun getHookObj(a): Any? {
        val amNativeClass = ReflectUtils.getClass("android.app.ActivityManagerNative")
        // Get the gDefault instance
        return ReflectUtils.readStaticField(amNativeClass, "gDefault")}override fun getHookField(a): Field? {
        return ReflectUtils.getField(ReflectUtils.getClass("android.util.Singleton"), "mInstance")}override fun getTarget(a): Any? {
        returngetHookField()? .get(getHookObj())
    }

    override fun getInterfaces(a): Array<Class<*>> {
        return arrayOf(ReflectUtils.getClass("android.app.IActivityManager"))}}Copy the code

The other is to reuse logic on Android P.

conclusion

With this approach, some of the methods that can be used to intercept calls through AMS’s Binder agents can be used to implement some unconventional functions. Although the requirements that have been done recently are quite unconventional (liumang), it is interesting to investigate these technologies beyond the requirements for development. Ha ~

Blog address

PS: RECENTLY I found that some articles I wrote were reprinted on other platforms, but the original blog link was deleted, and the source of the reprint was not indicated. 😀), like the Android graphics system review after the reprint of this article all obtained more than 400 points like, than I dig gold total points like more, eat a jin of lemon 🍋 ~^~ as a new person very happy someone reprint my article, but hope Xuowei indicated the source ha, personal or quite like the technical atmosphere of the nuggets, Registered before the account of other platform, tried to send an article found to be a dead end, sister thought of digging gold can harvest unexpected joy, far away.

If the content of the article is wrong, welcome to point out, common progress! Leave a “like” if you think it’s good

Insert a topic aside, if you can help cast a few votes: Uncle Xang er, very grateful!