获取 PendingIntent 的 Intent 详细信息

14

我想知道是否有可能从一个我没有创建的PendingIntent中获取更多信息。更准确地说,是否有可能以某种方式检索PendingIntent的原始Intent?我不需要执行它,但想打印出其内容。

浏览PendingIntent代码后,发现一个隐藏方法:

/** @hide */
public IIntentSender getTarget() {
    return mTarget;
}

然而,这个IIntentSender也是隐藏的,与Binder和更多IPC(我猜是)相关的东西有关。不太容易。有什么想法吗?


1
我找了一会儿,但没有找到任何东西。你找到如何打印“Intent”内容的解决方案了吗? - tomrozb
Commonsware在这里回答了一个类似的问题:https://dev59.com/ymAg5IYBdhLWcg3wU5m8#23725068 - Shubhang Malviya
3个回答

18

这种方法适用于Android 4.2.2及以上的版本:

/**
 * Return the Intent for PendingIntent.
 * Return null in case of some (impossible) errors: see Android source.
 * @throws IllegalStateException in case of something goes wrong.
 * See {@link Throwable#getCause()} for more details.
 */
public Intent getIntent(PendingIntent pendingIntent) throws IllegalStateException {
    try {
        Method getIntent = PendingIntent.class.getDeclaredMethod("getIntent");
        return (Intent) getIntent.invoke(pendingIntent);
    } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
        throw new IllegalStateException(e);
    }
}
下面是针对Android 2.3及以上版本的不完整实现。需要编写一个额外的本地(JNI)代码才能使其正常工作,也许会起到作用。请查看TODO注释以获取更多详细信息。
/**
 * Return the Intent for PendingIntent.
 * Return null in case of some (impossible) errors: see Android source.
 * @throws IllegalStateException in case of something goes wrong.
 * See {@link Throwable#getCause()} and {@link Throwable#getMessage()} for more details.
 */
public Intent getIntent(PendingIntent pendingIntent) throws IllegalStateException {
    try {
        Method getIntent = PendingIntent.class.getDeclaredMethod("getIntent");
        return (Intent) getIntent.invoke(pendingIntent);
    } catch (NoSuchMethodException e) {
        return getIntentDeep(pendingIntent);
    } catch (InvocationTargetException | IllegalAccessException e) {
        throw new IllegalStateException(e);
    }
}

private Intent getIntentDeep(PendingIntent pendingIntent) throws IllegalStateException {
    try {
        Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
        Method getDefault = activityManagerNativeClass.getDeclaredMethod("getDefault");
        Object defaultManager = getDefault.invoke(null);
        if (defaultManager == null) {
            throw new IllegalStateException("ActivityManagerNative.getDefault() returned null");
        }
        Field mTargetField = PendingIntent.class.getDeclaredField("mTarget");
        mTargetField.setAccessible(true);
        Object mTarget = mTargetField.get(pendingIntent);
        if (mTarget == null) {
            throw new IllegalStateException("PendingIntent.mTarget field is null");
        }
        String defaultManagerClassName = defaultManager.getClass().getName();
        switch (defaultManagerClassName) {
        case "android.app.ActivityManagerProxy":
            try {
                return getIntentFromProxy(defaultManager, mTarget);
            } catch (RemoteException e) {
                // Note from PendingIntent.getIntent(): Should never happen.
                return null;
            }
        case "com.android.server.am.ActivityManagerService":
            return getIntentFromService(mTarget);
        default:
            throw new IllegalStateException("Unsupported IActivityManager inheritor: " + defaultManagerClassName);
        }
    } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException | NoSuchFieldException e) {
        throw new IllegalStateException(e);
    }
}

private Intent getIntentFromProxy(Object defaultManager, Object sender) throws RemoteException {
    Class<?> activityManagerProxyClass;
    IBinder mRemote;
    int GET_INTENT_FOR_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 160;
    String iActivityManagerDescriptor = "android.app.IActivityManager";
    try {
        activityManagerProxyClass = Class.forName("android.app.ActivityManagerProxy");
        Field mRemoteField = activityManagerProxyClass.getDeclaredField("mRemote");
        mRemoteField.setAccessible(true);
        mRemote = (IBinder) mRemoteField.get(defaultManager);
    } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
        throw new IllegalStateException(e);
    }

    // From ActivityManagerProxy.getIntentForIntentSender()
    Parcel data = Parcel.obtain();
    Parcel reply = Parcel.obtain();
    data.writeInterfaceToken(iActivityManagerDescriptor);
    data.writeStrongBinder(((IInterface) sender).asBinder());
    transact(mRemote, data, reply, 0);
    reply.readException();
    Intent res = reply.readInt() != 0
            ? Intent.CREATOR.createFromParcel(reply) : null;
    data.recycle();
    reply.recycle();
    return res;
}

private boolean transact(IBinder remote, Parcel data, Parcel reply, int i) {
    // TODO: Here must be some native call to convert ((BinderProxy) remote).mObject int
    // to IBinder* native pointer and do some more magic with it.
    // See android_util_Binder.cpp: android_os_BinderProxy_transact() in the Android sources.
}

private Intent getIntentFromService(Object sender) {
    String pendingIntentRecordClassName = "com.android.server.am.PendingIntentRecord";
    if (!(sender.getClass().getName().equals(pendingIntentRecordClassName))) {
        return null;
    }
    try {
        Class<?> pendingIntentRecordClass = Class.forName(pendingIntentRecordClassName);
        Field keyField = pendingIntentRecordClass.getDeclaredField("key");
        Object key = keyField.get(sender);
        Class<?> keyClass = Class.forName("com.android.server.am.PendingIntentRecord$Key");
        Field requestIntentField = keyClass.getDeclaredField("requestIntent");
        requestIntentField.setAccessible(true);
        Intent requestIntent = (Intent) requestIntentField.get(key);
        return requestIntent != null ? new Intent(requestIntent) : null;
    } catch (ClassCastException e) {
    } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
        throw new IllegalStateException(e);
    }
    return null;
}

1
这个方法被标记为 @hide,这意味着它不能被正常调用。但是,反射可能会起作用,除非它需要系统级权限或其他东西。 - matiash
1
当然,我们需要反射来实现这个功能,因为没有正常的方法可以做到。我认为不需要额外的权限。无论如何,这很容易检查。唯一明显的问题是,此调用仅适用于Android 4.2.2及以上版本,因此无法在之前的版本上使用。而且可能(但不太可能)会在未来版本中消失。 - praetorian droid
我已经测试了这个方法,看起来很好。对于由Google+登录创建的'PendingIntent'可正常工作。如果能在4.2.2之前的设备上提供类似的解决方案将会很好,因为这段代码在生产环境中使用并不安全。我认为公共方法应该捕获所有异常,这意味着无法获取'Intent'并返回'null'而不是使应用程序崩溃。 - tomrozb
它在早期版本上不起作用,因为 GET_INTENT_FOR_INTENT_SENDER_TRANSACTION 常量及其处理是在 Android 4.2.2 中添加的。 - praetorian droid
1
如果您尝试使用反射从意图中获取一个可解析的捆绑包,那么这个解决方案将在4.4(我假设以上版本也是如此)上失败。 - Kristy Welsh
显示剩余2条评论

2
如果您想在Robolectric中进行测试目的的Intent,那么请使用ShadowPendingIntent
public static Intent getIntent(PendingIntent pendingIntent) {
    return ((ShadowPendingIntent) ShadowExtractor.extract(pendingIntent))
        .getSavedIntent();
}

-1
你可以使用从PendingIntent.getIntentSender()获得的IntentSenderIntentSender的函数getCreatorPackage(),将返回创建这个PendingIntent的包名。

2
是的,我理解,但我需要原始意图。这不会给我那个。 - Peterdk
或者更确切地说,您如何从IntentSender对象的Intent中获取Extras? - Michael

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接