从parcelable,contentView或contentIntent提取通知文本

所以我得到了我的AccessibilityService使用下面的代码:

@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(); } } } 

阅读创build通知时显示的文本(1)可以正常工作

在这里输入图像说明

唯一的问题是,我还需要用户打开通知栏时显示的(3)的值。 (2)对我来说并不重要,但是知道如何读出它是很好的。 正如你可能知道的,所有的值都可以不同。

在这里输入图像说明

那么,我怎样才能读出(3) ? 我怀疑这是不可能的,但我的notificationList似乎只有一个条目(至less只有一个吐司显示)。

非常感谢!

/编辑:我可以提取通知包裹

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

但是,我不知道如何从notificationnotification.contentView / notification.contentIntent提取通知的消息。

有任何想法吗?

/编辑:澄清这里问的是什么:我怎样才能读出(3)

我浪费了最后几天的时间去想办法去做你(也是,我也一样)想做的事情。 通过两次查看RemoteViews的整个源代码后,我认为完成这个任务的唯一方法是使用古老的,丑陋的和黑客的Javareflection。

这里是:

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

此代码在Android 4.0.3的Nexus S上运行良好。 不过,我没有testing它是否适用于其他版本的Android。 一些值,特别是viewId很可能会改变。 我认为应该有方法来支持所有版本的Android,而不用硬编码所有可能的ID,但是这是另一个问题的答案…;)

PS:你正在寻找的价值(在你原来的问题中被称为“(3)”)是“文字”价值。

上周我花了一个类似的问题,可以提出类似Tom Tache(使用reflection)的解决scheme,但是可能会更容易理解。 如果可能的话,以下方法将梳理出现的任何文本的通知,并返回ArrayList中的文本。

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

因为这可能看起来有点像黑魔法,让我更详细地解释。 我们首先从通知本身拉取RemoteViews对象。 这代表实际通知内的观点。 为了访问这些视图,我们要么必须膨胀RemoteViews对象(这将只在存在活动上下文时才起作用),要么使用reflection。 反思将会在任何情况下都起作用,并且是在这里使用的方法。

如果您在此查看RemoteView的源代码,您将看到其中一个私有成员是Action对象的ArrayList。 这代表了夸大后的意见。 例如,在创build视图之后,将在每个属于层次结构一部分的TextView的某个点处调用setText()以分配正确的string。 我们所做的就是获得这个动作列表,并遍历它。 行动定义如下:

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

RemoteViews中定义了许多具体的Action子类。 我们感兴趣的是ReflectionAction,定义如下:

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

此操作用于为视图分配值。 这个类的单个实例可能具有值{“setText”,10,“textview的内容”}。 因此,我们只对作为“ReflectionAction”对象的mActions的元素感兴趣,并以某种方式分配文本。 我们可以通过检查Action中的“TAG”字段来告诉一个特定的“Action”是一个“ReflectionAction”,它始终是从包裹中读取的第一个值。 2的标签代表ReflectionAction对象。

之后,我们只需根据书写顺序从包裹中读取值(如果您好奇,请参阅源代码链接)。 我们find任何用setText()设置的string并将其保存在列表中。 (setTime()也包含在通知时间的情况下,否则可以安全删除)。

虽然我通常反对在这种情况下使用反思,但有些时候是必要的。 除非有可用的活动环境,否则“标准”方法将无法正常工作,所以这是唯一的select。

如果您不想使用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()来确保充气视图对应于通知的一个。

然后,你可以检索一些或多或less的标准文本浏览

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

为了我自己的目的(这是窥探我自己的包发布的通知),我select遍历localView下的整个层次结构,并收集所有现有的TextViews。

添加Remi的答案,以确定不同的通知types和提取数据,使用下面的代码。

 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状态的快照)的事件源。 请注意,访问事件源的权限,即窗口内容,必须明确要求。 (请参阅AccessibilityEvent )

我们可以通过在Android清单文件中为服务设置元数据来显式请求这个特权:

 <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibilityservice" /> 

你的XML文件可能看起来像这样:

 <?xml version="1.0" encoding="utf-8"?> <accessibility-service android:accessibilityEventTypes="typeNotificationStateChanged" android:canRetrieveWindowContent="true" /> 

我们明确要求特权访问事件源(窗口内容),并指定(使用accessibilityEventTypes )这个服务想要接收的事件types(在你的情况下只有typeNotificationStateChanged )。 请参阅AccessibilityService以获取更多您可以在xml文件中设置的选项。

通常情况下 (见下面为什么不在这种情况下),应该可以调用event.getSource()并获取AccessibilityNodeInfo并遍历窗口内容,因为“可视性事件是由视图树中最顶层的视图发送的”。

虽然现在看起来有可能工作,但进一步阅读AccessibilityEvent文档告诉我们:

如果辅助function服务没有请求检索窗口内容,则该事件将不包含对其源的引用。 对于types为TYPE_NOTIFICATION_STATE_CHANGED的事件,源永远不可用。

显然,这是因为安全的目的…


钩住如何从notification或notification.contentView / notification.contentIntent中提取通知消息。 我不认为你可以。

contentView是一个RemoteView ,不提供任何获取器来获取有关通知的信息。

同样,contentIntent是一个PendingIntent ,它不提供任何获取器来获取有关点击通知时将启动的意图的信息。 (即你不能从意图获得额外的例子)。

此外,由于您没有提供任何信息来说明您为什么要获取通知的描述以及您想要如何处理这些信息,所以我无法为您提供解决scheme。

如果你在Android 4.2.2上试过TomTasche的解决scheme,你会注意到它不起作用。 扩展他的回答,和user1060919的评论…这是一个适用于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(); } 

Achep的AcDisplay项目提供了一个解决scheme,从Extractor.java中检查代码

您也可以查看通知对象的私人字段的一些额外的信息,
contentTitle,contentTexttickerText

这里是相关的代码

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