-->

Retrieve text from a RemoteViews Object

2019-03-20 17:24发布

问题:

I need to retrieve some text from a RemoteViews object. It is possible for me to get the LayoutId, but I have no idea how to retrieve text from a TextView that is in this RemoteView (namely a notification).

Also the RemoteView only contains setters, but no getters, so I guess I have to use the LayoutId (somehow).

Can you help me with that? Thanks!

/edit: The reason why I am asking this, is because I have an AccessibilityService that retrieves the notification. Therefore this is the only way of retrieving the value.

/edit2: I use this code for receiving the notification:

@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();
        }
        if (!(parcel instanceof Notification)) {
            return;
        }
        final Notification notification = (Notification) parcel;
        doMoreStuff();

    }
}

With the notification object I have access to a RemoteViews (notification.contentView) and to a PendingIntent (notification.contentIntent). To get the layoutId, I can call contentView.getLayoutId()

回答1:

I proposed a similar solution here that also uses reflection to solve the problem, but in a more approachable fashion. This is my solution. In this context, the RemoteViews came from a Notification, so the first three lines can probably be ignored if you already have access to the RemoteViews object. The link on the page provides a much more detailed explanation of what is actually going on. I hope this will help anyone with a similar problem.

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


回答2:

Taken from Extract notification text from parcelable, contentView or contentIntent :

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


回答3:

CommonsWare in this question says:

... App widgets are write-only: you can push data to them, but you cannot read them. Instead, when you update your app widget with new text, you will need to store that text somewhere, perhaps in a file.

His answer seems to be logical.



回答4:

If you are targeting on Android 19+, you can use the following code for getting title/text from a Notification object without using any private APIs.

Notification noty = ...;
Bundle extras = noty.extras;
if (extras != null) {
  String title = extras.getString(Notification.EXTRA_TITLE);
  String text = extras.getString(Notification.EXTRA_TEXT);
}