Получить информацию о намерении PendingIntent

Мне было интересно, можно ли получить дополнительную информацию из PendingIntent, которую я сам не создал. Точнее: возможно ли каким-то образом восстановить исходный Intent PendingIntent ? Мне не нужно его выполнять, но хотелось бы распечатать его содержимое.

Просматривая код PendingIntent , он показывает скрытый метод:

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

Однако этот IIntentSender также скрыт и связан с Binder и другими предметами IPC (я думаю). Не так просто. Есть идеи?

12
nl ja de
Я искал какое-то время и ничего не нашел. Вы нашли решение о том, как печатать содержимое Intent ?
добавлено автор tomrozb, источник
Commonsware ответил на аналогичный вопрос здесь stackoverflow.com/a/23725068/2319390
добавлено автор Shubhang Malviya, источник

3 ответы

Этот метод будет работать на 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;
}
17
добавлено
Я тестировал этот метод, и он выглядит хорошо. Прекрасно работает для PendingIntent , созданного в Google+. Было бы неплохо иметь аналогичное решение для устройств старше 4.2.2, потому что этот код небезопасен для использования в производстве. Я думаю, что общедоступный метод должен улавливать все исключения, что означает, что нет способа получить Intent и вернуть null вместо сбой приложения.
добавлено автор tomrozb, источник
Этот метод отмечен @hide , что означает, что его нельзя вызвать нормально. Отражение может работать, хотя, если оно не требует разрешения на системном уровне или чего-то еще.
добавлено автор matiash, источник
Это решение провалится на 4.4 (и я предполагаю выше), если вы попытаетесь получить пакет Parceable из намерения с использованием отражения.
добавлено автор Kristy Welsh, источник
Конечно, нам нужно размышлять здесь, потому что нет нормального способа сделать это. И я не думаю, что необходимы дополнительные разрешения. Во всяком случае, это легко проверить. Единственная очевидная проблема заключается в том, что этот вызов доступен с Android 4.2.2, поэтому его нельзя использовать в предыдущих версиях. И возможно (но вряд ли) он может исчезнуть в будущих версиях.
добавлено автор praetorian droid, источник
Этот код должен работать на Android 2.3 и выше. Единственное исключение, которое вызывается в этом коде - IllegalStateException с подробностями о том, что происходит не так. Если вам не нужны эти детали, не проблема, поймайте его и верните null.
добавлено автор praetorian droid, источник
Ну, он не работает в более ранних версиях, потому что константа GET_INTENT_FOR_INTENT_SENDER_TRANSACTION и ее обработка были добавлены в Android 4.2.2.
добавлено автор praetorian droid, источник
Решение для более ранних версий Android требует наличия собственного кода (со всеми его недостатками), который не гарантирует успех. Поэтому я сдался :)
добавлено автор praetorian droid, источник

Если вы хотите намерение для тестирования в Robolectric, используйте ShadowPendingIntent :

public static Intent getIntent(PendingIntent pendingIntent) {
    return ((ShadowPendingIntent) ShadowExtractor.extract(pendingIntent))
        .getSavedIntent();
}
2
добавлено

Вы можете использовать IntentSender , который можно получить из PendingIntent.getIntentSender() . Функция IntentSender getCreatorPackage() предоставит пакет, который создал этот PendingIntent .

1
добавлено
Да, я понимаю это, но я нуждаюсь в первоначальном намерении. Это не даст мне этого.
добавлено автор Peterdk, источник
Или, может быть, более важно то, как вы получите Extras от намерения от объекта IntentSender?
добавлено автор Michael, источник
Mobile Dev Jobs — вакансии и аналитика
Mobile Dev Jobs — вакансии и аналитика
6 187 участник(ов)

Публикуем вакансии и запросы на поиск работы по направлению iOS, Android, Xamarin и т.д. ВАЖНО: Правила публикации и правила канала: Ссылка – https://telegra.ph/Pravila-oformleniya-vakansij-i-rezyume-11-09-2

Android Developers
Android Developers
4 476 участник(ов)

Общаемся на темы, посвященным Android-разработке, SDK, Kotlin, Realm и т.д.

Android Architecture
Android Architecture
2 186 участник(ов)

Русскоязычный чат по архитектуре в андроид приложениях. Подробнее: http://telegra.ph/Android-Architecture-12-24

rus-speaking/android
rus-speaking/android
1 705 участник(ов)

Основной чат по Android разработке (вопрос-ответ). ПРАВИЛА: bit.ly/andr-rules. NEWS: bit.ly/AnrdResId ЧАТЫ: Основной: bit.ly/andr-main IDE, сборка, Git, сервисы: bit.ly/andr-tools Оффтоп: bit.ly/andr-offtop Конференции, события: bit.ly/andr-events Вакансии, найм: bit.ly/andr-job Архитектура: bit.ly/andr-patterns Rx: bit.ly/andr-rx Тестирование: bit.ly/andr-test Kotlin: bit.ly/andr-kotlin Хаmarin: bit.ly/andr-xamarin За мат, спам, агрессию, предложения о работе, оффтоп в этом канале - бан на сутки и более ☢☢☢

Android Dev Подкаст
Android Dev Подкаст
1 325 участник(ов)

Комната для обсуждения Android Dev подкаста apptractor.ru/AndroidDev/ Общее обсуждение Android: https://t.me/android_ru Остальные чаты про Android: http://t.me/devChats Наши новости https://t.me/androiddevpodcast_news

Android Guards
Android Guards
602 участник(ов)

Обсуждение любых вопросов касающихся безопасности Android. - Защита системы и приложений - Уязвимости и эксплойты - Вредоносное ПО - Копание в кишках системы и приложений (RE)

Android JOB
Android JOB
466 участник(ов)

Публикуем вакансии и запросы на поиск работы по направлению Android (full-time, part-time, remote и разовые подработки)

AndroidDev :: Разработка. It's Android time now!
AndroidDev :: Разработка. It's Android time now!
458 участник(ов)

It's Android time now! Чат разработчиков Android. Вакансии, резюме и информацию о митапах размещать можно. Публикацию скрытой и явной рекламы ваших каналов и сайтов после получения разрешения от @olegushakov

Aandroid Talks!
Aandroid Talks!
212 участник(ов)

Чат об общих вопросах по ОС Android. Чат для разработки под андроид - pro.android: https://t.me/joinchat/AAAAAEKIFKnmRT9cMebb9w

Android Rus
Android Rus
68 участник(ов)