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 com.android.internal.R; 20 21import android.app.AlertDialog; 22import android.app.Dialog; 23import android.content.DialogInterface.OnDismissListener; 24import android.content.BroadcastReceiver; 25import android.content.Context; 26import android.content.DialogInterface; 27import android.content.Intent; 28import android.content.IntentFilter; 29import android.content.res.Resources; 30import android.media.AudioManager; 31import android.media.AudioService; 32import android.media.AudioSystem; 33import android.media.RingtoneManager; 34import android.media.ToneGenerator; 35import android.net.Uri; 36import android.os.Handler; 37import android.os.Message; 38import android.os.Vibrator; 39import android.util.Log; 40import android.view.WindowManager.LayoutParams; 41import android.widget.ImageView; 42import android.widget.SeekBar; 43import android.widget.SeekBar.OnSeekBarChangeListener; 44 45import java.util.HashMap; 46 47/** 48 * Handle the volume up and down keys. 49 * 50 * This code really should be moved elsewhere. 51 * 52 * Seriously, it really really should be moved elsewhere. This is used by 53 * android.media.AudioService, which actually runs in the system process, to 54 * show the volume dialog when the user changes the volume. What a mess. 55 * 56 * @hide 57 */ 58public class VolumePanel extends Handler implements OnSeekBarChangeListener, View.OnClickListener 59{ 60 private static final String TAG = "VolumePanel"; 61 private static boolean LOGD = false; 62 63 /** 64 * The delay before playing a sound. This small period exists so the user 65 * can press another key (non-volume keys, too) to have it NOT be audible. 66 * <p> 67 * PhoneWindow will implement this part. 68 */ 69 public static final int PLAY_SOUND_DELAY = 300; 70 71 /** 72 * The delay before vibrating. This small period exists so if the user is 73 * moving to silent mode, it will not emit a short vibrate (it normally 74 * would since vibrate is between normal mode and silent mode using hardware 75 * keys). 76 */ 77 public static final int VIBRATE_DELAY = 300; 78 79 private static final int VIBRATE_DURATION = 300; 80 private static final int BEEP_DURATION = 150; 81 private static final int MAX_VOLUME = 100; 82 private static final int FREE_DELAY = 10000; 83 private static final int TIMEOUT_DELAY = 3000; 84 85 private static final int MSG_VOLUME_CHANGED = 0; 86 private static final int MSG_FREE_RESOURCES = 1; 87 private static final int MSG_PLAY_SOUND = 2; 88 private static final int MSG_STOP_SOUNDS = 3; 89 private static final int MSG_VIBRATE = 4; 90 private static final int MSG_TIMEOUT = 5; 91 private static final int MSG_RINGER_MODE_CHANGED = 6; 92 private static final int MSG_MUTE_CHANGED = 7; 93 private static final int MSG_REMOTE_VOLUME_CHANGED = 8; 94 private static final int MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN = 9; 95 private static final int MSG_SLIDER_VISIBILITY_CHANGED = 10; 96 private static final int MSG_DISPLAY_SAFE_VOLUME_WARNING = 11; 97 98 // Pseudo stream type for master volume 99 private static final int STREAM_MASTER = -100; 100 // Pseudo stream type for remote volume is defined in AudioService.STREAM_REMOTE_MUSIC 101 102 protected Context mContext; 103 private AudioManager mAudioManager; 104 protected AudioService mAudioService; 105 private boolean mRingIsSilent; 106 private boolean mShowCombinedVolumes; 107 private boolean mVoiceCapable; 108 109 // True if we want to play tones on the system stream when the master stream is specified. 110 private final boolean mPlayMasterStreamTones; 111 112 /** Dialog containing all the sliders */ 113 private final Dialog mDialog; 114 /** Dialog's content view */ 115 private final View mView; 116 117 /** The visible portion of the volume overlay */ 118 private final ViewGroup mPanel; 119 /** Contains the sliders and their touchable icons */ 120 private final ViewGroup mSliderGroup; 121 /** The button that expands the dialog to show all sliders */ 122 private final View mMoreButton; 123 /** Dummy divider icon that needs to vanish with the more button */ 124 private final View mDivider; 125 126 /** Currently active stream that shows up at the top of the list of sliders */ 127 private int mActiveStreamType = -1; 128 /** All the slider controls mapped by stream type */ 129 private HashMap<Integer,StreamControl> mStreamControls; 130 131 private enum StreamResources { 132 BluetoothSCOStream(AudioManager.STREAM_BLUETOOTH_SCO, 133 R.string.volume_icon_description_bluetooth, 134 R.drawable.ic_audio_bt, 135 R.drawable.ic_audio_bt, 136 false), 137 RingerStream(AudioManager.STREAM_RING, 138 R.string.volume_icon_description_ringer, 139 R.drawable.ic_audio_ring_notif, 140 R.drawable.ic_audio_ring_notif_mute, 141 false), 142 VoiceStream(AudioManager.STREAM_VOICE_CALL, 143 R.string.volume_icon_description_incall, 144 R.drawable.ic_audio_phone, 145 R.drawable.ic_audio_phone, 146 false), 147 AlarmStream(AudioManager.STREAM_ALARM, 148 R.string.volume_alarm, 149 R.drawable.ic_audio_alarm, 150 R.drawable.ic_audio_alarm_mute, 151 false), 152 MediaStream(AudioManager.STREAM_MUSIC, 153 R.string.volume_icon_description_media, 154 R.drawable.ic_audio_vol, 155 R.drawable.ic_audio_vol_mute, 156 true), 157 NotificationStream(AudioManager.STREAM_NOTIFICATION, 158 R.string.volume_icon_description_notification, 159 R.drawable.ic_audio_notification, 160 R.drawable.ic_audio_notification_mute, 161 true), 162 // for now, use media resources for master volume 163 MasterStream(STREAM_MASTER, 164 R.string.volume_icon_description_media, //FIXME should have its own description 165 R.drawable.ic_audio_vol, 166 R.drawable.ic_audio_vol_mute, 167 false), 168 RemoteStream(AudioService.STREAM_REMOTE_MUSIC, 169 R.string.volume_icon_description_media, //FIXME should have its own description 170 R.drawable.ic_media_route_on_holo_dark, 171 R.drawable.ic_media_route_disabled_holo_dark, 172 false);// will be dynamically updated 173 174 int streamType; 175 int descRes; 176 int iconRes; 177 int iconMuteRes; 178 // RING, VOICE_CALL & BLUETOOTH_SCO are hidden unless explicitly requested 179 boolean show; 180 181 StreamResources(int streamType, int descRes, int iconRes, int iconMuteRes, boolean show) { 182 this.streamType = streamType; 183 this.descRes = descRes; 184 this.iconRes = iconRes; 185 this.iconMuteRes = iconMuteRes; 186 this.show = show; 187 } 188 }; 189 190 // List of stream types and their order 191 private static final StreamResources[] STREAMS = { 192 StreamResources.BluetoothSCOStream, 193 StreamResources.RingerStream, 194 StreamResources.VoiceStream, 195 StreamResources.MediaStream, 196 StreamResources.NotificationStream, 197 StreamResources.AlarmStream, 198 StreamResources.MasterStream, 199 StreamResources.RemoteStream 200 }; 201 202 /** Object that contains data for each slider */ 203 private class StreamControl { 204 int streamType; 205 ViewGroup group; 206 ImageView icon; 207 SeekBar seekbarView; 208 int iconRes; 209 int iconMuteRes; 210 } 211 212 // Synchronize when accessing this 213 private ToneGenerator mToneGenerators[]; 214 private Vibrator mVibrator; 215 216 private static AlertDialog sConfirmSafeVolumeDialog; 217 private static Object sConfirmSafeVolumeLock = new Object(); 218 219 private static class WarningDialogReceiver extends BroadcastReceiver 220 implements DialogInterface.OnDismissListener { 221 private Context mContext; 222 private Dialog mDialog; 223 224 WarningDialogReceiver(Context context, Dialog dialog) { 225 mContext = context; 226 mDialog = dialog; 227 IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 228 context.registerReceiver(this, filter); 229 } 230 231 @Override 232 public void onReceive(Context context, Intent intent) { 233 mDialog.cancel(); 234 synchronized (sConfirmSafeVolumeLock) { 235 sConfirmSafeVolumeDialog = null; 236 } 237 } 238 239 public void onDismiss(DialogInterface unused) { 240 mContext.unregisterReceiver(this); 241 synchronized (sConfirmSafeVolumeLock) { 242 sConfirmSafeVolumeDialog = null; 243 } 244 } 245 } 246 247 248 public VolumePanel(final Context context, AudioService volumeService) { 249 mContext = context; 250 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 251 mAudioService = volumeService; 252 253 // For now, only show master volume if master volume is supported 254 boolean useMasterVolume = context.getResources().getBoolean( 255 com.android.internal.R.bool.config_useMasterVolume); 256 if (useMasterVolume) { 257 for (int i = 0; i < STREAMS.length; i++) { 258 StreamResources streamRes = STREAMS[i]; 259 streamRes.show = (streamRes.streamType == STREAM_MASTER); 260 } 261 } 262 263 LayoutInflater inflater = (LayoutInflater) context 264 .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 265 View view = mView = inflater.inflate(R.layout.volume_adjust, null); 266 mView.setOnTouchListener(new View.OnTouchListener() { 267 public boolean onTouch(View v, MotionEvent event) { 268 resetTimeout(); 269 return false; 270 } 271 }); 272 mPanel = (ViewGroup) mView.findViewById(R.id.visible_panel); 273 mSliderGroup = (ViewGroup) mView.findViewById(R.id.slider_group); 274 mMoreButton = (ImageView) mView.findViewById(R.id.expand_button); 275 mDivider = (ImageView) mView.findViewById(R.id.expand_button_divider); 276 277 mDialog = new Dialog(context, R.style.Theme_Panel_Volume) { 278 public boolean onTouchEvent(MotionEvent event) { 279 if (isShowing() && event.getAction() == MotionEvent.ACTION_OUTSIDE) { 280 forceTimeout(); 281 return true; 282 } 283 return false; 284 } 285 }; 286 mDialog.setTitle("Volume control"); // No need to localize 287 mDialog.setContentView(mView); 288 mDialog.setOnDismissListener(new OnDismissListener() { 289 public void onDismiss(DialogInterface dialog) { 290 mActiveStreamType = -1; 291 mAudioManager.forceVolumeControlStream(mActiveStreamType); 292 } 293 }); 294 // Change some window properties 295 Window window = mDialog.getWindow(); 296 window.setGravity(Gravity.TOP); 297 LayoutParams lp = window.getAttributes(); 298 lp.token = null; 299 // Offset from the top 300 lp.y = mContext.getResources().getDimensionPixelOffset( 301 com.android.internal.R.dimen.volume_panel_top); 302 lp.type = LayoutParams.TYPE_VOLUME_OVERLAY; 303 lp.width = LayoutParams.WRAP_CONTENT; 304 lp.height = LayoutParams.WRAP_CONTENT; 305 window.setAttributes(lp); 306 window.addFlags(LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_NOT_TOUCH_MODAL 307 | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH); 308 309 mToneGenerators = new ToneGenerator[AudioSystem.getNumStreamTypes()]; 310 mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE); 311 312 mVoiceCapable = context.getResources().getBoolean(R.bool.config_voice_capable); 313 mShowCombinedVolumes = !mVoiceCapable && !useMasterVolume; 314 // If we don't want to show multiple volumes, hide the settings button and divider 315 if (!mShowCombinedVolumes) { 316 mMoreButton.setVisibility(View.GONE); 317 mDivider.setVisibility(View.GONE); 318 } else { 319 mMoreButton.setOnClickListener(this); 320 } 321 322 boolean masterVolumeOnly = context.getResources().getBoolean( 323 com.android.internal.R.bool.config_useMasterVolume); 324 boolean masterVolumeKeySounds = mContext.getResources().getBoolean( 325 com.android.internal.R.bool.config_useVolumeKeySounds); 326 327 mPlayMasterStreamTones = masterVolumeOnly && masterVolumeKeySounds; 328 329 listenToRingerMode(); 330 } 331 332 private void listenToRingerMode() { 333 final IntentFilter filter = new IntentFilter(); 334 filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); 335 mContext.registerReceiver(new BroadcastReceiver() { 336 337 public void onReceive(Context context, Intent intent) { 338 final String action = intent.getAction(); 339 340 if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(action)) { 341 removeMessages(MSG_RINGER_MODE_CHANGED); 342 sendMessage(obtainMessage(MSG_RINGER_MODE_CHANGED)); 343 } 344 } 345 }, filter); 346 } 347 348 private boolean isMuted(int streamType) { 349 if (streamType == STREAM_MASTER) { 350 return mAudioManager.isMasterMute(); 351 } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) { 352 return (mAudioService.getRemoteStreamVolume() <= 0); 353 } else { 354 return mAudioManager.isStreamMute(streamType); 355 } 356 } 357 358 private int getStreamMaxVolume(int streamType) { 359 if (streamType == STREAM_MASTER) { 360 return mAudioManager.getMasterMaxVolume(); 361 } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) { 362 return mAudioService.getRemoteStreamMaxVolume(); 363 } else { 364 return mAudioManager.getStreamMaxVolume(streamType); 365 } 366 } 367 368 private int getStreamVolume(int streamType) { 369 if (streamType == STREAM_MASTER) { 370 return mAudioManager.getMasterVolume(); 371 } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) { 372 return mAudioService.getRemoteStreamVolume(); 373 } else { 374 return mAudioManager.getStreamVolume(streamType); 375 } 376 } 377 378 private void setStreamVolume(int streamType, int index, int flags) { 379 if (streamType == STREAM_MASTER) { 380 mAudioManager.setMasterVolume(index, flags); 381 } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) { 382 mAudioService.setRemoteStreamVolume(index); 383 } else { 384 mAudioManager.setStreamVolume(streamType, index, flags); 385 } 386 } 387 388 private void createSliders() { 389 LayoutInflater inflater = (LayoutInflater) mContext 390 .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 391 mStreamControls = new HashMap<Integer, StreamControl>(STREAMS.length); 392 Resources res = mContext.getResources(); 393 for (int i = 0; i < STREAMS.length; i++) { 394 StreamResources streamRes = STREAMS[i]; 395 int streamType = streamRes.streamType; 396 if (mVoiceCapable && streamRes == StreamResources.NotificationStream) { 397 streamRes = StreamResources.RingerStream; 398 } 399 StreamControl sc = new StreamControl(); 400 sc.streamType = streamType; 401 sc.group = (ViewGroup) inflater.inflate(R.layout.volume_adjust_item, null); 402 sc.group.setTag(sc); 403 sc.icon = (ImageView) sc.group.findViewById(R.id.stream_icon); 404 sc.icon.setTag(sc); 405 sc.icon.setContentDescription(res.getString(streamRes.descRes)); 406 sc.iconRes = streamRes.iconRes; 407 sc.iconMuteRes = streamRes.iconMuteRes; 408 sc.icon.setImageResource(sc.iconRes); 409 sc.seekbarView = (SeekBar) sc.group.findViewById(R.id.seekbar); 410 int plusOne = (streamType == AudioSystem.STREAM_BLUETOOTH_SCO || 411 streamType == AudioSystem.STREAM_VOICE_CALL) ? 1 : 0; 412 sc.seekbarView.setMax(getStreamMaxVolume(streamType) + plusOne); 413 sc.seekbarView.setOnSeekBarChangeListener(this); 414 sc.seekbarView.setTag(sc); 415 mStreamControls.put(streamType, sc); 416 } 417 } 418 419 private void reorderSliders(int activeStreamType) { 420 mSliderGroup.removeAllViews(); 421 422 StreamControl active = mStreamControls.get(activeStreamType); 423 if (active == null) { 424 Log.e("VolumePanel", "Missing stream type! - " + activeStreamType); 425 mActiveStreamType = -1; 426 } else { 427 mSliderGroup.addView(active.group); 428 mActiveStreamType = activeStreamType; 429 active.group.setVisibility(View.VISIBLE); 430 updateSlider(active); 431 } 432 433 addOtherVolumes(); 434 } 435 436 private void addOtherVolumes() { 437 if (!mShowCombinedVolumes) return; 438 439 for (int i = 0; i < STREAMS.length; i++) { 440 // Skip the phone specific ones and the active one 441 final int streamType = STREAMS[i].streamType; 442 if (!STREAMS[i].show || streamType == mActiveStreamType) { 443 continue; 444 } 445 StreamControl sc = mStreamControls.get(streamType); 446 mSliderGroup.addView(sc.group); 447 updateSlider(sc); 448 } 449 } 450 451 /** Update the mute and progress state of a slider */ 452 private void updateSlider(StreamControl sc) { 453 sc.seekbarView.setProgress(getStreamVolume(sc.streamType)); 454 final boolean muted = isMuted(sc.streamType); 455 sc.icon.setImageResource(muted ? sc.iconMuteRes : sc.iconRes); 456 if (sc.streamType == AudioManager.STREAM_RING && 457 mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) { 458 sc.icon.setImageResource(R.drawable.ic_audio_ring_notif_vibrate); 459 } 460 if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) { 461 // never disable touch interactions for remote playback, the muting is not tied to 462 // the state of the phone. 463 sc.seekbarView.setEnabled(true); 464 } else if (sc.streamType != mAudioManager.getMasterStreamType() && muted) { 465 sc.seekbarView.setEnabled(false); 466 } else { 467 sc.seekbarView.setEnabled(true); 468 } 469 } 470 471 private boolean isExpanded() { 472 return mMoreButton.getVisibility() != View.VISIBLE; 473 } 474 475 private void expand() { 476 final int count = mSliderGroup.getChildCount(); 477 for (int i = 0; i < count; i++) { 478 mSliderGroup.getChildAt(i).setVisibility(View.VISIBLE); 479 } 480 mMoreButton.setVisibility(View.INVISIBLE); 481 mDivider.setVisibility(View.INVISIBLE); 482 } 483 484 private void collapse() { 485 mMoreButton.setVisibility(View.VISIBLE); 486 mDivider.setVisibility(View.VISIBLE); 487 final int count = mSliderGroup.getChildCount(); 488 for (int i = 1; i < count; i++) { 489 mSliderGroup.getChildAt(i).setVisibility(View.GONE); 490 } 491 } 492 493 private void updateStates() { 494 final int count = mSliderGroup.getChildCount(); 495 for (int i = 0; i < count; i++) { 496 StreamControl sc = (StreamControl) mSliderGroup.getChildAt(i).getTag(); 497 updateSlider(sc); 498 } 499 } 500 501 public void postVolumeChanged(int streamType, int flags) { 502 if (hasMessages(MSG_VOLUME_CHANGED)) return; 503 synchronized (this) { 504 if (mStreamControls == null) { 505 createSliders(); 506 } 507 } 508 removeMessages(MSG_FREE_RESOURCES); 509 obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget(); 510 } 511 512 public void postRemoteVolumeChanged(int streamType, int flags) { 513 if (hasMessages(MSG_REMOTE_VOLUME_CHANGED)) return; 514 synchronized (this) { 515 if (mStreamControls == null) { 516 createSliders(); 517 } 518 } 519 removeMessages(MSG_FREE_RESOURCES); 520 obtainMessage(MSG_REMOTE_VOLUME_CHANGED, streamType, flags).sendToTarget(); 521 } 522 523 public void postRemoteSliderVisibility(boolean visible) { 524 obtainMessage(MSG_SLIDER_VISIBILITY_CHANGED, 525 AudioService.STREAM_REMOTE_MUSIC, visible ? 1 : 0).sendToTarget(); 526 } 527 528 /** 529 * Called by AudioService when it has received new remote playback information that 530 * would affect the VolumePanel display (mainly volumes). The difference with 531 * {@link #postRemoteVolumeChanged(int, int)} is that the handling of the posted message 532 * (MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN) will only update the volume slider if it is being 533 * displayed. 534 * This special code path is due to the fact that remote volume updates arrive to AudioService 535 * asynchronously. So after AudioService has sent the volume update (which should be treated 536 * as a request to update the volume), the application will likely set a new volume. If the UI 537 * is still up, we need to refresh the display to show this new value. 538 */ 539 public void postHasNewRemotePlaybackInfo() { 540 if (hasMessages(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN)) return; 541 // don't create or prevent resources to be freed, if they disappear, this update came too 542 // late and shouldn't warrant the panel to be displayed longer 543 obtainMessage(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN).sendToTarget(); 544 } 545 546 public void postMasterVolumeChanged(int flags) { 547 postVolumeChanged(STREAM_MASTER, flags); 548 } 549 550 public void postMuteChanged(int streamType, int flags) { 551 if (hasMessages(MSG_VOLUME_CHANGED)) return; 552 synchronized (this) { 553 if (mStreamControls == null) { 554 createSliders(); 555 } 556 } 557 removeMessages(MSG_FREE_RESOURCES); 558 obtainMessage(MSG_MUTE_CHANGED, streamType, flags).sendToTarget(); 559 } 560 561 public void postMasterMuteChanged(int flags) { 562 postMuteChanged(STREAM_MASTER, flags); 563 } 564 565 public void postDisplaySafeVolumeWarning() { 566 if (hasMessages(MSG_DISPLAY_SAFE_VOLUME_WARNING)) return; 567 obtainMessage(MSG_DISPLAY_SAFE_VOLUME_WARNING, 0, 0).sendToTarget(); 568 } 569 570 /** 571 * Override this if you have other work to do when the volume changes (for 572 * example, vibrating, playing a sound, etc.). Make sure to call through to 573 * the superclass implementation. 574 */ 575 protected void onVolumeChanged(int streamType, int flags) { 576 577 if (LOGD) Log.d(TAG, "onVolumeChanged(streamType: " + streamType + ", flags: " + flags + ")"); 578 579 if ((flags & AudioManager.FLAG_SHOW_UI) != 0) { 580 synchronized (this) { 581 if (mActiveStreamType != streamType) { 582 reorderSliders(streamType); 583 } 584 onShowVolumeChanged(streamType, flags); 585 } 586 } 587 588 if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) { 589 removeMessages(MSG_PLAY_SOUND); 590 sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY); 591 } 592 593 if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) { 594 removeMessages(MSG_PLAY_SOUND); 595 removeMessages(MSG_VIBRATE); 596 onStopSounds(); 597 } 598 599 removeMessages(MSG_FREE_RESOURCES); 600 sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY); 601 602 resetTimeout(); 603 } 604 605 protected void onMuteChanged(int streamType, int flags) { 606 607 if (LOGD) Log.d(TAG, "onMuteChanged(streamType: " + streamType + ", flags: " + flags + ")"); 608 609 StreamControl sc = mStreamControls.get(streamType); 610 if (sc != null) { 611 sc.icon.setImageResource(isMuted(sc.streamType) ? sc.iconMuteRes : sc.iconRes); 612 } 613 614 onVolumeChanged(streamType, flags); 615 } 616 617 protected void onShowVolumeChanged(int streamType, int flags) { 618 int index = getStreamVolume(streamType); 619 620 mRingIsSilent = false; 621 622 if (LOGD) { 623 Log.d(TAG, "onShowVolumeChanged(streamType: " + streamType 624 + ", flags: " + flags + "), index: " + index); 625 } 626 627 // get max volume for progress bar 628 629 int max = getStreamMaxVolume(streamType); 630 631 switch (streamType) { 632 633 case AudioManager.STREAM_RING: { 634// setRingerIcon(); 635 Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri( 636 mContext, RingtoneManager.TYPE_RINGTONE); 637 if (ringuri == null) { 638 mRingIsSilent = true; 639 } 640 break; 641 } 642 643 case AudioManager.STREAM_MUSIC: { 644 // Special case for when Bluetooth is active for music 645 if ((mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC) & 646 (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP | 647 AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES | 648 AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0) { 649 setMusicIcon(R.drawable.ic_audio_bt, R.drawable.ic_audio_bt_mute); 650 } else { 651 setMusicIcon(R.drawable.ic_audio_vol, R.drawable.ic_audio_vol_mute); 652 } 653 break; 654 } 655 656 case AudioManager.STREAM_VOICE_CALL: { 657 /* 658 * For in-call voice call volume, there is no inaudible volume. 659 * Rescale the UI control so the progress bar doesn't go all 660 * the way to zero and don't show the mute icon. 661 */ 662 index++; 663 max++; 664 break; 665 } 666 667 case AudioManager.STREAM_ALARM: { 668 break; 669 } 670 671 case AudioManager.STREAM_NOTIFICATION: { 672 Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri( 673 mContext, RingtoneManager.TYPE_NOTIFICATION); 674 if (ringuri == null) { 675 mRingIsSilent = true; 676 } 677 break; 678 } 679 680 case AudioManager.STREAM_BLUETOOTH_SCO: { 681 /* 682 * For in-call voice call volume, there is no inaudible volume. 683 * Rescale the UI control so the progress bar doesn't go all 684 * the way to zero and don't show the mute icon. 685 */ 686 index++; 687 max++; 688 break; 689 } 690 691 case AudioService.STREAM_REMOTE_MUSIC: { 692 if (LOGD) { Log.d(TAG, "showing remote volume "+index+" over "+ max); } 693 break; 694 } 695 } 696 697 StreamControl sc = mStreamControls.get(streamType); 698 if (sc != null) { 699 if (sc.seekbarView.getMax() != max) { 700 sc.seekbarView.setMax(max); 701 } 702 703 sc.seekbarView.setProgress(index); 704 if (((flags & AudioManager.FLAG_FIXED_VOLUME) != 0) || 705 (streamType != mAudioManager.getMasterStreamType() && 706 streamType != AudioService.STREAM_REMOTE_MUSIC && 707 isMuted(streamType))) { 708 sc.seekbarView.setEnabled(false); 709 } else { 710 sc.seekbarView.setEnabled(true); 711 } 712 } 713 714 if (!mDialog.isShowing()) { 715 int stream = (streamType == AudioService.STREAM_REMOTE_MUSIC) ? -1 : streamType; 716 // when the stream is for remote playback, use -1 to reset the stream type evaluation 717 mAudioManager.forceVolumeControlStream(stream); 718 mDialog.setContentView(mView); 719 // Showing dialog - use collapsed state 720 if (mShowCombinedVolumes) { 721 collapse(); 722 } 723 mDialog.show(); 724 } 725 726 // Do a little vibrate if applicable (only when going into vibrate mode) 727 if ((streamType != AudioService.STREAM_REMOTE_MUSIC) && 728 ((flags & AudioManager.FLAG_VIBRATE) != 0) && 729 mAudioService.isStreamAffectedByRingerMode(streamType) && 730 mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) { 731 sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY); 732 } 733 } 734 735 protected void onPlaySound(int streamType, int flags) { 736 737 if (hasMessages(MSG_STOP_SOUNDS)) { 738 removeMessages(MSG_STOP_SOUNDS); 739 // Force stop right now 740 onStopSounds(); 741 } 742 743 synchronized (this) { 744 ToneGenerator toneGen = getOrCreateToneGenerator(streamType); 745 if (toneGen != null) { 746 toneGen.startTone(ToneGenerator.TONE_PROP_BEEP); 747 sendMessageDelayed(obtainMessage(MSG_STOP_SOUNDS), BEEP_DURATION); 748 } 749 } 750 } 751 752 protected void onStopSounds() { 753 754 synchronized (this) { 755 int numStreamTypes = AudioSystem.getNumStreamTypes(); 756 for (int i = numStreamTypes - 1; i >= 0; i--) { 757 ToneGenerator toneGen = mToneGenerators[i]; 758 if (toneGen != null) { 759 toneGen.stopTone(); 760 } 761 } 762 } 763 } 764 765 protected void onVibrate() { 766 767 // Make sure we ended up in vibrate ringer mode 768 if (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_VIBRATE) { 769 return; 770 } 771 772 mVibrator.vibrate(VIBRATE_DURATION); 773 } 774 775 protected void onRemoteVolumeChanged(int streamType, int flags) { 776 // streamType is the real stream type being affected, but for the UI sliders, we 777 // refer to AudioService.STREAM_REMOTE_MUSIC. We still play the beeps on the real 778 // stream type. 779 if (LOGD) Log.d(TAG, "onRemoteVolumeChanged(stream:"+streamType+", flags: " + flags + ")"); 780 781 if (((flags & AudioManager.FLAG_SHOW_UI) != 0) || mDialog.isShowing()) { 782 synchronized (this) { 783 if (mActiveStreamType != AudioService.STREAM_REMOTE_MUSIC) { 784 reorderSliders(AudioService.STREAM_REMOTE_MUSIC); 785 } 786 onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, flags); 787 } 788 } else { 789 if (LOGD) Log.d(TAG, "not calling onShowVolumeChanged(), no FLAG_SHOW_UI or no UI"); 790 } 791 792 if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) { 793 removeMessages(MSG_PLAY_SOUND); 794 sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY); 795 } 796 797 if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) { 798 removeMessages(MSG_PLAY_SOUND); 799 removeMessages(MSG_VIBRATE); 800 onStopSounds(); 801 } 802 803 removeMessages(MSG_FREE_RESOURCES); 804 sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY); 805 806 resetTimeout(); 807 } 808 809 protected void onRemoteVolumeUpdateIfShown() { 810 if (LOGD) Log.d(TAG, "onRemoteVolumeUpdateIfShown()"); 811 if (mDialog.isShowing() 812 && (mActiveStreamType == AudioService.STREAM_REMOTE_MUSIC) 813 && (mStreamControls != null)) { 814 onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, 0); 815 } 816 } 817 818 819 /** 820 * Handler for MSG_SLIDER_VISIBILITY_CHANGED 821 * Hide or show a slider 822 * @param streamType can be a valid stream type value, or VolumePanel.STREAM_MASTER, 823 * or AudioService.STREAM_REMOTE_MUSIC 824 * @param visible 825 */ 826 synchronized protected void onSliderVisibilityChanged(int streamType, int visible) { 827 if (LOGD) Log.d(TAG, "onSliderVisibilityChanged(stream="+streamType+", visi="+visible+")"); 828 boolean isVisible = (visible == 1); 829 for (int i = STREAMS.length - 1 ; i >= 0 ; i--) { 830 StreamResources streamRes = STREAMS[i]; 831 if (streamRes.streamType == streamType) { 832 streamRes.show = isVisible; 833 if (!isVisible && (mActiveStreamType == streamType)) { 834 mActiveStreamType = -1; 835 } 836 break; 837 } 838 } 839 } 840 841 protected void onDisplaySafeVolumeWarning() { 842 synchronized (sConfirmSafeVolumeLock) { 843 if (sConfirmSafeVolumeDialog != null) { 844 return; 845 } 846 sConfirmSafeVolumeDialog = new AlertDialog.Builder(mContext) 847 .setMessage(com.android.internal.R.string.safe_media_volume_warning) 848 .setPositiveButton(com.android.internal.R.string.yes, 849 new DialogInterface.OnClickListener() { 850 public void onClick(DialogInterface dialog, int which) { 851 mAudioService.disableSafeMediaVolume(); 852 } 853 }) 854 .setNegativeButton(com.android.internal.R.string.no, null) 855 .setIconAttribute(android.R.attr.alertDialogIcon) 856 .create(); 857 final WarningDialogReceiver warning = new WarningDialogReceiver(mContext, 858 sConfirmSafeVolumeDialog); 859 860 sConfirmSafeVolumeDialog.setOnDismissListener(warning); 861 sConfirmSafeVolumeDialog.getWindow().setType( 862 WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 863 sConfirmSafeVolumeDialog.show(); 864 } 865 } 866 867 /** 868 * Lock on this VolumePanel instance as long as you use the returned ToneGenerator. 869 */ 870 private ToneGenerator getOrCreateToneGenerator(int streamType) { 871 if (streamType == STREAM_MASTER) { 872 // For devices that use the master volume setting only but still want to 873 // play a volume-changed tone, direct the master volume pseudostream to 874 // the system stream's tone generator. 875 if (mPlayMasterStreamTones) { 876 streamType = AudioManager.STREAM_SYSTEM; 877 } else { 878 return null; 879 } 880 } 881 synchronized (this) { 882 if (mToneGenerators[streamType] == null) { 883 try { 884 mToneGenerators[streamType] = new ToneGenerator(streamType, MAX_VOLUME); 885 } catch (RuntimeException e) { 886 if (LOGD) { 887 Log.d(TAG, "ToneGenerator constructor failed with " 888 + "RuntimeException: " + e); 889 } 890 } 891 } 892 return mToneGenerators[streamType]; 893 } 894 } 895 896 897 /** 898 * Switch between icons because Bluetooth music is same as music volume, but with 899 * different icons. 900 */ 901 private void setMusicIcon(int resId, int resMuteId) { 902 StreamControl sc = mStreamControls.get(AudioManager.STREAM_MUSIC); 903 if (sc != null) { 904 sc.iconRes = resId; 905 sc.iconMuteRes = resMuteId; 906 sc.icon.setImageResource(isMuted(sc.streamType) ? sc.iconMuteRes : sc.iconRes); 907 } 908 } 909 910 protected void onFreeResources() { 911 synchronized (this) { 912 for (int i = mToneGenerators.length - 1; i >= 0; i--) { 913 if (mToneGenerators[i] != null) { 914 mToneGenerators[i].release(); 915 } 916 mToneGenerators[i] = null; 917 } 918 } 919 } 920 921 @Override 922 public void handleMessage(Message msg) { 923 switch (msg.what) { 924 925 case MSG_VOLUME_CHANGED: { 926 onVolumeChanged(msg.arg1, msg.arg2); 927 break; 928 } 929 930 case MSG_MUTE_CHANGED: { 931 onMuteChanged(msg.arg1, msg.arg2); 932 break; 933 } 934 935 case MSG_FREE_RESOURCES: { 936 onFreeResources(); 937 break; 938 } 939 940 case MSG_STOP_SOUNDS: { 941 onStopSounds(); 942 break; 943 } 944 945 case MSG_PLAY_SOUND: { 946 onPlaySound(msg.arg1, msg.arg2); 947 break; 948 } 949 950 case MSG_VIBRATE: { 951 onVibrate(); 952 break; 953 } 954 955 case MSG_TIMEOUT: { 956 if (mDialog.isShowing()) { 957 mDialog.dismiss(); 958 mActiveStreamType = -1; 959 } 960 break; 961 } 962 case MSG_RINGER_MODE_CHANGED: { 963 if (mDialog.isShowing()) { 964 updateStates(); 965 } 966 break; 967 } 968 969 case MSG_REMOTE_VOLUME_CHANGED: { 970 onRemoteVolumeChanged(msg.arg1, msg.arg2); 971 break; 972 } 973 974 case MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN: 975 onRemoteVolumeUpdateIfShown(); 976 break; 977 978 case MSG_SLIDER_VISIBILITY_CHANGED: 979 onSliderVisibilityChanged(msg.arg1, msg.arg2); 980 break; 981 982 case MSG_DISPLAY_SAFE_VOLUME_WARNING: 983 onDisplaySafeVolumeWarning(); 984 break; 985 } 986 } 987 988 private void resetTimeout() { 989 removeMessages(MSG_TIMEOUT); 990 sendMessageDelayed(obtainMessage(MSG_TIMEOUT), TIMEOUT_DELAY); 991 } 992 993 private void forceTimeout() { 994 removeMessages(MSG_TIMEOUT); 995 sendMessage(obtainMessage(MSG_TIMEOUT)); 996 } 997 998 public void onProgressChanged(SeekBar seekBar, int progress, 999 boolean fromUser) { 1000 final Object tag = seekBar.getTag(); 1001 if (fromUser && tag instanceof StreamControl) { 1002 StreamControl sc = (StreamControl) tag; 1003 if (getStreamVolume(sc.streamType) != progress) { 1004 setStreamVolume(sc.streamType, progress, 0); 1005 } 1006 } 1007 resetTimeout(); 1008 } 1009 1010 public void onStartTrackingTouch(SeekBar seekBar) { 1011 } 1012 1013 public void onStopTrackingTouch(SeekBar seekBar) { 1014 final Object tag = seekBar.getTag(); 1015 if (tag instanceof StreamControl) { 1016 StreamControl sc = (StreamControl) tag; 1017 // because remote volume updates are asynchronous, AudioService might have received 1018 // a new remote volume value since the finger adjusted the slider. So when the 1019 // progress of the slider isn't being tracked anymore, adjust the slider to the last 1020 // "published" remote volume value, so the UI reflects the actual volume. 1021 if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) { 1022 seekBar.setProgress(getStreamVolume(AudioService.STREAM_REMOTE_MUSIC)); 1023 } 1024 } 1025 } 1026 1027 public void onClick(View v) { 1028 if (v == mMoreButton) { 1029 expand(); 1030 } 1031 resetTimeout(); 1032 } 1033} 1034