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