Извлечение текста извещных, contentView или contentIntent

Поэтому я получил свою службу AccessibilityService со следующим кодом:

@Override public void onAccessibilityEvent(AccessibilityEvent event) { if (event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) { List notificationList = event.getText(); for (int i = 0; i < notificationList.size(); i++) { Toast.makeText(this.getApplicationContext(), notificationList.get(i), 1).show(); } } } 

Он отлично работает для чтения текста, отображаемого при создании notifcation (1) .

введите описание изображения здесь

Единственная проблема – мне также нужно значение (3), которое отображается, когда пользователь открывает панель уведомлений. (2) для меня не важно, но было бы неплохо узнать, как его прочитать. Как вы, наверное, знаете, все значения могут быть разными.

введите описание изображения здесь

Итак, как я могу прочитать (3) ? Я сомневаюсь, что это невозможно, но в моем notificationList есть только одна запись (по крайней мере, показан только один тост).

Большое спасибо!

/ edit: я мог бы извлечь

 if (!(parcel instanceof Notification)) { return; } final Notification notification = (Notification) parcel; 

Однако я не знаю, как извлечь сообщение notifcation из notification или notification.contentView / notification.contentIntent .

Есть идеи?

/ edit: Чтобы уточнить, что здесь задается: как я могу прочитать (3) ?

Я потратил несколько часов в последние дни на то, чтобы выяснить, как вы (и, кстати, мне тоже, кстати) захотите. Просматривая весь источник RemoteViews дважды, я решил, что единственный способ выполнить эту задачу – это старый добрый, уродливый и взломанный Java Reflections.

Вот:

  Notification notification = (Notification) event.getParcelableData(); RemoteViews views = notification.contentView; Class secretClass = views.getClass(); try { Map text = new HashMap(); Field outerFields[] = secretClass.getDeclaredFields(); for (int i = 0; i < outerFields.length; i++) { if (!outerFields[i].getName().equals("mActions")) continue; outerFields[i].setAccessible(true); ArrayList actions = (ArrayList) 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(); } 

Этот код отлично работал на Nexus S с Android 4.0.3. Однако я не тестировал, работает ли он на других версиях Android. Очень вероятно, что некоторые значения, особенно viewId, изменились. Я думаю, что должны поддерживаться все версии Android без жесткого кодирования всех возможных идентификаторов, но это ответ на другой вопрос …;)

PS: Значение, которое вы ищете (обозначение «(3)» в исходном вопросе) – это значение «текст».

Я провел последнюю неделю, работая с аналогичной проблемой, и могу предложить решение, подобное Tom Tache (используя reflection), но может быть немного легче понять. Следующий метод будет расчесывать уведомление для любого присутствующего текста и возвращать этот текст в ArrayList, если это возможно.

 public static List 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 text = new ArrayList(); try { Field field = views.getClass().getDeclaredField("mActions"); field.setAccessible(true); @SuppressWarnings("unchecked") ArrayList actions = (ArrayList) 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; } 

Поскольку это, вероятно, немного напоминает черную магию, позвольте мне объяснить более подробно. Сначала мы вытаскиваем объект RemoteViews из самого уведомления. Это отражает мнения в реальном уведомлении. Чтобы получить доступ к этим представлениям, нам нужно либо раздуть объект RemoteViews (который будет работать только при наличии контекста активности), либо использовать reflection. Отражение будет работать в любом случае и является используемым здесь методом.

Если вы изучите источник для RemoteViews здесь , вы увидите, что один из частных членов является объектом ArrayList of Action. Это представляет собой то, что будет сделано для взглядов после того, как они будут завышены. Например, после создания представлений setText () будет вызываться в какой-то момент на каждом TextView, который является частью иерархии для назначения правильных строк. Мы получаем доступ к этому списку действий и итерации через него. Действие определяется следующим образом:

 private abstract static class Action implements Parcelable { ... } 

В RemoteViews существует ряд конкретных подclassов Action. Тот, который нас интересует, называется ReflectionAction и определяется следующим образом:

 private class ReflectionAction extends Action { String methodName; int type; Object value; } 

Это действие используется для назначения значений представлениям. У одного экземпляра этого classа, вероятно, будут значения {“setText”, 10, “content textview”}. Поэтому нас интересуют только элементы mActions, которые являются объектами ReflectionAction и каким-то образом назначают текст. Мы можем сказать, что конкретное «действие» – это «ReflectionAction», исследуя поле «TAG» внутри Action, которое всегда является первым значением, которое нужно прочитать из посылки. TAG 2 представляют объекты ReflectionAction.

После этого нам просто нужно прочитать значения из посылки в соответствии с порядком, в котором они были написаны (см. Ссылку источника, если вам интересно). Мы найдем любую строку, заданную с помощью setText (), и сохраним ее в списке. (setTime () также включен, если требуется время уведомления. Если нет, эти строки можно безопасно удалить.)

Хотя я обычно выступаю против использования рефлексии в подобных случаях, бывают случаи, когда это необходимо. Если не существует контекста активности, «стандартный» метод не будет работать должным образом, так что это единственный вариант.

Есть другой способ, если вы не хотите использовать reflection: вместо того, чтобы переходить список «действий», перечисленных в объекте RemoteViews, вы можете «переиграть» их в 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); 

Обратите внимание, что вы используете remoteView.getLayoutId (), чтобы убедиться, что раздутое представление соответствует одному из уведомлений.

Затем вы можете получить некоторые из более или менее стандартных текстовых просмотров с помощью

  TextView tv = (TextView) localView.findViewById(android.R.id.title); Log.d("blah", tv.getText()); 

Для моей собственной цели (которая должна отслеживать уведомления, отправленные моим собственным пакетом), я решил пройти всю иерархию в localView и собрать все существующие TextView.

Добавив ответ Реми, чтобы определить различные типы уведомлений и извлечь данные, используйте приведенный ниже код.

 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); 

Чтобы ответить на ваш вопрос: это не представляется возможным в вашем случае. Ниже я объясню, почему.

«Основная цель события доступности – предоставить достаточную информацию для AccessibilityService для предоставления значимой обратной связи пользователю». В таких случаях, как ваш:

службе доступности может потребоваться дополнительная контекстная информация, а другая – в случае оплаты. В таких случаях служба может получить источник событий, который является AccessibilityNodeInfo (моментальный снимок состояния View), который может использоваться для изучения содержимого windows. Обратите внимание, что привилегия для доступа к источнику события, а именно к содержимому windows, должна быть явно запрошена. (См. AccessibilityEvent )

Мы можем запросить эту привилегию явно, установив метаданные для службы в вашем файле манифеста Android :

  

Где может выглядеть ваш xml-файл:

   

Мы явно запрашиваем привилегию для доступа к источнику события (содержимое windows), и мы указываем (используя accessibilityEventTypes ) типы событий, которые эта служба хотела бы получить (в вашем случае только typeNotificationStateChanged ). См. AccessibilityService для получения дополнительных параметров, которые вы можете установить в XML-файле.

Обычно (см. Ниже, почему бы и нет в этом случае), должно быть возможно вызвать event.getSource() и получить AccessibilityNodeInfo и пройти через содержимое windows, так как «событие доступности отправляется самым верхним видом в дереве представлений».

Хотя, кажется, теперь можно заставить его работать, дальнейшее чтение в документации AccessibilityEvent говорит нам:

Если служба доступности не запрашивает получение содержимого windows, событие не будет содержать ссылку на его источник. Также для событий типа TYPE_NOTIFICATION_STATE_CHANGED источник никогда не доступен.

По-видимому, это из-за целей безопасности …


Чтобы подключиться к тому, как извлечь сообщение notifcation, либо из уведомления или уведомления .contentView / notification.contentIntent. Я не думаю, что ты можешь.

ContentView является RemoteView и не предоставляет каких-либо получателей для получения информации об уведомлении.

Точно так же contentIntent является PendingIntent , который не предоставляет никаких геттеров для получения информации о намерении, которое будет запущено при нажатии на уведомление. (т. е. вы не можете получить дополнительные функции от намерения, например).

Кроме того, поскольку вы не указали какую-либо информацию о том, почему вы хотели бы получить описание уведомления и что вы хотели бы сделать с ним, я не могу предоставить вам решение для решения этой проблемы.

Если вы попробовали решение TomTasche на Android 4.2.2, вы заметите, что оно не работает. Расширение его ответа и комментарий пользователя1060919 … Вот пример, который работает для 4.2.2:

 Notification notification = (Notification) event.getParcelableData(); RemoteViews views = notification.contentView; Class secretClass = views.getClass(); try { Map text = new HashMap(); Field outerField = secretClass.getDeclaredField("mActions"); outerField.setAccessible(true); ArrayList actions = (ArrayList) 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(); } 

Проект AcDis Achep предоставил решение, проверьте код из Extractor.java

Вы также можете посмотреть частные поля объекта Notification для получения дополнительной информации,
Подобно contentTitle, contentText и tickerText

Вот соответствующий код

 Notification notification = (Notification) event.getParcelableData(); getObjectProperty(notification, "contentTitle") getObjectProperty(notification, "tickerText"); getObjectProperty(notification, "contentText"); 

Вот метод 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; } 
  • Как использовать Notification.Builder
  • Обнаруживать новое уведомление для Android
  • Проверить доступ к уведомлениям с помощью NotificationListenerService
  • проблема с уведомлением о намерении андроида
  • Уведомление о восстановлении задачи, а не конкретной деятельности?
  • я хочу показать уведомление в 8:00 ежедневно
  • Как нарисовать представление поверх всего?
  • Как выполнить метод, нажав на уведомление
  • Как воспроизвести звук уведомления об андроиде
  • Как реализовать устаревшие методы уведомления
  • Android: удалить уведомление из панели уведомлений
  • Давайте будем гением компьютера.