/TalkBack/src/com/google/android/marvin/talkback/FeedbackController.java
Java | 234 lines | 97 code | 42 blank | 95 comment | 14 complexity | 02e6f6ac4ade2f7265f4c65d4c00a17e MD5 | raw file
1/* 2 * Copyright (C) 2011 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17package com.google.android.marvin.talkback; 18 19import android.content.Context; 20import android.content.res.Resources; 21import android.media.AudioManager; 22import android.media.SoundPool; 23import android.os.Vibrator; 24 25import java.util.Map; 26import java.util.TreeMap; 27 28/** 29 * Provides auditory and haptic feedback. 30 * 31 * @author alanv@google.com (Alan Viverette) 32 */ 33class FeedbackController { 34 private static FeedbackController sInstance; 35 36 /** 37 * @return The shared instance of FeedbackController. 38 */ 39 public static FeedbackController getInstance(Context context) { 40 if (sInstance == null) { 41 sInstance = new FeedbackController(context); 42 } 43 44 return sInstance; 45 } 46 47 /** 48 * Default stream for audio feedback. 49 */ 50 private static final int DEFAULT_STREAM = AudioManager.STREAM_MUSIC; 51 52 /** 53 * Default volume for sound playback. Use 1.0f to match the current stream 54 * volume. 55 */ 56 private static final float DEFAULT_VOLUME = 1.0f; 57 58 /** 59 * Default rate for sound playback. 60 */ 61 private static final float DEFAULT_RATE = 1.0f; 62 63 /** 64 * Number of channels to use in SoundPool for auditory icon feedback. 65 */ 66 private static final int NUMBER_OF_CHANNELS = 10; 67 68 /** 69 * The parent context. Required for mapping resource identifiers to 70 * resources. 71 */ 72 private final Context mContext; 73 74 /** 75 * Vibration service used to play vibration patterns. 76 */ 77 private final Vibrator mVibrator; 78 79 /** 80 * Sound pool used to play auditory icons. 81 */ 82 private final SoundPool mSoundPool; 83 84 /** 85 * Whether haptic feedback is enabled. 86 */ 87 private boolean mHapticEnabled = true; 88 89 /** 90 * Whether auditory feedback is enabled. 91 */ 92 private boolean mAuditoryEnabled = true; 93 94 /** Current volume (range 0..1). */ 95 private float mVolume = DEFAULT_VOLUME; 96 97 private final Map<Integer, long[]> mResourceIdToVibrationPatternMap; 98 private final Map<Integer, Integer> mResourceIdToSoundMap; 99 100 /** 101 * Constructs a new feedback controller. 102 * 103 * @param context The parent context. 104 */ 105 private FeedbackController(Context context) { 106 mContext = context; 107 mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); 108 mSoundPool = new SoundPool(NUMBER_OF_CHANNELS, DEFAULT_STREAM, 1); 109 mResourceIdToSoundMap = new TreeMap<Integer, Integer>(); 110 mResourceIdToVibrationPatternMap = new TreeMap<Integer, long[]>(); 111 112 // Since the sound pool loads resources asynchronously, we need to play 113 // resources immediately after they load. 114 mSoundPool.setOnLoadCompleteListener(mOnLoadCompleteListener); 115 } 116 117 /** 118 * Sets the current volume for auditory feedback. 119 * 120 * @param volume Volume value (range 0..100). 121 */ 122 public void setVolume(int volume) { 123 mVolume = (Math.min(100, Math.max(0, volume)) / 100.0f); 124 } 125 126 /** 127 * @param enabled Whether haptic feedback should be enabled. 128 */ 129 public void setHapticEnabled(boolean enabled) { 130 mHapticEnabled = enabled; 131 } 132 133 /** 134 * @param enabled Whether auditory feedback should be enabled. 135 */ 136 public void setAuditoryEnabled(boolean enabled) { 137 mAuditoryEnabled = enabled; 138 } 139 140 /** 141 * Stops all active feedback. 142 */ 143 public void interrupt() { 144 mVibrator.cancel(); 145 mSoundPool.autoPause(); 146 } 147 148 /** 149 * Releases resources associated with this feedback controller. It is good 150 * practice to call this method when you're done using the controller. 151 */ 152 public void shutdown() { 153 mVibrator.cancel(); 154 mSoundPool.release(); 155 } 156 157 /** 158 * Plays the sound file specified by the given resource identifier at the 159 * specified rate. 160 * 161 * @param resId The sound file's resource identifier. 162 * @param rate The rate at which to play back the sound, where 1.0 is normal 163 * speed and 0.5 is half-speed. 164 * @return {@code true} if successful 165 */ 166 public boolean playSound(int resId, float rate) { 167 if (!mAuditoryEnabled) { 168 return false; 169 } 170 171 Integer soundId = mResourceIdToSoundMap.get(resId); 172 173 if (soundId == null) { 174 soundId = mSoundPool.load(mContext, resId, 1); 175 176 mResourceIdToSoundMap.put(resId, soundId); 177 178 // Since we need to play the sound immediately after it loads, we 179 // should just return true. 180 return true; 181 } 182 183 final int stream = mSoundPool.play(soundId, mVolume, mVolume, 1, 0, rate); 184 185 return (stream != 0); 186 } 187 188 /** 189 * Plays the vibration pattern specified by the given resource identifier. 190 * 191 * @param resId The vibration pattern's resource identifier. 192 * @return {@code true} if successful 193 */ 194 public boolean playVibration(int resId) { 195 if (!mHapticEnabled) { 196 return false; 197 } 198 199 long[] pattern = mResourceIdToVibrationPatternMap.get(resId); 200 201 if (pattern == null) { 202 final Resources resources = mContext.getResources(); 203 final int[] intPattern = resources.getIntArray(resId); 204 205 if (intPattern == null) { 206 return false; 207 } 208 209 pattern = new long[intPattern.length]; 210 211 for (int i = 0; i < pattern.length; i++) { 212 pattern[i] = intPattern[i]; 213 } 214 215 mResourceIdToVibrationPatternMap.put(resId, pattern); 216 } 217 218 mVibrator.vibrate(pattern, -1); 219 220 return true; 221 } 222 223 private final SoundPool.OnLoadCompleteListener mOnLoadCompleteListener = 224 new SoundPool.OnLoadCompleteListener() { 225 @Override 226 public void onLoadComplete(SoundPool soundPool, int sampleId, int status) { 227 if (status != 0) { 228 return; 229 } 230 231 soundPool.play(sampleId, mVolume, mVolume, 1, 0, DEFAULT_RATE); 232 } 233 }; 234}