2013-01-09 26 views
12

Zastanawiam się, czy można uzyskać więcej informacji z PendingIntent, których sam nie stworzyłem. Aby być bardziej precyzyjnym: czy można jakoś odzyskać oryginalny Intent z PendingIntent? Nie muszę go wykonywać, ale chciałbym wydrukować jego zawartość.Uzyskaj szczegółowe informacje na temat intencji Oczekujących

Patrząc przez kodeks PendingIntent pokazuje ukrytą metody:

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

Jednak to IIntentSender jest również ukryte i ma do czynienia z Binder i więcej IPC (chyba) związanej rzeczy. Nie takie łatwe. Jakieś pomysły?

+1

Szukałem jakiś czas i nie znalazłem nic. Czy znalazłeś jakieś rozwiązanie, jak wydrukować zawartość "Intent"? – tomrozb

+0

Commonsware odpowiedział na podobne pytanie tutaj http://stackoverflow.com/a/23725068/2319390 –

Odpowiedz

-1

Można użyć numeru IntentSender, który można uzyskać od PendingIntent.getIntentSender(). IntentSender - funkcja getCreatorPackage(), poda pakiet, który utworzył ten PendingIntent.

+2

Tak, rozumiem to, ale potrzebuję pierwotnego zamiaru. To mi nie daje. – Peterdk

+0

A może bardziej na temat, w jaki sposób uzyskać Extras z Intent z obiektu IntentSender? – Michael

16

Ta metoda będzie działać na Androidzie 4.2.2 i powyżej:

/** 
* 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); 
    } 
} 

Poniżej jest niepełna realizacja dla Android 2.3 i powyżej. Wymaga napisania dodatkowego kodu natywnego (JNI). Wtedy może to zadziała. Zobacz komentarz TODO, aby uzyskać więcej informacji.

/** 
* 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; 
} 
+0

Ta metoda jest oznaczona przez '@ hide', co oznacza, że ​​nie można jej wywołać normalnie. Odbicie może jednak działać, chyba że wymaga pozwolenia na poziomie systemu lub czegoś takiego. – matiash

+0

Oczywiście potrzebujemy refleksji tutaj, ponieważ nie ma normalnego sposobu, aby to zrobić. I nie sądzę, że konieczne są dodatkowe uprawnienia. W każdym razie łatwo to sprawdzić. Jedynym oczywistym problemem jest to, że to połączenie jest dostępne od wersji Androida 4.2.2, więc nie można go używać w poprzednich wersjach. I prawdopodobnie (ale mało prawdopodobne) może zniknąć w przyszłych wersjach. –

+0

Testowałem tę metodę i wygląda dobrze. Działa dobrze dla 'PendingIntent's stworzonych przez logowanie do Google+. Byłoby miło mieć podobne rozwiązanie dla urządzeń starszych niż 4.2.2, ponieważ ten kod nie jest bezpieczny w użyciu. Myślę, że metoda publiczna powinna wychwycić wszystkie wyjątki, co oznacza, że ​​nie ma możliwości uzyskania 'Intent' i zwrócenia' null' zamiast awarii aplikacji. – tomrozb

2

Jeśli chcesz, przeznaczona do celów testowych w Robolectric, a następnie użyć ShadowPendingIntent:

public static Intent getIntent(PendingIntent pendingIntent) { 
    return ((ShadowPendingIntent) ShadowExtractor.extract(pendingIntent)) 
     .getSavedIntent(); 
} 
Powiązane problemy