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.