* * Note: This code sample will work only on devices shipped with the default Clock * application. If you are running Android 1.6 of Android 2.0 you should enable first * ClockBack and then TalkBack since in these releases accessibility services are * notified in the order of registration. * *
*/ public class Test extends AccessibilityService { /** Tag for logging from this service. */ private static final String LOG_TAG = "ClockBackService"; // Fields for configuring how the system handles this accessibility service. /** Minimal timeout between accessibility events we want to receive. */ private static final int EVENT_NOTIFICATION_TIMEOUT_MILLIS = 80; /** Packages we are interested in. ** * Note: This code sample will work only on devices shipped with the * default Clock application. * *
*/ // This works with AlarmClock and Clock whose package name changes in different releases private static final String[] PACKAGE_NAMES = new String[] { "com.android.alarmclock", "com.google.android.deskclock", "com.android.deskclock" }; // Message types we are passing around. /** Speak. */ private static final int MESSAGE_SPEAK = 1; /** Stop speaking. */ private static final int MESSAGE_STOP_SPEAK = 2; /** Start the TTS service. */ private static final int MESSAGE_START_TTS = 3; /** Stop the TTS service. */ private static final int MESSAGE_SHUTDOWN_TTS = 4; /** Play an earcon. */ private static final int MESSAGE_PLAY_EARCON = 5; /** Stop playing an earcon. */ private static final int MESSAGE_STOP_PLAY_EARCON = 6; /** Vibrate a pattern. */ private static final int MESSAGE_VIBRATE = 7; /** Stop vibrating. */ private static final int MESSAGE_STOP_VIBRATE = 8; // Screen state broadcast related constants. /** Feedback mapping index used as a key for the screen-on broadcast. */ private static final int INDEX_SCREEN_ON = 0x00000100; /** Feedback mapping index used as a key for the screen-off broadcast. */ private static final int INDEX_SCREEN_OFF = 0x00000200; // Ringer mode change related constants. /** Feedback mapping index used as a key for normal ringer mode. */ private static final int INDEX_RINGER_NORMAL = 0x00000400; /** Feedback mapping index used as a key for vibration ringer mode. */ private static final int INDEX_RINGER_VIBRATE = 0x00000800; /** Feedback mapping index used as a key for silent ringer mode. */ private static final int INDEX_RINGER_SILENT = 0x00001000; // Speech related constants. /** * The queuing mode we are using - interrupt a spoken utterance before * speaking another one. */ private static final int QUEUING_MODE_INTERRUPT = 2; /** The space string constant. */ private static final String SPACE = " "; /** * The class name of the number picker buttons with no text we want to * announce in the Clock application. */ private static final String CLASS_NAME_NUMBER_PICKER_BUTTON_CLOCK = "android.widget.NumberPickerButton"; /** * The class name of the number picker buttons with no text we want to * announce in the AlarmClock application. */ private static final String CLASS_NAME_NUMBER_PICKER_BUTTON_ALARM_CLOCK = "com.android.internal.widget.NumberPickerButton"; /** * The class name of the edit text box for hours and minutes we want to * better announce. */ private static final String CLASS_NAME_EDIT_TEXT = "android.widget.EditText"; /** * Mapping from integer to string resource id where the keys are generated * from the {@link AccessibilityEvent#getText()}, * {@link AccessibilityEvent#getItemCount()} and * {@link AccessibilityEvent#getCurrentItemIndex()} properties. *
* Note: In general, computing these mappings includes the widget position on
* the screen. This is fragile and should be used as a last resort since
* changing the layout could potentially change the widget position. This is
* a workaround since the widgets of interest are image buttons that do not
* have contentDescription attribute set (plus/minus buttons) or no other
* information in the accessibility event is available to distinguish them
* aside of their positions on the screen (hour/minute inputs).
* If you are owner of the target application (Clock in this case) you
* should add contentDescription attribute to all image buttons such that a
* screen reader knows how to speak them. For input fields (while not
* applicable for the hour and minute inputs since they are not empty) a
* hint text should be set to enable better announcement.
*
* 1. {@link AudioManager#RINGER_MODE_SILENT}
* Goal: Provide only custom haptic feedback.
* Approach: Take over the haptic feedback by configuring this service to provide
* such and do so. This way the system will not call the default haptic
* feedback service KickBack.
* Take over the audible and spoken feedback by configuring this
* service to provide such feedback but not doing so. This way the system
* will not call the default spoken feedback service TalkBack and the
* default audible feedback service SoundBack.
*
* 2. {@link AudioManager#RINGER_MODE_VIBRATE}
* Goal: Provide custom audible and default haptic feedback.
* Approach: Take over the audible feedback and provide custom one.
* Take over the spoken feedback but do not provide such.
* Let some other service provide haptic feedback (KickBack).
*
* 3. {@link AudioManager#RINGER_MODE_NORMAL}
* Goal: Provide custom spoken, default audible and default haptic feedback.
* Approach: Take over the spoken feedback and provide custom one.
* Let some other services provide audible feedback (SounBack) and haptic
* feedback (KickBack).
*
* Note: The feedbackType parameter is an bitwise or of all * feedback types this service would like to provide. *
*/ private void setServiceInfo(int feedbackType) { 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); } @Override public void onAccessibilityEvent(AccessibilityEvent event) { Log.i(LOG_TAG, mProvidedFeedbackType + " " + event.toString()); // Here we act according to the feedback type we are currently providing. if (mProvidedFeedbackType == AccessibilityServiceInfo.FEEDBACK_SPOKEN) { mHandler.obtainMessage(MESSAGE_SPEAK, formatUtterance(event)).sendToTarget(); } else if (mProvidedFeedbackType == AccessibilityServiceInfo.FEEDBACK_AUDIBLE) { mHandler.obtainMessage(MESSAGE_PLAY_EARCON, event.getEventType(), 0).sendToTarget(); } else if (mProvidedFeedbackType == AccessibilityServiceInfo.FEEDBACK_HAPTIC) { mHandler.obtainMessage(MESSAGE_VIBRATE, event.getEventType(), 0).sendToTarget(); } else { throw new IllegalStateException("Unexpected feedback type " + mProvidedFeedbackType); } } @Override public void onInterrupt() { // Here we act according to the feedback type we are currently providing. if (mProvidedFeedbackType == AccessibilityServiceInfo.FEEDBACK_SPOKEN) { mHandler.obtainMessage(MESSAGE_STOP_SPEAK).sendToTarget(); } else if (mProvidedFeedbackType == AccessibilityServiceInfo.FEEDBACK_AUDIBLE) { mHandler.obtainMessage(MESSAGE_STOP_PLAY_EARCON).sendToTarget(); } else if (mProvidedFeedbackType == AccessibilityServiceInfo.FEEDBACK_HAPTIC) { mHandler.obtainMessage(MESSAGE_STOP_VIBRATE).sendToTarget(); } else { throw new IllegalStateException("Unexpected feedback type " + mProvidedFeedbackType); } } /** * Formats an utterance from an {@link AccessibilityEvent}. * * @param event The event from which to format an utterance. * @return The formatted utterance. */ private String formatUtterance(AccessibilityEvent event) { StringBuilder utterance = mUtterance; // Clear the utterance before appending the formatted text. utterance.setLength(0); List