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 17 package android.view; 18 19 import android.bluetooth.HeadsetBase; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.res.Resources; 23 import android.media.AudioManager; 24 import android.media.AudioService; 25 import android.media.AudioSystem; 26 import android.media.RingtoneManager; 27 import android.media.ToneGenerator; 28 import android.net.Uri; 29 import android.os.Handler; 30 import android.os.Message; 31 import android.os.Vibrator; 32 import android.util.Config; 33 import android.util.Log; 34 import android.widget.ImageView; 35 import android.widget.ProgressBar; 36 import android.widget.TextView; 37 import android.widget.Toast; 38 39 /** 40 * Handle the volume up and down keys. 41 * 42 * This code really should be moved elsewhere. 43 * 44 * @hide 45 */ 46 public class VolumePanel extends Handler 47 { 48 private static final String TAG = "VolumePanel"; 49 private static boolean LOGD = false; 50 51 /** 52 * The delay before playing a sound. This small period exists so the user 53 * can press another key (non-volume keys, too) to have it NOT be audible. 54 * <p> 55 * PhoneWindow will implement this part. 56 */ 57 public static final int PLAY_SOUND_DELAY = 300; 58 59 /** 60 * The delay before vibrating. This small period exists so if the user is 61 * moving to silent mode, it will not emit a short vibrate (it normally 62 * would since vibrate is between normal mode and silent mode using hardware 63 * keys). 64 */ 65 public static final int VIBRATE_DELAY = 300; 66 67 private static final int VIBRATE_DURATION = 300; 68 private static final int BEEP_DURATION = 150; 69 private static final int MAX_VOLUME = 100; 70 private static final int FREE_DELAY = 10000; 71 72 private static final int MSG_VOLUME_CHANGED = 0; 73 private static final int MSG_FREE_RESOURCES = 1; 74 private static final int MSG_PLAY_SOUND = 2; 75 private static final int MSG_STOP_SOUNDS = 3; 76 private static final int MSG_VIBRATE = 4; 77 78 private static final int RINGTONE_VOLUME_TEXT = com.android.internal.R.string.volume_ringtone; 79 private static final int MUSIC_VOLUME_TEXT = com.android.internal.R.string.volume_music; 80 private static final int INCALL_VOLUME_TEXT = com.android.internal.R.string.volume_call; 81 private static final int ALARM_VOLUME_TEXT = com.android.internal.R.string.volume_alarm; 82 private static final int UNKNOWN_VOLUME_TEXT = com.android.internal.R.string.volume_unknown; 83 private static final int NOTIFICATION_VOLUME_TEXT = 84 com.android.internal.R.string.volume_notification; 85 private static final int BLUETOOTH_INCALL_VOLUME_TEXT = 86 com.android.internal.R.string.volume_bluetooth_call; 87 88 protected Context mContext; 89 private AudioManager mAudioManager; 90 protected AudioService mAudioService; 91 private boolean mRingIsSilent; 92 93 private final Toast mToast; 94 private final View mView; 95 private final TextView mMessage; 96 private final TextView mAdditionalMessage; 97 private final ImageView mSmallStreamIcon; 98 private final ImageView mLargeStreamIcon; 99 private final ProgressBar mLevel; 100 101 // Synchronize when accessing this 102 private ToneGenerator mToneGenerators[]; 103 private Vibrator mVibrator; 104 VolumePanel(Context context, AudioService volumeService)105 public VolumePanel(Context context, AudioService volumeService) { 106 mContext = context; 107 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 108 mAudioService = volumeService; 109 mToast = new Toast(context); 110 111 LayoutInflater inflater = (LayoutInflater) context 112 .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 113 View view = mView = inflater.inflate(com.android.internal.R.layout.volume_adjust, null); 114 mMessage = (TextView) view.findViewById(com.android.internal.R.id.message); 115 mAdditionalMessage = 116 (TextView) view.findViewById(com.android.internal.R.id.additional_message); 117 mSmallStreamIcon = (ImageView) view.findViewById(com.android.internal.R.id.other_stream_icon); 118 mLargeStreamIcon = (ImageView) view.findViewById(com.android.internal.R.id.ringer_stream_icon); 119 mLevel = (ProgressBar) view.findViewById(com.android.internal.R.id.level); 120 121 mToneGenerators = new ToneGenerator[AudioSystem.getNumStreamTypes()]; 122 mVibrator = new Vibrator(); 123 } 124 postVolumeChanged(int streamType, int flags)125 public void postVolumeChanged(int streamType, int flags) { 126 if (hasMessages(MSG_VOLUME_CHANGED)) return; 127 removeMessages(MSG_FREE_RESOURCES); 128 obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget(); 129 } 130 131 /** 132 * Override this if you have other work to do when the volume changes (for 133 * example, vibrating, playing a sound, etc.). Make sure to call through to 134 * the superclass implementation. 135 */ onVolumeChanged(int streamType, int flags)136 protected void onVolumeChanged(int streamType, int flags) { 137 138 if (LOGD) Log.d(TAG, "onVolumeChanged(streamType: " + streamType + ", flags: " + flags + ")"); 139 140 if ((flags & AudioManager.FLAG_SHOW_UI) != 0) { 141 onShowVolumeChanged(streamType, flags); 142 } 143 144 if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) { 145 removeMessages(MSG_PLAY_SOUND); 146 sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY); 147 } 148 149 if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) { 150 removeMessages(MSG_PLAY_SOUND); 151 removeMessages(MSG_VIBRATE); 152 onStopSounds(); 153 } 154 155 removeMessages(MSG_FREE_RESOURCES); 156 sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY); 157 } 158 onShowVolumeChanged(int streamType, int flags)159 protected void onShowVolumeChanged(int streamType, int flags) { 160 int index = mAudioService.getStreamVolume(streamType); 161 int message = UNKNOWN_VOLUME_TEXT; 162 int additionalMessage = 0; 163 mRingIsSilent = false; 164 165 if (LOGD) { 166 Log.d(TAG, "onShowVolumeChanged(streamType: " + streamType 167 + ", flags: " + flags + "), index: " + index); 168 } 169 170 // get max volume for progress bar 171 int max = mAudioService.getStreamMaxVolume(streamType); 172 173 switch (streamType) { 174 175 case AudioManager.STREAM_RING: { 176 setRingerIcon(); 177 message = RINGTONE_VOLUME_TEXT; 178 Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri( 179 mContext, RingtoneManager.TYPE_RINGTONE); 180 if (ringuri == null) { 181 additionalMessage = 182 com.android.internal.R.string.volume_music_hint_silent_ringtone_selected; 183 mRingIsSilent = true; 184 } 185 break; 186 } 187 188 case AudioManager.STREAM_MUSIC: { 189 message = MUSIC_VOLUME_TEXT; 190 if (mAudioManager.isBluetoothA2dpOn()) { 191 additionalMessage = 192 com.android.internal.R.string.volume_music_hint_playing_through_bluetooth; 193 setLargeIcon(com.android.internal.R.drawable.ic_volume_bluetooth_ad2p); 194 } else { 195 setSmallIcon(index); 196 } 197 break; 198 } 199 200 case AudioManager.STREAM_VOICE_CALL: { 201 /* 202 * For in-call voice call volume, there is no inaudible volume. 203 * Rescale the UI control so the progress bar doesn't go all 204 * the way to zero and don't show the mute icon. 205 */ 206 index++; 207 max++; 208 message = INCALL_VOLUME_TEXT; 209 setSmallIcon(index); 210 break; 211 } 212 213 case AudioManager.STREAM_ALARM: { 214 message = ALARM_VOLUME_TEXT; 215 setSmallIcon(index); 216 break; 217 } 218 219 case AudioManager.STREAM_NOTIFICATION: { 220 message = NOTIFICATION_VOLUME_TEXT; 221 setSmallIcon(index); 222 Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri( 223 mContext, RingtoneManager.TYPE_NOTIFICATION); 224 if (ringuri == null) { 225 additionalMessage = 226 com.android.internal.R.string.volume_music_hint_silent_ringtone_selected; 227 mRingIsSilent = true; 228 } 229 break; 230 } 231 232 case AudioManager.STREAM_BLUETOOTH_SCO: { 233 /* 234 * For in-call voice call volume, there is no inaudible volume. 235 * Rescale the UI control so the progress bar doesn't go all 236 * the way to zero and don't show the mute icon. 237 */ 238 index++; 239 max++; 240 message = BLUETOOTH_INCALL_VOLUME_TEXT; 241 setLargeIcon(com.android.internal.R.drawable.ic_volume_bluetooth_in_call); 242 break; 243 } 244 } 245 246 String messageString = Resources.getSystem().getString(message); 247 if (!mMessage.getText().equals(messageString)) { 248 mMessage.setText(messageString); 249 } 250 251 if (additionalMessage == 0) { 252 mAdditionalMessage.setVisibility(View.GONE); 253 } else { 254 mAdditionalMessage.setVisibility(View.VISIBLE); 255 mAdditionalMessage.setText(Resources.getSystem().getString(additionalMessage)); 256 } 257 258 if (max != mLevel.getMax()) { 259 mLevel.setMax(max); 260 } 261 mLevel.setProgress(index); 262 263 mToast.setView(mView); 264 mToast.setDuration(Toast.LENGTH_SHORT); 265 mToast.setGravity(Gravity.TOP, 0, 0); 266 mToast.show(); 267 268 // Do a little vibrate if applicable (only when going into vibrate mode) 269 if ((flags & AudioManager.FLAG_VIBRATE) != 0 && 270 mAudioService.isStreamAffectedByRingerMode(streamType) && 271 mAudioService.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE && 272 mAudioService.shouldVibrate(AudioManager.VIBRATE_TYPE_RINGER)) { 273 sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY); 274 } 275 } 276 onPlaySound(int streamType, int flags)277 protected void onPlaySound(int streamType, int flags) { 278 279 if (hasMessages(MSG_STOP_SOUNDS)) { 280 removeMessages(MSG_STOP_SOUNDS); 281 // Force stop right now 282 onStopSounds(); 283 } 284 285 synchronized (this) { 286 ToneGenerator toneGen = getOrCreateToneGenerator(streamType); 287 toneGen.startTone(ToneGenerator.TONE_PROP_BEEP); 288 } 289 290 sendMessageDelayed(obtainMessage(MSG_STOP_SOUNDS), BEEP_DURATION); 291 } 292 onStopSounds()293 protected void onStopSounds() { 294 295 synchronized (this) { 296 int numStreamTypes = AudioSystem.getNumStreamTypes(); 297 for (int i = numStreamTypes - 1; i >= 0; i--) { 298 ToneGenerator toneGen = mToneGenerators[i]; 299 if (toneGen != null) { 300 toneGen.stopTone(); 301 } 302 } 303 } 304 } 305 onVibrate()306 protected void onVibrate() { 307 308 // Make sure we ended up in vibrate ringer mode 309 if (mAudioService.getRingerMode() != AudioManager.RINGER_MODE_VIBRATE) { 310 return; 311 } 312 313 mVibrator.vibrate(VIBRATE_DURATION); 314 } 315 316 /** 317 * Lock on this VolumePanel instance as long as you use the returned ToneGenerator. 318 */ getOrCreateToneGenerator(int streamType)319 private ToneGenerator getOrCreateToneGenerator(int streamType) { 320 synchronized (this) { 321 if (mToneGenerators[streamType] == null) { 322 return mToneGenerators[streamType] = new ToneGenerator(streamType, MAX_VOLUME); 323 } else { 324 return mToneGenerators[streamType]; 325 } 326 } 327 } 328 329 /** 330 * Makes the small icon visible, and hides the large icon. 331 * 332 * @param index The volume index, where 0 means muted. 333 */ setSmallIcon(int index)334 private void setSmallIcon(int index) { 335 mLargeStreamIcon.setVisibility(View.GONE); 336 mSmallStreamIcon.setVisibility(View.VISIBLE); 337 338 mSmallStreamIcon.setImageResource(index == 0 339 ? com.android.internal.R.drawable.ic_volume_off_small 340 : com.android.internal.R.drawable.ic_volume_small); 341 } 342 343 /** 344 * Makes the large image view visible with the given icon. 345 * 346 * @param resId The icon to display. 347 */ setLargeIcon(int resId)348 private void setLargeIcon(int resId) { 349 mSmallStreamIcon.setVisibility(View.GONE); 350 mLargeStreamIcon.setVisibility(View.VISIBLE); 351 mLargeStreamIcon.setImageResource(resId); 352 } 353 354 /** 355 * Makes the ringer icon visible with an icon that is chosen 356 * based on the current ringer mode. 357 */ setRingerIcon()358 private void setRingerIcon() { 359 mSmallStreamIcon.setVisibility(View.GONE); 360 mLargeStreamIcon.setVisibility(View.VISIBLE); 361 362 int ringerMode = mAudioService.getRingerMode(); 363 int icon; 364 365 if (LOGD) Log.d(TAG, "setRingerIcon(), ringerMode: " + ringerMode); 366 367 if (ringerMode == AudioManager.RINGER_MODE_SILENT) { 368 icon = com.android.internal.R.drawable.ic_volume_off; 369 } else if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) { 370 icon = com.android.internal.R.drawable.ic_vibrate; 371 } else { 372 icon = com.android.internal.R.drawable.ic_volume; 373 } 374 mLargeStreamIcon.setImageResource(icon); 375 } 376 onFreeResources()377 protected void onFreeResources() { 378 // We'll keep the views, just ditch the cached drawable and hence 379 // bitmaps 380 mSmallStreamIcon.setImageDrawable(null); 381 mLargeStreamIcon.setImageDrawable(null); 382 383 synchronized (this) { 384 for (int i = mToneGenerators.length - 1; i >= 0; i--) { 385 if (mToneGenerators[i] != null) { 386 mToneGenerators[i].release(); 387 } 388 mToneGenerators[i] = null; 389 } 390 } 391 } 392 393 @Override handleMessage(Message msg)394 public void handleMessage(Message msg) { 395 switch (msg.what) { 396 397 case MSG_VOLUME_CHANGED: { 398 onVolumeChanged(msg.arg1, msg.arg2); 399 break; 400 } 401 402 case MSG_FREE_RESOURCES: { 403 onFreeResources(); 404 break; 405 } 406 407 case MSG_STOP_SOUNDS: { 408 onStopSounds(); 409 break; 410 } 411 412 case MSG_PLAY_SOUND: { 413 onPlaySound(msg.arg1, msg.arg2); 414 break; 415 } 416 417 case MSG_VIBRATE: { 418 onVibrate(); 419 break; 420 } 421 422 } 423 } 424 425 } 426