fiore@0: /* fiore@0: CCmI Diagram Editor for Android fiore@0: fiore@0: Copyright (C) 2012 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/) fiore@0: fiore@0: This program is free software: you can redistribute it and/or modify fiore@0: it under the terms of the GNU General Public License as published by fiore@0: the Free Software Foundation, either version 3 of the License, or fiore@0: (at your option) any later version. fiore@0: fiore@0: This program is distributed in the hope that it will be useful, fiore@0: but WITHOUT ANY WARRANTY; without even the implied warranty of fiore@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the fiore@0: GNU General Public License for more details. fiore@0: fiore@0: You should have received a copy of the GNU General Public License fiore@0: along with this program. If not, see . fiore@0: */ fiore@0: package uk.ac.qmul.eecs.ccmi.accessibility; fiore@0: fiore@0: import java.util.EnumMap; fiore@0: import java.util.Locale; fiore@0: fiore@0: import uk.ac.qmul.eecs.ccmi.activities.R; fiore@0: fiore@0: import android.content.Context; fiore@0: import android.media.AudioManager; fiore@0: import android.media.SoundPool; fiore@0: import android.os.Vibrator; fiore@0: import android.speech.tts.TextToSpeech; fiore@0: import android.util.Log; fiore@0: fiore@0: /** fiore@0: * Encloses all the services needed to implement a custom accessibility system. Text-to-speech synthesis, sound and fiore@0: * device vibration. fiore@0: * fiore@0: */ fiore@0: public class AccessibilityService { fiore@0: private static final long VIBRATE_TIME = 200; fiore@0: private static final float SPEECH_RATE = 2.0f; fiore@0: private TextToSpeech tts; fiore@0: private SoundPool soundPool; fiore@0: private AudioManager audioManager; fiore@0: private Vibrator vibrator; fiore@0: private Context context; fiore@0: private EnumMap soundIDs; fiore@0: private boolean soundLoaded; fiore@0: /** fiore@0: * fiore@0: * A list of events that have a sound associated. The sounds are built in and cannot be modified. fiore@0: * fiore@0: * fiore@0: */ fiore@0: public enum SoundEvent { fiore@0: V_CANCEL, fiore@0: V_COLLAPSE, fiore@0: V_DRAG, fiore@0: V_EDITING_MODE, fiore@0: V_ENDOFLIST, fiore@0: V_ERROR, fiore@0: V_EXPAND, fiore@0: V_HOOK_OFF, fiore@0: V_HOOK_ON, fiore@0: V_JUMP, fiore@0: V_MAGNET_OFF, fiore@0: V_MAGNET_ON, fiore@0: V_OK fiore@0: } fiore@0: fiore@0: private static AccessibilityService singleton; fiore@0: fiore@0: /** fiore@0: * Creates an instance of {@code AccessibilityService}. It implements the singleton pattern, therefore fiore@0: * successive calls to this method will always return the same object. fiore@0: * fiore@0: * @param context The context to use. Usually your {@code Application} or {@code Activity} object. fiore@0: * fiore@0: * @return a singleton instance of {@code AccessibilityService} fiore@0: */ fiore@0: public static AccessibilityService getInstance(Context context){ fiore@0: if(singleton == null){ fiore@0: singleton = new AccessibilityService(context); fiore@0: } fiore@0: return singleton; fiore@0: } fiore@0: fiore@0: /** fiore@0: * Constructs a new {@code AccessibilityService}. fiore@0: * fiore@0: * @param context The current context fiore@0: */ fiore@0: public AccessibilityService(Context context){ fiore@0: soundPool = new SoundPool(2, AudioManager.STREAM_MUSIC, 0); fiore@0: this.context = context; fiore@0: audioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); fiore@0: soundIDs = new EnumMap(SoundEvent.class); fiore@0: tts = new TextToSpeech(context, new TextToSpeech.OnInitListener(){ fiore@0: @Override fiore@0: public void onInit(int status) { fiore@0: if (status == TextToSpeech.SUCCESS) { fiore@0: int result = tts.setLanguage(Locale.UK); fiore@0: if (result == TextToSpeech.LANG_MISSING_DATA fiore@0: || result == TextToSpeech.LANG_NOT_SUPPORTED) { fiore@0: Log.w("TEXT TO SPEECH", "Text to Speech UK language not supported"); fiore@0: } fiore@0: } fiore@0: } fiore@0: }); fiore@0: tts.setSpeechRate(SPEECH_RATE); fiore@0: vibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE); fiore@0: } fiore@0: fiore@0: /** fiore@0: * Loads the built in sound library. This method must be called before any call to fiore@0: * {@code playSound} or {@code stopSound}. fiore@0: */ fiore@0: public void loadSounds(){ fiore@0: if(soundLoaded) fiore@0: return; fiore@0: soundIDs.put(SoundEvent.V_CANCEL,soundPool.load(context, R.raw.cancel, 1)); fiore@0: soundIDs.put(SoundEvent.V_COLLAPSE,soundPool.load(context, R.raw.collapse, 1)); fiore@0: soundIDs.put(SoundEvent.V_EXPAND,soundPool.load(context, R.raw.expand, 1)); fiore@0: soundIDs.put(SoundEvent.V_ENDOFLIST,soundPool.load(context, R.raw.endoflist, 1)); fiore@0: soundIDs.put(SoundEvent.V_OK,soundPool.load(context, R.raw.ok, 1)); fiore@0: soundIDs.put(SoundEvent.V_DRAG,soundPool.load(context, R.raw.drag, 1)); fiore@0: soundIDs.put(SoundEvent.V_EDITING_MODE,soundPool.load(context, R.raw.editingmode, 1)); fiore@0: soundIDs.put(SoundEvent.V_ERROR,soundPool.load(context, R.raw.error, 1)); fiore@0: soundIDs.put(SoundEvent.V_HOOK_OFF,soundPool.load(context, R.raw.hookoff, 1)); fiore@0: soundIDs.put(SoundEvent.V_HOOK_ON,soundPool.load(context, R.raw.hookon, 1)); fiore@0: soundIDs.put(SoundEvent.V_JUMP,soundPool.load(context, R.raw.jump, 1)); fiore@0: soundIDs.put(SoundEvent.V_MAGNET_OFF,soundPool.load(context, R.raw.magnetoff, 1)); fiore@0: soundLoaded = true; fiore@0: } fiore@0: fiore@0: /** fiore@0: * Plays the sound associated to a {@code SoundEvent}. fiore@0: * fiore@0: * @param event the event to play the sound of fiore@0: * @param loop whether to play the sound in a continuous loop or not fiore@0: */ fiore@0: public void playSound(SoundEvent event, boolean loop){ fiore@0: if(!soundLoaded) fiore@0: throw new IllegalStateException("loadSounds() must be called before any call to playSound"); fiore@0: float streamVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC); fiore@0: streamVolume = streamVolume / audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); fiore@0: soundPool.play(soundIDs.get(event), streamVolume, streamVolume, 1, loop ? -1 : 0, 1f); fiore@0: } fiore@0: fiore@0: /** fiore@0: * Plays the sound associated to a {@code SoundEvent} once. This call is equivalent to fiore@0: * {@code playSound(event,false)}. fiore@0: * fiore@0: * @param event the event to play the sound of fiore@0: */ fiore@0: public void playSound(SoundEvent event){ fiore@0: playSound(event,false); fiore@0: } fiore@0: fiore@0: /** fiore@0: * Stop all the sounds being played at the moment of the call. Useful to stop a sound that is looping fiore@0: * after a call to {@code playSound(event,true)}. fiore@0: */ fiore@0: public void stopSound(){ fiore@0: if(!soundLoaded) fiore@0: throw new IllegalStateException("loadSounds() must be called before any call to pauseSound"); fiore@0: soundPool.autoPause(); fiore@0: } fiore@0: fiore@0: /** fiore@0: * Utters a text via android text to speech synthesizer fiore@0: * fiore@0: * @param text the string to be uttered fiore@0: **/ fiore@0: public void speak(String text){ fiore@0: tts.speak(text, TextToSpeech.QUEUE_FLUSH, null); fiore@0: } fiore@0: fiore@0: fiore@0: /** fiore@0: * Make the device vibrate fiore@0: */ fiore@0: public void vibrate(){ fiore@0: vibrator.vibrate(VIBRATE_TIME); fiore@0: } fiore@0: fiore@0: /** fiore@0: * Dispose the resources allocated during initialization. To be called when the class fiore@0: * is no longer needed by client classes. fiore@0: */ fiore@0: public void dispose(){ fiore@0: /* dispose sound */ fiore@0: for(SoundEvent evt : soundIDs.keySet()){ fiore@0: soundPool.unload(soundIDs.get(evt)); fiore@0: } fiore@0: /* dispose tts */ fiore@0: if(tts != null){ fiore@0: tts.stop(); fiore@0: tts.shutdown(); fiore@0: } fiore@0: } fiore@0: fiore@0: }