PageRenderTime 25ms CodeModel.GetById 15ms app.highlight 7ms RepoModel.GetById 1ms app.codeStats 1ms

/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
 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}