example - open activity from notification android
Extraiga el texto de notificación de parcelable, contentView o contentIntent (8)
Agregando la respuesta de Remi, para identificar diferentes tipos de notificación y extraer datos, use el siguiente código.
Resources resources = null;
try {
PackageManager pkm = getPackageManager();
resources = pkm.getResourcesForApplication(strPackage);
} catch (Exception ex){
Log.e(strTag, "Failed to initialize ids: " + ex.getMessage());
}
if (resources == null)
return;
ICON = resources.getIdentifier("android:id/icon", null, null);
TITLE = resources.getIdentifier("android:id/title", null, null);
BIG_TEXT = resources.getIdentifier("android:id/big_text", null, null);
TEXT = resources.getIdentifier("android:id/text", null, null);
BIG_PIC = resources.getIdentifier("android:id/big_picture", null, null);
EMAIL_0 = resources.getIdentifier("android:id/inbox_text0", null, null);
EMAIL_1 = resources.getIdentifier("android:id/inbox_text1", null, null);
EMAIL_2 = resources.getIdentifier("android:id/inbox_text2", null, null);
EMAIL_3 = resources.getIdentifier("android:id/inbox_text3", null, null);
EMAIL_4 = resources.getIdentifier("android:id/inbox_text4", null, null);
EMAIL_5 = resources.getIdentifier("android:id/inbox_text5", null, null);
EMAIL_6 = resources.getIdentifier("android:id/inbox_text6", null, null);
INBOX_MORE = resources.getIdentifier("android:id/inbox_more", null, null);
Así que conseguí mi AccessibilityService trabajando con el siguiente código:
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) {
List<CharSequence> notificationList = event.getText();
for (int i = 0; i < notificationList.size(); i++) {
Toast.makeText(this.getApplicationContext(), notificationList.get(i), 1).show();
}
}
}
Funciona bien para leer el texto que se muestra cuando se creó la notificación (1) .
El único problema es que también necesito el valor de (3) que se muestra cuando el usuario abre la barra de notificaciones. (2) no es importante para mí, pero sería bueno saber cómo leerlo. Como probablemente sepas, todos los valores pueden ser diferentes.
Entonces, ¿cómo puedo leer (3) ? Dudo que esto sea imposible, pero mi lista de notificationList
parece tener solo una entrada (al menos solo se muestra una tostada).
¡Muchas gracias!
/ edit: podría extraer el paquete de notificación con
if (!(parcel instanceof Notification)) {
return;
}
final Notification notification = (Notification) parcel;
Sin embargo, no tengo ni idea de cómo extraer el mensaje de la notification
de notification
o notification.contentView
/ notification.contentIntent
.
¿Algunas ideas?
/ edit: para aclarar lo que se pregunta aquí: ¿Cómo puedo leer (3) ?
El proyecto AcDisplay de AcDisplay ha proporcionado una solución, verifique el código de Extractor.java
Hay otra forma si no desea utilizar la reflexión: en lugar de recorrer la lista de "acciones" que se enumeran en el objeto RemoteViews, puede "reproducirlas" en un ViewGroup:
/* Re-create a ''local'' view group from the info contained in the remote view */
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
ViewGroup localView = (ViewGroup) inflater.inflate(remoteView.getLayoutId(), null);
remoteView.reapply(getApplicationContext(), localView);
Tenga en cuenta que utiliza remoteView.getLayoutId () para asegurarse de que la vista inflada corresponda a la de la notificación.
Luego, puede recuperar algunas de las vistas de texto más o menos estándar con
TextView tv = (TextView) localView.findViewById(android.R.id.title);
Log.d("blah", tv.getText());
Para mi propio propósito (que es espiar las notificaciones publicadas por un paquete propio), elegí atravesar toda la jerarquía en localView y reunir todas las vistas de texto existentes.
He pasado la última semana trabajando con un problema similar y puedo proponer una solución similar a la de Tom Tache (usando la reflexión), pero podría ser un poco más fácil de entender. El siguiente método combinará una notificación para cualquier texto presente y devolverá ese texto en un ArrayList si es posible.
public static List<String> getText(Notification notification)
{
// We have to extract the information from the view
RemoteViews views = notification.bigContentView;
if (views == null) views = notification.contentView;
if (views == null) return null;
// Use reflection to examine the m_actions member of the given RemoteViews object.
// It''s not pretty, but it works.
List<String> text = new ArrayList<String>();
try
{
Field field = views.getClass().getDeclaredField("mActions");
field.setAccessible(true);
@SuppressWarnings("unchecked")
ArrayList<Parcelable> actions = (ArrayList<Parcelable>) field.get(views);
// Find the setText() and setTime() reflection actions
for (Parcelable p : actions)
{
Parcel parcel = Parcel.obtain();
p.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
// The tag tells which type of action it is (2 is ReflectionAction, from the source)
int tag = parcel.readInt();
if (tag != 2) continue;
// View ID
parcel.readInt();
String methodName = parcel.readString();
if (methodName == null) continue;
// Save strings
else if (methodName.equals("setText"))
{
// Parameter type (10 = Character Sequence)
parcel.readInt();
// Store the actual string
String t = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel).toString().trim();
text.add(t);
}
// Save times. Comment this section out if the notification time isn''t important
else if (methodName.equals("setTime"))
{
// Parameter type (5 = Long)
parcel.readInt();
String t = new SimpleDateFormat("h:mm a").format(new Date(parcel.readLong()));
text.add(t);
}
parcel.recycle();
}
}
// It''s not usually good style to do this, but then again, neither is the use of reflection...
catch (Exception e)
{
Log.e("NotificationClassifier", e.toString());
}
return text;
}
Debido a que esto probablemente se parece un poco a la magia negra, déjame explicarte con más detalle. Primero extraemos el objeto RemoteViews de la notificación en sí. Esto representa las vistas dentro de la notificación real. Para acceder a esas vistas, tenemos que inflar el objeto RemoteViews (que solo funcionará cuando haya un contexto de actividad) o usar la reflexión. La reflexión funcionará en cualquier circunstancia y es el método utilizado aquí.
Si examina la fuente de RemoteViews here , verá que uno de los miembros privados es un ArrayList of Action objetos. Esto representa lo que se hará a las vistas después de que se inflan. Por ejemplo, después de crear las vistas, se llamará a setText () en algún punto de cada TextView que sea parte de la jerarquía para asignar las cadenas adecuadas. Lo que hacemos es obtener acceso a esta lista de acciones y recorrerla. La acción se define de la siguiente manera:
private abstract static class Action implements Parcelable
{
...
}
Hay una serie de subclases concretas de Acción definidas en RemoteViews. El que nos interesa se llama ReflectionAction y se define de la siguiente manera:
private class ReflectionAction extends Action
{
String methodName;
int type;
Object value;
}
Esta acción se utiliza para asignar valores a las vistas. Es probable que una sola instancia de esta clase tenga los valores {"setText", 10, "contenido de textview"}. Por lo tanto, solo nos interesan los elementos de mActions que son objetos de "Acción de reflexión" y asignan texto de alguna manera. Podemos decir que una "Acción" en particular es una "Acción de Reflexión" al examinar el campo "ETIQUETA" dentro de la Acción, que siempre es el primer valor que se lee desde la parcela. Los TAG de 2 representan objetos de ReflectionAction.
Después de eso, solo tenemos que leer los valores de la parcela según el orden en que fueron escritos (vea el enlace de la fuente, si tiene curiosidad). Encontramos cualquier cadena que se establece con setText () y la guardamos en la lista. (También se incluye setTime (), en caso de que también se necesite el tiempo de notificación. De lo contrario, esas líneas se pueden eliminar de manera segura).
Si bien normalmente me opongo al uso de la reflexión en casos como este, hay ocasiones en que es necesario. A menos que haya un contexto de actividad disponible, el método "estándar" no funcionará correctamente, por lo que esta es la única opción.
He perdido algunas horas de los últimos días buscando una manera de hacer lo que tú (y yo también, por cierto) queremos hacer. Después de revisar la fuente completa de RemoteViews dos veces, pensé que la única forma de realizar esta tarea es hacer reflexiones de Java viejas, feas y piratas.
Aquí está:
Notification notification = (Notification) event.getParcelableData();
RemoteViews views = notification.contentView;
Class secretClass = views.getClass();
try {
Map<Integer, String> text = new HashMap<Integer, String>();
Field outerFields[] = secretClass.getDeclaredFields();
for (int i = 0; i < outerFields.length; i++) {
if (!outerFields[i].getName().equals("mActions")) continue;
outerFields[i].setAccessible(true);
ArrayList<Object> actions = (ArrayList<Object>) outerFields[i]
.get(views);
for (Object action : actions) {
Field innerFields[] = action.getClass().getDeclaredFields();
Object value = null;
Integer type = null;
Integer viewId = null;
for (Field field : innerFields) {
field.setAccessible(true);
if (field.getName().equals("value")) {
value = field.get(action);
} else if (field.getName().equals("type")) {
type = field.getInt(action);
} else if (field.getName().equals("viewId")) {
viewId = field.getInt(action);
}
}
if (type == 9 || type == 10) {
text.put(viewId, value.toString());
}
}
System.out.println("title is: " + text.get(16908310));
System.out.println("info is: " + text.get(16909082));
System.out.println("text is: " + text.get(16908358));
}
} catch (Exception e) {
e.printStackTrace();
}
Este código funcionó bien en un Nexus S con Android 4.0.3. Sin embargo, no probé si funciona en otras versiones de Android. Es muy probable que algunos valores, especialmente el viewId hayan cambiado. Creo que debería haber formas de admitir todas las versiones de Android sin codificar todas las identificaciones posibles, pero esa es la respuesta a otra pregunta ...;)
PD: el valor que está buscando (refiriéndose a como "(3)" en su pregunta original) es el valor "texto".
Para responder a su pregunta: Esto no parece posible en su caso. A continuación te explico por qué.
"El propósito principal de un evento de accesibilidad es exponer suficiente información para que un Servicio de Accesibilidad brinde una retroalimentación significativa al usuario". En casos, como el tuyo:
un servicio de accesibilidad puede necesitar más información contextual que la del evento pay-load. En tales casos, el servicio puede obtener el origen del evento, que es un AccessibilityNodeInfo (instantánea de un estado de Vista) que se puede usar para explorar el contenido de la ventana. Tenga en cuenta que el privilegio para acceder al origen de un evento, y por lo tanto al contenido de la ventana, debe solicitarse explícitamente. (Ver AccessibilityEvent )
Podemos solicitar este privilegio explícitamente configurando metadatos para el servicio en su archivo de manifiesto de Android :
<meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibilityservice" />
Donde podría verse tu archivo xml:
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service
android:accessibilityEventTypes="typeNotificationStateChanged"
android:canRetrieveWindowContent="true"
/>
Solicitamos explícitamente el privilegio para acceder a la fuente de un evento (el contenido de la ventana) y especificamos (mediante accessibilityEventTypes
) los tipos de eventos que este servicio desearía recibir (en su caso, solo typeNotificationStateChanged
). Consulte AccessibilityService para ver más opciones que puede configurar en el archivo xml.
Normalmente (vea más abajo por qué no en este caso), debería ser posible llamar a event.getSource()
y obtener un AccessibilityNodeInfo
y recorrer el contenido de la ventana, ya que "el evento de accesibilidad lo envía la vista superior en el árbol de la vista".
Si bien, parece posible que funcione ahora, una lectura adicional en la documentación de AccessibilityEvent nos dice:
Si un servicio de accesibilidad no ha solicitado recuperar el contenido de la ventana, el evento no contendrá referencia a su origen. También para eventos de tipo TYPE_NOTIFICATION_STATE_CHANGED, la fuente nunca está disponible.
Al parecer, esto es por razones de seguridad ...
Para enganchar a cómo extraer el mensaje de la notificación, ya sea desde notification o notification.contentView / notification.contentIntent. No creo que puedas.
ContentView es una RemoteView y no proporciona ningún captador para obtener información sobre la notificación.
De manera similar, contentIntent es un PendingIntent , que no proporciona ningún captador para obtener información sobre la intención que se lanzará cuando se haga clic en la notificación. (Es decir, no puede obtener los extras de la intención, por ejemplo).
Además, dado que no ha proporcionado ninguna información sobre por qué le gustaría obtener la descripción de la notificación y qué le gustaría hacer con ella, realmente no puedo proporcionarle una solución para resolverlo.
Si has probado la solución de TomTasche en Android 4.2.2, notarás que no funciona. Ampliando su respuesta y el comentario del usuario 1060919 ... Aquí hay un ejemplo que funciona para 4.2.2:
Notification notification = (Notification) event.getParcelableData();
RemoteViews views = notification.contentView;
Class secretClass = views.getClass();
try {
Map<Integer, String> text = new HashMap<Integer, String>();
Field outerField = secretClass.getDeclaredField("mActions");
outerField.setAccessible(true);
ArrayList<Object> actions = (ArrayList<Object>) outerField.get(views);
for (Object action : actions) {
Field innerFields[] = action.getClass().getDeclaredFields();
Field innerFieldsSuper[] = action.getClass().getSuperclass().getDeclaredFields();
Object value = null;
Integer type = null;
Integer viewId = null;
for (Field field : innerFields) {
field.setAccessible(true);
if (field.getName().equals("value")) {
value = field.get(action);
} else if (field.getName().equals("type")) {
type = field.getInt(action);
}
}
for (Field field : innerFieldsSuper) {
field.setAccessible(true);
if (field.getName().equals("viewId")) {
viewId = field.getInt(action);
}
}
if (value != null && type != null && viewId != null && (type == 9 || type == 10)) {
text.put(viewId, value.toString());
}
}
System.out.println("title is: " + text.get(16908310));
System.out.println("info is: " + text.get(16909082));
System.out.println("text is: " + text.get(16908358));
} catch (Exception e) {
e.printStackTrace();
}
También puede consultar los campos privados del objeto Notificación para obtener información adicional,
Me gusta contentTitle, contentText y tickerText
Aquí está el código relevante
Notification notification = (Notification) event.getParcelableData();
getObjectProperty(notification, "contentTitle")
getObjectProperty(notification, "tickerText");
getObjectProperty(notification, "contentText");
Aquí está el método getObjectProperty
public static Object getObjectProperty(Object object, String propertyName) {
try {
Field f = object.getClass().getDeclaredField(propertyName);
f.setAccessible(true);
return f.get(object);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}