Catch toast event (from any app) and get toast message

accessibility-api, android, android-4.0-ice-cream-sandwich, toast

Solution

TYPE_NOTIFICATION_STATE_CHANGED generally refers to NotificationManager and icons placed in the status bar. Nonetheless, the below code should help shed some light as to the origin of a Toast message. On Android 4.0.4 ICS a Toast has a class of `android.widget.Toast` so `getClassName` should do the trick.

For what its worth, the change seems to have been made in Android 4.0.3 to add and use the following method in `Toast.TN`

private void trySendAccessibilityEvent() {
        AccessibilityManager accessibilityManager =
                AccessibilityManager.getInstance(mView.getContext());
        if (!accessibilityManager.isEnabled()) {
            return;
        }
        // treat toasts as notifications since they are used to
        // announce a transient piece of information to the user
        AccessibilityEvent event = AccessibilityEvent.obtain(
                AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
        event.setClassName(getClass().getName());
        event.setPackageName(mView.getContext().getPackageName());
        mView.dispatchPopulateAccessibilityEvent(event);
        accessibilityManager.sendAccessibilityEvent(event);
    }

You can see the `Toast` class in all versions of Android here.

private final String getEventType(AccessibilityEvent event) {
    switch (event.getEventType()) {
        case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:
            return "TYPE_NOTIFICATION_STATE_CHANGED";
        case AccessibilityEvent.TYPE_VIEW_CLICKED:
            return "TYPE_VIEW_CLICKED";
        case AccessibilityEvent.TYPE_VIEW_FOCUSED:
            return "TYPE_VIEW_FOCUSED";
        case AccessibilityEvent.TYPE_VIEW_LONG_CLICKED:
            return "TYPE_VIEW_LONG_CLICKED";
        case AccessibilityEvent.TYPE_VIEW_SELECTED:
            return "TYPE_VIEW_SELECTED";
        case AccessibilityEvent.TYPE_VIEW_SCROLLED:
            return "TYPE_VIEW_SCROLLED";
        case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT:
            return "TYPE_VIEW_HOVER_EXIT";
        case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER:
            return "TYPE_VIEW_HOVER_ENTER";
        case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START:
            return "TYPE_TOUCH_EXPLORATION_GESTURE_START";
        case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END:
            return "TYPE_TOUCH_EXPLORATION_GESTURE_END";
        case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
            return "TYPE_WINDOW_STATE_CHANGED";
        case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
            return "TYPE_WINDOW_CONTENT_CHANGED";
        case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED:
            return "TYPE_VIEW_TEXT_SELECTION_CHANGED";
        case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
            return "TYPE_VIEW_TEXT_CHANGED";
    }

    return "default";
}

private final String getEventText(AccessibilityEvent event) {
    StringBuilder sb = new StringBuilder();
    for (CharSequence s : event.getText()) {
        sb.append(s);
        sb.append('\n');
    }
    return sb.toString();
}

@Override
public void onAccessibilityEvent(AccessibilityEvent event)
{
    Log.v(TAG, String.format(
        "onAccessibilityEvent: [type] %s [class] %s [package] %s [time] 
        %s [fullscreen] %s [text] %s", getEventType(event), event.getClassName(),
        event.getPackageName(), event.getEventTime(), Boolean.toString(
        event.isFullScreen()), getEventText(event)));

    if (android.os.Build.VERSION.SDK_INT >= 14)
        Log.v(TAG, "Window ID: " + Integer.toString(event.getWindowId()) + ".");
}

private void setServiceInfo(int feedbackType)
{
    final AccessibilityServiceInfo info = new AccessibilityServiceInfo();
    // We are interested in all types of accessibility events.
    info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
    // We want to provide specific type of feedback.
    info.feedbackType = feedbackType;
    // We want to receive events in a certain interval.
    // info.notificationTimeout = EVENT_NOTIFICATION_TIMEOUT_MILLIS;
    // We want to receive accessibility events only from certain packages.
    // info.packageNames = PACKAGE_NAMES;
    setServiceInfo(info);
}

private boolean isInfrastructureInitialized = false;

@Override
public void onServiceConnected()
{   
    if (isInfrastructureInitialized) return;

    // Claim the events with which to listen to.
    setServiceInfo(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);

    // We are in an initialized state now.
    isInfrastructureInitialized = true;
}

Source: personal experience.

Problem

As I understood this is possible, from here Detecting toast messages But I am unable to catch any event with code snippet from the link. MyAccessibilityService.java ``` package com.test.toasts2; import android.accessibilityservice.AccessibilityService; import android.accessibilityservice.AccessibilityServiceInfo; import android.app.Notification; import android.os.Parcelable; import android.view.accessibility.AccessibilityEvent; import android.widget.Toast; public class MyAccessibilityService extends AccessibilityService { @Override public void onAccessibilityEvent(AccessibilityEvent event) { System.out.println("event catched"); Toast.makeText(this, "catched " + "!", Toast.LENGTH_SHORT).show(); if(event.getEventType() != AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) return; // event is not a notification String sourcePackageName = (String)event.getPackageName(); Parcelable parcelable = event.getParcelableData(); if(parcelable instanceof Notification){ // Statusbar Notification } else{ // something else, e.g. a Toast message String log = "Message: "+event.getText().get(0)+" [Source: "+sourcePackageName+"]"; System.out.println(log); // write `log` to file... } } @Override public void onInterrupt() { // TODO Auto-generated method stub } @Override protected void onServiceConnected() { // TODO Auto-generated method stub super.onServiceConnected(); AccessibilityServiceInfo info = new AccessibilityServiceInfo(); info.feedbackType = AccessibilityServiceInfo.DEFAULT; setServiceInfo(info); } } ``` AndroidManifest.xml ``` <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.test.toasts2" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="15" /> <application> <service android:name=".MyAccessibilityService" android:label="label"> <intent-filter> <action android:name="android.accessibilityservice.AccessibilityService" /> </intent-filter> </service> </application> </manifest> ``` Seems like this service is simply not started. What I am doing wrong? Why I am doing this: I am installing many shortcuts on the stock launcher from my app. I am having the problem that theese shortcuts are placed one over another in one cell (even Sleep 500 did not help). So I am finding a way to install them one by another. But how to know when shortcut was successfully installed? I have found only a message that ics launcher shows to user.

Original source

Related problems