VolumePanel.java revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
1/* 2 * Copyright (C) 2007 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 android.view; 18 19import android.media.ToneGenerator; 20import android.media.AudioManager; 21import android.media.AudioService; 22import android.content.Context; 23import android.os.Handler; 24import android.os.Message; 25import android.os.Vibrator; 26import android.util.Config; 27import android.util.Log; 28import android.widget.ImageView; 29import android.widget.ProgressBar; 30import android.widget.TextView; 31import android.widget.Toast; 32 33/** 34 * Handle the volume up and down keys. 35 * 36 * This code really should be moved elsewhere. 37 * 38 * @hide 39 */ 40public class VolumePanel extends Handler 41{ 42 private static final String TAG = "VolumePanel"; 43 private static boolean LOGD = false || Config.LOGD; 44 45 /** 46 * The delay before playing a sound. This small period exists so the user 47 * can press another key (non-volume keys, too) to have it NOT be audible. 48 * <p> 49 * PhoneWindow will implement this part. 50 */ 51 public static final int PLAY_SOUND_DELAY = 300; 52 53 /** 54 * The delay before vibrating. This small period exists so if the user is 55 * moving to silent mode, it will not emit a short vibrate (it normally 56 * would since vibrate is between normal mode and silent mode using hardware 57 * keys). 58 */ 59 public static final int VIBRATE_DELAY = 300; 60 61 private static final int VIBRATE_DURATION = 300; 62 private static final int BEEP_DURATION = 150; 63 private static final int MAX_VOLUME = 100; 64 private static final int FREE_DELAY = 10000; 65 66 private static final int MSG_VOLUME_CHANGED = 0; 67 private static final int MSG_FREE_RESOURCES = 1; 68 private static final int MSG_PLAY_SOUND = 2; 69 private static final int MSG_STOP_SOUNDS = 3; 70 private static final int MSG_VIBRATE = 4; 71 72 private final String RINGTONE_VOLUME_TEXT; 73 private final String MUSIC_VOLUME_TEXT; 74 private final String INCALL_VOLUME_TEXT; 75 private final String ALARM_VOLUME_TEXT; 76 private final String UNKNOWN_VOLUME_TEXT; 77 78 protected Context mContext; 79 protected AudioService mAudioService; 80 81 private Toast mToast; 82 private View mView; 83 private TextView mMessage; 84 private ImageView mOtherStreamIcon; 85 private ImageView mRingerStreamIcon; 86 private ProgressBar mLevel; 87 88 // Synchronize when accessing this 89 private ToneGenerator mToneGenerators[]; 90 private Vibrator mVibrator; 91 92 public VolumePanel(Context context, AudioService volumeService) { 93 mContext = context; 94 mAudioService = volumeService; 95 mToast = new Toast(context); 96 97 RINGTONE_VOLUME_TEXT = context.getString(com.android.internal.R.string.volume_ringtone); 98 MUSIC_VOLUME_TEXT = context.getString(com.android.internal.R.string.volume_music); 99 INCALL_VOLUME_TEXT = context.getString(com.android.internal.R.string.volume_call); 100 ALARM_VOLUME_TEXT = context.getString(com.android.internal.R.string.volume_alarm); 101 UNKNOWN_VOLUME_TEXT = context.getString(com.android.internal.R.string.volume_unknown); 102 103 LayoutInflater inflater = (LayoutInflater) context 104 .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 105 View view = mView = inflater.inflate(com.android.internal.R.layout.volume_adjust, null); 106 mMessage = (TextView) view.findViewById(com.android.internal.R.id.message); 107 mOtherStreamIcon = (ImageView) view.findViewById(com.android.internal.R.id.other_stream_icon); 108 mRingerStreamIcon = (ImageView) view.findViewById(com.android.internal.R.id.ringer_stream_icon); 109 mLevel = (ProgressBar) view.findViewById(com.android.internal.R.id.level); 110 111 mToneGenerators = new ToneGenerator[AudioManager.NUM_STREAMS]; 112 mVibrator = new Vibrator(); 113 } 114 115 public void postVolumeChanged(int streamType, int flags) { 116 if (hasMessages(MSG_VOLUME_CHANGED)) return; 117 removeMessages(MSG_FREE_RESOURCES); 118 obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget(); 119 } 120 121 /** 122 * Override this if you have other work to do when the volume changes (for 123 * example, vibrating, playing a sound, etc.). Make sure to call through to 124 * the superclass implementation. 125 */ 126 protected void onVolumeChanged(int streamType, int flags) { 127 128 if (LOGD) Log.d(TAG, "onVolumeChanged(streamType: " + streamType + ", flags: " + flags + ")"); 129 130 if ((flags & AudioManager.FLAG_SHOW_UI) != 0) { 131 onShowVolumeChanged(streamType, flags); 132 } 133 134 if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0) { 135 removeMessages(MSG_PLAY_SOUND); 136 sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY); 137 } 138 139 if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) { 140 removeMessages(MSG_PLAY_SOUND); 141 removeMessages(MSG_VIBRATE); 142 onStopSounds(); 143 } 144 145 removeMessages(MSG_FREE_RESOURCES); 146 sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY); 147 } 148 149 protected void onShowVolumeChanged(int streamType, int flags) { 150 int index = mAudioService.getStreamVolume(streamType); 151 String message = UNKNOWN_VOLUME_TEXT; 152 153 if (LOGD) { 154 Log.d(TAG, "onShowVolumeChanged(streamType: " + streamType 155 + ", flags: " + flags + "), index: " + index); 156 } 157 158 switch (streamType) { 159 160 case AudioManager.STREAM_RING: { 161 message = RINGTONE_VOLUME_TEXT; 162 setRingerIcon(index); 163 break; 164 } 165 166 case AudioManager.STREAM_MUSIC: { 167 message = MUSIC_VOLUME_TEXT; 168 setOtherIcon(index); 169 break; 170 } 171 172 case AudioManager.STREAM_VOICE_CALL: { 173 message = INCALL_VOLUME_TEXT; 174 /* 175 * For in-call voice call volume, there is no inaudible volume 176 * level, so never show the mute icon 177 */ 178 setOtherIcon(index == 0 ? 1 : index); 179 break; 180 } 181 182 case AudioManager.STREAM_ALARM: { 183 message = ALARM_VOLUME_TEXT; 184 setOtherIcon(index); 185 break; 186 } 187 } 188 189 if (!mMessage.getText().equals(message)) { 190 mMessage.setText(message); 191 } 192 193 int max = mAudioService.getStreamMaxVolume(streamType); 194 if (max != mLevel.getMax()) { 195 mLevel.setMax(max); 196 } 197 mLevel.setProgress(index); 198 199 mToast.setView(mView); 200 mToast.setDuration(Toast.LENGTH_SHORT); 201 mToast.setGravity(Gravity.TOP, 0, 0); 202 mToast.show(); 203 204 // Do a little vibrate if applicable (only when going into vibrate mode) 205 if ((flags & AudioManager.FLAG_VIBRATE) != 0 && 206 mAudioService.isStreamAffectedByRingerMode(streamType) && 207 mAudioService.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE && 208 mAudioService.shouldVibrate(AudioManager.VIBRATE_TYPE_RINGER)) { 209 sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY); 210 } 211 212 } 213 214 protected void onPlaySound(int streamType, int flags) { 215 216 if (hasMessages(MSG_STOP_SOUNDS)) { 217 removeMessages(MSG_STOP_SOUNDS); 218 // Force stop right now 219 onStopSounds(); 220 } 221 222 synchronized (this) { 223 ToneGenerator toneGen = getOrCreateToneGenerator(streamType); 224 toneGen.startTone(ToneGenerator.TONE_PROP_BEEP); 225 } 226 227 sendMessageDelayed(obtainMessage(MSG_STOP_SOUNDS), BEEP_DURATION); 228 } 229 230 protected void onStopSounds() { 231 232 synchronized (this) { 233 for (int i = AudioManager.NUM_STREAMS - 1; i >= 0; i--) { 234 ToneGenerator toneGen = mToneGenerators[i]; 235 if (toneGen != null) { 236 toneGen.stopTone(); 237 } 238 } 239 } 240 } 241 242 protected void onVibrate() { 243 244 // Make sure we ended up in vibrate ringer mode 245 if (mAudioService.getRingerMode() != AudioManager.RINGER_MODE_VIBRATE) { 246 return; 247 } 248 249 mVibrator.vibrate(VIBRATE_DURATION); 250 } 251 252 /** 253 * Lock on this VolumePanel instance as long as you use the returned ToneGenerator. 254 */ 255 private ToneGenerator getOrCreateToneGenerator(int streamType) { 256 synchronized (this) { 257 if (mToneGenerators[streamType] == null) { 258 return mToneGenerators[streamType] = new ToneGenerator(streamType, MAX_VOLUME); 259 } else { 260 return mToneGenerators[streamType]; 261 } 262 } 263 } 264 265 private void setOtherIcon(int index) { 266 mRingerStreamIcon.setVisibility(View.GONE); 267 mOtherStreamIcon.setVisibility(View.VISIBLE); 268 269 mOtherStreamIcon.setImageResource(index == 0 270 ? com.android.internal.R.drawable.ic_volume_off_small 271 : com.android.internal.R.drawable.ic_volume_small); 272 } 273 274 private void setRingerIcon(int index) { 275 mOtherStreamIcon.setVisibility(View.GONE); 276 mRingerStreamIcon.setVisibility(View.VISIBLE); 277 278 int ringerMode = mAudioService.getRingerMode(); 279 int icon; 280 281 if (LOGD) Log.d(TAG, "setRingerIcon(index: " + index+ "), ringerMode: " + ringerMode); 282 283 if (ringerMode == AudioManager.RINGER_MODE_SILENT) { 284 icon = com.android.internal.R.drawable.ic_volume_off; 285 } else if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) { 286 icon = com.android.internal.R.drawable.ic_vibrate; 287 } else { 288 icon = com.android.internal.R.drawable.ic_volume; 289 } 290 mRingerStreamIcon.setImageResource(icon); 291 } 292 293 protected void onFreeResources() { 294 // We'll keep the views, just ditch the cached drawable and hence 295 // bitmaps 296 mOtherStreamIcon.setImageDrawable(null); 297 mRingerStreamIcon.setImageDrawable(null); 298 299 synchronized (this) { 300 for (int i = mToneGenerators.length - 1; i >= 0; i--) { 301 if (mToneGenerators[i] != null) { 302 mToneGenerators[i].release(); 303 } 304 mToneGenerators[i] = null; 305 } 306 } 307 } 308 309 @Override 310 public void handleMessage(Message msg) { 311 switch (msg.what) { 312 313 case MSG_VOLUME_CHANGED: { 314 onVolumeChanged(msg.arg1, msg.arg2); 315 break; 316 } 317 318 case MSG_FREE_RESOURCES: { 319 onFreeResources(); 320 break; 321 } 322 323 case MSG_STOP_SOUNDS: { 324 onStopSounds(); 325 break; 326 } 327 328 case MSG_PLAY_SOUND: { 329 onPlaySound(msg.arg1, msg.arg2); 330 break; 331 } 332 333 case MSG_VIBRATE: { 334 onVibrate(); 335 break; 336 } 337 338 } 339 } 340 341} 342