fiore@0
|
1 /*
|
fiore@0
|
2 CCmI Diagram Editor for Android
|
fiore@0
|
3
|
fiore@0
|
4 Copyright (C) 2012 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
|
fiore@0
|
5
|
fiore@0
|
6 This program is free software: you can redistribute it and/or modify
|
fiore@0
|
7 it under the terms of the GNU General Public License as published by
|
fiore@0
|
8 the Free Software Foundation, either version 3 of the License, or
|
fiore@0
|
9 (at your option) any later version.
|
fiore@0
|
10
|
fiore@0
|
11 This program is distributed in the hope that it will be useful,
|
fiore@0
|
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
|
fiore@0
|
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
fiore@0
|
14 GNU General Public License for more details.
|
fiore@0
|
15
|
fiore@0
|
16 You should have received a copy of the GNU General Public License
|
fiore@0
|
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
|
fiore@0
|
18 */
|
fiore@0
|
19 package uk.ac.qmul.eecs.ccmi.accessibility;
|
fiore@0
|
20
|
fiore@0
|
21 import java.util.EnumMap;
|
fiore@0
|
22 import java.util.Locale;
|
fiore@0
|
23
|
fiore@0
|
24 import uk.ac.qmul.eecs.ccmi.activities.R;
|
fiore@0
|
25
|
fiore@0
|
26 import android.content.Context;
|
fiore@0
|
27 import android.media.AudioManager;
|
fiore@0
|
28 import android.media.SoundPool;
|
fiore@0
|
29 import android.os.Vibrator;
|
fiore@0
|
30 import android.speech.tts.TextToSpeech;
|
fiore@0
|
31 import android.util.Log;
|
fiore@0
|
32
|
fiore@0
|
33 /**
|
fiore@0
|
34 * Encloses all the services needed to implement a custom accessibility system. Text-to-speech synthesis, sound and
|
fiore@0
|
35 * device vibration.
|
fiore@0
|
36 *
|
fiore@0
|
37 */
|
fiore@0
|
38 public class AccessibilityService {
|
fiore@0
|
39 private static final long VIBRATE_TIME = 200;
|
fiore@0
|
40 private static final float SPEECH_RATE = 2.0f;
|
fiore@0
|
41 private TextToSpeech tts;
|
fiore@0
|
42 private SoundPool soundPool;
|
fiore@0
|
43 private AudioManager audioManager;
|
fiore@0
|
44 private Vibrator vibrator;
|
fiore@0
|
45 private Context context;
|
fiore@0
|
46 private EnumMap<SoundEvent,Integer> soundIDs;
|
fiore@0
|
47 private boolean soundLoaded;
|
fiore@0
|
48 /**
|
fiore@0
|
49 *
|
fiore@0
|
50 * A list of events that have a sound associated. The sounds are built in and cannot be modified.
|
fiore@0
|
51 *
|
fiore@0
|
52 *
|
fiore@0
|
53 */
|
fiore@0
|
54 public enum SoundEvent {
|
fiore@0
|
55 V_CANCEL,
|
fiore@0
|
56 V_COLLAPSE,
|
fiore@0
|
57 V_DRAG,
|
fiore@0
|
58 V_EDITING_MODE,
|
fiore@0
|
59 V_ENDOFLIST,
|
fiore@0
|
60 V_ERROR,
|
fiore@0
|
61 V_EXPAND,
|
fiore@0
|
62 V_HOOK_OFF,
|
fiore@0
|
63 V_HOOK_ON,
|
fiore@0
|
64 V_JUMP,
|
fiore@0
|
65 V_MAGNET_OFF,
|
fiore@0
|
66 V_MAGNET_ON,
|
fiore@0
|
67 V_OK
|
fiore@0
|
68 }
|
fiore@0
|
69
|
fiore@0
|
70 private static AccessibilityService singleton;
|
fiore@0
|
71
|
fiore@0
|
72 /**
|
fiore@0
|
73 * Creates an instance of {@code AccessibilityService}. It implements the singleton pattern, therefore
|
fiore@0
|
74 * successive calls to this method will always return the same object.
|
fiore@0
|
75 *
|
fiore@0
|
76 * @param context The context to use. Usually your {@code Application} or {@code Activity} object.
|
fiore@0
|
77 *
|
fiore@0
|
78 * @return a singleton instance of {@code AccessibilityService}
|
fiore@0
|
79 */
|
fiore@0
|
80 public static AccessibilityService getInstance(Context context){
|
fiore@0
|
81 if(singleton == null){
|
fiore@0
|
82 singleton = new AccessibilityService(context);
|
fiore@0
|
83 }
|
fiore@0
|
84 return singleton;
|
fiore@0
|
85 }
|
fiore@0
|
86
|
fiore@0
|
87 /**
|
fiore@0
|
88 * Constructs a new {@code AccessibilityService}.
|
fiore@0
|
89 *
|
fiore@0
|
90 * @param context The current context
|
fiore@0
|
91 */
|
fiore@0
|
92 public AccessibilityService(Context context){
|
fiore@0
|
93 soundPool = new SoundPool(2, AudioManager.STREAM_MUSIC, 0);
|
fiore@0
|
94 this.context = context;
|
fiore@0
|
95 audioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
|
fiore@0
|
96 soundIDs = new EnumMap<SoundEvent,Integer>(SoundEvent.class);
|
fiore@0
|
97 tts = new TextToSpeech(context, new TextToSpeech.OnInitListener(){
|
fiore@0
|
98 @Override
|
fiore@0
|
99 public void onInit(int status) {
|
fiore@0
|
100 if (status == TextToSpeech.SUCCESS) {
|
fiore@0
|
101 int result = tts.setLanguage(Locale.UK);
|
fiore@0
|
102 if (result == TextToSpeech.LANG_MISSING_DATA
|
fiore@0
|
103 || result == TextToSpeech.LANG_NOT_SUPPORTED) {
|
fiore@0
|
104 Log.w("TEXT TO SPEECH", "Text to Speech UK language not supported");
|
fiore@0
|
105 }
|
fiore@0
|
106 }
|
fiore@0
|
107 }
|
fiore@0
|
108 });
|
fiore@0
|
109 tts.setSpeechRate(SPEECH_RATE);
|
fiore@0
|
110 vibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
|
fiore@0
|
111 }
|
fiore@0
|
112
|
fiore@0
|
113 /**
|
fiore@0
|
114 * Loads the built in sound library. This method must be called before any call to
|
fiore@0
|
115 * {@code playSound} or {@code stopSound}.
|
fiore@0
|
116 */
|
fiore@0
|
117 public void loadSounds(){
|
fiore@0
|
118 if(soundLoaded)
|
fiore@0
|
119 return;
|
fiore@0
|
120 soundIDs.put(SoundEvent.V_CANCEL,soundPool.load(context, R.raw.cancel, 1));
|
fiore@0
|
121 soundIDs.put(SoundEvent.V_COLLAPSE,soundPool.load(context, R.raw.collapse, 1));
|
fiore@0
|
122 soundIDs.put(SoundEvent.V_EXPAND,soundPool.load(context, R.raw.expand, 1));
|
fiore@0
|
123 soundIDs.put(SoundEvent.V_ENDOFLIST,soundPool.load(context, R.raw.endoflist, 1));
|
fiore@0
|
124 soundIDs.put(SoundEvent.V_OK,soundPool.load(context, R.raw.ok, 1));
|
fiore@0
|
125 soundIDs.put(SoundEvent.V_DRAG,soundPool.load(context, R.raw.drag, 1));
|
fiore@0
|
126 soundIDs.put(SoundEvent.V_EDITING_MODE,soundPool.load(context, R.raw.editingmode, 1));
|
fiore@0
|
127 soundIDs.put(SoundEvent.V_ERROR,soundPool.load(context, R.raw.error, 1));
|
fiore@0
|
128 soundIDs.put(SoundEvent.V_HOOK_OFF,soundPool.load(context, R.raw.hookoff, 1));
|
fiore@0
|
129 soundIDs.put(SoundEvent.V_HOOK_ON,soundPool.load(context, R.raw.hookon, 1));
|
fiore@0
|
130 soundIDs.put(SoundEvent.V_JUMP,soundPool.load(context, R.raw.jump, 1));
|
fiore@0
|
131 soundIDs.put(SoundEvent.V_MAGNET_OFF,soundPool.load(context, R.raw.magnetoff, 1));
|
fiore@0
|
132 soundLoaded = true;
|
fiore@0
|
133 }
|
fiore@0
|
134
|
fiore@0
|
135 /**
|
fiore@0
|
136 * Plays the sound associated to a {@code SoundEvent}.
|
fiore@0
|
137 *
|
fiore@0
|
138 * @param event the event to play the sound of
|
fiore@0
|
139 * @param loop whether to play the sound in a continuous loop or not
|
fiore@0
|
140 */
|
fiore@0
|
141 public void playSound(SoundEvent event, boolean loop){
|
fiore@0
|
142 if(!soundLoaded)
|
fiore@0
|
143 throw new IllegalStateException("loadSounds() must be called before any call to playSound");
|
fiore@0
|
144 float streamVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
|
fiore@0
|
145 streamVolume = streamVolume / audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
|
fiore@0
|
146 soundPool.play(soundIDs.get(event), streamVolume, streamVolume, 1, loop ? -1 : 0, 1f);
|
fiore@0
|
147 }
|
fiore@0
|
148
|
fiore@0
|
149 /**
|
fiore@0
|
150 * Plays the sound associated to a {@code SoundEvent} once. This call is equivalent to
|
fiore@0
|
151 * {@code playSound(event,false)}.
|
fiore@0
|
152 *
|
fiore@0
|
153 * @param event the event to play the sound of
|
fiore@0
|
154 */
|
fiore@0
|
155 public void playSound(SoundEvent event){
|
fiore@0
|
156 playSound(event,false);
|
fiore@0
|
157 }
|
fiore@0
|
158
|
fiore@0
|
159 /**
|
fiore@0
|
160 * Stop all the sounds being played at the moment of the call. Useful to stop a sound that is looping
|
fiore@0
|
161 * after a call to {@code playSound(event,true)}.
|
fiore@0
|
162 */
|
fiore@0
|
163 public void stopSound(){
|
fiore@0
|
164 if(!soundLoaded)
|
fiore@0
|
165 throw new IllegalStateException("loadSounds() must be called before any call to pauseSound");
|
fiore@0
|
166 soundPool.autoPause();
|
fiore@0
|
167 }
|
fiore@0
|
168
|
fiore@0
|
169 /**
|
fiore@0
|
170 * Utters a text via android text to speech synthesizer
|
fiore@0
|
171 *
|
fiore@0
|
172 * @param text the string to be uttered
|
fiore@0
|
173 **/
|
fiore@0
|
174 public void speak(String text){
|
fiore@0
|
175 tts.speak(text, TextToSpeech.QUEUE_FLUSH, null);
|
fiore@0
|
176 }
|
fiore@0
|
177
|
fiore@0
|
178
|
fiore@0
|
179 /**
|
fiore@0
|
180 * Make the device vibrate
|
fiore@0
|
181 */
|
fiore@0
|
182 public void vibrate(){
|
fiore@0
|
183 vibrator.vibrate(VIBRATE_TIME);
|
fiore@0
|
184 }
|
fiore@0
|
185
|
fiore@0
|
186 /**
|
fiore@0
|
187 * Dispose the resources allocated during initialization. To be called when the class
|
fiore@0
|
188 * is no longer needed by client classes.
|
fiore@0
|
189 */
|
fiore@0
|
190 public void dispose(){
|
fiore@0
|
191 /* dispose sound */
|
fiore@0
|
192 for(SoundEvent evt : soundIDs.keySet()){
|
fiore@0
|
193 soundPool.unload(soundIDs.get(evt));
|
fiore@0
|
194 }
|
fiore@0
|
195 /* dispose tts */
|
fiore@0
|
196 if(tts != null){
|
fiore@0
|
197 tts.stop();
|
fiore@0
|
198 tts.shutdown();
|
fiore@0
|
199 }
|
fiore@0
|
200 }
|
fiore@0
|
201
|
fiore@0
|
202 }
|