/documentation/ClockBackTutorial/ClockBack0/src/com/google/android/marvin/clockback/ClockBackService.java

http://eyes-free.googlecode.com/ · Java · 235 lines · 101 code · 41 blank · 93 comment · 7 complexity · a8e89f605bfcc3bfed106686d5958233 MD5 · raw file

  1. /*
  2. * Copyright (C) 2010 The Android Open Source Project
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.google.android.marvin.clockback;
  17. import android.accessibilityservice.AccessibilityService;
  18. import android.accessibilityservice.AccessibilityServiceInfo;
  19. import android.content.Context;
  20. import android.content.Intent;
  21. import android.os.Handler;
  22. import android.os.Message;
  23. import android.speech.tts.TextToSpeech;
  24. import android.util.Log;
  25. import android.view.accessibility.AccessibilityEvent;
  26. import java.util.List;
  27. /**
  28. * This class is an {@link AccessibilityService} that provides custom feedback
  29. * for the Clock application that comes by default with Android devices. It
  30. * demonstrates the following key features of the Android accessibility APIs:
  31. * <ol>
  32. * <li>
  33. * Simple demonstration of how to use the accessibility APIs.
  34. * </li>
  35. * <li>
  36. * Hands-on example of various ways to utilize the accessibility API for
  37. * providing alternative and complementary feedback.
  38. * </li>
  39. * <li>
  40. * Providing application specific feedback - the service handles only
  41. * accessibility events from the clock application.
  42. * </li>
  43. * <li>
  44. * Providing dynamic, context-dependent feedback - feedback type changes
  45. * depending on the ringer state.</li>
  46. * <li>
  47. * Application specific UI enhancement - application domain knowledge is
  48. * utilized to enhance the provided feedback.
  49. * </li>
  50. * </ol>
  51. *
  52. * @author svetoslavganov@google.com (Svetoslav R. Ganov)
  53. */
  54. public class ClockBackService extends AccessibilityService {
  55. /** Tag for logging from this service */
  56. private static final String LOG_TAG = "ClockBackService";
  57. // fields for configuring how the system handles this accessibility service
  58. /** Minimal timeout between accessibility events we want to receive */
  59. private static final int EVENT_NOTIFICATION_TIMEOUT_MILLIS = 80;
  60. /** Packages we are interested in */
  61. // This works with AlarmClock and Clock whose package name changes in different releases
  62. private static final String[] PACKAGE_NAMES = new String[] {
  63. "com.android.alarmclock", "com.google.android.deskclock", "com.android.deskclock"
  64. };
  65. // message types we are passing around
  66. /** Speak */
  67. private static final int WHAT_SPEAK = 1;
  68. /** Stop speaking */
  69. private static final int WHAT_STOP_SPEAK = 2;
  70. /** Start the TTS service */
  71. private static final int WHAT_START_TTS = 3;
  72. /** Stop the TTS service */
  73. private static final int WHAT_SHUTDOWN_TTS = 4;
  74. // speech related constants
  75. /**
  76. * The queuing mode we are using - interrupt a spoken utterance before
  77. * speaking another one
  78. */
  79. private static final int QUEUING_MODE_INTERRUPT = 2;
  80. /** The empty string constant */
  81. private static final String SPACE = " ";
  82. // auxiliary fields
  83. /**
  84. * Handle to this service to enable inner classes to access the {@link Context}
  85. */
  86. private Context mContext;
  87. /** Reusable instance for building utterances */
  88. private final StringBuilder mUtterance = new StringBuilder();
  89. // feedback providing services
  90. /** The {@link TextToSpeech} used for speaking */
  91. private TextToSpeech mTts;
  92. /** Flag if the infrastructure is initialized */
  93. private boolean isInfrastructureInitialized;
  94. /** {@link Handler} for executing messages on the service main thread */
  95. Handler mHandler = new Handler() {
  96. @Override
  97. public void handleMessage(Message message) {
  98. switch (message.what) {
  99. case WHAT_SPEAK:
  100. String utterance = (String) message.obj;
  101. mTts.speak(utterance, QUEUING_MODE_INTERRUPT, null);
  102. return;
  103. case WHAT_STOP_SPEAK:
  104. mTts.stop();
  105. return;
  106. case WHAT_START_TTS:
  107. mTts = new TextToSpeech(mContext, null);
  108. return;
  109. case WHAT_SHUTDOWN_TTS:
  110. mTts.shutdown();
  111. return;
  112. }
  113. }
  114. };
  115. @Override
  116. public void onServiceConnected() {
  117. if (isInfrastructureInitialized) {
  118. return;
  119. }
  120. mContext = this;
  121. // send a message to start the TTS
  122. mHandler.sendEmptyMessage(WHAT_START_TTS);
  123. setServiceInfo(AccessibilityServiceInfo.FEEDBACK_SPOKEN);
  124. // we are in an initialized state now
  125. isInfrastructureInitialized = true;
  126. }
  127. @Override
  128. public boolean onUnbind(Intent intent) {
  129. if (isInfrastructureInitialized) {
  130. // stop the TTS service
  131. mHandler.sendEmptyMessage(WHAT_SHUTDOWN_TTS);
  132. // we are not in an initialized state anymore
  133. isInfrastructureInitialized = false;
  134. }
  135. return false;
  136. }
  137. /**
  138. * Sets the {@link AccessibilityServiceInfo} which informs the system how to
  139. * handle this {@link AccessibilityService}.
  140. *
  141. * @param feedbackType The type of feedback this service will provide. </p>
  142. * Note: The feedbackType parameter is an bitwise or of all
  143. * feedback types this service would like to provide.
  144. */
  145. private void setServiceInfo(int feedbackType) {
  146. AccessibilityServiceInfo info = new AccessibilityServiceInfo();
  147. // we are interested in all types of accessibility events
  148. info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
  149. // we want to provide specific type of feedback
  150. info.feedbackType = feedbackType;
  151. // we want to receive events in a certain interval
  152. info.notificationTimeout = EVENT_NOTIFICATION_TIMEOUT_MILLIS;
  153. // we want to receive accessibility events only from certain packages
  154. info.packageNames = PACKAGE_NAMES;
  155. setServiceInfo(info);
  156. }
  157. @Override
  158. public void onAccessibilityEvent(AccessibilityEvent event) {
  159. Log.i(LOG_TAG, event.toString());
  160. mHandler.obtainMessage(WHAT_SPEAK, formatUtterance(event)).sendToTarget();
  161. }
  162. @Override
  163. public void onInterrupt() {
  164. mHandler.obtainMessage(WHAT_STOP_SPEAK);
  165. }
  166. /**
  167. * Formats an utterance from an {@link AccessibilityEvent}.
  168. *
  169. * @param event The event from which to format an utterance.
  170. * @return The formatted utterance.
  171. */
  172. private String formatUtterance(AccessibilityEvent event) {
  173. StringBuilder utterance = mUtterance;
  174. // clear the utterance before appending the formatted text
  175. utterance.delete(0, utterance.length());
  176. List<CharSequence> eventText = event.getText();
  177. // We try to get the event text if such
  178. if (!eventText.isEmpty()) {
  179. for (CharSequence subText : eventText) {
  180. utterance.append(subText);
  181. utterance.append(SPACE);
  182. }
  183. return utterance.toString();
  184. }
  185. // There is no event text but we try to get the content description which is
  186. // an optional attribute for describing a view (typically used with ImageView)
  187. CharSequence contentDescription = event.getContentDescription();
  188. if (contentDescription != null) {
  189. utterance.append(contentDescription);
  190. return utterance.toString();
  191. }
  192. return utterance.toString();
  193. }
  194. }