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